読者です 読者をやめる 読者になる 読者になる

GDB

GDBの基本的な操作メモ。

サンプル用のプログラム。(sample.c)

#include <stdio.h>

int sum(int a, int b)
{
  int c;
  c = a + b;
  return c;
}


int main(int argc, char** argv)
{
  int a = 10;
  int b = 20;
  int c;

  c = sum(a, b);
  printf("%d\n", c);
}

準備

デバック用にコンパイル。(実行ファイル、a.outが生成される。)

$ gcc -g sample.c

起動

GDBの起動。

$ gdb ./a.out

-qオプションを付けると起動時の煩わしい情報が表示されなくなる。

ソースコードの表示

list、でソースコードの表示。省略形は l。

(gdb) list
4       {
5         int c;
6         c = a + b;
7         return c;
8       }
9
10
11      int main(int argc, char** argv)
12      {
13        int a = 10;

詳しいオプション等はソース上の行の表示なんかがわかりやすそう。

アセンブリ言語の表示

disassemble <関数名>、でその関数のアセンブリ言語を表示。省略形 disas。

(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004df <+0>:     push   %rbp
   0x00000000004004e0 <+1>:     mov    %rsp,%rbp
   0x00000000004004e3 <+4>:     sub    $0x20,%rsp
   0x00000000004004e7 <+8>:     mov    %edi,-0x14(%rbp)
   0x00000000004004ea <+11>:    mov    %rsi,-0x20(%rbp)
   0x00000000004004ee <+15>:    movl   $0xa,-0xc(%rbp)
   0x00000000004004f5 <+22>:    movl   $0x14,-0x8(%rbp)
   0x00000000004004fc <+29>:    mov    -0x8(%rbp),%edx
   0x00000000004004ff <+32>:    mov    -0xc(%rbp),%eax
   0x0000000000400502 <+35>:    mov    %edx,%esi
   0x0000000000400504 <+37>:    mov    %eax,%edi
   0x0000000000400506 <+39>:    callq  0x4004c4 <sum>
   0x000000000040050b <+44>:    mov    %eax,-0x4(%rbp)
   0x000000000040050e <+47>:    mov    $0x400628,%eax
   0x0000000000400513 <+52>:    mov    -0x4(%rbp),%edx
   0x0000000000400516 <+55>:    mov    %edx,%esi
   0x0000000000400518 <+57>:    mov    %rax,%rdi
   0x000000000040051b <+60>:    mov    $0x0,%eax
   0x0000000000400520 <+65>:    callq  0x4003b8 <printf@plt>
   0x0000000000400525 <+70>:    leaveq
   0x0000000000400526 <+71>:    retq
End of assembler dump.
(gdb) disas sum
Dump of assembler code for function sum:
   0x00000000004004c4 <+0>:     push   %rbp
   0x00000000004004c5 <+1>:     mov    %rsp,%rbp
   0x00000000004004c8 <+4>:     mov    %edi,-0x14(%rbp)
   0x00000000004004cb <+7>:     mov    %esi,-0x18(%rbp)
   0x00000000004004ce <+10>:    mov    -0x18(%rbp),%eax
   0x00000000004004d1 <+13>:    mov    -0x14(%rbp),%edx
   0x00000000004004d4 <+16>:    lea    (%rdx,%rax,1),%eax
   0x00000000004004d7 <+19>:    mov    %eax,-0x4(%rbp)
   0x00000000004004da <+22>:    mov    -0x4(%rbp),%eax
   0x00000000004004dd <+25>:    leaveq
   0x00000000004004de <+26>:    retq
End of assembler dump.

1行に1命令で表示されている。
左端の"0x00000000004004df"はその命令列が格納されているメモリアドレス(16進数)。
その次の"<+0>"は関数先頭の命令列のアドレスからその命令列のアドレスまでのオフセット。
これから命令列の長さもわかる。
例えば、sumの1つ目の命令列"push %rbp"は1バイト命令。sumの2つ目の命令列"mov %rsp,%rbp"は3バイト命令。

アセンプリ言語の表示形式変更

set disassembly-flavor <フレーバー名>、でアセンブリ言語のフォーマット(フレーバー)を変更。
デフォルトはattフレーバー。
intelフレーバーもある。

(gdb) set disassembly-flavor att
(gdb) disas sum
Dump of assembler code for function sum:
   0x00000000004004c4 <+0>:     push   %rbp
   0x00000000004004c5 <+1>:     mov    %rsp,%rbp
   0x00000000004004c8 <+4>:     mov    %edi,-0x14(%rbp)
   0x00000000004004cb <+7>:     mov    %esi,-0x18(%rbp)
   0x00000000004004ce <+10>:    mov    -0x18(%rbp),%eax
   0x00000000004004d1 <+13>:    mov    -0x14(%rbp),%edx
   0x00000000004004d4 <+16>:    lea    (%rdx,%rax,1),%eax
   0x00000000004004d7 <+19>:    mov    %eax,-0x4(%rbp)
   0x00000000004004da <+22>:    mov    -0x4(%rbp),%eax
   0x00000000004004dd <+25>:    leaveq
   0x00000000004004de <+26>:    retq
End of assembler dump.
(gdb) set disassembly-flavor intel
(gdb) disas sum
Dump of assembler code for function sum:
   0x00000000004004c4 <+0>:     push   rbp
   0x00000000004004c5 <+1>:     mov    rbp,rsp
   0x00000000004004c8 <+4>:     mov    DWORD PTR [rbp-0x14],edi
   0x00000000004004cb <+7>:     mov    DWORD PTR [rbp-0x18],esi
   0x00000000004004ce <+10>:    mov    eax,DWORD PTR [rbp-0x18]
   0x00000000004004d1 <+13>:    mov    edx,DWORD PTR [rbp-0x14]
   0x00000000004004d4 <+16>:    lea    eax,[rdx+rax*1]
   0x00000000004004d7 <+19>:    mov    DWORD PTR [rbp-0x4],eax
   0x00000000004004da <+22>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004dd <+25>:    leave
   0x00000000004004de <+26>:    ret
End of assembler dump.

これは好みの問題?
どっちが主流なんだろう?

ブレークポイントと実行

break <関数名>、でその関数実行前のあたりにブレークポイントを設定。省略形はb。
run、でブレークポイントを設定したところまで実行。
nexti、で1命令実行。省略形はni。
nextiは関数呼び出しがあったときに関数の中に入らない。
関数の中に入りたかったら、step(省略形 s)。

(gdb) b main
(gdb) run
(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004df <+0>:     push   rbp
   0x00000000004004e0 <+1>:     mov    rbp,rsp
   0x00000000004004e3 <+4>:     sub    rsp,0x20
   0x00000000004004e7 <+8>:     mov    DWORD PTR [rbp-0x14],edi
   0x00000000004004ea <+11>:    mov    QWORD PTR [rbp-0x20],rsi
=> 0x00000000004004ee <+15>:    mov    DWORD PTR [rbp-0xc],0xa
   0x00000000004004f5 <+22>:    mov    DWORD PTR [rbp-0x8],0x14
   0x00000000004004fc <+29>:    mov    edx,DWORD PTR [rbp-0x8]
   0x00000000004004ff <+32>:    mov    eax,DWORD PTR [rbp-0xc]
   0x0000000000400502 <+35>:    mov    esi,edx
   0x0000000000400504 <+37>:    mov    edi,eax
   0x0000000000400506 <+39>:    call   0x4004c4 <sum>
   0x000000000040050b <+44>:    mov    DWORD PTR [rbp-0x4],eax
   0x000000000040050e <+47>:    mov    eax,0x400628
   0x0000000000400513 <+52>:    mov    edx,DWORD PTR [rbp-0x4]
   0x0000000000400516 <+55>:    mov    esi,edx
   0x0000000000400518 <+57>:    mov    rdi,rax
   0x000000000040051b <+60>:    mov    eax,0x0
   0x0000000000400520 <+65>:    call   0x4003b8 <printf@plt>
   0x0000000000400525 <+70>:    leave
   0x0000000000400526 <+71>:    ret
End of assembler dump.
(gdb) ni
(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004df <+0>:     push   rbp
   0x00000000004004e0 <+1>:     mov    rbp,rsp
   0x00000000004004e3 <+4>:     sub    rsp,0x20
   0x00000000004004e7 <+8>:     mov    DWORD PTR [rbp-0x14],edi
   0x00000000004004ea <+11>:    mov    QWORD PTR [rbp-0x20],rsi
   0x00000000004004ee <+15>:    mov    DWORD PTR [rbp-0xc],0xa
=> 0x00000000004004f5 <+22>:    mov    DWORD PTR [rbp-0x8],0x14
   0x00000000004004fc <+29>:    mov    edx,DWORD PTR [rbp-0x8]
   0x00000000004004ff <+32>:    mov    eax,DWORD PTR [rbp-0xc]
   0x0000000000400502 <+35>:    mov    esi,edx
   0x0000000000400504 <+37>:    mov    edi,eax
   0x0000000000400506 <+39>:    call   0x4004c4 <sum>
   0x000000000040050b <+44>:    mov    DWORD PTR [rbp-0x4],eax
   0x000000000040050e <+47>:    mov    eax,0x400628
   0x0000000000400513 <+52>:    mov    edx,DWORD PTR [rbp-0x4]
   0x0000000000400516 <+55>:    mov    esi,edx
   0x0000000000400518 <+57>:    mov    rdi,rax
   0x000000000040051b <+60>:    mov    eax,0x0
   0x0000000000400520 <+65>:    call   0x4003b8 <printf@plt>
   0x0000000000400525 <+70>:    leave
   0x0000000000400526 <+71>:    ret
End of assembler dump.

disasでアセンブリ言語を表示すると、次にどの命令を実行するかまで表示してくれる。
runしたあとに次の命令列が<+11>になっているのは、<+0>から<+11>までの命令列はCで書いた関数内の処理を実行する前に必要な関数呼び出し時の処理(関数プロローグ)だから。

レジスタの値を表示

info registors、でGDBで実行中のプログラムのレジスタの現在の値を表示する。省略形はi r。
info registors <レジスタ名1> <レジスタ名2> ....、でレジスタ名を指定できる。

(gdb) i r
rax            0xa      10
rbx            0x0      0
rcx            0x0      0
rdx            0x14     20
rsi            0x14     20
rdi            0xa      10
rbp            0x7fffffffe3c0   0x7fffffffe3c0
rsp            0x7fffffffe3c0   0x7fffffffe3c0
r8             0x3c4b98f300     258966352640
r9             0x3c4b20eac0     258958486208
r10            0x7fffffffe240   140737488347712
r11            0x3c4b61ec60     258962746464
r12            0x4003e0 4195296
r13            0x7fffffffe4d0   140737488348368
r14            0x0      0
r15            0x0      0
rip            0x4004ce 0x4004ce <sum+10>
eflags         0x202    [ IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) i r rbp rsp rip
rbp            0x7fffffffe3c0   0x7fffffffe3c0
rsp            0x7fffffffe3c0   0x7fffffffe3c0
rip            0x4004ce 0x4004ce <sum+10>

メモリの内容表示

x/[表示数]<表示形式>[表示サイズ] <アドレス>、で<アドレス>から始まるメモリの中身を表示。
表示形式は0(8進数)、x(16進数)、u(符号なし10進数)、t(2進数)、c(ASCII文字表記)等がある。
表示サイズはb(バイト、1バイト)、h(ハーフワード、2バイト)、w(ワード、4バイト)等がある。
表示数は何個表示するか。

(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004df <+0>:     push   rbp
   0x00000000004004e0 <+1>:     mov    rbp,rsp
   0x00000000004004e3 <+4>:     sub    rsp,0x20
   0x00000000004004e7 <+8>:     mov    DWORD PTR [rbp-0x14],edi
   0x00000000004004ea <+11>:    mov    QWORD PTR [rbp-0x20],rsi
   0x00000000004004ee <+15>:    mov    DWORD PTR [rbp-0xc],0xa
   0x00000000004004f5 <+22>:    mov    DWORD PTR [rbp-0x8],0x14
=> 0x00000000004004fc <+29>:    mov    edx,DWORD PTR [rbp-0x8]
   0x00000000004004ff <+32>:    mov    eax,DWORD PTR [rbp-0xc]
   0x0000000000400502 <+35>:    mov    esi,edx
   0x0000000000400504 <+37>:    mov    edi,eax
   0x0000000000400506 <+39>:    call   0x4004c4 <sum>
   0x000000000040050b <+44>:    mov    DWORD PTR [rbp-0x4],eax
   0x000000000040050e <+47>:    mov    eax,0x400628
   0x0000000000400513 <+52>:    mov    edx,DWORD PTR [rbp-0x4]
   0x0000000000400516 <+55>:    mov    esi,edx
   0x0000000000400518 <+57>:    mov    rdi,rax
   0x000000000040051b <+60>:    mov    eax,0x0
   0x0000000000400520 <+65>:    call   0x4003b8 <printf@plt>
   0x0000000000400525 <+70>:    leave
   0x0000000000400526 <+71>:    ret
End of assembler dump.
(gdb) x/x $rbp - 0xc
0x7fffffffe3e4: 0x0000000a
(gdb) x/u $rbp - 0xc
0x7fffffffe3e4: 10
(gdb) x/4xb $rbp - 0xc
0x7fffffffe3e4: 0x0a    0x00    0x00    0x00
(gdb) x/x $rbp - 0x8
0x7fffffffe3e8: 0x14
(gdb) x/u $rbp - 0x8
0x7fffffffe3e8: 20

"$rbp - 0xc"で$rbpレジスタの内容をメモリアドレスとして解釈し、そこから0xcだけ引いたアドレス、という意味になる。
"$rbp - 0xc"にはプログラム中でaに代入した10が、"$rbp - 0x8"にはプログラム中でbに代入した20が格納されていることがわかる。
"$rbp - 0xc"の内容をバイトごとに表示すると"0x0a 0x00 0x00 0x00"となっており、4バイトまとめて表示したときと0x0aの位置が入れ替わっている。 このことからこのCPUがリトルエンディアンであることがわかる。