Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop condition for stack tracing in legacy GDB script

Tags:

c++

python

c

gcc

gdb

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?

like image 710
Rob Avatar asked Nov 01 '22 02:11

Rob


1 Answers

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.

like image 74
Rob Avatar answered Nov 09 '22 15:11

Rob