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 ใหม่อีกนะครับ ใช้วิธีนี้ดีกว่า สะดวกเร็ว และไม่เผลอทิ้งขยะไว้ในโค้ดด้วย
Last updated
Was this helpful?