文章來源:http://www.zghlxwxcb.cn/news/detail-854361.html
目錄
??1. 整體思路
??2. 準備內容
??2.1?配置.c文件
??2.2 準備測試程序
??2.3 GDB調試基礎
??3. GDB調試四層二叉樹
??3.1?測試程序分析
??3.2 gdb分析
??1. 設置斷點
??2. 啟動程序并執(zhí)行到斷點處
??3. 打印變量的值
??4. 單步執(zhí)行 s 進入buildTree函數內部
a. 第一層:根節(jié)點賦值
b. 第二層:節(jié)點賦值
c. 第三層:節(jié)點賦值
d. 第四層:節(jié)點賦值
e. 退出buildTree函數
??5.?單步執(zhí)行 s 進入traverseTree函數內部:跟蹤輸出結果
??6. 跟蹤錯誤
a. 查看指針 ptr 的值
b. 查看 ptr 所指向的地址
c. 回溯調用堆棧
d. 查看核心轉儲文件
??4.?gdb技巧
??4.1 打印輸出指定地址的值
??4.2 查看當前執(zhí)行到哪行代碼+代碼內容
??1. 整體思路
在案例中我使用c語言編寫了一個簡單的四層二叉樹進行 GDB 調試練習。這個程序故意在后面引發(fā)了一個段錯誤,導致程序崩潰。文章將使用 GDB 來診斷這個問題。
??2. 準備內容
建議閱讀前先查看gdb的技巧
傳送門:【GDB調試技巧】提高gdb的調試效率-CSDN博客
??2.1?配置.c文件
建議先配置一下.c文件使其顯示行數【方便后續(xù)快速定位bug】。默認情況下,GDB 不會在每次調試時自動顯示行號。
編輯 Vim 的配置文件 ~/.vimrc(如果不存在則創(chuàng)建它),并添加以下行:set number
詳細步驟如下:
打開配置文件 ~/.vimrc
nano ~/.vimrc
文件內容添加
set number
效果圖如下:
然后運行以下命令使其生效:
source ~/.bashrc
這樣使用vim 打開文件就會顯示行數了
??2.2 準備測試程序
使用vim文本編輯器新建一個.c文件
vim tree3_01.c
輸入測試程序:
#include <stdio.h>
#include <stdlib.h>
// 定義樹節(jié)點
typedef struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
// 創(chuàng)建一個新的樹節(jié)點
TreeNode* createNode(int data) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
if (newNode == NULL) {
fprintf(stderr, "Memory allocation failed.\n");
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
// 構建四層樹
TreeNode* buildTree() {
TreeNode* root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);
root->right->left = createNode(6);
root->right->right = createNode(7);
root->left->left->left = createNode(8);
root->left->left->right = createNode(9);
return root;
}
// 遞歸遍歷樹并打印節(jié)點數據
void traverseTree(TreeNode* root) {
if (root != NULL) {
printf("%d ", root->data);
traverseTree(root->left);
traverseTree(root->right);
}
}
int main() {
// 構建樹
TreeNode* root = buildTree();
// 打印樹的結構
printf("Tree Structure:\n");
traverseTree(root);
printf("\n");
// 故意制造一個段錯誤,導致core dump
int* ptr = NULL;
*ptr = 10; // 這里將會產生段錯誤
return 0;
}
gcc編譯:
gcc -g -o tree3_01 tree3_01.c
此時ls查看會出現可執(zhí)行文件tree3_01
??2.3 GDB調試基礎
在使用GNU調試器(GDB)時,以下是一些常用的命令:
run
(或r
): 啟動程序并開始調試。break
(或b
): 在指定的位置設置斷點。continue
(或c
): 繼續(xù)執(zhí)行程序直到下一個斷點。step
(或s
): 單步執(zhí)行程序,進入到函數中。next
(或n
): 單步執(zhí)行程序,跳過函數內部的細節(jié)。p
): 打印變量的值。backtrace
(或bt
): 打印函數調用棧。list
(或l
): 顯示源代碼。info
(或i
): 顯示調試信息,比如當前位置、變量類型等。quit
(或q
): 退出調試器。
??3. GDB調試四層二叉樹
??3.1?測試程序分析
測試程序是一個簡單的打印四層二叉樹的c語言程序。
對于樹TreeNode結構體和創(chuàng)建樹節(jié)點createNode函數屬于常規(guī)操作【不做分析】。
程序中的buildTree函數構建了一顆四層二叉樹,并使用traverseTree函數先序遍歷打印二叉樹的數據結構:1 2 4 8 9 5 3 6 7
??3.2 gdb分析
現在,啟動 GDB 并加載程序:
gdb ./tree3_01
進入 GDB,可以執(zhí)行下列步驟來逐步調試:
??1. 設置斷點
在程序出錯的地方設置斷點以停止程序執(zhí)行,并檢查變量。
break main
break main與b main等價。
這段輸出是在 GDB 中設置斷點的結果:
- (gdb): 這是 GDB 的提示符,表示它正在等待用戶輸入命令。
- break main: 這是用戶輸入的命令,表示在程序的 main 函數的起始處設置了一個斷點。
- Breakpoint 1 at 0x1398: 這一行顯示了斷點的信息。Breakpoint 1 表示這是第一個斷點。0x1398 是斷點的地址,表示斷點被設置在程序代碼的內存地址 0x1398 處。
- file tree3_01.c, line 49: 這一行顯示斷點被設置位置在文件 tree3_01.c 的第 49 行處【還未執(zhí)行】。
??2. 啟動程序并執(zhí)行到斷點處
run
run和r等價
這個輸出表明程序已經成功啟動,并且停在了之前設置的斷點處,也就是在 main 函數的第 49 行:
- Starting program: /root/host/my_program/tree3_01: 這是 GDB 啟動程序時的輸出,指示程序已經開始執(zhí)行。
- [Thread debugging using libthread_db enabled]: 這個消息表明 GDB 正在使用 libthread_db 庫進行線程調試,這是針對多線程程序的。
- Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1": 這條消息表明 GDB 正在使用指定的線程庫進行調試。
接著,輸出顯示了程序停在了 main 函數的第 49 行:
- Breakpoint 1, main () at tree3_01.c:49: 這表示斷點 1 已經觸發(fā),程序停在了 tree3_01.c 文件的第 49 行的 main 函數處。
- 49??????????? TreeNode* root = buildTree();:表示tree3_01.c 文件的第 49 行的代碼【此時該行代碼未執(zhí)行】。
現在可以使用 GDB 的其他命令來查看程序狀態(tài),比如打印變量的值、單步執(zhí)行等。
??3. 打印變量的值
可以使用 print 命令,后跟想要打印的變量名。
print root
print root和p root等價
這會打印 root 變量的值,即指向樹根節(jié)點的指針。在這里,我們期望 root 指向一個已經創(chuàng)建好的二叉樹的根節(jié)點。
打印 root 變量的結果顯示為 (TreeNode *) 0x0,這意味著 root 指針當前指向了內存地址 0x0,即空指針【也證明了run之后到達斷點的第49行代碼未執(zhí)行】。
??4. 單步執(zhí)行 s 進入buildTree函數內部
step
step和s等價
step 命令進入 buildTree() 函數后,GDB 顯示了當前所在的位置和執(zhí)行的下一行代碼。
- buildTree () at tree3_01.c:26: 這行顯示了當前所在的函數是buildTree以及函數參數為空。而 tree3_01.c:26 則表示這是在源文件 tree3_01.c 的第 26?行。
- 當前程序執(zhí)行到了 buildTree() 函數的開頭,即第 26 行【未執(zhí)行】
在buildTree函數內部單步執(zhí)行用到的還是n,除非需要進入buildTree函數里面的其他函數才用到s。
a. 第一層:根節(jié)點賦值
此時樹結構如下:
b. 第二層:節(jié)點賦值
?此時樹結構如下:
c. 第三層:節(jié)點賦值
?此時樹結構如下:
d. 第四層:節(jié)點賦值
?此時樹結構如下:
e. 退出buildTree函數
連續(xù)多次單步執(zhí)行 n 即可
??5.?單步執(zhí)行 s 進入traverseTree函數內部:跟蹤輸出結果
next
next和n等價。
跟蹤輸出的詳細過程如下:
跟蹤遞歸輸出顯示的輸出結果為:1 2 4 8 9 5 3 6 7
這和預期輸出的結果保持一致。
??6. 跟蹤錯誤
單步執(zhí)行 n 內容顯示:
Program received signal SIGSEGV, Segmentation fault.
0x00005555555553d7 in main () at tree3_01.c:58
58?? ? ? ?*ptr = 10; // 這里將會產生段錯誤
這個輸出是 GDB 在程序運行時遇到段錯誤時所提供的信息:
Program received signal SIGSEGV, Segmentation fault.:
這表示程序接收到了 SIGSEGV 信號,即段錯誤(Segmentation fault)信號。段錯誤通常發(fā)生在試圖訪問未分配給程序的內存或者訪問已釋放的內存時。
0x00005555555553d7 in main () at tree3_01.c:58:
這部分提供了造成段錯誤的代碼位置信息。其中:
0x00005555555553d7
是導致段錯誤的指令的地址。main ()
表示段錯誤發(fā)生在main
函數內部。tree3_01.c:58
指明了出錯的源文件以及代碼所在的行數,即在文件tree3_01.c
的第 58 行。*58 ptr = 10; // 這里將會產生段錯誤:
這是在發(fā)生段錯誤的位置處的代碼。具體地,這行代碼嘗試將值
10
寫入指針ptr
所指向的內存地址,但是ptr
指向了一個空地址,因此導致了段錯誤。
現在我們需要進一步分析,為什么會發(fā)生段錯誤??梢允褂靡韵聨追N方法:
a. 查看指針 ptr 的值
在發(fā)生段錯誤之前,可以查看指針 ptr 的值,看它是否為 NULL。
p ptr
這個輸出表示指針 ptr
的值是 0x0
,即空指針。
(int *)
表示這是一個指向整型數據的指針。0x0
是十六進制表示的地址,通常表示空指針。
因此,(int *) 0x0
表示指針 ptr
當前指向內存地址為 0x0
,即空指針,那么后續(xù)執(zhí)行的?*ptr = 10;
就會引發(fā)段錯誤。
b. 查看 ptr 所指向的地址
x ptr?
查看指針 ptr 所指向的地址中的內容。
x ptr
輸出表示 GDB 嘗試查看指針 ptr
所指向的內存地址上的內容時出現了問題:
0x0:
表示要查看的內存地址為0x0
。Cannot access memory at address 0x0
意味著 GDB 無法訪問內存地址0x0
。
說明:
- GDB 無法訪問內存地址
0x0
是因為這個地址通常被操作系統(tǒng)保留為無效地址,用來表示空指針或者未分配的內存。因此,當 GDB 嘗試訪問地址0x0
時,操作系統(tǒng)會阻止這種訪問,因為這個地址不屬于程序的有效內存范圍。 - 通常情況下,訪問空指針會導致程序出現段錯誤(Segmentation fault),這是因為試圖在未分配的內存地址上讀取或寫入數據會導致操作系統(tǒng)干預并終止程序的執(zhí)行,以保證系統(tǒng)的穩(wěn)定性和安全性。
綜合這些信息,由于 ptr
是空指針,即其指向的內存地址為 0x0,
會導致錯誤。
c. 回溯調用堆棧
可以使用 backtrace
?(或bt)命令來查看調用堆棧,確定是從哪個函數調用了 main 函數并傳遞了一個空指針。
bt
輸出表示了當前的函數調用堆棧情況,其中:
-
#0
:表示當前所在的調用堆棧幀的索引,從 0 開始計數。 -
0x00005555555553d7 in main () at tree3_01.c:58
:說明當前位于main
函數內,位于文件tree3_01.c
的第 58 行。
輸出表明程序在 main
函數的第 58 行出現了段錯誤(Segmentation fault),導致程序終止。
d. 查看核心轉儲文件
如果程序產生了核心轉儲文件,可以使用 GDB 打開它并查看導致段錯誤的堆棧跟蹤信息。
gdb program core
program是可執(zhí)行文件
core是coredump文件
gdb tree3_01 /tmp/dump/cores/core_tree3_01.50497_1712891407
其中gdb tree3_01 /tmp/dump/cores/core_tree3_01.50497_1712891407等價于
gdb ./tree3_01 /tmp/dump/cores/core_tree3_01.50497_1712891407
然后使用 backtrace(或bt)
命令來查看堆棧跟蹤信息。
bt
這是?bt
命令的輸出,表明當前程序執(zhí)行時的函數調用棧:
#0
: 表示當前棧幀的序號,這里是第一個棧幀。
0x0000564e4be613d7
: 這是當前正在執(zhí)行的函數main
的內存地址。
main ()
: 表示當前執(zhí)行的函數是main
。
at tree3_01.c:58
: 表示main
函數位于tree3_01.c
文件中,并且是在第 58 行開始的。這里的tree3_01.c
是源代碼文件名,而58
則是指示了具體的行號。
??4.?gdb技巧
【GDB調試技巧】提高gdb的調試效率-CSDN博客
文章來源地址http://www.zghlxwxcb.cn/news/detail-854361.html
到了這里,關于【gdb調試】在ubuntu環(huán)境使用gdb調試一棵四層二叉樹的數據結構詳解的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!