GDB查看棧信息


原文地址:https://www.cnblogs.com/jkin/p/13877679.html

GDB查看棧信息

當程序因某種異常停止運行時,我們要做的就是找到程序停止的具體位置,分析導致程序停止的原因。
對於 C、C++ 程序而言,異常往往出現在某個函數體內,例如 main() 主函數、調用的系統庫函數或者自定義的函數等。要知道,程序中每個被調用的函數在執行時,都會生成一些必要的信息,包括:

  • 函數調用發生在程序中的具體位置;
  • 調用函數時的參數;
  • 函數體內部各局部變量的值等等。

這些信息會集中存儲在一塊稱為“棧幀”的內存空間中。也就是說,程序執行時調用了多少個函數,就會相應產生多少個棧幀,其中每個棧幀自函數調用時生成,函數調用結束后自動銷毀。
注意,這些棧幀所在的位置也不是隨意的,它們集中位於一個大的內存區域里,我們通常將其稱為棧區或者棧。

當程序因某種異常暫停執行時,如果其發生在某個函數內部,我們可以嘗試借助該函數對應棧幀中記錄的信息,找到程序發生異常的原因。
慶幸的是,GDB 調試器為了方便用戶在調試程序時查看某個棧幀中記錄的信息,提供了 frame 和 backtrace 命令。接下來,我將給讀者詳細講解一下這 2 個命令的功能和用法。

GDB frame命令

任何一個被調用的函數,執行時都會生成一個存儲必要信息的棧幀。對於 C、C++ 程序而言,其至少也要包含一個函數,即 main() 主函數,這意味着程序執行時至少會生成一個棧幀。main() 主函數對應的棧幀,又稱為初始幀或者最外層的幀。除此之外,每當程序中多調用一個函數,執行過程中就會生成一個新的棧幀。更甚者,如果該函數是一個遞歸函數,則會生成多個棧幀。

在程序內部,各個棧幀用地址作為它們的標識符,注意這里的地址並不一定為棧幀的起始地址。我們知道,每個棧幀往往是由連續的多個字節構成,每個字節都有自己的地址,不同操作系統為棧幀選定地址標識符的規則不同,它們會選擇其中一個字節的地址作為棧幀的標識符。
然而,GDB 調試器並沒有套用地址標識符的方式來管理棧幀。對於當前調試環境中存在的棧幀,GDB 調試器會按照既定規則對它們進行編號:當前正被調用函數對應的棧幀的編號為 0,調用它的函數對應棧幀的編號為 1,以此類推。

frame 命令的常用形式有 2 個:
1) 根據棧幀編號或者棧幀地址,選定要查看的棧幀,語法格式如下:

(gdb) frame spec

該命令可以將 spec 參數指定的棧幀選定為當前棧幀。spec 參數的值,常用的指定方法有 3 種:

  1. 通過棧幀的編號指定。0 為當前被調用函數對應的棧幀號,最大編號的棧幀對應的函數通常就是 main() 主函數;
  2. 借助棧幀的地址指定。棧幀地址可以通過 info frame 命令(后續會講)打印出的信息中看到;
  3. 通過函數的函數名指定。注意,如果是類似遞歸函數,其對應多個棧幀的話,通過此方法指定的是編號最小的那個棧幀。

除此之外,對於選定一個棧幀作為當前棧幀,GDB 調試器還提供有 up 和 down 兩個命令。其中,up 命令的語法格式為:

(gdb) up n

其中 n 為整數,默認值為 1。該命令表示在當前棧幀編號(假設為 m)的基礎上,選定 m+n 為編號的棧幀作為新的當前棧幀。
相對地,down 命令的語法格式為:

(gdb) down n

其中 n 為整數,默認值為 1。該命令表示在當前棧幀編號(假設為 m)的基礎上,選定 m-n 為編號的棧幀作為新的當前棧幀。
2) 借助如下命令,我們可以查看當前棧幀中存儲的信息:

(gdb) info frame

該命令會依次打印出當前棧幀的如下信息:

  • 當前棧幀的編號,以及棧幀的地址;
  • 當前棧幀對應函數的存儲地址,以及該函數被調用時的代碼存儲的地址
  • 當前函數的調用者,對應的棧幀的地址;
  • 編寫此棧幀所用的編程語言;
  • 函數參數的存儲地址以及值;
  • 函數中局部變量的存儲地址;
  • 棧幀中存儲的寄存器變量,例如指令寄存器(64位環境中用 rip 表示,32為環境中用 eip 表示)、堆棧基指針寄存器(64位環境用 rbp 表示,32位環境用 ebp 表示)等。

除此之外,還可以使用info args命令查看當前函數各個參數的值;使用info locals命令查看當前函數中各局部變量的值。

GDB backtrace命令

backtrace 命令用於打印當前調試環境中所有棧幀的信息,常用的語法格式如下:

(gdb) backtrace [-full] [n]

其中,用 [ ] 括起來的參數為可選項,它們的含義分別為:

  • n:一個整數值,當為正整數時,表示打印最里層的 n 個棧幀的信息;n 為負整數時,那么表示打印最外層 n 個棧幀的信息;
  • -full:打印棧幀信息的同時,打印出局部變量的值。

注意,當調試多線程程序時,該命令僅用於打印當前線程中所有棧幀的信息。如果想要打印所有線程的棧幀信息,應執行thread apply all backtrace命令。
基於以上對 frame 和 backtrace 命令的介紹,這里以調試如下 C 語言程序為例,給大家演示這 2 個命令的作用。

 1 #include <stdio.h>
 2
 3 int funC(int* num){
 4     if(*num > 0){
 5         (*num)--;
 6     }else{
 7         (*num)++;
 8     }
 9
10     printf("In funC num: %d\n", *num);
11
12     return *num;
13 }
14
15
16 void funCN(int num){
17     if(num > 0){
18         (num)--;
19     }else{
20         (num)++;
21     }
22
23     printf("In funCN num: %d\n", num);
24
25     return;
26 }
27
28 int funD(int num){
29     if(num == 1){
30         return 1;
31     }else{
32         return num * funD(num - 1);
33     }
34 }
35
36 int main(int argc, char **argv)
37 {
38     printf("Hello world!\n");
39
40     int num = 0;
41     printf("please input num: ");
42     scanf("%d", &num);
43
44     printf("num mul: %d\n", funD(num));
45
46     printf("in main num: %d\n", funC(&num));
47
48     int tmp = funD(num);
49     printf("num mul: %d\n", tmp);
50
51     funCN(num);
52     printf("in main num: %d\n", (num));
53
54     return 0;
55 }

不難發現,funC()是一個能夠修改傳入參數的函數,funCN()是一個不能修改傳入參數的函數;funD() 是一個遞歸函數,已編譯為可供 GDB 調試的 main 可執行文件。在此基礎上,進行如下調試:

復制代碼
(gdb) b 3
Breakpoint 1 at 0x4004cf: file main.c, line 3.
(gdb) r
Starting program: ~/demo/main.exe

Breakpoint 1, func (num=5) at main.c:3
3     if(num==1){
(gdb) c
Continuing.

Breakpoint 1, func (num=4) at main.c:3
3     if(num==1){
(gdb) p num
$1 = 4
(gdb) backtrace        <-- 打印所有的棧幀信息
#0  func (num=4) at main.c:3
#1  0x00000000004004e9 in func (num=5) at main.c:6
#2  0x0000000000400508 in main () at main.c:12
(gdb) info frame       <-- 打印當前棧幀的詳細信息
Stack level 0, frame at 0x7fffffffe240:            <-- 棧幀編號 0,地址 0x7fffffffe240
rip = 0x4004cf in func (main.c:3); saved rip 0x4004e9   <-- 函數的存儲地址 0x4004cf,調用它的函數地址為 0x4004e9
called by frame at 0x7fffffffe260        <-- 當前棧幀的上一級棧幀(編號 1 的棧幀)的地址為 0x7fffffffe260
source language c.
Arglist at 0x7fffffffe230, args: num=4  <-- 函數參數的地址和值
Locals at 0x7fffffffe230, Previous frame's sp is 0x7fffffffe240  <--函數內部局部變量的存儲地址
Saved registers:    <-- 棧幀內部存儲的寄存器
  rbp at 0x7fffffffe230, rip at 0x7fffffffe238
(gdb) info args          <-- 打印當前函數參數的值
num = 4
(gdb) info locals        <-- 打印當前函數內部局部變量的信息(這里沒有)
No locals.
(gdb) up   <-- 查看編號為 1 的棧幀
#1  0x00000000004004e9 in func (num=5) at main.c:6
6         return num*func(num-1);
(gdb) frame 1           <-- 當編號為 1 的棧幀作為當前棧幀
#1  0x00000000004004e9 in func (num=5) at main.c:6
6         return num*func(num-1);
(gdb) info frame      <-- 打印 1 號棧幀的詳細信息
Stack level 1, frame at 0x7fffffffe260:
rip = 0x4004e9 in func (main.c:6); saved rip 0x400508
called by frame at 0x7fffffffe280, caller of frame at 0x7fffffffe240  <--上一級棧幀地址為 0x7fffffffe280,下一級棧幀地址為 0x7fffffffe240
source language c.
Arglist at 0x7fffffffe250, args: num=5
Locals at 0x7fffffffe250, Previous frame's sp is 0x7fffffffe260
Saved registers:
  rbp at 0x7fffffffe250, rip at 0x7fffffffe258
(gdb)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM