🍃

デバッガを作る(とりあえず、単純なデバッガを動かす)

2021/05/09に公開

はじめに

lldb, gdbのようなデバッガがどのような仕組みで動いているかを知りたくなりました。
簡易的なデバッガを作成しながら理解を深めたいと思いました。
その前に、調べたら簡易的なデバッガがgithub上にあったので
こちらを動かすことから始めてみます。

デバッガとは?

wikipedia(日本語)より

デバッガの機能
多くのデバッガは、大体似たような機能を持つ。

ブレークポイント
<省略>
ステップ実行
<省略>
ステップイン
<省略>
ステップオーバー
<省略>
ステップアウト

英語の方も見てみると

Typically, debuggers offer a query processor, a symbol resolver, an expression > interpreter, and a debug support interface at its top level.[1] Debuggers also offer more sophisticated functions such as running a program step by step
(single-stepping or program animation), stopping (breaking) (pausing the
program to examine the current state) at some event or specified instruction
by means of a breakpoint, and tracking the values of variables.[2] Some
debuggers have the ability to modify program state while it is running. It may also be possible to continue execution at a different location in the program to bypass a crash or logical error.

参考になりそうな情報

https://blog.tartanllama.xyz/writing-a-linux-debugger-setup/

https://medium.com/@lizrice/a-debugger-from-scratch-part-1-7f55417bc85f

http://www.alexonlinux.com/how-debugger-works

これらのページを読めば少し理解が深まりそう。
(まだ、全部読めていない)

単純なサンプル(A Toy Debugger)

github内を探したら、以下のシンプルなデバッガ(A Toy Debugger)が見つかりました。

https://github.com/satabdidas/debugger

こちらを動かしてみましょう。

コードの取得、中身を確認

コード自体はdebugger.chello.cのみです。
デバッガ自体のコードも130行程度です。

$ git clone https://github.com/satabdidas/debugger.git
$ debugger/
$ ls
README.md debugger.c hello.c

debugger.cの中身を見ると、以下のような記載があります。

debugger.c
void run_debugger(pid_t pid) {
  int wait_status;
  unsigned long int addr = 0x80483e9;
  // 省略
  long old_data = set_breakpoint(pid, addr);

ここで記載したアドレス(0x80483e9)に来たら
(hello.cを実行して、アドレス0x80483e9に来たら)
メッセージを出力します。

ここで、hello.cの中身を確認してみます。
中身は以下の様になっています。

hello.c
#include "stdio.h"

int main() {
    printf("Hello before breakpoint\n");
    printf("Hello after breakpoint\n");
    return 0;
}

では、printf("Hello after breakpoint\n");にブレークポイントを
貼ってみます。
("Hello after breakpoint"が出力される前にデバッガの出力をさせてみます。)

アドレスの調査

hello.cを以下のようにコンパイルします。

$ gcc -no-pie -g -o hello hello.c

ここで-no-pieをつけているのは、
ブレークポイントに指定するアドレスを見たいためです。
(-no-pieをつけないと、objdumpで見ると相対アドレスになる)
また、-gオプションを付けているのは
objdumpコマンドで、コードとアドレスを表示させたいためです。

このあと、objdump -d -S helloでmain関数あたりを
確認します。

$ objdump -d -S hello | grep -A 20 "<main>"
0000000000401136 <main>:
#include "stdio.h"

int main() {
  401136:	f3 0f 1e fa          	endbr64 
  40113a:	55                   	push   %rbp
  40113b:	48 89 e5             	mov    %rsp,%rbp
    printf("Hello before breakpoint\n");
  40113e:	48 8d 3d bf 0e 00 00 	lea    0xebf(%rip),%rdi        # 402004 <_IO_stdin_used+0x4>
  401145:	e8 f6 fe ff ff       	callq  401040 <puts@plt>
    printf("Hello after breakpoint\n");
  40114a:	48 8d 3d cb 0e 00 00 	lea    0xecb(%rip),%rdi        # 40201c <_IO_stdin_used+0x1c>
  401151:	e8 ea fe ff ff       	callq  401040 <puts@plt>
    return 0;
  401156:	b8 00 00 00 00       	mov    $0x0,%eax
}
  40115b:	5d                   	pop    %rbp
  40115c:	c3                   	retq   
  40115d:	0f 1f 00             	nopl   (%rax)

0000000000401160 <__libc_csu_init>:


printf("Hello after breakpoint\n");の処理が
アドレス0x40114aから始まっているようです。
なので、debugger.cのブレークポイントの指定の位置を以下のように修正します。

diff --git a/debugger.c b/debugger.c
index 385ec0a..3670383 100644
--- a/debugger.c
+++ b/debugger.c
@@ -98,7 +98,7 @@ unsigned long int get_rip(pid_t pid) {
 
 void run_debugger(pid_t pid) {
   int wait_status;
-  unsigned long int addr = 0x80483e9;
+  unsigned long int addr = 0x40114a;
   /* unsigned long int addr = 0x400534; */
 
   /* Wait for the child to stop first */

これで、debugger.cをビルドします。
(ワーニングが出ますが、とりあえず無視。。。)

$ gcc -o debugger debugger.c

実行して見ましょう。
Hello after breakpointの出力の前に
(Hello before breakpointの後に)
デバッガからのメッセージ出力がされました。

$ ./debugger hello
Setting a breakpoint at 40114a
Value at the address 40114a cb3d8d48
Value at the address 40114a after setting the int 3 opcode cb3d8dcc
Done setting the breatpoint at 40114a

Hello before breakpoint
Hit a breakpoint
Restored the old value cb3d8d48 at the address 40114a
Setting the instruction pointer to address 40114a
Done setting the instruction pointer

Hello after breakpoint
Child process exited

まとめ

とりあえず、デバッガをビルド、実行し、
指定したアドレスでブレーク(ここではメッセージの表示のみ)させることができました。

そもそもなんでこんなこと(指定アドレスでブレークをかけれたのか)などが
全然書けていないので、これから書いていこうかと思っています。

参考

-no-pieオプションについて

https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Link-Options.html#index-no-pie

pieはposition-independent executableの意味です。

Discussion