1. Overview
  2. Debugging workflow
  3. Debugging workflow: CI
  4. Basic interface
  5. Visualizing control flow
  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. Javascript
  20. Browser UI integration
  21. Screenshots
  22. Additional views
  23. GDB
  24. System debug info
  25. Compiler issues
  26. The Pernosco vision
  27. Related work

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.