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

35

我想要在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)

另一方面,我可以逐行执行代码,而不是使用continue指令,通过使用step (s)或next (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键,最后输入的命令(step或next)将重复执行(在第二个例子中留下了一个稍长一点的会话,以显示“next”保持在相同的级别,“step”步进到被调用的函数内部)。但是,正如可以看到的那样,取决于step或next运行的情况,可能需要一段时间才能达到结果,因此,我不希望坐着手卡在Enter键上等待10分钟:)

所以,我的问题是 - 我是否可以以某种方式指示gdb在不需要进一步的用户干预的情况下运行到“断点2” - 同时打印出它经过的代码行,就像按下了step(或next)一样?


2
这篇类似但可能不完全相同的问题的答案可能会有所帮助,但我不确定如何(或是否)修改脚本以便在遇到断点时处理得更好:https://dev59.com/i1bUa4cB1Zd3GeqPAaOk#5813439 - Michael Burr
非常感谢 @Michael Burr - 我想唯一的区别在于停止条件(这里是断点,那里是段错误)- 很高兴知道至少有一种使用脚本的方法... 再次感谢 - 干杯! - sdaau
如果你找到了一种让脚本能够智能地知道何时停止的方法,请在这里发表评论。我认为这将是一种普遍有用的技术。 - Michael Burr
6个回答

26

我知道这不是易事 - 但我认为我已经在某种程度上解决了它 :) 我尝试了很多次失败(发布here); 相关代码如下。

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

我最初了解方便变量,想到 - 鉴于可用程序计数器等,必须有一些GDB方便变量给出“断点”状态,并且可以直接在GDB脚本中使用。然而,在GDB参考索引中寻找了一段时间后,我根本找不到任何这样的变量(我的尝试在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; 在此失败 - 因为这个: 然后,我想使用python脚本subprocess.Popen GDB程序,同时替换其stdin和stdout; 然后从那里继续控制GDB(pygdb-sub.py)-也失败了...(显然,因为我没有正确重定向stdin/out
然后,我想使用从GDB(通过source)调用的python脚本,在其中每当调用gdb.execute时内部分叉为pty,以便捕获其输出(pygdb-fork.gdb,pygdb-fork.py)...这个几乎起作用 - 因为有返回字符串; 但是GDB注意到某些东西不对:“[tcsetpgrp failed in terminal_inferior: Operation not permitted]”,并且随后的返回字符串似乎没有改变。
最终成功的方法是:将GDB输出暂时重定向到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脚本,为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命令:(

 

希望这能对某人有所帮助,
祝好!

 

这里是一些参考资料:


你把这个放到代码库里了吗?它被其他代码取代了吗? - Janus Troelsen
1
想补充一下,我发现有另外一种简单的方法可以做到同样的事情,而不需要使用Python脚本。 - Hi-Angel
请注意,这仅会执行直到按下 ctrl-c,如果您想在断点或信号处停止,并可能调整速度,请查看 hi-angel 的“非 Python API 简单方式”的深入工作链接:https://archive.blender.org/wiki/index.php/User:Ideasman42/#Program_Execution_Tracing。如需帮助,请参考 https://dev59.com/22w05IYBdhLWcg3w6GDH#67470615。 - Simon Sobisch

8
在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

4
根据@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]

2
作为新的回答,因为之前已经被霸占了 :) 基本上,如果要观察源代码(和/或汇编代码)作为程序运行时的执行情况 - 这通常是我在研究“自动打印输出”时的动机 - 那么,基本上,一个非常快速的方法是使用GDB TUI模式; 我引用:

{{link1:c-gdb行为:值优化出-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

干杯!


1

实际上,我有一个 Github repo,里面有一个Python-GDB扩展程序,它与您所描述的完全相同,但具有更多的功能。

您只需克隆该repo:

git clone https://github.com/Viaceslavus/gdb-debug-until.git

然后在GDB中使用以下命令将Python脚本加载到GDB中:

source gdb-debug-until/debug_until.py

(如有必要,请更改Python脚本路径)

现在,您可以使用以下命令运行代码的每一行,直到断点:

debug-until somefile.c:100 --args="" --end="somefile.c:200"

"somefile.c:100" 这里是起始断点,而 "somefile.c:200" 是最终断点。
"--args" 指定程序的一组参数(如果没有参数,则可以省略它)。

通过此扩展,您还可以多次运行代码(使用'-r'选项)并甚至指定在调试时应处理的一些事件。有关详细信息,请参见:
https://github.com/Viaceslavus/gdb-debug-until


1

目前被接受的答案包括大量文件IO,并且仅在断点上停止,但忽略了监视点、信号甚至程序结束。

使用Python API可以很好地处理这个问题:

  • 定义一个用户命令(带有附加参数以指定自动步进的速度)
  • 可选:为默认值定义一个参数(下面两种变体都适用)
  • 在Python中执行while循环,处理CTRL-C的“预期”键盘中断
  • 注册一个stop事件处理程序,检查停止原因并在那里存储步骤类型
  • 调整while循环以停止于“非简单”停止(断点/监视点/信号/...)

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

import gdb
import time
import traceback

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方式”),通过API添加一个新的参数,并按照以下方式使用它(还带有0 =无限制,处理进程退出,额外的自动下一步命令和更多类包装):

import gdb
import time
import traceback

class ParameterAutoSpeed (gdb.Parameter):
    """default speed for auto-step and auto-next commands (0-19, default 5)"""
    def __init__(self):
        self.set_doc = """Set speed for "auto-step" and "auto-next",
internally used to calculate sleep time between iterations of "step" / "next";
set "auto-speed 0" causes there to be no sleeping."""
        self.show_doc = "Speed value for auto-step/auto-next."
        super(ParameterAutoSpeed, self).__init__("auto-speed", gdb.COMMAND_RUNNING, gdb.PARAM_UINTEGER)
        self.value = 5
        self.backup = self.value

    def get_set_string (self):
        try:
            self.value = int(self.validate(self.value))
        except gdb.GdbError:
            self.value = int (self.backup)
            raise
        self.backup = self.value
        return ""

    def validate (self, argument):
        """validation for auto-step/auto-next 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 CmdAutoNext (gdb.Command):
    """Auto-Next through the code until something happens or manually interrupted.
An argument says how fast auto stepping is done (see parameter "auto-speed")."""
    def __init__(self, worker):
        self.worker = worker
        super(CmdAutoNext, self).__init__("auto-next", gdb.COMMAND_RUNNING)

    def invoke(self, argument, from_tty):
        self.worker.invoke (argument)

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-speed").

Note: To be usable you likely need several "skip" setup for not stepping into functions of
      the C library and other system libraries which may have no debug symbols available
      or are of no interest.
      You may press [CTRL]+[C] and execute "skip file", then "finish" to leave those."""
    def __init__(self, worker):
        self.worker = worker
        super(CmdAutoStep, self).__init__("auto-step", gdb.COMMAND_RUNNING)

    def invoke(self, argument, from_tty):
        self.worker.invoke (argument, do_step=True)

class AutoWorker ():
    def __init__(self):
        print('Registering parameter auto-speed and commands auto-step, auto-next')
        self.speed = ParameterAutoSpeed()
        CmdAutoStep(self)
        CmdAutoNext(self)
        gdb.events.stop.connect(self.stop_handler_auto)
        gdb.events.exited.connect(self.exit_handler_auto)

    def invoke(self, argument, do_step=False):
        # calculate sleep time
        if argument:
            number = self.speed.validate(argument) # raises an error if not valid
        else:
            number = self.speed.value
        if number:
            sleep_time = 3.0 / (1.4 ** number)
        else:
            sleep_time = 0

        # activate GDB scrolling, otherwise we'd auto-step/next 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
        self.last_stop_was_simple = True

        # actual auto-stepping
        try:
            while self.last_stop_was_simple:
                if do_step:
                    gdb.execute ("step")
                else:
                    gdb.execute ("next")
                time.sleep(sleep_time)
        # we just quit the loop as requested
        # pass keyboard and user errors unchanged
        except (KeyboardInterrupt, gdb.GdbError):
            raise
        # wrap GDB errors like "the program is not being run" to let them be
        # handled via default invoke error reporting, not as a python error
        except gdb.error as err:
            raise gdb.GdbError(err)
        # 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(self, 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)
        self.last_stop_was_simple = type(event) is gdb.StopEvent

    def exit_handler_auto(self, event):
        self.last_stop_was_simple = False

AutoWorker()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接