前端學習 C 語言 —— GDB偵錯程式

2023-07-05 12:00:58

GDB偵錯程式

我們在講指標時用 GDB 偵錯段錯誤。

本篇將詳細介紹 gdb 的最常用命令紀錄檔記錄檢測點,最後介紹如何用 gdb 偵錯程序以及用gdb 偵錯一個開源專案的偵錯版本 —— glmark2。

gdb介紹

GDB, the GNU Project debugger —— gdb官網

gdb 是一款偵錯程式,能打斷點。支援多種語言,例如 c、c++、go。

Tip:有關 GNU Project,請看本篇擴充套件

官網顯示最新版本是13.2(20230704)。點選官網頂部[documentation]可檢視檔案。

安裝GDB

筆者已經用 apt 源安裝了gbd:

jjj-pc:~/pj/glmark2$ sudo apt install gdb
正在讀取軟體包列表... 完成
正在分析軟體包的依賴關係樹
正在讀取狀態資訊... 完成
gdb 已經是最新版 (9.1-0kylin1)。
下列軟體包是自動安裝的並且現在不需要了:
  archdetect-deb dmeventd libaio1 libdebian-installer4 libdevmapper-event1.02.1 liblvm2cmd2.03 localechooser-data lvm2 user-setup
使用'sudo apt autoremove'來解除安裝它(它們)。
升級了 0 個軟體包,新安裝了 0 個軟體包,要解除安裝 0 個軟體包,有 6 個軟體包未被升級。

筆者gbd版本是 9.1

jjj-pc:~/pj/glmark2$ gdb --version
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.

最常用命令

man gdb 告訴我們最常用的命令有:break、run、print、c、next、list、step、quit。

// 以下是一些最常用的GDB命令:
Here are some of the most frequently needed GDB commands:

       break [file:]function
           Set a breakpoint at function (in file).

       run [arglist]
           Start your program (with arglist, if specified).

       bt  Backtrace: display the program stack.

       print expr
           // 顯示錶示式的值
           Display the value of an expression.

       c   Continue running your program (after stopping, e.g. at a breakpoint).

       next
           // 執行下一條程式語句(在停止後);跳過該行中的任何函數呼叫。
           Execute next program line (after stopping); step over any function calls in the line.

       edit [file:]function
           look at the program line where it is presently stopped.

       list [file:]function
           type the text of the program in the vicinity of where it is presently stopped.

       step
           Execute next program line (after stopping); step into any function calls in the line.

       help [name]
           Show information about GDB command name, or general information about using GDB.

       quit
           Exit from GDB.

準備一段 C 程式碼用作gdb命令學習:

#include <stdio.h>

// add 函數
int add(int a, int b) {
    int sum = a + b;
    return sum;
}

int main() {
    int num1 = 3;
    int num2 = 5;
    
    int result = add(num1, num2);
    
    printf("兩個整數的和為:%d\n", result);
    
    return 0;
}

run 和 quit

通過 gdb demo 執行進入gdb模式,輸入 run 執行程式,輸入 quit 則退出gdb。詳細請看:

// 通過 -g 編譯出有偵錯資訊的可執行檔案
jjj-pc:~/pj$ gcc demo.c -o demo -g
// gdb 執行
jjj-pc:~/pj$ gdb demo
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
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 demo...
// 輸入 run 執行程式
(gdb) run
Starting program: /home/jjj/pj/demo
兩個整數的和為:8
[Inferior 1 (process 3022770) exited normally]
// 輸入 quit 退出
(gdb) quit
jjj-pc:~/pj$

Tip: ctrl + z 能直接退出gdb

list

如果不知道在哪行或哪個方法打斷點,可以通過 list 檢視原始碼。請看範例:

jjj-pc:~/pj$ gdb demo
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
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 demo...
// 檢視原始碼
(gdb) list
1       #include <stdio.h>
2
3       // 自定義函數,用於計算兩個整數的和
4       int add(int a, int b) { // a, b 叫形參
5           int sum = a + b;
6           return sum;
7       }
8
9       int main() {
10          int num1 = 3;
// 一次顯示不完,繼續檢視後面10行
(gdb) list
11          int num2 = 5;
12
13          // 呼叫自定義函數計算兩個整數的和
14          int result = add(num1, num2); 
15
16          printf("兩個整數的和為:%d\n", result);
17
18          return 0;
19      }
// 到底了。
(gdb) list
Line number 20 out of range; demo.c has 19 lines.
// 檢視5到10行
(gdb) list 5,10
5           int sum = a + b;
6           return sum;
7       }
8
9       int main() {
10          int num1 = 3;
(gdb)

break 和 info break

break(簡寫 b) 用於打斷點,info break 用於檢視打了哪些斷點。請看範例:

jjj-pc:~/pj$ gdb demo
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
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 demo...
// 給 mian 方法打斷點
(gdb) break main
Breakpoint 1 at 0x1167: file demo.c, line 9.
// 給11行打斷點
(gdb) b 11
Breakpoint 2 at 0x117a: file demo.c, line 11.
// 檢視打了哪些斷點
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001167 in main at demo.c:9
2       breakpoint     keep y   0x000000000000117a in main at demo.c:11
// 檢視打了哪些斷點
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001167 in main at demo.c:9
2       breakpoint     keep y   0x000000000000117a in main at demo.c:11

next 和 step

程式碼中斷後,輸入 next 和 step 都會執行下一行,然而 next 會跳過函數,step 會進入函數。請看範例:

  • next 跳過函數
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
// 打斷點
(gdb) b 9
Breakpoint 1 at 0x1167: file demo.c, line 9.
// 執行
(gdb) run
Starting program: /home/jjj/pj/demo
// 在斷點處停止
Breakpoint 1, main () at demo.c:9
9       int main() {
// 下一行
(gdb) next
10          int num1 = 3;
(gdb) next
// 下一行
11          int num2 = 5;
(gdb) next
// 下一行。跳過函數
14          int result = add(num1, num2);
(gdb) next
// 下一行
16          printf("兩個整數的和為:%d\n", result);
(gdb) next
兩個整數的和為:8
18          return 0;
  • step 進入函數
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
(gdb) b 9
Breakpoint 1 at 0x1167: file demo.c, line 9.
(gdb) run
Starting program: /home/jjj/pj/demo

Breakpoint 1, main () at demo.c:9
9       int main() {
(gdb) step
10          int num1 = 3;
(gdb) step
11          int num2 = 5;
(gdb) step
14          int result = add(num1, num2); 
(gdb) step
add (a=21845, b=1431654909) at demo.c:4
4       int add(int a, int b) { 
(gdb) step
5           int sum = a + b;
(gdb) step
6           return sum;
(gdb) step
7       }
(gdb) step
main () at demo.c:16
16          printf("兩個整數的和為:%d\n", result);
(gdb) step
兩個整數的和為:8
18          return 0;

continue

next 和 step 會執行下一行,而continue(簡寫c) 會執行到下一個斷點處停止。 請看範例:

Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
// 斷點
(gdb) b main
Breakpoint 1 at 0x1167: file demo.c, line 9.
// 斷點
(gdb) b 16
Breakpoint 2 at 0x1193: file demo.c, line 16.
// 執行後在第一斷點處停止
(gdb) run
Starting program: /home/jjj/pj/demo

Breakpoint 1, main () at demo.c:9
9       int main() {
// 下一個斷點
(gdb) c
Continuing.

Breakpoint 2, main () at demo.c:16
16          printf("兩個整數的和為:%d\n", result);
(gdb)

print

print 用於檢視表示式的值。請看範例:

// 在11行打斷點,檢視 num1 
(gdb) list 9,11
9       int main() {
10          int num1 = 3;
11          int num2 = 5;
(gdb) b 11
Breakpoint 1 at 0x117a: file demo.c, line 11.
(gdb) run
Starting program: /home/jjj/pj/demo

Breakpoint 1, main () at demo.c:11
11          int num2 = 5;
// 檢視num1的值
(gdb) print num1
$1 = 3
// 檢視num1的地址
(gdb) print &num1
$2 = (int *) 0x7fffffffe2c4

gdb 小技巧

shell

gdb 可以執行 shell 命令。請看範例:

Type "apropos word" to search for commands related to "word"...
Reading symbols from demo2...
// ll 命令沒有
(gdb) shell ll
bash: ll:未找到命令
// 執行 ls 命令
(gdb) shell ls
demo2  demo2.c
// 執行 cat 命令
(gdb) shell cat demo2.c
#include <stdio.h>

int main() {
    int i;

    for (i = 1; i <= 5; i++) {
        printf("Iteration %d\n", i);
    }

    return 0;
}

紀錄檔記錄

set logging on 用於開啟紀錄檔記錄功能。該命令執行後,GDB將記錄所有互動式對談的輸入和輸出到一個紀錄檔檔案中。請看範例:

jjj-pc:~/pj/dir1$ gdb demo2
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
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 demo2...
// 開啟紀錄檔功能
(gdb) set logging on
Copying output to gdb.txt.
Copying debug output to gdb.txt.
// 以下是一系列操作
(gdb) info b
No breakpoints or watchpoints.
(gdb) list
1       #include <stdio.h>
2
3       int main() {
4           int i;
5
6           for (i = 1; i <= 5; i++) {
7               printf("Iteration %d\n", i);
8           }
9
10          return 0;
(gdb) b 4
Breakpoint 1 at 0x1155: file demo2.c, line 6.
(gdb) run
Starting program: /home/jjj/pj/dir1/demo2

Breakpoint 1, main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
(gdb) n
7               printf("Iteration %d\n", i);
(gdb) n
Iteration 1
6           for (i = 1; i <= 5; i++) {
(gdb) print i
$1 = 1
(gdb) c
Continuing.
Iteration 2
Iteration 3
Iteration 4
Iteration 5
[Inferior 1 (process 3040270) exited normally]
(gdb) quit
// 退出 gdb 後,發現同級目錄生成一個叫 gdb.txt 檔案。
jjj-pc:~/pj/dir1$ ls
demo2  demo2.c  gdb.txt
// 紀錄檔檔案內容
jjj-pc:~/pj/dir1$ cat gdb.txt
No breakpoints or watchpoints.
1       #include <stdio.h>
2
3       int main() {
4           int i;
5
6           for (i = 1; i <= 5; i++) {
7               printf("Iteration %d\n", i);
8           }
9
10          return 0;
Breakpoint 1 at 0x1155: file demo2.c, line 6.
Starting program: /home/jjj/pj/dir1/demo2

Breakpoint 1, main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
7               printf("Iteration %d\n", i);
6           for (i = 1; i <= 5; i++) {
$1 = 1
Continuing.
[Inferior 1 (process 3040270) exited normally]

監視點

GDB中的"watch points"(監視點)是一種偵錯功能,用於在程式執行期間監視變數記憶體地址的更改。請看範例:

比如有個大程式,我要檢視某個變數變化狀態,被誰修改的。

用以下範例模擬,假如要監視變數 i 的變化情況。做法如下:

jjj-pc:~/pj/dir1$ cat demo2.c
#include <stdio.h>

int main() {
    int i;

    for (i = 1; i <= 5; i++) {
        printf("Iteration %d\n", i);
    }

    return 0;
}
  • 方式1:監視變數
jjj-pc:~/pj/dir1$ gdb demo2
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
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 demo2...
// 在 `int i` 處打斷點
(gdb) b 4
Breakpoint 1 at 0x1155: file demo2.c, line 6.
// 執行,並在斷點處中斷
(gdb) run
Starting program: /home/jjj/pj/dir1/demo2

Breakpoint 1, main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
// 監視變數 i
(gdb) watch i
Hardware watchpoint 2: i
(gdb) info watchpoinst
Undefined info command: "watchpoinst".  Try "help info".
// 使用 info watchpoints 命令檢視所有當前設定的監視點的狀態
(gdb) info watchpoints
Num     Type           Disp Enb Address            What
2       hw watchpoint  keep y                      i
// 繼續。在 i 的值變化時中斷,現在是1,是在第 6 行改變的
(gdb) c
Continuing.

Hardware watchpoint 2: i
// 舊值 0 變成 新值 1
Old value = 0
New value = 1
main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
// 繼續
(gdb) c
Continuing.
Iteration 1

Hardware watchpoint 2: i

Old value = 1
New value = 2
0x0000555555555178 in main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
// 繼續
(gdb) c
Continuing.
Iteration 2

Hardware watchpoint 2: i

Old value = 2
New value = 3
...
  • 方式2:監視記憶體地址指向的內容
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo2...
(gdb) b 4
Breakpoint 1 at 0x1155: file demo2.c, line 6.
(gdb) run
Starting program: /home/jjj/pj/dir1/demo2

Breakpoint 1, main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
// 取得變數的地址
(gdb) print &i
$1 = (int *) 0x7fffffffe2bc
// 使用 * 操作符是因為你想要監視記憶體地址 0x7fffffffe2bc 處的內容,而不是該地址本身。
(gdb) watch *0x7fffffffe2bc
Hardware watchpoint 2: *0x7fffffffe2bc
// 繼續。在 i 的值變化時中斷,現在是1,是在第 6 行改變的
(gdb) c
Continuing.

Hardware watchpoint 2: *0x7fffffffe2bc

Old value = 0
New value = 1
main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
(gdb) c
Continuing.
Iteration 1

Hardware watchpoint 2: *0x7fffffffe2bc

Old value = 1
New value = 2
0x0000555555555178 in main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
(gdb) c
...

擴充套件

GNU專案

GNU(GNU's Not Unix)專案是由Richard Stallman於1983年發起的一個自由軟體運動。該專案旨在建立一個完全自由和開源的作業系統,以提供使用者自由執行、研究、分發和改進軟體的權利。

GNU專案的目標是為使用者提供一個類似Unix的作業系統,但不同於Unix,它完全由自由軟體組成。自由軟體指的是使用者擁有執行、複製、分發、研究和修改軟體的自由。GNU專案的理念是,使用者應該擁有對計算機軟體的完全控制權,能夠自由地使用、學習和修改軟體,從而推動自由軟體社群的發展和分享。

為實現這一目標,GNU專案開發了一系列自由軟體工具和應用程式,如GNU編譯器套件(GCC)、GNU偵錯程式(GDB)、GNU Bash shell、GNU Core Utilities等。此外,GNU專案還推動了GNU宇宙檔案計劃,提供了豐富的自由檔案資源,幫助使用者學習和使用自由軟體。

總而言之,GNU專案是一個致力於推廣自由軟體概念的運動,旨在提供一個完全自由和開源的作業系統。通過開發和推廣自由軟體工具,GNU專案為使用者提供了更多選擇和控制權,同時促進了自由軟體社群的發展。

core檔案

Core檔案是作業系統在程式崩潰或異常終止時生成的一種特殊檔案。它記錄了程式發生異常時的記憶體狀態、暫存器狀態和其他偵錯資訊,以便後續進行偵錯。

Core檔案通常與可執行檔案位於同一目錄,並以檔名字首為 "core" 開頭,後跟程序ID號。例如,如果程式的可執行檔名為 "my_program",並且程序ID為1234,則生成的core檔案可能命名為 "core.1234"。

作業系統是否允許生成core檔案,可以使用ulimit命令。ulimit -c 如果返回值是0,則表示禁止生成core檔案。如果返回值不是0,則表示core檔案生成被允許,並且返回值表示core檔案的最大大小(以KB為單位)。

筆者這裡對核檔案沒有限制:

// 核檔案的最大大小無限制
jjj-pc:~/pj/dir1$ ulimit -c
unlimited

Tip:ulimit 用於修改 shell 資源限制。

jjj-pc:~/pj/dir1$ ulimit --help
ulimit: ulimit [-SHabcdefiklmnpqrstuvxPT] [限制]
    修改 shell 資源限制。

    在允許此類控制的系統上,提供對於 shell 及其建立的程序所可用的
    資源的控制。

    選項:
      -S        使用軟 (`soft') 資源限制
      -H        使用硬 (`hard') 資源限制
      -a        所有當前限制都被報告
      -b        通訊端快取尺寸
      -c        建立的核檔案的最大尺寸
      -d        一個程序的資料區的最大尺寸
      -e        最高的排程優先順序 (`nice')
      -f        有 shell 及其子程序可以寫的最大檔案尺寸
      -i        最多的可以掛起的訊號數
      -k        分配給此程序的最大 kqueue 數量
      -l        一個程序可以鎖定的最大記憶體尺寸
      -m        最大的記憶體進駐尺寸
      -n        最多的開啟的檔案描述符個數
      -p        管道緩衝區尺寸
      -q        POSIX 資訊佇列的最大位元組數
      -r        實時排程的最大優先順序
      -s        最大棧尺寸
      -t        最大的CPU時間,以秒為單位
      -u        最大使用者程序數
      -v        虛擬記憶體尺寸
      -x        最大的檔案鎖數量
      -P        最大偽終端數量
      -T        最大執行緒數量
// 所有當前限制都被報告
jjj-pc:~/pj/dir1$ ulimit -a
// core檔案大小限制為無限制
core file size          (blocks, -c) unlimited
// 資料段大小限制為無限制
data seg size           (kbytes, -d) unlimited
// 排程優先順序限制為0。
scheduling priority             (-e) 0
// 檔案大小限制為無限制。用於限制單個檔案的最大大小
file size               (blocks, -f) unlimited
// 待處理訊號數量限制為31429。
pending signals                 (-i) 31429
// 最大鎖定記憶體大小限制為65536 KB。
max locked memory       (kbytes, -l) 65536
// 最大記憶體大小限制為無限制。
max memory size         (kbytes, -m) unlimited
// 開啟檔案數量限制為1024。
open files                      (-n) 1024
// 管道大小限制為512位元組。
pipe size            (512 bytes, -p) 8
// POSIX訊息佇列大小限制為819200位元組。
POSIX message queues     (bytes, -q) 819200
// 實時優先順序限制為0
real-time priority              (-r) 0
// 棧大小限制為8192 KB。用於限制每個程序棧的大小
stack size              (kbytes, -s) 8192
// 用於限制一個程序在CPU上執行的最長時間
cpu time               (seconds, -t) unlimited
// 最大使用者程序數限制為31429
max user processes              (-u) 31429
// 虛擬記憶體大小限制為無限制。
virtual memory          (kbytes, -v) unlimited
// 檔案鎖定數量限制為無限制。
file locks                      (-x) unlimited

建立一個會產生core檔案的程式:

#include <stdio.h>

int main() {
    int* ptr = NULL;
    *ptr = 10; // 存取空指標將導致段錯誤

    return 0;
}

疑惑:筆者在偵錯臺中不能生成 core 檔案,暫時終止GDB core 檔案。

程序偵錯

寫個一直迴圈的程式:

jjj-pc:~/pj$ cat dir1/demo.c
#include <stdio.h>
#include <unistd.h>

int main() {
    while (1) {
        printf("Hello, World!\n");
        sleep(1); // 暫停1秒鐘
    }

    return 0;
}

編譯執行,得到程序號:

jjj-pc:~/pj/dir1$ gcc demo.c -o demo -g
// & 只後臺執行
jjj-pc:~/pj/dir1$ ./demo &
[1] 3072058

通過 gdb -p 3072058 即可偵錯程序:

// gdb 程序
jjj-pc:~/pj/dir1$ gdb -p 3072058
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
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".
Attaching to process 3072058
Reading symbols from /home/jjj/pj/dir1/demo...
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
(No debugging symbols found in /lib/x86_64-linux-gnu/libc.so.6)
Reading symbols from /lib64/ld-linux-x86-64.so.2...
(No debugging symbols found in /lib64/ld-linux-x86-64.so.2)
0x00007f3ae9acc1e4 in clock_nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
// 檢視原始碼
(gdb) list
1       #include <stdio.h>
2       #include <unistd.h>
3
4       int main() {
5           while (1) {
6               printf("Hello, World!\n");
7               sleep(1); // 暫停1秒鐘
8           }
9
10          return 0;
// next
(gdb) n
Single stepping until exit from function clock_nanosleep,
which has no line number information.
0x00007f3ae9ad1ef7 in nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) n
Single stepping until exit from function nanosleep,
which has no line number information.
0x00007f3ae9ad1e2e in sleep () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) n
Single stepping until exit from function sleep,
which has no line number information.
main () at demo.c:6
6               printf("Hello, World!\n");
// next
(gdb) n
7               sleep(1); // 暫停1秒鐘
// next
(gdb) n
6               printf("Hello, World!\n");
// next
(gdb) n
7               sleep(1); // 暫停1秒鐘
(gdb) n
6               printf("Hello, World!\n");
(gdb)

Tipman gdb 中也有gdb程序或core的介紹:

You can also start with both an executable program and a core file specified:

        gdb program core

You can, instead, specify a process ID as a second argument or use option "-p", if you want to debug a running process:

        gdb program 1234
        gdb -p 1234

glmark2 偵錯版本

glmark2是一個用於測試OpenGL渲染效能的工具,它通過執行一系列圖形場景和基準測試來評估計算機的圖形處理能力。

筆者需要偵錯版本進行定位,故決定原始碼編譯。

  • 首先下載專案
git clone https://github.com/glmark2/glmark2.git
cd glmark2
  • 修改 glmark2/wscript 檔案,在def options(處增加一行
def options(opt):
    opt.add_option('--debug', action='store_true', default=False, help='Enable debug mode')

通過 ./waf --help 則會顯示 --debug

jjj-pc:~/pj/glmark2$ ./waf --help
waf [commands] [options]
...

Options:
  --version             show program's version number and exit
  ...
  // debug
  --debug               Build with debug symbols
  • 執行./waf configure 命令來設定構建環境
jjj-pc:~/pj/glmark2$ ./waf configure --with-flavors=x11-gl --debug
Setting top to                           : /home/jjj/pj/glmark2
Setting out to                           : /home/jjj/pj/glmark2/build
Checking for 'gcc' (C compiler)          : /usr/bin/gcc
Checking for 'g++' (C++ compiler)        : /usr/bin/g++
Checking for header stdlib.h             : yes
Checking for header string.h             : yes
Checking for header stdint.h             : yes
Checking for header stdio.h              : yes
Checking for header dlfcn.h              : yes
Checking for header unistd.h             : yes
Checking for header jpeglib.h            : yes
Checking for header math.h               : yes
Checking for header string.h             : yes
Checking for library m                   : yes
Checking for library jpeg                : yes
Checking for function memset             : yes
Checking for function sqrt               : yes
Checking for program 'pkg-config'        : /usr/bin/pkg-config
Checking for 'libpng12'                  : yes
Checking for 'x11'                       : yes
Checking for 'libdrm'                    : yes
Checking for 'gbm'                       : yes
Checking for 'libudev'                   : yes
Checking for 'wayland-client'            : yes
Checking for 'wayland-cursor'            : yes
Checking for 'wayland-egl'               : yes
Prefix                                   : /usr/local
Data path                                : /usr/local/share/glmark2
Including extras                         : No
Building flavors                         : ['x11-gl']
'configure' finished successfully (2.189s)

:筆者安裝了一些依賴包,由於未做筆記,也就忘記了,請讀者自行完成,通常根據報錯也能搜尋到缺少什麼包。

Tip: 關於偵錯版除了增加 --debug,還可以直接修改 wscript 中如下程式碼:

// 修改前
if is_win:
    configure_win32(ctx)
else:
    configure_linux(ctx)

// 修改後
if is_win:
    configure_win32(ctx)
    ctx.env.append_unique('CXXFLAGS', '/Zi')
    ctx.env.append_unique('LINKFLAGS', '/DEBUG')
else:
    configure_linux(ctx)
    ctx.env.append_unique('CXXFLAGS', '-g')
    ctx.env.append_unique('LINKFLAGS', '-g')
  • 執行./waf build 命令來編譯glmark2的偵錯版本
jjj-pc:~/pj/glmark2$ ./waf build
Waf: Entering directory `/home/jjj/pj/glmark2/build'
[ 1/71] Compiling src/main.cpp
[ 2/71] Compiling src/canvas-generic.cpp
[ 3/71] Compiling src/native-state-x11.cpp
[ 4/71] Compiling src/gl-state-glx.cpp
[ 5/71] Compiling src/glad/src/glx.c

...

[64/71] Compiling src/scene.cpp
[65/71] Compiling src/shared-library.cpp
[66/71] Compiling src/text-renderer.cpp
[67/71] Compiling src/texture.cpp
[68/71] Compiling doc/glmark2.1.in
[69/71] Linking build/src/libmatrix-gl.a
[70/71] Linking build/src/libcommon-gl.a
[71/71] Linking build/src/glmark2
Waf: Leaving directory `/home/jjj/pj/glmark2/build'
'build' finished successfully (36.123s)
jjj-pc:~/pj/glmark2$
  • 檢視生成的 glmark2 可執行檔案,並通過 gdb 給 main 函數打斷點
// 查詢生成的可執行檔案位置
jjj-pc:~/pj/glmark2$ find -name glmark2
./android/src/org/linaro/glmark2
./build/src/glmark2
// gdb 可執行檔案
jjj-pc:~/pj/glmark2$ gdb ./build/src/glmark2
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
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 ./build/src/glmark2...
// 檢視原始碼。說明已經是偵錯版本
(gdb) list
146     {
147         std::stringstream ss;
148
149         for (int i = 0; i < argc; i++)
150         {
151             if (i > 0) ss << " ";
152             ss << argv[i];
153         }
154
155         return ss.str();
(gdb) b main
Breakpoint 1 at 0x1b670: file ../src/main.cpp, line 160.
(gdb) run
Starting program: /home/jjj/pj/glmark2/build/src/glmark2

Breakpoint 1, main (argc=1, argv=0x7fffffffe388) at ../src/main.cpp:160
160     {
(gdb) n
162         NativeStateX11 native_state;
(gdb) n
175         if (!Options::parse_args(argc, argv))
(gdb) n
179         Log::init(Util::appname_from_path(argv[0]), Options::show_debug);
(gdb) list 179
174
175         if (!Options::parse_args(argc, argv))
176             return 1;
177
178         /* Initialize Log class */
179         Log::init(Util::appname_from_path(argv[0]), Options::show_debug);
180
181         if (!ResultsFile::init(Options::results_file)) {
182             Log::error("%s: Could not initialize results file\n", __FUNCTION__);
183             return 1;
(gdb) list 178
173     #endif
174
175         if (!Options::parse_args(argc, argv))
176             return 1;
177
178         /* Initialize Log class */
179         Log::init(Util::appname_from_path(argv[0]), Options::show_debug);
180
181         if (!ResultsFile::init(Options::results_file)) {
182             Log::error("%s: Could not initialize results file\n", __FUNCTION__);
(gdb) n
80            new_allocator() _GLIBCXX_USE_NOEXCEPT { }
(gdb) n
179         Log::init(Util::appname_from_path(argv[0]), Options::show_debug);
(gdb)

Tip:如果 gdb 對應的可執行檔案不是一個偵錯版本,輸入 list 是看不到原始碼的

gdb 中斷

筆者需要排查一個glmark2應用卡住的問題,由於現象是偶現的,所以才有上面編譯glmark2偵錯版本的需求。

打算用gdb執行glmark2偵錯版本,當glmark2異常時,gdb會中斷,然後分析問題。

:如果看起來 glmark2 卡住,但其實並沒有卡死,還在好好地跑著,那麼這個偵錯方法就沒有用

我們用範例演示一下 glmark2 異常,gdb 會中斷。還是上文程序偵錯的範例:

gdb 執行程式:

jjj-pc:~/pj/dir1$ cat demo.c
#include <stdio.h>
#include <unistd.h>

int main() {
    while (1) {
        sleep(1); // 暫停1秒鐘
    }

    return 0;
}
jjj-pc:~/pj/dir1$ gcc demo.c -o demo -g
jjj-pc:~/pj/dir1$ gdb demo
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
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 demo...
(gdb) list
1       #include <stdio.h>
2       #include <unistd.h>
3
4       int main() {
5           while (1) {
6               sleep(1); // 暫停1秒鐘
7           }
8
9           return 0;
10      }
// 執行程式
(gdb) run
Starting program: /home/jjj/pj/dir1/demo

下面我們用 kill 傳送訊號模擬程式出錯

kill 用於給程序傳送訊號。它有很多訊號,平時常寫的 kill -9 111等於kill -s SIGKILL 111,也就是強制中止程序。kill -11 用於向程序傳送 SIGSEGV訊號的命令,SIGSEGV是一種段錯誤訊號,用於指示程序存取了無效的或不允許存取的記憶體地址,導致了段錯誤。

// kill - 給程序傳送訊號
jjj-pc:~$ whatis kill
kill (1)             - send a signal to a process
// 有這麼多資訊
// kill -9 111 等於 kill -s SIGKILL 111
jjj-pc:~$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
jjj-pc:~$

檢視剛才執行程式的程序號,並給該程序傳送段錯誤資訊。

jjj-pc:~$ ps aux |grep demo
jjj     2056  0.0  0.0 240000  4744 ?        Sl   6月07   0:00 /usr/libexec/geoclue-2.0/demos/agent
// 我們的程式路徑就是 /home/jjj/pj/dir1/
jjj  3119734  0.0  0.0   2356   588 pts/1    S+   19:57   0:00 /home/jjj/pj/dir1/demo
jjj  3119754  0.0  0.0  12120   720 pts/6    S+   19:57   0:00 grep --color=auto demo
// 給程序傳送段錯誤的訊號
jjj-pc:~$ kill -11 3119734

gdb 接收到訊號,於是中斷如下:

Starting program: /home/jjj/pj/dir1/demo

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e991e4 in clock_nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
(gdb)

在使用GDB(GNU偵錯程式)執行一個帶有偵錯資訊的程式時,除了程式發生段錯誤導致GDB中斷之外,還有其他一些場景可能導致GDB中斷:斷點、手動暫停(按下Ctrl+C鍵)、接收訊號(如SIGINT)、異常情況。