关于单元测试:如何编写gdb脚本(使用python)? 示例添加断点,运行,我们打了什么断点?

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

我正在尝试使用gdb创建一个小单元测试,
适用于由OpenOCD控制的嵌入式MCU(可通过gdb服务器控制目标)。

所以我想用一些gdb脚本来自动化它。

我想为gdb编写某种或多或少的脚本:

  • 添加几个断点
  • 启动程序
  • 当我们停止时,它在哪里停止(获取帧信息)
  • 退出。
  • 有任何想法吗?

    关于如何在python gdb脚本中执行此操作的示例将是不错的。

    谢谢
    约翰

    注意:

    假设我们有这个基本结构,
    或多或少都进入了test_failed()或test_success()
    取决于函数start_test()返回的内容。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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);    
    }

    在gdb中手动执行此操作非常困难,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    (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)

    因此,我尝试的下一步是将这些gdb命令添加到一个或多或少看起来像这样的gdb启动脚本中。

    1
    2
    3
    4
    5
    break test_success
    break test_failed
    target remote localhost:3333
    cont
    frame

    然后开始

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

    这种工作,但不是很好。
    如何使用"新颖而又酷"的python脚本来做到这一点,
    该gdb似乎支持。


    仅供参考,最新的gdb版本可在Python中编写脚本。您可以从gdb命令行调用python代码。这将打开一个全新的世界,请查看相关文档。从命令行运行:

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

    如果您不喜欢基于文本的信息浏览器,则可以使用以下一种图形浏览器:

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

    这是一个示例gdb-python脚本。它将gdb附加到运行的第一个" your_program"上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/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))


    我目前正在使用的简化示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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")

    您可以从gdb的提示符处执行以下脚本:

    1
    (gdb) source script.py

    或从命令行:

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

    有关更多信息,请参见完整的GDB Python API文档。


    好的,我在问问题时找到了答案……这确实很简单。

    如果希望它们以特定顺序执行,则不应同时使用" --command"和" --eval"!

    一种更可预测的方法是将所有内容都放入commands.gdb文件中,并忽略--eval。

    这样就变成了这样的东西:

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

    命令.gdb如下所示:

    1
    2
    3
    4
    5
    break test_success
    break test_failed
    target remote localhost:3333
    cont
    frame

    但是用python之类的东西来做这件事可能会更好。


    只是想记下我每次回到该主题时都会感到困惑的地方(请注意,我当前使用的是Ubuntu 14.04,GNU gdb(Ubuntu 7.7.1-0ubuntu5?14.04.3)7.7.1):

    首先,有关于它"可以调用gdb作为解释器"的参考:

    • 9.编写gdb脚本-独立性的悬崖(http://tromey.com/blog/?p=548)
    • http://blog.scottt.tw/2012/01/exploring-gdb-python-api-with-ipython_31.html

    ...的意思是,将用shebang行#!/usr/bin/gbd -P#!/usr/bin/gbd --python编写脚本文本文件,然后在其中编写Python代码,然后使其可执行chmod +x pygdbscript,然后运行./pygdbscript; ...但是就像这篇文章:

    • GDB-缺少" --python"参数? /新手专区/ Arch Linux论坛

    ...,如果我尝试类似的操作,则会得到gdb: unrecognized option '--python'。显然,此选项是gdb的某些"弓形手"分支中的一个功能吗?

    因此,为了在gdb中运行Python脚本,实际上有两种方法:

    • 用扩展名.py命名脚本文件;在这里说test.py
    1
    2
    3
    4
    5
    def Something():
      print("hello from python")

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

    注意,在这种情况下,您只需要编写简单的Python代码即可;并且您不需要import gdb即可访问gdb对象。您可以使用以下任一方法来运行此程序:

    1
    2
    3
    4
    5
    6
    gdb -x test.py
    gdb -x=test.py
    gdb --command test.py
    gdb --command=test.py
    gdb -command test.py
    gdb -command=test.py

    ...似乎是等效的,因为在脚本指示gdb退出之前,所有这些结果都是相同的打印输出:

    1
    2
    3
    4
    5
    6
    $ 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

    请注意,在这种情况下,test.gdb.py之类的名称也将被解释为纯Python脚本,因为它们以.py结尾。

    • 为脚本命名,只要不以.py扩展名结尾即可;在这里说test.pygdb
    1
    2
    3
    4
    5
    6
    7
    python
    def Something():
      print("hello from python")

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

    在这种情况下,gdb将脚本解释为gdb脚本,即使用gdb命令-这意味着,无论您要在此处编写的Python代码如何,都必须用" python"包装作为起点," end"作为Python代码的末尾。同样,将使用以下任何等效调用来调用它:

    1
    2
    3
    4
    5
    6
    gdb -x test.pygdb
    gdb -x=test.pygdb
    gdb --command test.pygdb
    gdb --command=test.pygdb
    gdb -command test.pygdb
    gdb -command=test.pygdb

    ...,然后输出与前面的情况相同(因为它正在运行相同的Python脚本):

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

    并响应OP:如果OP中的C代码在/tmp/myprog.c中-在顶部加上int start_test() { return rand() % 50; },否则它将不编译-,并用gcc -g myprog.c -o myprog.exe编译为/tmp/myprog.exe;那么您可以使用myprog.gdb.py脚本,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    # 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)

    ...然后使用以下命令运行此脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    $ 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)

    请注意,在此脚本的末尾,(gdb)交互式提示符仍然存在,您可以在此处正常使用它。如果不需要交互式提示,则可以像上述脚本一样执行gdb.execute("quit");,以强制gdb退出,而不是在脚本执行结束时退出。

    另外,有关在gdb Python中对断点类进行子类化的示例,请参见如何在GDB中的断点处打印当前源代码行,而没有其他内容?