Scripting GDB with Python

in programming •  6 years ago  (edited)

Every tutorial on how to use Python to control GDB (the GNU Debugger) starts with a line like "say you have hundreds of threads."

Well, I have hundreds of threads. But I also have hundreds of socket objects in the C++ program I'm looking at! I can't really afford to examine them one at a time and get any insight. I'm not even sure what I'm looking for in the deadlock situation I'm investigating. Python's a good language for this sort of exploratory data analysis. (I have not yet checked whether I can get pandas and matplotlib to work within GDB--- an adventure for another time.)

The documentation on using Python inside GDB can be found here: https://sourceware.org/gdb/onlinedocs/gdb/Python.html

You type python, enter some Python code, finish with end, and it gets executed by a Python interpreter embedded in the GDB. Just in case you have a burning need to do some bignum arithmetic:

(gdb) python
>print pow( 2, 104 )
>end
20282409603651670423947251286016

The gdb package provided by GDB contains an enormous breadth of functionality. You can define new commands, access types and values, decorate stack frames, even replace an existing C++ method.

Inside Python, you can call gdb.parse_and_eval to use GDB's ability to parse C++ syntax and evaluate it. This is usually the starting point--- "print the value of some variable." But what you get back is actually an object, and most of what I need is in that Values object. This does magic so that fields in an object show up as dictionary entries in the resulting Python object.

(gdb) python
>foo = gdb.parse_and_eval( "this->mutex_" )
>print foo['cleanupPolicy_']
>end
MutexBasic::Destroy

It turns out this doesn't always work the way you'd expect. While the documentation claims that types that can be converted to Python basic types will be, I experienced some very strange behavior trying to call max() and do subtraction on 64-bit values. I worked around this by explicitly converting to long.

Also. C++ strings failed to convert to Python strings:

(gdb) python
>foo = gdb.parse_and_eval( "*('Framework::PthreadThread' *)(this->thread_.ptr_)" )
>bar = foo['name_'].string()
>print bar[7:]
>end
Traceback (most recent call last):
  File "<string>", line 2, in <module>
gdb.error: Trying to read string with inappropriate type `String'.
Error while executing Python code.

This might just be a problem with the rather old compiler and debugger combination I happen to be using. A workaround is to cast to a char * (which happens to work with the way the GCC standard library implements std::basic_string.)

(gdb) python
>char_ptr = gdb.lookup_type( 'char' ).pointer()
>foo = gdb.parse_and_eval( "*('Framework::PthreadThread' *)(this->thread_.ptr_)" )
>bar = foo['name_'].cast( char_ptr ).string()
>print bar[7:]
>end
/GSReceiveThread

But, one thing that does work is incrementing a pointer by 1 to move to the next object; the Python wrapper overloads addition so that the "right thing" happens.

Here's the script I ended up using to walk an array of threads and an array of sockets in each thread, and print a few key stats neatly.

def sendSockets():
    sockets = [] 
    maxTime = 0
    
    char_ptr = gdb.lookup_type( 'char' ).pointer()
    ggss = gdb.parse_and_eval( 'GlobalGenericSocketState' )
    for i in xrange( 0, ggss['dataSendEpollThreadCount_'] ):
        thread = (ggss['dataSendEpollThreadArray_'] + i).dereference()
        for j in xrange( 0, thread['socketCount_'] ):
            socket = ( thread['socketArray_'] + j ).dereference().dereference()
            client = socket['client_'].cast( char_ptr ).string()
            sockets.append( (client, socket) )
            maxTime = max( maxTime,
                           long( socket['lastEpollWaitRecvNs_'] ),
                           long( socket['lastEpollWaitSendNs_'] ) )

    sockets.sort( key=lambda x:x[0] )
    print len( sockets), "sockets"
    
    for ( c, socket ) in sockets:
            print "{0:20} {1:4} {2:15} lastWaitRecv {3:7} lastWaitSend {4:7}".format(
                c, socket['fd_'], socket['socketStats_']['sendBytes_'],
                ( long( socket['lastEpollWaitRecvNs_'] ) - maxTime ) / 1000000000, 
                ( long( socket['lastEpollWaitSendNs_'] ) - maxTime ) / 1000000000 )
Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!