如何在到达给定断点之前自动打印 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 has parse_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 中的每个执行行?的主要内容,如果未能解决你的问题,请参考以下文章

如何在达到给定次数的点之后使GDB断点断开?

调试利器GDB-中

调试利器GDB-中

跟踪所有变量以执行gdb中的函数

GDB简单使用

使用 GDB 在堆栈上打印符号