gdbの使い方のメモ

よく使いそうなものの覚書。

実行ファイルを指定して起動する

$ gdb a.out

次のようにオプションを指定すると、

  • ライセンス表示を出さない
  • アセンブリコードをIntel形式で表示する
  • 停止するたびに直後の命令を表示する

ようにできる。

$ gdb -q -ex 'set disassembly-flavor intel' -ex 'disp/i $pc' a.out

起動中のプロセスにアタッチする

$ gdb -p [PID]

ヘルプ、コマンド検索

(gdb) help break
(gdb) apropos breakpoint

実行する

main関数で止める場合。引数にはシェルの構文がそのまま使える。

(gdb) start AAAA

main関数で止めない場合。

(gdb) run AAAA
(gdb) r AAAA

引数を別途セットすることもできる。

(gdb) set args AAAA
(gdb) start

起動時に引数をセットするには、実行ファイルの前に--argsをつける。

$ gdb --args a.out AAAA

シェルコマンドを実行する

(gdb) shell ls
(gdb) !ls

コマンドを指定しなければ、シェルそのものが起動する。

(gdb) shell

Pythonインタプリタを直で起動することもできる。

(gdb) shell python

アセンブリコードを表示する

アセンブリコードをIntel形式で表示させる。 デフォルトではAT&T形式になっている。

(gdb) set disassembly-flavor intel

対象が関数の場合。

(gdb) disassemble main
(gdb) disas main

引数を指定しない場合は、現在のフレームに対応する関数のアセンブリコードが表示される。

(gdb) disas

対象が関数でない場合はxコマンドを使う。 引数に$pcを指定すれば、直後に実行される命令列が見れる。

(gdb) x/10i $pc

停止するたびに特定の値を表示するようにする(Auto Display)

直後に実行される命令を表示するようにする。

(gdb) display/i $pc
(gdb) disp/i $pc

引数を指定しない場合、設定されているものを再度表示する。

(gdb) disp

ブレークポイントをセットする

アドレスはいろいろな形式で指定できる。

(gdb) break main
(gdb) b main
(gdb) b *main+100
(gdb) b *0x08048695

tbreakを使うと、一度限りで消えるブレークポイントをセットできる。

(gdb) tbreak *main+100
(gdb) tb *main+100

特定のメモリアドレスの値について、watchで書き換え時、rwatchで読み取り時、awatchで読み書き時にブレークできる(watchpoint)。 メモリブレークポイント、データブレークポイントとも呼ばれる。

(gdb) watch *0xbffff76c
(gdb) rwatch *0xbffff76c
(gdb) awatch *0xbffff76c

catchを使うと、特定のシステムコール呼び出し時にブレークできる(catchpoint)。

(gdb) catch syscall write

セットされているブレークポイントを表示したり、削除したりする。

(gdb) info breakpoints
(gdb) i b
(gdb) delete breakpoints 1
(gdb) d 1

break時に特定のコマンド列を実行させる。 silentでブレーク時の表示抑制、continueまたはcで止めずに処理の継続ができる。 endで入力終了。 たとえば、次の例ではブレークのたびにalレジスタに入っているascii文字を表示し、処理を継続させる。

(gdb) commands 1
>silent
>printf "%c", $al
>c
>end

特定のブレークポイントを指定した回数だけ無視する(ここでは1番を10回)。

(gdb) ignore 1 10

ステップ実行

関数の中に入らない。

(gdb) nexti
(gdb) ni

関数の中に入る。

(gdb) stepi
(gdb) si

処理を継続する。

(gdb) continue
(gdb) c

関数を抜けるまで処理を継続する。

(gdb) finish
(gdb) fin

特定のアドレスに到達するまで処理を継続する。

(gdb) until *main+43
(gdb) u *main+43

レジスタの値を表示する

(gdb) info registers
(gdb) i r

表示させるレジスタを絞ることもできる。

(gdb) i r esp ebp

値同士の計算をする

16進表示するには/xをつける必要がある。

(gdb) p/x $ebp-$esp

メモリの内容を表示する

32bit単位で、100個16進表示する。

(gdb) x/100xw $esp

64bit単位で、100個16進表示する。

(gdb) x/100xg $esp

espレジスタの位置に置かれたアドレスの先にあるデータを表示する。

(gdb) x/4xw {int}$esp
(gdb) x/4xw *(int *)$esp

レジスタの値を書き換える

(gdb) set $eax=42

メモリの中身を書き換える

ワード単位で書き換える場合。

(gdb) set {int}0x804a000=42

バイト単位で書き換える場合。

(gdb) set {char}0x804a000='A'

メモリの中身を検索する

実行ファイルが置かれたメモリ領域0x8048000-0x804b000から、__libc_start_main関数のアドレス0xb7e423e0を探す。

(gdb) find 0x8048000,0x804b000-1,0xb7e423e0
0x804a00c <__libc_start_main@got.plt>
1 pattern found.

バイト列を探す場合は、/bをつける。

(gdb) find/b 0x8048000,0x804b000-1,0xe4,0xb7
0x804a00e <__libc_start_main@got.plt+2>
1 pattern found.

実際に入っている値を表示する。

(gdb) x/wx $_-2
0x804a00c <__libc_start_main@got.plt>:  0xb7e423e0

特定のアドレスにジャンプする(eipを変更する)

ジャンプして処理を継続する。

(gdb) jump *main+100

処理を止めたまま、eipのみを変える。

(gdb) set $pc=*main+100

バックトレースを表示する

(gdb) backtrace
(gdb) bt

特定のフレームに移動する。

(gdb) frame 4
(gdb) f 4

上下のフレームに移動する。

(gdb) up
(gdb) down

メモリ配置を確認する

(gdb) info proc mappings
(gdb) i proc map

各メモリ領域のアクセス保護属性も確認するには、実行中プロセスのPIDを調べた上で/proc/$PID/mapsを直接表示させる。

(gdb) i proc
(gdb) shell cat /proc/[PID]/maps

また、次のようにすると、各アドレスがどのライブラリのどのセクションに対応しているかを調べることができる。

(gdb) i files

forkした子プロセスを追いかける

デフォルトでは追いかけないようになっている。

(gdb) set follow-fork-mode child

子プロセスのmain関数でブレークするには次のようにする。

(gdb) set follow-fork-mode child
(gdb) b main
(gdb) r
(gdb) c

ASLRを有効にする

デフォルトではASLRは無効化されている。

(gdb) set disable-randomization off

Reverse Execution

まず最初に、recordコマンドで記録を開始しておく。

(gdb) record

以降、rni/rsiで前の状態に戻れるようになる。

(gdb) reverse-nexti
(gdb) rni
(gdb) reverse-stepi
(gdb) rsi

異常終了時におけるレジスタ、メモリの内容を調べる

あらかじめ、プロセスが異常終了した際にコアダンプを書き出すように設定しておく。

$ ulimit -c unlimited

プロセスに対応するコアダンプを与えて起動する。

$ gdb a.out core

関連リンク