How to script gdb (with python)? Example add breakpoints, run, what breakpoint did we hit?

24,637

Solution 1

FYI recent gdb versions are scriptable in Python. You can call python code from the gdb command line. This opens a whole new world, check the relevant documentation. From the command line run:

 dnf/yum/apt-get install gdb-doc
 info gdb extending python

If you do not like the text-based info browser, here is one (among many?) alternative, graphical browser:

yelp 'info:gdb' # , go to "Extending"

Here is a sample gdb-python script. It attaches gdb to the first "your_program" found running.

#!/usr/bin/python

import subprocess
import string

def backquotes(cmdwords):
        output = subprocess.Popen(cmdwords, stdout=subprocess.PIPE).communicate()[0]
        return output.strip()

pid = backquotes(['pgrep', 'your_program'])

gdb.execute("attach " + str(pid))

Solution 2

A reduced example that I'm currently using:

class DebugPrintingBreakpoint(gdb.Breakpoint):
    debugging_IDs = frozenset({37, 153, 420})
    def stop(self):
        top = gdb.newest_frame()
        someVector = top.read_var('aVectorVar')
        # Access the begin() & end() pointer of std::vector in GNU Standard C++ lib
        first = someVector['_M_impl']['_M_start']
        last = someVector['_M_impl']['_M_finish']
        values = []
        while first != last:
            values.append(int(first.dereference()['intID']))
            first = first + 1
        if not set(values) & debugging_IDs:
            return False # skip: none of the items we're looking for can be found by ID in the vector on the stack
        print("Found other accompanying IDs: {}".format(values))
        return True # drop to gdb's prompt
# Ensure shared libraries are loaded already
gdb.execute("start")
# Set our breakpoint, which happens to reside in some shared lib, hence the "start" previously
DebugPrintingBreakpoint("source.cpp:42")
gdb.execute("continue")

You can execute this script from gdb's prompt like this:

(gdb) source script.py

Or from the command-line:

$ gdb --command script.py ./executable.elf

See the complete GDB Python API docs for further info.

Solution 3

OK, I found the answer while asking the question... and it and it was a really simple thing.

You should not use both the "--command" and the "--eval" at the same time if you expect them to be executed in a specific order!

A more predicable way is to put everything in the commands.gdb file and ignore --eval.

So it becomes something like this:

arm-none-eabi-gdb --batch --command=commands.gdb main.elf

Where commands.gdb looks like this:

break test_success
break test_failed
target remote localhost:3333
cont 
frame

But it would probably be so much nicer to do this with something like python instead.

Solution 4

Just wanted to note something that I find confusing whenever I come back to this topic (Note, I'm currently on Ubuntu 14.04, GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1):

First, there are references about it being "possible to invoke gdb as an interpreter":

... meaning, one would write a script text file with the shebang line #!/usr/bin/gbd -P or #!/usr/bin/gbd --python, then write Python code in it, then make it executable chmod +x pygdbscript, then run ./pygdbscript; ... but as in this post:

..., I get gdb: unrecognized option '--python' if I try anything like that. Apparently this option is/was a feature in some "archer" branch of gdb?!


So, in order to run a Python script in gdb, there are actually two ways:

  • Name your script file with extension .py; say test.py here:
def Something():
  print("hello from python")

Something()
gdb.execute("quit");

Note, in this case, you just write plain Python code; and you do not need to import gdb in order to access the gdb object. This you can run with either of:

gdb -x test.py
gdb -x=test.py
gdb --command test.py
gdb --command=test.py
gdb -command test.py
gdb -command=test.py

... which seem to be equivalent, as the result for any of these is the same printout, before gdb is instructed to exit by the script:

$ gdb -x=test.py
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
...
For help, type "help".
Type "apropos word" to search for commands related to "word".
hello from python

NOTE that for this case, also names like test.gdb.py will be interpreted as pure Python scripts, since then end in .py.

  • Name your script anything else - as long as it does not end with .py extension; say test.pygdb here:
python
def Something():
  print("hello from python")

Something()
gdb.execute("quit");
end

In this case, gdb interprets the script as being a gdb script, i.e. with gdb commands - and that means, that whatever Python code you may want to write in here, must be wrapped in "python" as a starting line and "end" at end of the Python code. Again, it would be called with any of these equivalent calls:

gdb -x test.pygdb
gdb -x=test.pygdb
gdb --command test.pygdb
gdb --command=test.pygdb
gdb -command test.pygdb
gdb -command=test.pygdb

... and then the output is the same as in the previous case (since it is the same Python script running):

$ gdb -x test.pygdb 
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
...
hello from python

And in response to OP: if the C code in OP is in /tmp/myprog.c - with an added int start_test() { return rand() % 50; } on top, otherwise it won't compile - , and is compiled with with gcc -g myprog.c -o myprog.exe into /tmp/myprog.exe; then you can use a myprog.gdb.py script like this:

# need to specify the executable file which we debug (in this case, not from command line)
# here `gdb` command `file` is used - it does not have a Python equivalent (https://sourceware.org/gdb/onlinedocs/gdb/Objfiles-In-Python.html#index-Objfile_002eframe_005ffilters)
# so we must use gdb.execute:

myexefile="/tmp/myprog.exe"
print("""
### myprog.gdb.py is running: """ + myexefile + """ - and adding breakpoints:
""")

gdb.execute("file " + myexefile)
gdb.execute("set pagination off")

ax = gdb.Breakpoint("test_success")
bx = gdb.Breakpoint("test_failed")

gdb.execute("run")

# here the program will break, so we can do:

print("""
### myprog.gdb.py after the break - current stack frame:
""")

current_frame_at_break = gdb.selected_frame()
print(current_frame_at_break) # instead of gdb.execute("frame")

print("""
### myprog.gdb.py - backtrace:
""")

gdb.execute("backtrace 2")

print("""
### myprog.gdb.py - go to frame that called current frame:
""")

parent_frame = current_frame_at_break.older()
print(parent_frame)
status_var = parent_frame.read_var("status")
print("status_var is: ", status_var)

... then run this script with:

$ gdb -x myprog.gdb.py
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
....
For help, type "help".
Type "apropos word" to search for commands related to "word".

### myprog.gdb.py is running: /tmp/myprog.exe - and adding breakpoints:

Breakpoint 1 at 0x400565: file myprog.c, line 8.
Breakpoint 2 at 0x40055f: file myprog.c, line 4.

Breakpoint 2, test_failed () at myprog.c:4
4       while(1);    

### myprog.gdb.py after the break - current stack frame:

{stack=0x7fffffffdc70,code=0x40055b,!special}

### myprog.gdb.py - backtrace:

#0  test_failed () at myprog.c:4
#1  0x000000000040058c in main () at myprog.c:15

### myprog.gdb.py - go to frame that called current frame:

{stack=0x7fffffffdc90,code=0x400567,!special}
status_var is: 33
(gdb) 

Note that at the end of this script, the (gdb) interactive prompt remains, and you can use it normally here; if you don't need the interactive prompt, you can do gdb.execute("quit"); as in the above scripts to force gdb to exit instead at end of script execution.

Also, for an example of subclassing breakpoint class in gdb Python, see How to print the current line of source at breakpoint in GDB and nothing else?

Share:
24,637
Johan
Author by

Johan

Make sure you edit your ignore list to something like this, otherwise there is to much noise.... c# .net asp.net dotnet dotnetnuke matlab rails oracle vb.net visualstudio visualstudio2008 visualstudio2005 delphi flash sharepoint sharepoint2007 iis6 asp.net-2.0 javascript visio exchange exchange-server office-2007 msoffice asp.net-ajax asp.net-1.1 asp.net-mvc asp.net-mvc-beta1 vista vista64 ms-access sqlserver2005 sqlserver excel iphone ruby vb6 vc++ .net3.5 windows .net3.5sp1 visualstudio2008express c#.net game-development vs2008 silverlight silverlight-2.0 silverlight-3.0 silverlight-2-rc0 msvcrt msi win32 windowsserver2008 windowsserver2003 microsoft vs2005 sqlserver2000 vbscript iis iis7 iis5 visual-studio visual-studio-2008 visual-studio-2005 msword sql-server windows-mobile c#4.0 sql-server-2008 .net-4.0 vc6 sql-server-2005 vb visual-studio-2010 ms-access-2007 actionscript-3 flex ie adobe-air visual wcf-performance msbuild dllexport exchange2007 exchange-2003 visual-c++ visual-basic microsoft.visualbasic ie6 microsoft-ecsp actionscript wcf ie7 ie7.js ms ado.net visual-studio-team-system iis-7.5 asp.net-mvc-2 tsql c#3.0 asp-classic ie8 ie8-developer-tools etc etc etc Ctrl+Alt+Delete

Updated on December 14, 2020

Comments

  • Johan
    Johan over 3 years

    I'm trying to create a little unit test with gdb, for a embedded mcu that is controlled by OpenOCD (that gives me control over my target via a gdb server).

    So I would like to automate this with some scripting of gdb.

    I would like to write some kind of script for gdb that more or less does this:

    1. Add a couple of breakpoints
    2. Start the program
    3. When we stop, where did it stop (get the frame info)
    4. Quit.

    Any ideas?

    A example on how to do this in python gdb scripting would be nice.

    Thanks Johan


    Note:

    Let's say that we have this basic structure, that more or less goes into test_failed() or test_success() depending on what the function start_test() returns.

    void test_failed() {    
        while(1);    
    }
    
    void test_success() {    
        while(1);    
    }
    
    int main(void) {    
        int status = start_test();    
    
        if( status > 0 ) {    
            test_failed();    
        }    
        test_success();
    
        while(1);    
    }
    

    To do this manually in gdb is very strait forward,

    (gdb) break test_success
    Breakpoint 1 at 0x20: file src/main.c, line 9.
    (gdb) break test_failed
    Breakpoint 2 at 0x18: file src/main.c, line 5.
    (gdb) cont
    Continuing.
    
    Breakpoint 1, test_success () at src/main.c:9
    9       while(1);
    (gdb) frame
    #0  test_success () at src/main.c:9
    9       while(1);
    (gdb) 
    

    So the next step I tried was to add those gdb commands into a gdb startup script that more or less just looked like this.

    break test_success
    break test_failed
    target remote localhost:3333
    cont 
    frame
    

    and start it with

    arm-none-eabi-gdb --batch --command=commands.gdb main.elf
    

    And this kind of works, but it is not very nice. How do I do this with the "new and cool" python scripts, that gdb seem to support.