imshota / system_development_project_application_1

講義用
0 stars 0 forks source link

GDBによるデバッグ #1

Open imshota opened 3 years ago

imshota commented 3 years ago

演習1 自分のOSSプロダクトをUBサニタイザで検査してみよ

作業記録

clang -fsanitize=hoge foo.c サニタイザができる。(hoge = undefinedだとUBサニタイザ)

以下のサンプルコードsanitize.cppを使用

#include <iostream>
int main(int argc, char** argv) {
  int a[3];
  a[1000] = 2;
  return 0;
}

やってみる。

% clang++ -fsanitize=undefined sanitize.cpp 
% ./a.out
sanitize.cpp:4:3: runtime error: index 1000 out of bounds for type 'int [3]'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior sanitize.cpp:4:3 in 
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==5254==ERROR: UndefinedBehaviorSanitizer: stack-overflow on address 0x7ffee29f062c (pc 0x00010d213f02 bp 0x7ffee29ef6a0 sp 0x7ffee29ef670 T159563)
    #0 0x10d213f02 in main+0xc2 (a.out:x86_64+0x100003f02)
    #1 0x7fff693fbcc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)

SUMMARY: UndefinedBehaviorSanitizer: stack-overflow (a.out:x86_64+0x100003f02) in main+0xc2
==5254==ABORTING
zsh: abort      ./a.out

4行目がおかしいことを言ってくれる。

-fanitize=addressでもやってみる。

% clang -fsanitize=address sanitize.cpp 
% ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==54995==ERROR: AddressSanitizer: stack-overflow on address 0x7ffee1a52600 (pc 0x00010e1b1ea7 bp 0x7ffee1a51720 sp 0x7ffee1a51640 T0)
    #0 0x10e1b1ea7 in main+0x127 (a.out:x86_64+0x100003ea7)
    #1 0x7fff697f2cc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)

SUMMARY: AddressSanitizer: stack-overflow (a.out:x86_64+0x100003ea7) in main+0x127
==54995==ABORTING
zsh: abort      ./a.out

スタックオーバーフローのことを通知してくれた。

imshota commented 3 years ago

演習2 自分のOSSプロダクトに対し,GDBでブレークポイントを設定して変数値を確認せよ

作業記録

gdbコマンドがないと言われたので、brewでインストールする。

brew install gdb

macOSではgdbを使うのに、コード署名が必要らしい。 https://qiita.com/takahashim/items/204ffa698afe09bd4e28

% gdb gdbtest
GNU gdb (GDB) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin19.5.0".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
--Type <RET> for more, q to quit, c to continue without paging--c
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from gdbtest...
Reading symbols from /Users/shota/Documents/tsclass/プログラム応用開発/GDB/gdbtest.dSYM/Contents/Resources/DWARF/gdbtest...
(gdb) run
Starting program: /Users/shota/Documents/tsclass/プログラム応用開発/GDB/gdbtest 
Unable to find Mach task port for process-id 55456: (os/kern) failure (0x5).

よって、macのデバッグツールである、lldbを使うことにした。 lldbのコマンドは、こちらのサイトを参考にした。 https://yokaze.github.io/2018/01/06/ コマンド一覧はhelpでも見ることは可能。

使えそうなコマンドをまとめる。 コマンド 意味
r/run デバッガ上で実行可能ファイルを起動
s 現在の位置から 1 行分だけ処理を進める
b ブレークポイントを設定する
c 現在のプロセスのすべてのスレッドの実行を再開
p 現在のスレッド上で式を評価する
log LLDB の内部ログを制御するためのコマンド
% lldb gdbtest
(lldb) target create "gdbtest"
Current executable set to '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/gdbtest' (x86_64).
(lldb) b sanitize.cpp:3
Breakpoint 1: where = gdbtest`main + 29 at sanitize.cpp:4:11, address = 0x0000000100003f5d
(lldb) run
Process 55886 launched: '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/gdbtest' (x86_64)
Process 55886 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003f5d gdbtest`main at sanitize.cpp:4:11
   1    #include <iostream>
   2    int main() {
   3      int a[3];
-> 4      a[1000] = 2;
   5      return 0;
   6    }
Target 0: (gdbtest) stopped.
lldb) p a
(int [3]) $0 = ([0] = 0, [1] = -272632288, [2] = 32766)
(lldb) c
Process 55886 resuming
Process 55886 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x7ffeefc0059c)
    frame #0: 0x0000000100003f5d gdbtest`main at sanitize.cpp:4:11
   1    #include <iostream>
   2    int main() {
   3      int a[3];
-> 4      a[1000] = 2;
   5      return 0;
   6    }
Target 0: (gdbtest) stopped.
imshota commented 3 years ago

演習3 コアダンプを利用したデバッグを試せ

作業記録

自分の環境だと、/coresに権限がかかっているので、chmodで/coresの権限を変える。 また、ulimitもunlimitedに変える。

% sudo chmod 777 /cores
% ulimit -c unlimited                
% ./coretest  
zsh: segmentation fault (core dumped)  ./coretest
% ls /cores
core.2110

lldbでコアコアダンプを解析

% lldb ./coretest /cores/core.2110
(lldb) target create "./coretest"
Current executable set to '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/coretest' (x86_64).
(lldb) settings set -- target.run-args  "/cores/core.2110"
(lldb) run
Process 2174 launched: '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/coretest' (x86_64)
Process 2174 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x7ffeefc0079c)
    frame #0: 0x0000000100003f5d coretest`main at sanitize.cpp:4:11
   1    #include <iostream>
   2    int main() {
   3      int a[3];
-> 4      a[1000] = 2;
   5      return 0;
   6    }
Target 0: (coretest) stopped.
imshota commented 3 years ago

演習4 自分のOSSプロダクトにint3命令を埋め込んでデバッグを実験せよ

作業記録 ターゲットプログラム int3test.cpp

#include <iostream>
int main() {
  int a[3];
  a[0] = 0;
  asm("int3");
  a[1] = 1;
  asm("int3");
  a[2] = 2;
  asm("int3");
  a[3] = 3;
  asm("int3");
  return 0;
}

lldbでデバッグをかけてみるとa[3]=3でstop reason = signal SIGABRTとなっており、 ここが悪いことがわかる。

% lldb ./int3test                  
(lldb) target create "./int3test"
Current executable set to '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/int3test' (x86_64).
(lldb) run
Process 2380 launched: '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/int3test' (x86_64)
Process 2380 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x0000000100003f55 int3test`main at int3test.cpp:6:8
   3      int a[3];
   4      a[0] = 0;
   5      asm("int3");
-> 6      a[1] = 1;
   7      asm("int3");
   8      a[2] = 2;
   9      asm("int3");
Target 0: (int3test) stopped.
(lldb) c
Process 2380 resuming
Process 2380 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x0000000100003f5d int3test`main at int3test.cpp:8:8
   5      asm("int3");
   6      a[1] = 1;
   7      asm("int3");
-> 8      a[2] = 2;
   9      asm("int3");
   10     a[3] = 3;
   11     asm("int3");
Target 0: (int3test) stopped.
(lldb) c
Process 2380 resuming
Process 2380 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x0000000100003f65 int3test`main at int3test.cpp:10:8
   7      asm("int3");
   8      a[2] = 2;
   9      asm("int3");
-> 10     a[3] = 3;
   11     asm("int3");
   12     return 0;
   13   }
Target 0: (int3test) stopped.
(lldb) c
Process 2380 resuming
Process 2380 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x0000000100003f6d int3test`main at int3test.cpp:11:3
   8      a[2] = 2;
   9      asm("int3");
   10     a[3] = 3;
-> 11     asm("int3");
   12     return 0;
   13   }
Target 0: (int3test) stopped.
(lldb) c
Process 2380 resuming
Process 2380 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fff6954333a libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
->  0x7fff6954333a <+10>: jae    0x7fff69543344            ; <+20>
    0x7fff6954333c <+12>: movq   %rax, %rdi
    0x7fff6954333f <+15>: jmp    0x7fff6953d629            ; cerror_nocancel
    0x7fff69543344 <+20>: retq   
Target 0: (int3test) stopped.
imshota commented 3 years ago

演習5 自分のOSSプロダクトの変数変化をウォッチポイントを用いて観察せよ

作業記録

g++ -g watchtest.cpp -o watchtest

一回main関数にブレイクポイントをおき、 そのあとで、watchlistに付け加える。 sumがold value からnew valueに移り変わっているところで止まっているのがわかる。

% lldb ./watchtest                   
(lldb) target create "./watchtest"
Current executable set to '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/watchtest' (x86_64).
(lldb) breakpoint set -n main
Breakpoint 1: where = watchtest`main + 8 at watchtest.cpp:3:7, address = 0x0000000100003e37
(lldb) run
Process 2679 launched: '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/watchtest' (x86_64)
Process 2679 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003e37 watchtest`main at watchtest.cpp:3:7
   1    #include <iostream>
   2    int main() {
-> 3      int a[3] = {2, 4, 6};
   4      int sum = 0;
   5      for (int i = 0; i < 2; i++) {
   6        sum += a[i];
   7      }
Target 0: (watchtest) stopped.
(lldb) watchpoint set variable sum
Watchpoint created: Watchpoint 1: addr = 0x7ffeefbff57c size = 4 state = enabled type = w
    declare @ '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/watchtest.cpp:4'
    watchpoint spec = 'sum'
    new value: 1
(lldb) c
Process 2679 resuming

Watchpoint 1 hit:
old value: 1
new value: 0
Process 2679 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1
    frame #0: 0x0000000100003e53 watchtest`main at watchtest.cpp:5:12
   2    int main() {
   3      int a[3] = {2, 4, 6};
   4      int sum = 0;
-> 5      for (int i = 0; i < 2; i++) {
   6        sum += a[i];
   7      }
   8      std::cout << sum << std::endl;
Target 0: (watchtest) stopped.
(lldb) c
Process 2679 resuming

Watchpoint 1 hit:
old value: 0
new value: 2
Process 2679 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1
    frame #0: 0x0000000100003e6c watchtest`main at watchtest.cpp:5:3
   2    int main() {
   3      int a[3] = {2, 4, 6};
   4      int sum = 0;
-> 5      for (int i = 0; i < 2; i++) {
   6        sum += a[i];
   7      }
   8      std::cout << sum << std::endl;
Target 0: (watchtest) stopped.
(lldb) c
Process 2679 resuming

Watchpoint 1 hit:
old value: 2
new value: 6
Process 2679 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = watchpoint 1
    frame #0: 0x0000000100003e6c watchtest`main at watchtest.cpp:5:3
   2    int main() {
   3      int a[3] = {2, 4, 6};
   4      int sum = 0;
-> 5      for (int i = 0; i < 2; i++) {
   6        sum += a[i];
   7      }
   8      std::cout << sum << std::endl;
Target 0: (watchtest) stopped.
imshota commented 3 years ago

作業6 自分のOSSプロダクトのデバッグを作業記録を取りながら行おう

作業記録 線形リストのやってしまう典型的パターン test.c

#include <stdio.h>

typedef struct pstruct {
  int value;
  struct pstruct* next;
} plist;

plist* Print(plist *p)
{
  printf("%d\n",p->value);
  if(p->next != NULL){
    Print(p->next);
  }
}

int main ()
{
  plist a, b;
  a.value = 1;
  a.next = &b;
  b.value = 2;

  Print(&a);
  Print(NULL);
}

これで実際に実行すると

% clang test.c -o test 
% ./test
1
2
zsh: segmentation fault  ./test

Printは線形リストの内容を表示するものだが、NULLが渡されるとNULL参照になってしまう。 では、これをデバッグで見つけてみる。

% clang -g -fsanitize=undefined test.c -o test
% ./test                                      
1
2
test.c:10:20: runtime error: member access within null pointer of type 'plist' (aka 'struct pstruct')
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior test.c:10:20 in 
test.c:10:20: runtime error: load of null pointer of type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior test.c:10:20 in 
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==5205==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x0001080abd56 bp 0x7ffee7b57650 sp 0x7ffee7b57610 T156378)
==5205==The signal is caused by a READ memory access.
==5205==Hint: address points to the zero page.
    #0 0x1080abd56 in Print test.c:10
    #1 0x1080abf12 in main test.c:24
    #2 0x7fff693fbcc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)

==5205==Register values:
rax = 0x0000000000000000  rbx = 0x0000000000000000  rcx = 0x0000000000000000  rdx = 0x000000000000003f  
rdi = 0x0000000000000000  rsi = 0x0000000108bdbf80  rbp = 0x00007ffee7b57650  rsp = 0x00007ffee7b57610  
 r8 = 0x00000001081eb100   r9 = 0x00007ffee7b568e0  r10 = 0x0000000000000000  r11 = 0x0000000000000206  
r12 = 0x0000000000000000  r13 = 0x0000000000000000  r14 = 0x0000000000000000  r15 = 0x0000000000000000  
UndefinedBehaviorSanitizer can not provide additional info.
SUMMARY: UndefinedBehaviorSanitizer: SEGV test.c:10 in Print
==5205==ABORTING
zsh: abort      ./test

10行目でNULL参照しているんだなということがわかる。 他にも試してみる。

% lldb ./test                                 
(lldb) target create "./test"
Current executable set to '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/test' (x86_64).
(lldb) run
Process 5218 launched: '/Users/shota/Documents/tsclass/プログラム応用開発/GDB/test' (x86_64)
1
2
Process 5218 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = Null pointer use
    frame #0: 0x000000010011eb60 libclang_rt.ubsan_osx_dynamic.dylib`__ubsan_on_report
libclang_rt.ubsan_osx_dynamic.dylib`__ubsan_on_report:
->  0x10011eb60 <+0>: pushq  %rbp
    0x10011eb61 <+1>: movq   %rsp, %rbp
    0x10011eb64 <+4>: popq   %rbp
    0x10011eb65 <+5>: retq   
Target 0: (test) stopped.

lldbでのデバッグは、原因が分かりやすいような気がする。