情報処理のWeb教科書―IPA情報処理試験対策のお供に!
gdbは、デバックツールの1つで、ブレークポイントというコードの位置を設定し、そこで一時停止し、変数の値を確認するなどプログラマの顕微鏡みたいなツールです。アセンブリ言語の解説も行いつつ、レジスタ表示やcore解析などgdbコマンドの使い方についてまとめています。
この記事の目次です。
1. gdbとは
2. レジスタ表示などgdbコマンドの簡単な使い方
3. gdbコマンドでの逆アセンブリ結果の表示方法
4. アセンブリ言語についての補足
5. gdbコマンドでcore(コアダンプ)解析する方法
6. gdbコマンドの一覧
gdbは、デバックツールの1つで、ブレークポイントというコードの位置を設定し、そこで一時停止し、変数の値を確認するなどプログラマの顕微鏡みたいなツールです。 gdbを使用しながらC言語やコンピュータの理解を深めていきたいと思います。
基礎知識を補いながら、gdbの簡単な使い方を説明していきます。
プロセッサは、コンピュータの中でプログラムに記述された命令セットを実行するためのハードウェアです。演算装置、命令や情報を格納するレジスタ、周辺回路などから構成されます。 プロセッサはCPUやGPUなどの総称で、システムの中心となるものをCPU、グラフィックボードで使われるプロセッサをGPUといいます。 プロセッサのアーキテクチャにはx86などがあります。
かなり昔に、Intelが8086というCPUを開発・製造しました。 そのファミリ製品として80286、80386、80486・・・と改良版が開発されてきました。 これら製品の86の前の数字をxとしてファミリ製品を呼ぶようになりました。X86はIntelのCPUの製品ファミリを指す言葉が生まれ、そのプロセッサのアーキテクチャがX86アーキテクチャと呼ばれます。 このアーキテクチャはAMDなどIntel以外でも採用され、今日に至ります。
レジスタとは、プロセッサなどが内蔵する記憶回路です。 x86プロセッサには、プロセッサ用の内部変数ともいうべきレジスタが複数搭載されています。
Hello World!!を10回表示するプログラムをGDBで見ていきたいと思います。サンプルソースは以下です。
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
printf("Hello world!!\n");
}
return 0;
}
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のqオプションで実行形式ファイルのa.outを起動しています。 その後、breakでmain関数にブレークポイントを設定して、runでプログラムをブレークポイントまで実行し、 その後、info registersでブレークポイントのところではレジスタの状態を表示し、quitで終了しています。
表示されたレジスタについて解説します。
今度は逆アセンブリ結果を表示してみます。
ここでもHello World!!を10回表示するプログラムをgdbコマンドで見ていきたいと思います。サンプルソースは以下です。
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
printf("Hello world!!\n");
}
return 0;
}
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)
listは、gccのgオプションで付加したデバック情報を表示します。 set disassembly-flavor intelは、intel形式で表示しています。 disassembleは、逆アセンブリ結果を表示するコマンドです。
簡単に動作について解説すると以下のようになります。
※ediは、ディストネーションポインタ、メモリのアドレスを覚えておくのに使います。
アセンブリ言語について補足していきます。
gdbで、レジスタの値を表示すると16進表記のバイト群が表示されます。 これがx86プロセッサ向けのマシン語命令です。
16進数値で表示されていますが、CPU上では2進数の0と1の羅列として扱われます。 コンピュータが誕生したころは、この0と1の羅列でプログラミングが行われていたわけですが、見やすくないのでアセンブリ言語が登場しました。 アセンブリ言語は、マシン語の命令を覚えやすい略語(ニーモニック)に置き換えただけのものです。 その後さらにわかりやすくしたC言語などの言語が登場しました。
アセンブリ言語の命令の書式は以下のようにシンプルです。
命令語 <操作の対象>, <参照元>
操作の対象と参照元は、レジスタやメモリアドレス、値のいずれかを指定します。命令語はmovやsubなどのニーモニックと意味が分かりやすくした単語が使われます。
pushは、スタックにデータを格納する命令です。
push rbp
push rbp命令は、関数の最初に必ず書かれます。rbpはベースポインタです。 rbpは、簡単にいうと「今実行中の関数が使用しているスタック領域の底」です。底だからベースです。
movは、参照元から操作の対象に向けたデータの移送の命令です。
mov rbp,rsp
実行例のmovの命令では、rspレジスタからrbpレジスタに値を移送する命令になります。
rspはスタックポインタ、rbpはベースポインタで、主にポインタやインデックスに用いられる汎用レジスタです。
subは格納先から読込元を引いて(減算)、結果を格納先に格納します。
sub <格納先>, <読込元>
以下の例の場合、rsp(スタックポインタ)から0x10を引いて(減算)、結果をrspに格納しています。
sub rsp,0x10
DWORDは4バイトのことです。主レジスタ、ポインタレジスタにアドレスを入れると、DWORD PTR[rbp] のようにして参照できます。
以下の例の場合、rbp(ベースポインタ)の値から4引いた値をアドレスとして用い、そのアドレスから開始している4バイトの値として、0x0を代入するという命令になります。
mov DWORD PTR [rbp-0x4],0x0
jmpは<操作の対象>へジャンプさせる命令です。
jump <操作の対象>
以下の例の場合、0x40053c <main+31>へジャンプする処理になります。
jmp 0x40053c <main+31>
cmpは比較で、jleは「直前の比較結果が等しいか、それ未満である場合に分岐する」という命令です。
以下の場合、DWORD PTR [rbp-0x4]が0x9以下の場合、 0x40052e <main+17>にジャンプするという命令になります。
cmp DWORD PTR [rbp-0x4],0x9
jle 0x40052e <main+17>
LinuxやUNIXでアプリケーションがクラッシュするとcoreというファイルが生成されます。 これをcore(コアダンプ)と読んだりしますが、このファイルにはクラッシュした原因を解析するために有用な情報が含まれています。
gdbコマンドを使用することでこのcore(コアダンプ)が解析できます。 gdbはオープンソースの一般的なデバッガですが、この用途で使用する人の方が多いのではないかと思います。
gdbコマンドの使い方は次の通りです。
gdb <実行プログラム> <コアファイル>
以下は、よく使うgdbコマンドの一覧です。
| コマンド | 省略形 | 効果 |
| run | デバッグ対象プログラムを実行する | |
| backtrace | bt | バックトレースを表示する |
| frame N | f | フレームNに移動する |
| list | l | 現在の関数のソースコードを表示する |
| print EXPR | p | 式EXPRの値を表示する |
| continue | c | 続きを実行する |
| quit | q | 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/".
Windowsでも使えるフリーソフトのgccコンパイラで学ぶC言語入門用のオリジナルテキストをまとめています。
プログラミング作法などプログラミングについてまとめています。Python、C言語、流れ図などプログラミングのオリジナル入門テキスト問形でまとめています。
Copyright (C) 2010-2023 情報処理のWeb教科書. All Rights Reserved.