Upgrade to Pro — share decks privately, control downloads, hide ads and more …

デバッガを自作してみよう (M3 tech talk)

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

デバッガを自作してみよう (M3 tech talk)

Avatar for Yutaro Oguri

Yutaro Oguri

March 03, 2023
Tweet

More Decks by Yutaro Oguri

Other Decks in Programming

Transcript

  1. 自己紹介 • 名前: 小栗 悠太郎 (Yutaro Oguri) @irungo_ic • 所属:

    東京大学 工学部 電子情報工学科B3 ◦ 現在、M3 AIチームにてインターンシップに参加 • 趣味: 音楽(ヴァイオリン🎻)、お酒(🍺、🍶)
  2. デバッガの例: gdb 1. -gをつけてコンパイル 2. 起動 3. Breakpointを設置 4. 実行

    5. 状態を観察 e.g.) レジスタの中身を出力 -> (gdb) info registers rax 0x7fffffffd310 140737488343824 rbx 0x401260 4199008 rcx 0x0 0 rdx 0x7fffffffd2f0 140737488343792 rsi 0x402004 4202500 rdi 0x402004 4202500 rbp 0x7fffffffd410 0x7fffffffd410 rsp 0x7fffffffd2f0 0x7fffffffd2f0 r8 0x0 0 r9 0x7ffff7fe0d60 140737354009952 r10 0x402004 4202500 r11 0x7ffff7de7c90 140737351941264 r12 0x401050 4198480 r13 0x7fffffffd500 140737488344320 r14 0x0 0 r15 0x0 0 rip 0x7ffff7de7d21 0x7ffff7de7d21 <__printf+145> eflags 0x246 [ PF ZF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0
  3. ptrace request: 今回使うもの • PTRACE_GETREGS / PTRACE_SETREGS traceeのレジスタ値をRead / Write

    する • PTRACE_CONT 停止していたtraceeの実行を再開する • PTRACE_SINGLESTEP 停止していたtraceeの実行を次の命令まで進め、再び停止させる
  4. ELF形式 LinuxなどのOSで広く採用されている実行形式 ELF Header: ファイルのメタデータ・Offset Program Header: 実行時に使う情報 Section Header:

    リンク時に使う情報 +------------------------+ | ELF header | +------------------------+ |+----------------------+| || Program header table || |+----------------------+| ||+--------------------+|| ||| ||| ||| data ||| ||| ||| ||+--------------------+|| |+----------------------+| || Setion header table || |+----------------------+| +------------------------ + source
  5. ELF形式 LinuxなどのOSで広く採用されている実行形式 ELF Header: ファイルのメタデータ・Offset Program Header: 実行時に使う情報 Section Header:

    リンク時に使う情報 +------------------------+ | ELF header | +------------------------+ |+----------------------+| || Program header table || |+----------------------+| ||+--------------------+|| ||| ||| ||| data ||| ||| ||| ||+--------------------+|| |+----------------------+| || Setion header table || |+----------------------+| +------------------------ + source
  6. 実装: ELFのHandler構造体 ELFファイルの中身、 ヘッダ、Traceeの情報を 管理する構造体 #include <elf.h> #include <sys/user.h> ...

    typedef struct ElfHandler { Elf64_Ehdr *ehdr; // ELF header Elf64_Phdr *phdr; // program header Elf64_Shdr *shdr; // section header uint8_t *mem; // memory map of the executable char *exec_cmd; // exec command char *symbol_name; // symbol name to be traced Elf64_Addr symbol_addr; // symbol address struct user_regs_struct regs; // registers } ElfHandler_t;
  7. 実装: ELFファイルの読み込み ELFファイルの読み込み 巨大かもしれないので mmapで #include <sys/mman.h> ... // read

    mode int fd = open(argv[1], O_RDONLY); // ファイルサイズの取得のためにstatを使用 struct stat st; fstat(fd, &st); // fdの内容をmapする (copy-on-write) eh.mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); // ヘッダを抽出 eh.ehdr = (Elf64_Ehdr *)eh.mem; eh.phdr = (Elf64_Phdr *)(eh.mem + eh.ehdr->e_phoff); eh.shdr = (Elf64_Shdr *)(eh.mem + eh.ehdr->e_shoff);
  8. 実装: BPを置きたいSymbolのAddrを特定 1. section header tableを総当たり. symbol tableに辿り着くまで. 2. symbol

    tableの実体を取ってくる. 3. Linkされているsymbol name tableを取ってくる. 4. symbol tableを総当たり. 目的のSymbol名に一致するEntryを返し, Addressを取 得 eh.symbol_addr = lookup_symbol_addr_by_name(&eh, eh.symbol_name);
  9. 実装: traceeの実行開始 fork/exec/waitで子プロセスを実行 PTRACE_TRACEMEによりtrace // process id int pid =

    fork(); ... // child executes the given program if (pid == 0) { ptrace(PTRACE_TRACEME, 0, NULL, NULL); execve(eh.exec_cmd, args, envp); exit(EXIT_SUCCESS); } int status; wait(&status);
  10. 実装: Breakpointを設置 BPの設置 = Trap命令の差し込み Trap命令: ソフトウェア割り込みを生成 // trap命令のopcode (x86)

    #define OPCODE_INT3 0xcc ... // get original instruction const long original_inst = ptrace(PTRACE_PEEKTEXT, pid, eh.symbol_addr, NULL); // modify to trap instruction const long trap_inst = (original_inst & ~0xff) | OPCODE_INT3; ptrace(PTRACE_POKETEXT, pid, eh.symbol_addr, trap_inst);
  11. 実装: main loop Trap! ↓ レジスタ読み取り ↓ 元の命令/レジスタを復元し、 1つ前から再実行 ↓

    trap命令を復元 while (1) { // resume process execution ptrace(PTRACE_CONT, pid, NULL, NULL); … if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { // get registers info and display them ptrace(PTRACE_GETREGS, pid, NULL, &eh.regs); display_registers(&eh); … // restore original instruction ptrace(PTRACE_POKETEXT, pid, eh.symbol_addr, original_inst); // single step to execute the original instruction eh.regs.rip -= 1; ptrace(PTRACE_SETREGS, pid, NULL, &eh.regs); ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL); … // restore trap instruction ptrace(PTRACE_POKETEXT, pid, eh.symbol_addr, trap_inst); } }
  12. サンプルを実行してみる 足し算を3回する。 関数add(a, b, c)の引数と、 レジスタrdi, rsi, rdx*の値が 一致していればOK! *x86の呼び出し規約

    int add (int a, int b, int c) { return a + b + c; } int main(int argc, char **argv, char **envp) { int a = 1; int b = 2; int c = 9; printf("a = %d, b = %d, c = %d\n", a, b, c); int d = add(a, b, 23); // 1(1) + 2(2) + 23(17) = 26(1a) printf("%d + %d + %d = %d\n", a, b, 23, d); int e = add(d, c, 54); // 26(1a) + 9(9) + 54(36) = 89(59) printf("%d + %d + %d = %d\n", d, c, 54, e); int f = add(e, 1, 7); // 89(59) + 1(1) + 7(7) = 97(61) printf("%d + %d + %d = %d\n", e, 1, 7, f); return 0; }
  13. 実行結果 add関数(symbol)にBPを設置 期待通りのレジスタ状態を観測できた $ ./debugger ./test_add add Tracing pid:43130 at

    symbol addr 401136 a = 1, b = 2, c = 9 %rax: 1 %rbx: 401260 %rcx: 2 %rdx: 17 // add関数の第3引数 %rsi: 2 // add関数の第2引数 %rdi: 1 // add関数の第1引数 %rbp: 7ffd2c1049d0 %rsp: 7ffd2c104988 ... %gs: 0 Please hit [ENTER] key to continue:
  14. Reference • はじめてのgdb, https://qiita.com/arene-calix/items/a08363db88f21c81d351 • ELF Formatについて, https://www.hazymoon.jp/OpenBSD/annex/elf.html • 最小限のELF,

    https://keens.github.io/blog/2020/04/12/saishougennoelf/ • ptraceシステムコール入門 ― プロセスの出力を覗き見してみよう! , https://itchyny.hatenablog.com/entry/2017/07/31/090000 • Ryan "elfmaster" O'Neill, Learning Linux Binary Analysis, 2016, Packt • man page of MMAP, https://linuxjm.osdn.jp/html/LDP_man-pages/man2/mmap.2.html • x86_64で関数の引数とレジスタの対応を確認する (アセンブラ), https://qiita.com/hara0219/items/6556ef17d00922536fa8 • デバッガとは何ぞや, https://zenn.dev/satoru_takeuchi/articles/8de139a52af5c4