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、$o0
SPARC、$r0
ARM 等上。
使用 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