I have a legacy GDB command script for getting Python stack traces based on a GDB script that ships with Python 2.6 source (SO doesn't allow a hyperlink, but here's the URL: http://#%20http://svn.python.org/view/*checkout*/python/branches/release26-maint/Misc/
)
The script has a while loop that has a rather fragile check based on the program counter to exit, which (as noted in the original code in a comment) is probably only applicable if you're running Python directly, and not if the interpreter is started from within a C/C++ application.
The existing while loop looks like this:
while $pc < Py_Main || $pc > Py_GetArgcArgv
# ...
# code for extracting Python stack from local vars in relevant frames
# of C stack
# ...
up-silently 1
For the program I want to debug, the checks against Py_Main
and Py_GetArgcArgv
aren't going to work nicely, so I'm looking for a loop condition that will evaluate to false when it gets to main
.
So I've been toying around with the idea of using the program counter, frame pointer and stack pointer, on the basis that if up-silently
fails, they're going to have the same values they had previously, which means I'm at the top of the stack, like so:
set $oldpc = -1
set $oldfp = -1
set $oldsp = -1
while !($oldpc == $pc && $oldfp == $fp && $oldsp == $sp)
# ...
# code for extracting Python stack from local vars in relevant frames
# of C stack
# ...
set $oldpc = $pc
set $oldsp = $sp
set $oldfp = $fp
up-silently 1
I think this should do the trick, and preliminary checks indicate that it works fine. However, I'm not overly familiar with the sorts of optimisations compilers can do and I'm worried there may be corner cases where they may validly be the same somewhere in the middle of the stack.
It looks like$fp
can be zero for calls where the frame pointer has been optimised away (e.g. by compiling using -g -O3
using GCC). I'm also not sure whether $pc
can be relied on to be different, particularly if recursive calls are happening. I'm hoping $sp
is going to be different while there is still valid stack to be processed, but I have a vague suspicion that optimisations related to tail recursion may result in $sp being the same.
Any advice would be greatly appreciated.
Specific questions:
Question 1: Is there a better way in legacy (non-Python) GDB scripts to figure out if you're at the top of the stack?
Question 2: Will my assumption on $sp
, $pc
and $fp
hold true for most or all optimisation scenarios?
So I don't have an answer to Question 1, but I think I can partially answer Question 2.
Tail recursion will indeed reuse the existing stack pointer and frame pointer. What this means for stack traces, is that multiple calls to the same optimised tail-recursive function will only show up in GDB once, because (obviously) the stack pointer is being reused (and no new stack or frame pointers are pushed).
This would seem to imply that you could just check against previous and current values of $sp for a stop condition. Unfortunately, the $sp value can be the same in the middle of the stack. This seems to occur when some function calls have been optimised away.
So the stop-condition I proposed in my question is probably rather fragile, even though it works for several real-world examples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With