如何在GDB中自动打印每条执行的行,直到达到给定的断点?

时间:2011-08-04 19:24:17

标签: c debugging automation gdb

我希望能够在GDB中设置一个断点,让它运行到那一点 - 并在此过程中,打印出已经“逐步完成”的行。

这是一个示例,基于这个带有main和函数的简单文件,每个文件有两个断点:

$ cat > test.c <<EOF
#include "stdio.h"

int count=0;

void doFunction(void) {
  // two steps forward
  count += 2;
  // one step back
  count--;
}

int main(void) {
  // some pointless init commands;
  count = 1;
  count += 2;
  count = 0;
  //main loop
  while(1) {
    doFunction();
    printf("%d\n", count);
  }
}
EOF

$ gcc -g -Wall test.c -o test.exe
$ chmod +x test.exe
$ gdb -se test.exe
...
Reading symbols from /path/to/test.exe...done.
(gdb) b main
Breakpoint 1 at 0x80483ec: file test.c, line 14.
(gdb) b doFunction
Breakpoint 2 at 0x80483c7: file test.c, line 7.

要启动会话,我需要运行(r)程序,然后在第一个断点(main)处停止:

(gdb) r
Starting program: /path/to/test.exe 

Breakpoint 1, main () at test.c:14
14    count = 1;
(gdb) 

此时 - 我可以,例如,点击继续(c);并且该过程将贯穿,而不是输出任何内容,并在请求的行中断:

(gdb) c
Continuing.

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb)

另一方面,不是继续 - 我可以逐行,通过使用步骤(s)或下一步(n);例如:

14    count = 1;
(gdb) n
15    count += 2;
(gdb) s
16    count = 0;
(gdb) s
19      doFunction();
(gdb) s

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb) s
9     count--;
(gdb) s
10  }
(gdb) s
main () at test.c:20
20      printf("%d\n", count);
(gdb) s
...
(gdb) s
_IO_vfprintf_internal (s=Cannot access memory at address 0xe5853361
) at vfprintf.c:210
210 vfprintf.c: No such file or directory.
    in vfprintf.c
(gdb) s
245 in vfprintf.c
(gdb) s
210 in vfprintf.c
(gdb) n
245 in vfprintf.c
...
(gdb) n
2006    in vfprintf.c
(gdb) n
__printf (format=0x80484f0 "%d\n") at printf.c:39
39  printf.c: No such file or directory.
    in printf.c
(gdb) n
main () at test.c:21
21    }
(gdb) n
19      doFunction();
(gdb) n

Breakpoint 2, doFunction () at test.c:7
7     count += 2;
(gdb) 

无论如何,我知道我可以按 Enter ,最后输入的命令(步骤或下一个)将重复(在第二种情况下留下更长的会话,以显示'next'保持在同一级别,'step'步骤称为)。但是,正如可以看到的,取决于步骤或下一步是否运行,可能需要一段时间才能达到结果 - 所以,我不想坐在10分钟,我的手卡在Enter按钮上:)

所以,我的问题是 - 我可以以某种方式指示gdb在没有进一步用户干预的情况下运行到'断点2' - 同时打印出它经过的行,就好像按下了步骤(或下一步)一样? / p>

5 个答案:

答案 0 :(得分:19)

嗯,这并不容易 - 但我想我有点得到了:)我经历了一系列失败的尝试(发布here);相关代码如下。

基本上,“下一步/直到断点”的问题是如果调试器停止(步骤),如何确定是否“断开”断点。另请注意,我使用GDB 7.2-1ubuntu11(Ubuntu 11.04的当前版本)。所以,它是这样的:

  • 我首先发现了Convenience Variables,并且认为 - 鉴于有程序计数器等可用,必须有一些GDB便利变量给出“断点”状态,并且可以直接在GDB脚本中使用。然而,在浏览GDB reference Index一段时间之后,我根本找不到任何此类变量(我的尝试位于nub.gdb
  • 缺少这样的“断点状态”内部变量 - 唯一要做的就是将GDB的('stdout')命令行输出(作为对命令的响应)捕获为字符串,并解析它(寻找“断点”)
  • 然后,我发现了{GDB} Python APIgdb.execute("CMDSTR", toString=True)命令 - 这似乎是捕获输出所需要的:“默认情况下,命令产生的任何输出发送到gdb的标准输出。如果to_string参数为True,则输出将由gdb.execute收集并作为字符串[1] 返回!

最后,有效的方法是:暂时将GDB输出从gdb.execute重定向到RAM中的日志文件(Linux:/dev/shm);然后读回来,解析它并从python中打印 - python也处理一个简单的while循环,直到达到断点为止。

具有讽刺意味的是 - 大多数这些错误,通过重定向日志文件引起了这个解决方案,实际上最近修复了SVN;这意味着那些将在不久的将来传播到发行版,并且可以直接使用gdb.execute("CMDSTR", toString=True):/但是,因为我现在不能冒险从源代码构建GDB(并且可能碰到可能的新的不兼容性),这对我来说也足够好了:))

以下是相关文件(部分也在pygdb-fork.gdbpygdb-fork.py中):

pygdb-logg.gdb是:

# gdb script: pygdb-logg.gdb
# easier interface for pygdb-logg.py stuff
# from within gdb: (gdb) source -v pygdb-logg.gdb
# from cdmline: gdb -x pygdb-logg.gdb -se test.exe

# first, "include" the python file:
source -v pygdb-logg.py

# define shorthand for nextUntilBreakpoint():
define nub
  python nextUntilBreakpoint()
end

# set up breakpoints for test.exe:
b main
b doFunction

# go to main breakpoint
run

pygdb-logg.py是:

# gdb will 'recognize' this as python
#  upon 'source pygdb-logg.py'
# however, from gdb functions still have
#  to be called like:
#  (gdb) python print logExecCapture("bt")

import sys
import gdb
import os

def logExecCapture(instr):
  # /dev/shm - save file in RAM
  ltxname="/dev/shm/c.log"

  gdb.execute("set logging file "+ltxname) # lpfname
  gdb.execute("set logging redirect on")
  gdb.execute("set logging overwrite on")
  gdb.execute("set logging on")
  gdb.execute(instr)
  gdb.execute("set logging off")

  replyContents = open(ltxname, 'r').read() # read entire file
  return replyContents

# next until breakpoint
def nextUntilBreakpoint():
  isInBreakpoint = -1;
  # as long as we don't find "Breakpoint" in report:
  while isInBreakpoint == -1:
    REP=logExecCapture("n")
    isInBreakpoint = REP.find("Breakpoint")
    print "LOOP:: ", isInBreakpoint, "\n", REP

基本上,pygdb-logg.gdb加载pygdb-logg.py python脚本,为nub设置别名nextUntilBreakpoint,并初始化会话 - 其他一切都由python脚本处理。这是一个示例会话 - 关于OP中的测试源:

$ gdb -x pygdb-logg.gdb -se test.exe
...
Reading symbols from /path/to/test.exe...done.
Breakpoint 1 at 0x80483ec: file test.c, line 14.
Breakpoint 2 at 0x80483c7: file test.c, line 7.

Breakpoint 1, main () at test.c:14
14    count = 1;
(gdb) nub
LOOP::  -1
15    count += 2;

LOOP::  -1
16    count = 0;

LOOP::  -1
19      doFunction();

LOOP::  1

Breakpoint 2, doFunction () at test.c:7
7     count += 2;

(gdb) nub
LOOP::  -1
9     count--;

LOOP::  -1
10  }

LOOP::  -1
main () at test.c:20
20      printf("%d\n", count);

1
LOOP::  -1
21    }

LOOP::  -1
19      doFunction();

LOOP::  1

Breakpoint 2, doFunction () at test.c:7
7     count += 2;

(gdb)

......就像我想要的那样:P只是不知道它有多可靠(以及是否可以在avr-gdb中使用,这就是我需要的:)编辑:Ubuntu 11.04中的avr-gdb版本目前是6.4,它无法识别python命令:(

嗯,希望这有助于某人,
干杯!

这里有一些参考文献:

答案 1 :(得分:4)

使用命令文件在gdb中这样做是怎么回事?根据需要更改文件参数和循环计数。

gdb -x run.gdb

run.gdb:

set pagination off
set logging file gdb.log
set logging on
set $i = 0
file main
break main
break WriteData
# sadly, commands not getting executed on reaching breakpoint 2
commands 2
  set $i=1000
  print "commands 2 : %d",$i
end
run
while ( $i < 1000 )
  step
  # next
  # continue
  set $i = $i + 1
end

答案 2 :(得分:3)

根据@ sdaau的答案(http://www.mail-archive.com/gdb@gnu.org/msg00031.html)中的链接,我创建了自己的脚本,只需继续发送's'并连续读取gdb的输出,同时将输出打印到textfile和终端,当然,我的脚本可以修改以满足其他人的需求,但是,我希望我所做的修改应该适合大多数人的需要。

http://www.codeground.net/coding/gdb-step-into-all-lines-to-get-full-application-flow/

wget http://www.codeground.net/downloads/gdbwalkthrough.c
gcc gdbwalkthrough.c -o gdbwalkthrough
./gdbwalkthrough <application full path> [application arguments]

答案 3 :(得分:1)

作为一个新的答案,因为前一个已经陷入困境:)基本上,如果重点是观察执行源(和/或汇编)代码行作为程序运行 - 因为在寻找时通常对我有动力进入“自动打印输出” - 那么,基本上,一种非常快捷的方法是使用GDB TUI模式;我引用:

c - gdb behavior : value optimized out - Stack Overflow #1354762

  

使用GDB TUI模式。当我键入减号和Enter时,我的GDB副本启用它。然后键入C-x 2(按住Control并按X,释放两个然后按2)。这将把它放入拆分源和反汇编显示中。然后使用stepi和nexti一次移动一台机器指令。使用C-x o在TUI窗口之间切换。

这里的诀窍是,即使你点击continue - 这个时间源也会在TUI上显示和显示;程序运行后跟随:

GDB TUI Screenshot

...这对我来说避免了很多情况,我必须在“自动步进上下文”中编写断点脚本(虽然仍然存在这种情况)。关于TUI的文档:TUI - Debugging with GDB

干杯!

答案 4 :(得分:0)

当前接受的答案包括很多文件 io 并且只在断点处停止,但忽略观察点、信号甚至程序结束。

使用 python api 可以很好地处理:

  • 定义一个用户命令(带有额外的参数来说明自动步进的速度)
  • 可选:为默认定义一个参数(为了简单起见,这里用固定值替换)
  • 在python中执行while循环,处理CTRL-C的“预期”键盘中断
  • 注册一个 stop 事件处理程序,用于检查停止原因并将步骤类型存储在那里
  • 调整 while 循环以停止“不简单”的停止(断点/观察点/信号/...)

以下代码可以放在 gdb-auto-step.py 中,您可以随时使用 source gdb-auto-step.py 激活它(或包含在 .gdbinit 文件中以使其始终可用):

import gdb
import time

class CmdAutoStep (gdb.Command):
    """Auto-Step through the code until something happens or manually interrupted.
An argument says how fast auto stepping is done (1-19, default 5)."""
    def __init__(self):
        print('Registering command auto-step')
        super(CmdAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING)
        gdb.events.stop.connect(stop_handler_auto_step)
    def invoke(self, argument, from_tty):

        number = 5 # optional: use a parameter for the default
        if argument:
            if not argument.isdigit():
                raise gdb.GdbError("argument must be a digit, not " + argument)
            number = int(argument)
            if number == 0 or number > 19:
                raise gdb.GdbError("argument must be a digit between 1 and 19")

        sleep_time = 3.0 / (number * 1.4)
        try:
            frame = gdb.newest_frame()
        except gdb.error:
            raise gdb.GdbError("No stack.")

        global last_stop_was_simple
        last_stop_was_simple = True

        pagination = gdb.execute("show pagination", False, True).find("on")
        if pagination:
            gdb.execute("set pagination off", False, False)
        
        try:
            while (last_stop_was_simple):
                gdb.execute ("step")
                time.sleep(sleep_time)
        except KeyboardInterrupt as ki:
            if pagination:
                gdb.execute("set pagination on", False, False)
        except gdb.GdbError as user_error:
            if pagination:
                gdb.execute("set pagination on", False, False)
            # pass user errors unchanged
            raise user_error
        except:
            if pagination:
                gdb.execute("set pagination on", False, False)
            traceback.print_exc()

def stop_handler_auto_step(event):
    # check the type of stop, the following is the common one after step/next,
    # a more complex one would be a subclass (for example breakpoint or signal)
    global last_stop_was_simple
    last_stop_was_simple = type(event) is gdb.StopEvent

CmdAutoStep()

注意:如果有人有更好的检查分页值的方法,请发表评论。