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.