GDBの使い方メモ
Written on 2019-04-28
最近、コンパイラを作ろうとチャレンジしている。コンパイラは基本的にアセンブラコードを出力する。その結果が意図通りに動いてテストに通れば良いが、開発中はそうならない。このアセンブラ出力が意図どおりかどうかをデバッグすることになる。ただし、コンパイラが自作でヘボいので、それが出力するアセンブラは、ソースコードデバッグができない。デバッグ情報が無い実行ファイルをデバッグするのと同じ状況になる。そんな状況でよく使う gdb コマンドをまとめた。あんまり細工していない、素のgdbコマンドを使う助けになると思う。
組み込みRustを書いていた時も、ターゲットにビルドされたバイナリを解析するために、素のgdbを使うことになった。そのような時にも gdb の使い方は役に立つ知識となる。
起動
$ gdb target_executable
ターゲットプログラムの実行
(gdb) run
ターゲットプログラムを実行する。(gdb) run arg1 arg2
コマンドライン引数を付けたい時。target arg1 arg2
を実行しているのと同じ状態になる。(gdb) start
実行を再開してmain()
でブレーク。(gdb) c
continue. 実行継続。
OpenOCDを使ったリモートでバッグ
sudo openocd -f board/st_nucleo_f103rb.cfg
別端末で OpenOCDのサーバを起動する。arm-none-eabi-gdb target/thumbv7m-none-eabi/debug/cubemx
クロスの gdb を、バイナリーファイルを指定して起動する。このバイナリーは、ターゲットにロードしてあるものと同じものだ。(gdb) target remote localhost:3333
localhost:3333 で起動している OpenOCD server に接続する。
設定
(gdb) set disassembly-flavor intel
インテル形式での表示。(gdb) disp/i $pc
ステップ実行で停止した時に次の命令を表示。
ステップ実行
(gdb) n
next line. 次の行を実行。(gdb) s
step. 次の行を実行。関数の中に入る。ライブラリ関数の中に入ってしまった時、s
を押すと(通常はライブラリ関数はデバッグ情報が無いので)ターゲットまで戻ってくる。(gdb) si
アセンブラの1命令を実行。デバッグ情報が無くても、アセンブラを1命令実行できる。(gdb) f
現在の関数を抜けるまで実行。(gdb) u
現在のループを抜けるまで実行。(gdb) ret -1
返り値を -1 として関数を終了。
ブレークポイント
(gdb) b <関数名>
break
と々。(gdb) b *<関数名>+0x12
関数名+オフセット(gdb) b *0x040022
アドレス(gdb) i b
info break point
現在のブレークポイントを表示(gdb) delete <ブレークポイント番号>
ブレークポイントを削除する
表示
(gdb) i r
(gdb) info register
レジスタ値を表示。(gdb) disas
ディスアセンブラ表示。(gdb) bt
backtrace。関数呼び出しのスタックを表示。突然死んだ時に、どのような呼び出しで死んだかがわかる。
通常、バグがあると SEGV などで死ぬので、まずはデバッガ上でロードして(gdb target_program
)して、実行し(run
)、死んだら、bt
でどんな呼び出しで死んだのかを把握する。そこで適切なブレークポイントを設定して(b
)、再実行し(start
)、レジスタi r
、メモリ表示x/????
、ステップ実行(si
)でバグを発生している箇所を追い詰める、というのが通常の手順だろう。
メモリ内容の表示
(gdb) x/32xw 0x89b6f0
0x89b6f0 番地から32ワード(1ワード=32bit)を16進表示(gdb) x/32xw 0x89b6f0 0x89b6f0: 0x00000000 0x00000000 0x00000000 0x00000000 0x89b700: 0x0071f5d0 0x00000000 0x00000021 0x00000000 0x89b710: 0x0089b730 0x00000000 0x00000010 0x00000000 0x89b720: 0x00000000 0x00000000 0x00000091 0x00000000 0x89b730: 0x00000000 0x00000000 0x00000000 0x00000000 0x89b740: 0x00000000 0x00000000 0x00000000 0x00000000 0x89b750: 0x00000000 0x00000000 0x00000000 0x00000000 0x89b760: 0x00000000 0x00000000 0x00000000 0x00000000
(gdb) x/64c $rdi
rdi
レジスタを文字列へのポインタとして、ポインタの指すアドレスから64文字分表示。(gdb) x/64c $rdi 0x6f02c0: 46 '.' 76 'L' 46 '.' 115 's' 116 't' 114 'r' 37 '%' 100 'd' 0x6f02c8: 0 '\000' 117 'u' 110 'n' 100 'd' 101 'e' 102 'f' 105 'i' 110 'n' 0x6f02d0: 101 'e' 100 'd' 32 ' ' 118 'v' 97 'a' 114 'r' 105 'i' 97 'a' 0x6f02d8: 98 'b' 108 'l' 101 'e' 0 '\000' 117 'u' 110 'n' 100 'd' 101 'e' 0x6f02e0: 102 'f' 105 'i' 110 'n' 101 'e' 100 'd' 32 ' ' 102 'f' 117 'u' 0x6f02e8: 110 'n' 99 'c' 116 't' 105 'i' 111 'o' 110 'n' 0 '\000' 115 's' 0x6f02f0: 116 't' 97 'a' 116 't' 101 'e' 109 'm' 101 'e' 110 'n' 116 't' 0x6f02f8: 32 ' ' 101 'e' 120 'x' 112 'p' 114 'r' 101 'e' 115 's' 115 's'