15

这是我的代码结构的简化示例:

void increment(int j);

int main()
{
  int i = 0;

  while(1) {
    i = increment(i);
  }

  return 0;
}

int increment(int j)
{
  return j + 1;
}

这是相应的 GDB 脚本:

b increment
command 1
finish
print i
continue
end

问题是该finish命令会阻止它后面的命令(即print iand continue)不被调用。

有没有办法告诉 GDBi在任何increment呼叫后立即打印?

4

5 回答 5

15

您显然可以通过将所有命令包装在单个 python 调用中来解决此错误,例如

(gdb) break doSomething
Breakpoint 1 at 0x400478: file iter.c, line 5.
(gdb) commands
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>python gdb.execute("print i"); gdb.execute("finish"); gdb.execute("print i");
>end

Breakpoint 1, doSomething () at iter.c:5
5     while (i < 5)
$1 = 0
main (argc=1, argv=0x7fffffffe178) at iter.c:13
13    return 0;
$2 = 5

编辑:不需要python的第二个解决方法似乎是定义一个新的gdb命令并在命令中运行它:

define foo
print *i
set $addrOfI = i
finish
print *$addrOfI
end

break doSomething
commands
foo
end
于 2012-05-10T07:11:51.197 回答
4

问题是完成似乎停止中止为它之后的第一个断点设置的命令。

这是预期的行为:任何恢复劣质(被调试)进程的命令(就像finish那样)也会停止执行固定命令序列。

更新:

另请参阅此GDB 错误报告

有没有办法告诉 GDB 在任何增量调用后立即打印 i ?

是的:

  1. 使用命令反汇编increment例程。disas在它的末尾找到ret指令(只有一个)。
  2. break *0xNNNNN使用语法在该指令上设置断点。
  3. 将命令附加到该断点:

    command N
     print $rax    # or $eax if you are on 32-bit x86 platform
     continue
    end
    

瞧:你应该得到从increment()打印返回的值(就在被返回之前)。

于 2012-05-08T14:58:59.987 回答
3

作为@Matt 答案的替代方案,如果您使用 GDB 7.4,则可以使用 FinishBreakpoints,例如(未经测试——我不确定这里是否接受评论):

(gdb) python #first defined the class
class MyFinishBreakpoint (gdb.FinishBreakpoint):
    def stop (self):
        print "%s" % gdb.parse_and_eval("i")
        return False # don't want to stop
end
(gdb) break doSomething
(gdb) commands
# then set the FinishBreakpoint silently
silent
py MyFinishBreakpoint()
continue

(以及文档链接)

于 2012-05-11T07:34:29.430 回答
1

你真的试过编译这个吗?您的increment()函数已声明void,但需要声明int。更改后,它对我来说很好:

% gdb test
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
[...]
Reading symbols from test...done.
(gdb) b increment 
Breakpoint 1 at 0x4004bb: file test.c, line 5.
(gdb) r
Starting program: test 

Breakpoint 1, increment (j=0) at test.c:5
5               return j+1;
(gdb) fin
Run till exit from #0  increment (j=0) at test.c:5
0x00000000004004dc in main () at test.c:11
11                      i = increment(i);
Value returned is $1 = 1
(gdb) n
12              }
(gdb) p i
$2 = 1
(gdb)
于 2012-05-08T15:50:14.357 回答
1

GDB 断点命令列表的局限性在于它们忽略第一个步进/继续命令之后的任何命令(截至 2017 年 3 月,GDB 7.12)。这在 GDB 手册中有记录,其动机是当前实现无法同时执行两个命令列表(参见GDB #10852 - 命令序列意外中断)。

此限制仅通过直接存在于命令列表中的步进/继续命令来实施。因此,可以解决这个问题 - 但限制仍然适用,例如 GDB 手册在 Python API 部分中警告:“你不应该改变下级的执行状态(即步骤、下一步等)”

因此,当需要在函数进入和函数退出时执行 GDB 命令时,可靠的解决方案是使用多个断点并拆分命令列表。这意味着需要为正在研究的函数的每个返回指令设置额外的断点。

这可以类似于:

(gdb) b my_function
(gdb) commands
silent
printf "my_function: %d -> ", j
end
(gdb) set pagination off
(gdb) set logging file gdb.log
(gdb) set logging overwrite on
(gdb) set logging on
(gdb) disas my_function
(gdb) set logging off
(gdb) shell grep ret gdb.log
0x00007ffff76ad095 <+245>:  retq
(gdb) b *0x00007ffff76ad095
(gdb) commands
silent
printf "%lu\n", $rax
end

哪个寄存器包含返回值取决于调用约定并且取决于体系结构。在x86-64 上,它位于$rax. 其他选择$eax在 x86-32、$o0SPARC、$r0ARM 等上。

使用 GDB 的脚本支持可以自动创建额外的断点。

Python

最近的 GDB 版本带有一个非常适合这种自动化的Python API 。发行版提供的 GDB 包通常默认启用 Python 支持。

作为第一个示例,在给定函数的每个 ret 指令上自动设置断点:

(gdb) py fn='myfunc'; list(map(lambda l: gdb.execute('b *{}'.format(l[0])), \
    filter(lambda l : l[2].startswith('ret'), map(lambda s : s.split(), \
        gdb.execute('disas '+fn, to_string=True).splitlines()))))

(假设 GDB 是用 Python3 支持编译的,例如 Fedora 25 之一)

为了自动创建打印返回值(即 register 的值$rax)然后继续的断点,需要对类进行子类gdb.Breakpoint化:

py
class RBP(gdb.Breakpoint):
  def stop(self):
    print(gdb.parse_and_eval('$rax'))
    return False

end

然后可以像这样创建断点:

py RBP('*0x000055555555894e')

结合这两个步骤来创建一个新的自定义命令:

py
class Pret_Cmd(gdb.Command):
  '''print return value via breakpoint command

  pret FUNCTION
  '''
  def __init__(self):
    super().__init__('pret', gdb.COMMAND_BREAKPOINTS)

  def install(self, fn):
    for l in filter(lambda l : l[2].startswith('ret'),
        map(lambda s : s.split(),
            gdb.execute('disas '+fn, to_string=True).splitlines())):
      RBP('*{}'.format(l[0]))

  def invoke(self, arg, from_tty):
    self.install(arg)

Pret_Cmd()
end

使用此新命令的示例:

(gdb) help breakpoints
(gdb) help pret
(gdb) pret myfunc

诡计

如果您不喜欢 Python 和/或 GDB 禁用了 Python 支持 - 但启用了Guile 支持- 也可以通过 Guile 自动设置断点。

Guile 中的自定义命令定义:

(gdb) gu (use-modules (gdb))
(gdb) gu
(register-command!
  (make-command "pret" #:command-class COMMAND_BREAKPOINTS #:doc
    "print return value via breakpoint command\n\npret FUNCTION"
    #:invoke
    (lambda (fn)
      (map (lambda (x)
             (let ((bp (make-breakpoint (string-append "*" x))))
               (register-breakpoint! bp)
               (set-breakpoint-stop!
                 bp
                 (lambda (x)
                   (display (parse-and-eval "$rax"))
                   (newline)
                   #f))
               bp))
           (map (lambda (x) (list-ref x 0))
                (filter
                  (lambda (x)
                    (and (not (null? x))
                         (string-prefix? "ret" (list-ref x 2))))
                  (map (lambda (x) (string-tokenize x))
                       (string-split
                         (execute
                           (string-append "disas " fn)
                           #:to-string
                           #t)
                         #\newline))))))))

end
于 2017-03-05T10:10:51.583 回答