GDBブレークポイントコマンドリストは、最初のステッピング/続行コマンドの後のコマンドを無視するという制限があります(2017年3月現在、GDB 7.12)。これはGDBマニュアルに記載されており、現在の実装では2つのコマンドリストを同時に実行できません(GDB#10852を参照-コマンドシーケンスが予期せず中断されました)。
この制限は、コマンドリストに直接存在するstepping/continueコマンドでのみ適用されます。したがって、これを回避することはできますが、制限は引き続き適用されます。たとえば、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バージョンには、この自動化に最適なPythonAPIが付属しています。ディストリビューションによって提供される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など)でコンパイルされていると仮定します)
戻り値(つまり、レジスタの値$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
Guile
Pythonが気に入らない場合や、Pythonサポートが無効になっているGDBがあるが、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