1. Overview
  2. Debugging and CI
  3. Other workflows
  4. Debugging as a service
  5. Basic interface
  6. Call stacks
  7. Explaining dataflow
  8. Multiprocess
  9. Search box
  10. Source files
  11. Condition and print expressions
  12. Toolbox
  13. Alerts
  14. Application logs
  15. Callees
  16. View operators
  17. Notebook
  18. Instruction execution
  19. Browser UI integration
  20. Additional views
  21. GDB
  22. System debug info
  23. Compiler issues
  24. Future work
  25. Related work
  26. Conclusions

Call stacks

Call stacks are useful because they summarize control flow leading to a program point, in terms of abstractions chosen by developers.

Traditional debuggers produce call stacks by inspecting stack memory to extract stack frames. This approach is defeated by stack corruption, optimizations such as tail calls that eliminate stack frames, and code such as JITted code that doesn't use standard calling conventions and doesn't provide debuginfo needed to interpret stack memory. When debugging applications containing JITted code, such as Firefox, gdb will normally only be able to unwind the stack up to the nearest JITted code frame and the rest of the stack is lost.

Pernosco instead looks at the history of the thread to determine the set of function calls that have not yet returned. Pernosco stacks depend only on the application code using regular call instructions (or jmp instructions to function symbols) to call functions. In particular Pernosco produces correct stacks in the presence of tail-call optimizations and unknown JITted code. Of course Pernosco still relies on the presence of debuginfo to obtain function names and parameter values for stack frames. Pernosco stack frames show the values parameters had when a function was called, not the "current" values, because we find that more useful.

<< Basic interface Explaining dataflow >>