更新

2024-07-05 本书已经出版 image 由于本库的草稿是我之前一个人写的,所以质量和正确性都不如经过两位作者和出版社编辑审阅和校正过的书稿。 如果你想阅读更加完善的版本,推荐购买正版书籍。

将重复的工作变成一个命令

比如在调试的时候,你知道当前栈指向一个字符串,但是你不知道具体在哪里,你想遍历这个栈将它找出来,那么你可以借助Python自定义一个命令"stackwalk",这个命令可以直接用Python代码遍历栈,将字符串找出来。

#####################################################
# Usage: to load this to gdb run:
# (gdb) source ..../path/to/<script_file>.py

import gdb

class StackWalk(gdb.Command):
    def __init__(self):
        # This registers our class as "StackWalk"
        super(StackWalk, self).__init__("stackwalk", gdb.COMMAND_DATA)

    def invoke(self, arg, from_tty):
        # When we call "StackWalk" from gdb, this is the method
        # that will be called.
        print("Hello from StackWalk!")
        # get the register
        rbp = gdb.parse_and_eval('$rbp')
        rsp = gdb.parse_and_eval('$rsp')
        ptr = rsp
        ppwc = gdb.lookup_type('wchar_t').pointer().pointer()
        while ptr < rbp:
            try:
                print('pointer is {}'.format(ptr))
                print(gdb.execute('wc_print {}'.format(ptr.cast(ppwc).dereference())))
                print('===')
            except:
                pass
            ptr += 8
        

# This registers our class to the gdb runtime at "source" time.
StackWalk()

Note: wc_print是我写的另外一个简单Python命令,用于打印给定地址的宽字符串,具体实现留作习题~ 更快的调试bug

当你调试多线程的时候,你发现callstack 一堆,而且好多都是重复的,如果它们可以自动去重或者折叠多好,这样你只需要关注一小部分。好消息!Python可以让你用一个命令就可以轻松搞定。而且已经有人写好了相应的代码,你只需要导入即可。详细介绍请看https://fy.blackhats.net.au/blog/html/2017/08/04/so_you_want_to_script_gdb_with_python.html

# From https://fy.blackhats.net.au/blog/html/2017/08/04/so_you_want_to_script_gdb_with_python.html
#####################################################
#
# Usage: to load this to gdb run:
# (gdb) source ..../path/to/debug_naughty.py
#
# To have this automatically load, you need to put the script
# in a path related to your binary. If you make /usr/sbin/foo,
# You can ship this script as:
# /usr/share/gdb/auto-load/ <PATH TO BINARY>
# /usr/share/gdb/auto-load/usr/sbin/foo
#
# This will trigger gdb to autoload the script when you start
# to acces a core or the live binary from this location.
#

import gdb


class StackFold(gdb.Command):
    def __init__(self):
        super(StackFold, self).__init__("stackfold", gdb.COMMAND_DATA)

    def invoke(self, arg, from_tty):
        # An inferior is the 'currently running applications'. In this case we only
        # have one.
        stack_maps = {}
        # This creates a dict where each element is keyed by backtrace.
        # Then each backtrace contains an array of "frames"
        #
        inferiors = gdb.inferiors()
        for inferior in inferiors:
            for thread in inferior.threads():
                try:
                    # Change to our threads context
                    thread.switch()
                    # Get the thread IDS
                    (tpid, lwpid, tid) = thread.ptid
                    gtid = thread.num
                    # Take a human readable copy of the backtrace, we'll need this for display later.
                    o = gdb.execute('bt', to_string=True)
                    # Build the backtrace for comparison
                    backtrace = []
                    gdb.newest_frame()
                    cur_frame = gdb.selected_frame()
                    while cur_frame is not None:
                        if cur_frame.name() is not None:
                            backtrace.append(cur_frame.name())

                        cur_frame = cur_frame.older()
                    # Now we have a backtrace like ['pthread_cond_wait@@GLIBC_2.3.2', 'lazy_thread', 'start_thread', 'clone']
                    # dicts can't use lists as keys because they are non-hashable, so we turn this into a string.
                    # Remember, C functions can't have spaces in them ...
                    s_backtrace = ' '.join(backtrace)
                    # Let's see if it exists in the stack_maps
                    if s_backtrace not in stack_maps:
                        stack_maps[s_backtrace] = []
                    # Now lets add this thread to the map.
                    stack_maps[s_backtrace].append({'gtid': gtid, 'tpid' : tpid, 'bt': o} )
                except Exception as e:
                    print(e)
        # Now at this point we have a dict of traces, and each trace has a "list" of pids that match. Let's display them
        for smap in stack_maps:
            # Get our human readable form out.
            o = stack_maps[smap][0]['bt']
            for t in stack_maps[smap]:
                # For each thread we recorded
                print("Thread %s (LWP %s))" % (t['gtid'], t['tpid']))
            print(o)

# This registers our class to the gdb runtime at "source" time.
StackFold()

等等!还有好多,毕竟Python图灵完备,而且GDB提供了许多API,你想要啥基本都能实现。

注:lldb也支持Python扩展,所以同样的道理可以用于lldb。 References:

  1. https://undo.io/resources/gdb-watchpoint/python-gdb/
  2. https://codeyarns.com/2014/07/17/how-to-enable-pretty-printing-for-stl-in-gdb/