如何在到达给定断点之前自动打印 GDB 中的每个执行行?
Posted
技术标签:
【中文标题】如何在到达给定断点之前自动打印 GDB 中的每个执行行?【英文标题】:How to print every executed line in GDB automatically until a given breakpoint is reached? 【发布时间】:2011-10-20 07:34:52 【问题描述】:我希望能够在 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)
另一方面,我可以使用 step (s
) 或 next (n
) 来逐行执行,而不是 continue;例如:
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' 步入被调用的函数)。但是,可以看出,根据是 step 还是 next 运行,可能需要一段时间才能达到结果 - 所以,我不想坐 10 分钟,我的手卡在 Enter 按钮上 :)
所以,我的问题是 - 我能否以某种方式指示 gdb
在没有进一步用户干预的情况下运行到“断点 2” - 同时打印出它经过的行,就像按下步骤(或下一步)一样?
【问题讨论】:
这个对类似(但可能不重复)问题的回答可能会有所帮助,但我不确定如何(或是否)可以修改脚本以很好地处理断点: ***.com/questions/5812411/gdb-automatic-nexting/… 非常感谢@Michael Burr - 我想这和这个问题之间的唯一区别是停止的条件(这里是断点,那里有段错误) - 很高兴知道至少有一种方法使用脚本...再次感谢 - 干杯! 如果您想办法让脚本对何时停止有任何智能,请在此处发表评论。我认为这将是一种普遍有用的技术。 【参考方案1】:嗯,这并不容易 - 但我想我有点明白了 :) 我经历了一系列失败的尝试(发布 here);相关代码如下。
基本上,“直到断点的下一步/步骤”中的问题是,如果调试器停止(在某个步骤),如何确定您是否“打开”断点。另请注意,我使用 GDB 7.2-1ubuntu11(当前适用于 Ubuntu 11.04)。所以,它是这样的:
我首先发现了Convenience Variables,并认为——鉴于有程序计数器等可用,必须有一些 GDB 便利变量给出“断点”状态,并且可以直接在 GDB 脚本中使用。但是,在浏览GDB reference Index 一段时间后,我根本找不到任何此类变量(我的尝试在nub.gdb) 由于缺少这样的“断点状态”内部变量 - 唯一要做的就是将 GDB 的 ('stdout') 命令行输出(响应命令)捕获为字符串,并解析它(寻找“断点”) 然后,我发现了 Python API 到 GDB,以及gdb.execute("CMDSTR", toString=True)
命令 - 这似乎正是捕获输出所需要的:“默认情况下,命令生成的任何输出都发送到 gdb 的标准输出。如果 to_string 参数为 True,则输出将被 gdb.execute 收集并作为字符串返回[1]"!
所以,首先我尝试制作一个脚本(pygdb-nub.py,gdbwrap),以推荐的方式使用gdb.execute
;在这里失败 - 因为这个:
Bug 627506 – python: gdb.execute([...], to_string=True) partly prints to stdout/stderr
Bug 10808 – Allow GDB/Python API to capture and store GDB output
然后,我想我会使用 python 脚本来subprocess.Popen
GDB 程序,同时替换它的标准输入和标准输出;然后从那里继续控制 GDB (pygdb-sub.py) - 这也失败了......(显然,因为我没有正确重定向标准输入/输出)
然后,我想我会使用从 GDB 调用的 python 脚本(通过source
),只要调用gdb.execute
,它就会在内部分叉成一个pty,以便捕获它的输出(pygdb-fork.gdb, pygdb-fork.py)...这几乎工作了 - 因为有返回的字符串;但是 GDB 发现有些不对劲:“[tcsetpgrp failed in terminal_inferior: Operation not allowed]”,随后的返回字符串似乎没有改变。
最后,可行的方法是:临时将 GDB 输出从 gdb.execute
重定向到 RAM 中的日志文件(Linux:/dev/shm
);然后读回它,解析它并从 python 打印它——python 还处理一个简单的 while 循环,该循环会一直执行,直到到达断点。
具有讽刺意味的是 - 大多数通过重定向日志文件导致此解决方案的错误实际上最近在 SVN 中得到修复;这意味着这些将在不久的将来传播到发行版,并且可以直接使用gdb.execute("CMDSTR", toString=True)
:/ 然而,因为我现在不能冒险从源代码构建 GDB(并且可能会遇到可能的新不兼容性),这很好对我来说也足够了:)
这里是相关文件(部分也在pygdb-fork.gdb,pygdb-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 脚本,为nextUntilBreakpoint
设置别名nub
,并初始化会话 - 其他一切都由 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 命令:()
好吧,希望这对某人有所帮助, 干杯!
这里有一些参考:
GDB: error detected on stdin GDB has problems with getting commands piped to STDIN Re: [Gdb] How do i use GDB other input? gdb doesn't accept input on stdin Using gdb in an IDE - comp.os.linux.development.apps | Google Groups rmathew: Terminal Sickness [TUTORIAL] Calling an external program in C (Linux) - GIDForums shell - how to use multiple arguments with a shebang (i.e. #!)? - Stack Overflow Redirecting/storing output of shell into GDB variable? - Stack Overflow Corey Goldberg: Python - Redirect or Turn Off STDOUT and STDERR The Cliffs of Inanity › 9. Scripting gdb gdb python scripting: where hasparse_and_eval
gone? - Stack Overflow
shell - Invoke gdb to automatically pass arguments to the program being debugged - Stack Overflow
Storing Files/Directories In Memory With tmpfs | HowtoForge - Linux Howtos and Tutorials
simple way to touch a file if it does not exist | Python | Python
os.fork() different in cgi-script? - Python
java - Writing tests that use GDB - how to capture output? - Stack Overflow
Debugging with GDB: How to create GDB Commands in Python - Wiki
GDB reference card
【讨论】:
你把它放在存储库中了吗?它被其他代码取代了吗? 想添加,我找到there's another simple way to do the same,没有python脚本。【参考方案2】:如果在 gdb 中使用命令文件这样做呢?根据需要更改文件参数和循环计数。
gdb -x run.gdb
运行.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
【讨论】:
【参考方案3】:基于@sdaau 的答案 (http://www.mail-archive.com/gdb@gnu.org/msg00031.html) 中的链接,我创建了自己的脚本,以简单地继续发送“s”并连续读取 gdb 的输出,同时将输出打印到文本文件和终端,当然,我的脚本可以修改以适应其他人的需要,但是,我希望我所做的修改应该适合大多数人的需要。
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]
【讨论】:
【参考方案4】:作为一个新的答案,因为以前的答案已经被占用了 :) 基本上,如果重点是在程序运行时观察源代码(和/或汇编)代码行的执行 - 因为我在寻找时通常是动机进入“自动打印输出”——然后,基本上,一个非常快速的方法是使用 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 上;并在程序运行时跟随:
...这对我来说避免了许多我必须在“自动步进上下文”中编写断点脚本的情况(尽管仍然存在这种情况)..有关 TUI 的文档:TUI - Debugging with GDB
干杯!
【讨论】:
【参考方案5】:当前接受的答案包含大量文件 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):
# sanity check - are we even active, prevents a spurious "no registers" exception
try:
gdb.newest_frame()
except gdb.error:
raise gdb.GdbError("The program is not being run.")
# calculate sleep time
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 / (1.4 ** number)
# activate GDB scrolling, otherwise we'd auto-step only one page
pagination = gdb.parameter("pagination")
if pagination:
gdb.execute("set pagination off", False, False)
# recognize the kind of stop via stop_handler_auto_step
global last_stop_was_simple
last_stop_was_simple = True
# actual auto-stepping
try:
while last_stop_was_simple:
gdb.execute("step")
time.sleep(sleep_time)
# we just quit the loop as requested
# pass keyboard and user errors unchanged
except (KeyboardInterrupt, gdb.GdbError):
raise
# that exception is unexpected, but we never know...
except Exception:
traceback.print_exc()
# never leave without cleanup...
finally:
if pagination:
gdb.execute("set pagination on", False, False)
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()
要使用参数指定默认值(也称为“gdb 方式”),请添加另一个参数并使用它(还附带 0 = 无限制)
class ParameterAutoStep (gdb.Parameter):
"""auto-step default speed (0-19, default 5)"""
def __init__(self):
self.set_doc = """Set speed for "auto-step", internally used to calculate sleep time between "step"s.
set "auto-step 0" causes there to be no sleeping."""
self.show_doc = "Speed value for auto-step."
super(ParameterAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING, gdb.PARAM_UINTEGER)
self.value = 5
self.backup = self.value
def get_set_string (self):
try:
self.value = int(ParameterAutoStep.validate(self.value))
except gdb.GdbError:
self.value = int (self.backup)
raise
self.backup = self.value
return ""
@staticmethod
def validate (argument):
"""validation for auto-step speed"""
try:
speed = int(argument)
if speed < 0 or speed > 19:
raise ValueError()
except (TypeError, ValueError):
raise gdb.GdbError("speed-argument must be an integer between 1 and 19, or 0")
return speed
class CmdAutoStep (gdb.Command):
"""Auto-Step through the code until something happens or manually interrupted.
An argument says how fast auto stepping is done (see parameter "auto-step")."""
def __init__(self):
print('Registering command and parameter auto-step')
super(CmdAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING)
self.defaultSpeed = ParameterAutoStep()
gdb.events.stop.connect(stop_handler_auto_step)
def invoke(self, argument, from_tty):
# sanity check - are we even active, prevents a spurious "no registers" exception
try:
gdb.newest_frame()
except gdb.error:
raise gdb.GdbError("The program is not being run.")
# calculate sleep time
if argument:
number = ParameterAutoStep.validate(argument) # raises an error if not valid
else:
number = self.defaultSpeed.value
if number:
sleep_time = 3.0 / (1.4 ** number)
else:
sleep_time = 0
# activate GDB scrolling, otherwise we'd auto-step only one page
pagination = gdb.parameter("pagination")
if pagination:
gdb.execute("set pagination off", False, False)
# recognize the kind of stop via stop_handler_auto_step
global last_stop_was_simple
last_stop_was_simple = True
# actual auto-stepping
try:
while last_stop_was_simple:
gdb.execute("step")
time.sleep(sleep_time)
# we just quit the loop as requested
# pass keyboard and user errors unchanged
except (KeyboardInterrupt, gdb.GdbError):
raise
# that exception is unexpected, but we never know...
except Exception:
traceback.print_exc()
# never leave without cleanup...
finally:
if pagination:
gdb.execute("set pagination on", False, False)
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()
【讨论】:
以上是关于如何在到达给定断点之前自动打印 GDB 中的每个执行行?的主要内容,如果未能解决你的问题,请参考以下文章