📉
GDBTutorial
  • Introduction
  • Why GDB
  • Preparation
  • Creating Coredump
  • Reading Coredump
  • Dynamic Running GDB
  • More Breaks
  • More Info
  • Changing Program Flow
  • Automating Debug
  • Example 1: Test the Patch
  • More configuration
Powered by GitBook
On this page

Was this helpful?

Reading Coredump

โดยปกติการอ่าน coredump เป็นพื้นฐานที่ง่ายที่สุดครับ เพราะเป็นการอ่านสิ่งที่ตายแล้ว ไม่ต้องขยับอะไรทั้งนั้น เราก็จะมาเริ่มใช้ GDB ที่อันนี้ก่อน เพื่อเข้าใจว่าอ่านค่าอะไรยังไงบ้าง

ถ้าเริ่มทำตามผมตั้งแต่ preparation จะได้ coredump มาแล้ว ก็เริ่มจากเอา GDB เข้าไปอ่าน core ให้ใช้คำสั่งตามนี้ครับ gdb ./<executable> <core_of_executable>

$ gdb ./example.o core
Reading symbols from ./example.o...done.
[New LWP 17137]
Core was generated by `./example.o'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x000055bc13b7b9a0 in getInputAndRun () at example.c:28
28        *result[1] = toupper(result[1][0]);
(gdb)

ดู stack ของโปรแกรม ปัจจุบันด้วย bt หรือ backtrace

(gdb) bt
#0  0x000055bc13b7b9a0 in getInputAndRun () at example.c:28
#1  0x000055bc13b7ba1f in main () at example.c:37
(gdb) f 1
#1  0x000055bc13b7ba1f in main () at example.c:37
37        if (getInputAndRun() == -1) break;
(gdb) f 0
#0  0x000055bc13b7b9a0 in getInputAndRun () at example.c:28
28        *result[1] = toupper(result[1][0]);

ดูง่ายนะครับ แปลว่า stack ของ program เราปัจจุบันรัน 2 function อยู่คือ main() และ getInputRun() เราสามารถกระโดดไปมาด้วยด้วยคำสั่ง frame <stack_no> หรือ f <stack_no>

ต่อมาดู source code ด้วย list

(gdb) f 0
#0  0x000055bc13b7b9a0 in getInputAndRun () at example.c:28
28        *result[1] = toupper(result[1][0]);
(gdb) list
23      fgets(input, 100, stdin);
24      if (strcmp(input, "exit") == 0) return -1;
25      else {
26        char** result = split(input);
27        *result[0] = toupper(result[0][0]);
28        *result[1] = toupper(result[1][0]);
29        printf("First str: %s\n", result[0]);
30        printf("Second str: %s\n", result[1]);
31        return 0;
32      }
(gdb) f 1
#1  0x000055bc13b7ba1f in main () at example.c:37
37        if (getInputAndRun() == -1) break;
(gdb) list
32      }
33    }
34
35    int main() {
36      while (1) {
37        if (getInputAndRun() == -1) break;
38      }
39      return 0;
40    }

หรือสั่ง list แบบมี parameter เพื่ออ่านบรรทัดที่ระบุ หรือ function ที่ระบุก็ได้ครับ

(gdb) list 1,2
1    #include <stdio.h>
2    #include <stdlib.h>
(gdb) list main
30        printf("Second str: %s\n", result[1]);
31        return 0;
32      }
33    }
34
35    int main() {
36      while (1) {
37        if (getInputAndRun() == -1) break;
38      }
39      return 0;

นอกจาก list ที่เป็น source code ภาษา c แล้ว เรายังสามารถอ่านเป็นภาษา assembly ได้ด้วย อันนี้แล้วแต่ที่ถนัดเลยครับ ถ้า compile พร้อม debug flag (-g) แทบไม่ต้องลง assembly เลย แต่หลายครั้งการลงไปดู assembly ก็ทำให้เราเห็นรายละเอียดอะไรเยอะกว่าเช่นกัน

(gdb) disassemble main
Dump of assembler code for function main:
   0x0000555555554a11 <+0>:    push   %rbp
   0x0000555555554a12 <+1>:    mov    %rsp,%rbp
=> 0x0000555555554a15 <+4>:    mov    $0x0,%eax
   0x0000555555554a1a <+9>:    callq  0x555555554905 <getInputAndRun>
   0x0000555555554a1f <+14>:    cmp    $0xffffffff,%eax
   0x0000555555554a22 <+17>:    je     0x555555554a26 <main+21>
   0x0000555555554a24 <+19>:    jmp    0x555555554a15 <main+4>
   0x0000555555554a26 <+21>:    nop
   0x0000555555554a27 <+22>:    mov    $0x0,%eax
   0x0000555555554a2c <+27>:    pop    %rbp
   0x0000555555554a2d <+28>:    retq   
End of assembler dump.

เนื่องจาก stack ของ main ไม่มีอะไรให้เราดู เราจะดู stack getInputAndRun() กันนะครับ เริ่มจากอ่านตัวแปรต่างๆ ด้วย p <variable_name> หรือ x/<type> <variable_or_address> อาจจะงงๆ มาดูตัวอย่างเลยดีกว่า

(gdb) f 0
#0  0x000055bc13b7b9a0 in getInputAndRun () at example.c:28
28        *result[1] = toupper(result[1][0]);
(gdb) p result[0]
$2 = 0x7ffc4f8c7060 "Helloworld\n"
(gdb) p result[1]
$3 = 0x0
(gdb) x/s result[0]
0x7ffc4f8c7060:    "Helloworld\n"
(gdb) x/s result[1]
0x0:    <error: Cannot access memory at address 0x0>
(gdb) x/16xb result[0]
0x7ffc4f8c7060:    0x48    0x65    0x6c    0x6c    0x6f    0x77    0x6f    0x72
0x7ffc4f8c7068:    0x6c    0x64    0x0a    0x00    0x00    0x00    0x00    0x00
(gdb) x/s 0x7ffc4f8c7060
0x7ffc4f8c7060:    "Helloworld\n"

ตัว p คือ print ค่าออกมาครับ ซึ่งมันจะพยายามเดาประเภทของค่านั้นๆแล้ว print ให้เราอ่านง่าย แต่หลายครั้งมันก็พยายามฉลาดเกินไป เช่น p result[1] มันเห็นว่าค่าเป็น 0 เลยน่าจะเป็น integer ก็เลย print 0x0 มาให้ แต่เรารู้ว่าจริงๆมันเป็น string พอสั่ง x/s result[1] จะเห็นว่าได้ error กลับมาครับ

อันนี้ก็จบละ พื้นฐาน GDB ง่ายที่สุด คือการ print ตัวแปรออกมาดูว่าเกิดอะไรขึ้น ซึ่งถ้าเรา compile -g ไว้ก็จะเรียกตัวแปรตรงๆได้เลย แต่ถ้าเราไม่ได้ใส่ -g มา แต่มี coredump อยู่ เราก็ยังคงเรียกดูค่าใน address ก็ได้เหมือนกัน (แต่ต้องอ่าน assembly เป็นถึงจะรู้ว่า address ไหนคืออะไร)

อย่างข้อนี้เราก็เห็นแล้วว่ามันตายที่ example.c:28 ซึ่งก็ตายเพราะ result[1] มันมีค่าเป็น 0 นั่นเองทำให้ไม่สามารถ access เพื่อสั่ง toupper() ได้ เราก็ไปดูต่อว่าทำไมมันมีค่าเป็น 0 ต่อไป

ก็หวังว่าผู้อ่านรู้ถึงตรงนี้แล้ว จะไม่ไปนั่ง debug ด้วยการใส่ printf f-word เข้าไปทีละบรรทัด แล้ว compile + run ใหม่อีกนะครับ ใช้วิธีนี้ดีกว่า สะดวกเร็ว และไม่เผลอทิ้งขยะไว้ในโค้ดด้วย

PreviousCreating CoredumpNextDynamic Running GDB

Last updated 5 years ago

Was this helpful?