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'