情報処理のWeb教科書―IPA情報処理試験対策のお供に!

gdbコマンドの使い方―レジスタ表示やcore解析など

トップ プログラミング C言語 中級 gdb

gdbは、デバックツールの1つで、ブレークポイントというコードの位置を設定し、そこで一時停止し、変数の値を確認するなどプログラマの顕微鏡みたいなツールです。アセンブリ言語の解説も行いつつ、レジスタ表示やcore解析などgdbコマンドの使い方についてまとめています。

▲記事トップへ

目次

この記事の目次です。

1. gdbとは
2. レジスタ表示などgdbコマンドの簡単な使い方
3. gdbコマンドでの逆アセンブリ結果の表示方法
4. アセンブリ言語についての補足
5. gdbコマンドでcore(コアダンプ)解析する方法
6. gdbコマンドの一覧

もっと知識を広げるための参考
更新履歴

1. gdbとは

gdbは、デバックツールの1つで、ブレークポイントというコードの位置を設定し、そこで一時停止し、変数の値を確認するなどプログラマの顕微鏡みたいなツールです。 gdbを使用しながらC言語やコンピュータの理解を深めていきたいと思います。

2. レジスタ表示などgdbコマンドの簡単な使い方

基礎知識を補いながら、gdbの簡単な使い方を説明していきます。

プロセッサ

プロセッサは、コンピュータの中でプログラムに記述された命令セットを実行するためのハードウェアです。演算装置、命令や情報を格納するレジスタ、周辺回路などから構成されます。 プロセッサはCPUやGPUなどの総称で、システムの中心となるものをCPU、グラフィックボードで使われるプロセッサをGPUといいます。 プロセッサのアーキテクチャにはx86などがあります。

x86

かなり昔に、Intelが8086というCPUを開発・製造しました。 そのファミリ製品として80286、80386、80486・・・と改良版が開発されてきました。 これら製品の86の前の数字をxとしてファミリ製品を呼ぶようになりました。X86はIntelのCPUの製品ファミリを指す言葉が生まれ、そのプロセッサのアーキテクチャがX86アーキテクチャと呼ばれます。 このアーキテクチャはAMDなどIntel以外でも採用され、今日に至ります。

レジスタ

レジスタとは、プロセッサなどが内蔵する記憶回路です。 x86プロセッサには、プロセッサ用の内部変数ともいうべきレジスタが複数搭載されています。

サンプルソース(hello.c)

Hello World!!を10回表示するプログラムをGDBで見ていきたいと思います。サンプルソースは以下です。

#include <stdio.h>
int main() {
        int i;
        for (i = 0; i < 10; i++) {
                printf("Hello world!!\n");
        }
        return 0;
}

gdbの使用例

gdbの使用例です。環境はCentOS7で仮想環境で起動しています。

# gcc hello.c
# gdb -q ./a.out
Reading symbols from /root/work/a.out...(no debugging symbols found)...done.
(gdb) break main
Breakpoint 1 at 0x400521
(gdb) run
Starting program: /root/work/./a.out

Breakpoint 1, 0x0000000000400521 in main ()
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.6.x86_64
(gdb) info registers
rax            0x40051d 4195613
rbx            0x0      0
rcx            0x400550 4195664
rdx            0x7fffffffe5c8   140737488348616
rsi            0x7fffffffe5b8   140737488348600
rdi            0x1      1
rbp            0x7fffffffe4d0   0x7fffffffe4d0
rsp            0x7fffffffe4d0   0x7fffffffe4d0
r8             0x7ffff7dd5e80   140737351868032
r9             0x0      0
r10            0x7fffffffe020   140737488347168
r11            0x7ffff7a303a0   140737348043680
r12            0x400430 4195376
r13            0x7fffffffe5b0   140737488348592
r14            0x0      0
r15            0x0      0
rip            0x400521 0x400521 <main+4>
eflags         0x246    [ PF ZF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) quit
A debugging session is active.

        Inferior 1 [process 1727] will be killed.

Quit anyway? (y or n) y

gdbコマンド

上記例ではgdbのqオプションで実行形式ファイルのa.outを起動しています。 その後、breakでmain関数にブレークポイントを設定して、runでプログラムをブレークポイントまで実行し、 その後、info registersでブレークポイントのところではレジスタの状態を表示し、quitで終了しています。

表示されたレジスタ

表示されたレジスタについて解説します。

主に一時変数に用いられる汎用レジスタ

主にポインタやインデックスに用いられる汎用レジスタ

実行されようとしている現在の命令が格納されているアドレス

比較演算やメモリのセグメント化など

3. gdbコマンドでの逆アセンブリ結果の表示方法

今度は逆アセンブリ結果を表示してみます。

サンプルソース(hello.c)

ここでもHello World!!を10回表示するプログラムをgdbコマンドで見ていきたいと思います。サンプルソースは以下です。

#include <stdio.h>
int main() {
        int i;
        for (i = 0; i < 10; i++) {
                printf("Hello world!!\n");
        }
        return 0;
}

gdbの使用例

gdbコマンドの使用例です。環境はCentOS7で仮想環境で起動しています。 gccはgオプションでデバック情報を付加してコンパイルしています。

# gcc -g hello.c
# gdb -q ./a.out
Reading symbols from /root/work/a.out...done.
(gdb) list
1       #include <stdio.h>
2       int main() {
3               int i;
4               for (i = 0; i < 10; i++) {
5                       printf("Hello world!!\n");
6               }
7               return 0;
8       }
9
(gdb) set disassembly-flavor intel
(gdb) disassemble main
Dump of assembler code for function main:
   0x000000000040051d <+0>:     push   rbp
   0x000000000040051e <+1>:     mov    rbp,rsp
   0x0000000000400521 <+4>:     sub    rsp,0x10
   0x0000000000400525 <+8>:     mov    DWORD PTR [rbp-0x4],0x0
   0x000000000040052c <+15>:    jmp    0x40053c <main+31>
   0x000000000040052e <+17>:    mov    edi,0x4005e0
   0x0000000000400533 <+22>:    call   0x400400 <puts@plt>
   0x0000000000400538 <+27>:    add    DWORD PTR [rbp-0x4],0x1
   0x000000000040053c <+31>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x0000000000400540 <+35>:    jle    0x40052e <main+17>
   0x0000000000400542 <+37>:    mov    eax,0x0
   0x0000000000400547 <+42>:    leave
   0x0000000000400548 <+43>:    ret
End of assembler dump.
(gdb)

gdbコマンドについて

listは、gccのgオプションで付加したデバック情報を表示します。 set disassembly-flavor intelは、intel形式で表示しています。 disassembleは、逆アセンブリ結果を表示するコマンドです。

簡単に動作について解説すると以下のようになります。

  1. 「push rbp」でrbp(ベースポインタ)をスタックに設定して
  2. 「mov rbp, rsp」でrsp(スタックポインタ)からrbpに値を移した後、
  3. 「sub rsp,0x10」でrspから0x10引いた値をrspに格納し、
  4. 「mov DWORD PTR [rbp-0x4],0x0」でrbpの値から4引いたところからのアドレス、4バイトに0x0の値を代入し、
  5. 「jmp 0x40053c <main+31>」で0x40053c <main+31>にジャンプして、
  6. 「cmp DWORD PTR [rbp-0x4],0x9」「jle 0x40052e <main+17>」でDWORD PTR [rbp-0x4],0x9が9以下の場合は 0x40052e <main+17>にジャンプ、
  7. 「mov edi,0x4005e0」で0x4005e0(「Hello World!!」という文字列があるアドレス)からedi※に値を移す、
  8. あとは、callでこれを引数にして出力して、addで変数iの値をプラス1して・・を繰り返して最後にeaxに0を設定してleave、retと処理を終了

※ediは、ディストネーションポインタ、メモリのアドレスを覚えておくのに使います。

4. アセンブリ言語についての補足

アセンブリ言語について補足していきます。

アセンブリ言語とC言語

gdbで、レジスタの値を表示すると16進表記のバイト群が表示されます。 これがx86プロセッサ向けのマシン語命令です。

16進数値で表示されていますが、CPU上では2進数の0と1の羅列として扱われます。 コンピュータが誕生したころは、この0と1の羅列でプログラミングが行われていたわけですが、見やすくないのでアセンブリ言語が登場しました。 アセンブリ言語は、マシン語の命令を覚えやすい略語(ニーモニック)に置き換えただけのものです。 その後さらにわかりやすくしたC言語などの言語が登場しました。

アセンブリ言語の書式

アセンブリ言語の命令の書式は以下のようにシンプルです。

命令語 <操作の対象>, <参照元>

操作の対象と参照元は、レジスタやメモリアドレス、値のいずれかを指定します。命令語はmovやsubなどのニーモニックと意味が分かりやすくした単語が使われます。

push

pushは、スタックにデータを格納する命令です。

push rbp

push rbp命令は、関数の最初に必ず書かれます。rbpはベースポインタです。 rbpは、簡単にいうと「今実行中の関数が使用しているスタック領域の底」です。底だからベースです。

mov

movは、参照元から操作の対象に向けたデータの移送の命令です。

mov    rbp,rsp

実行例のmovの命令では、rspレジスタからrbpレジスタに値を移送する命令になります。

rspはスタックポインタ、rbpはベースポインタで、主にポインタやインデックスに用いられる汎用レジスタです。

sub

subは格納先から読込元を引いて(減算)、結果を格納先に格納します。

 sub <格納先>, <読込元>

以下の例の場合、rsp(スタックポインタ)から0x10を引いて(減算)、結果をrspに格納しています。

sub    rsp,0x10

DWORD PTR[]

DWORDは4バイトのことです。主レジスタ、ポインタレジスタにアドレスを入れると、DWORD PTR[rbp] のようにして参照できます。

以下の例の場合、rbp(ベースポインタ)の値から4引いた値をアドレスとして用い、そのアドレスから開始している4バイトの値として、0x0を代入するという命令になります。

mov    DWORD PTR [rbp-0x4],0x0

jmp

jmpは<操作の対象>へジャンプさせる命令です。

 jump <操作の対象>

以下の例の場合、0x40053c <main+31>へジャンプする処理になります。

jmp    0x40053c <main+31>

cmp、jle

cmpは比較で、jleは「直前の比較結果が等しいか、それ未満である場合に分岐する」という命令です。

以下の場合、DWORD PTR [rbp-0x4]が0x9以下の場合、 0x40052e <main+17>にジャンプするという命令になります。

    cmp    DWORD PTR [rbp-0x4],0x9
    jle    0x40052e <main+17>

5. gdbコマンドでcore(コアダンプ)解析する方法

LinuxやUNIXでアプリケーションがクラッシュするとcoreというファイルが生成されます。 これをcore(コアダンプ)と読んだりしますが、このファイルにはクラッシュした原因を解析するために有用な情報が含まれています。

gdbコマンドを使用することでこのcore(コアダンプ)が解析できます。 gdbはオープンソースの一般的なデバッガですが、この用途で使用する人の方が多いのではないかと思います。

gdbコマンドの使い方は次の通りです。

gdb <実行プログラム> <コアファイル>

6. gdbコマンドの一覧

以下は、よく使うgdbコマンドの一覧です。

コマンド省略形効果
runデバッグ対象プログラムを実行する
backtracebtバックトレースを表示する
frame NfフレームNに移動する
listl現在の関数のソースコードを表示する
print EXPRp式EXPRの値を表示する
continuec続きを実行する
quitqgdbを終了する

gdbコマンドの引数

以下は、gdbコマンドの引数(コマンド実行時のオプション)です。「-help」で表示できます。

$ gdb -help
This is the GNU debugger.  Usage:

    gdb [options] [executable-file [core-file or process-id]]
    gdb [options] --args executable-file [inferior-arguments ...]
    gdb [options] [--python|-P] script-file [script-arguments ...]

Options:

  --args             Arguments after executable-file are passed to inferior
  -b BAUDRATE        Set serial port baud rate used for remote debugging.
  --batch            Exit after processing options.
  --batch-silent     As for --batch, but suppress all gdb stdout output.
  --return-child-result
                     GDB exit code will be the child's exit code.
  --cd=DIR           Change current directory to DIR.
  --command=FILE, -x Execute GDB commands from FILE.
  --eval-command=COMMAND, -ex
                     Execute a single GDB command.
                     May be used multiple times and in conjunction
                     with --command.
  --init-command=FILE, -ix Like -x but execute it before loading inferior.
  --init-eval-command=COMMAND, -iex Like -ex but before loading inferior.
  --core=COREFILE    Analyze the core dump COREFILE.
  --pid=PID          Attach to running process PID.
  --dbx              DBX compatibility mode.
  --directory=DIR    Search for source files in DIR.
  --exec=EXECFILE    Use EXECFILE as the executable.
  --fullname         Output information used by emacs-GDB interface.
  --help             Print this message.
  --interpreter=INTERP
                     Select a specific interpreter / user interface
  -l TIMEOUT         Set timeout in seconds for remote debugging.
  --nw               Do not use a window interface.
  --nx               Do not read any .gdbinit files.
  --nh               Do not read .gdbinit file from home directory.
  --python, -P       Following argument is Python script file; remaining
                     arguments are passed to script.
  --quiet            Do not print version number on startup.
  --readnow          Fully read symbol files on first access.
  --readnever        Do not read symbol files.
  --se=FILE          Use FILE as symbol file and executable file.
  --symbols=SYMFILE  Read symbols from SYMFILE.
  --tty=TTY          Use TTY for input/output by the program being debugged.
  --tui              Use a terminal user interface.
  --version          Print version information and then exit.
  -w                 Use a window interface.
  --write            Set writing into executable and core files.
  --xdb              XDB compatibility mode.

At startup, GDB reads the following init files and executes their commands:
   * system-wide init file: /etc/gdbinit

For more information, type "help" from within GDB, or consult the
GDB manual (available as on-line info or a printed manual).
Report bugs to "http://www.gnu.org/software/gdb/bugs/".

もっと知識を広げるための参考

更新履歴

戻る

スポンサーリンク

情報処理の知識体系

各試験の問題と解説

ランダム出題・採点アプリ

プログラミング

スポンサーリンク