成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

GDB 調試器如何通過調用幀信息來確定函數調用關系

開發
DWARF 調用幀信息為編譯器提供了一種靈活的方式來包含用于準確展開堆棧的信息,這使得可以確定當前活動的函數調用。

在我的 上一篇文章 中,我展示了如何使用 debuginfo 在當前指令指針(IP)和包含它的函數或行之間進行映射。該信息對于顯示 CPU 當前正在執行的代碼很有幫助。不過,如果能顯示更多的有關當前函數調用棧及其正在執行語句的上下文對我們定位問題來說也是十分有助的。

例如,將空指針作為參數傳遞到函數中而導致非法內存訪問的問題,只需查看當前執行函數行,即可發現該錯誤是由嘗試通過空指針進行訪問而觸發的。但是,你真正想知道的是導致空指針訪問的函數調用的完整上下文,以便確定該空指針最初是如何傳遞到該函數中的。此上下文信息由回溯提供,可以讓你確定哪些函數可能對空指針參數負責。

有一點是肯定的:確定當前活動的函數調用棧不是一項簡單的操作。

函數激活記錄

現代編程語言具有局部變量,并允許函數可以調用自身的遞歸。此外,并發程序具有多個線程,這些線程可能同時運行相同的函數。在這些情況下,局部變量不能存儲在全局位置。對于函數的每次調用,局部變量的位置必須是唯一的。它的工作原理如下:

  • 每次調用函數時,編譯器都會生成函數激活記錄,以將局部變量存儲在唯一位置。
  • 為了提高效率,處理器堆棧用于存儲函數激活記錄。
  • 當函數被調用時,會在處理器堆棧的頂部為該函數創建一條新的函數激活記錄。
  • 如果該函數調用另一個函數,則新的函數激活記錄將放置在現有函數激活記錄之上。
  • 每次函數返回時,其函數激活記錄都會從堆棧中刪除。

函數激活記錄的創建是由函數中稱為“序言prologue”的代碼創建的。函數激活記錄的刪除由函數“尾聲epilogue”處理。函數體可以利用堆棧上為其預留的內存來存儲臨時值和局部變量。

函數激活記錄的大小可以是可變的。對于某些函數,不需要空間來存儲局部變量。理想情況下,函數激活記錄只需要存儲調用 該

函數激活記錄的布局方式、調用函數的返回地址和舊幀指針是相對于當前幀指針的恒定偏移量。通過舊的幀指針,可以定位堆棧上下一個函數的激活幀。重復此過程,直到檢查完所有函數激活記錄為止。

優化復雜性

在代碼中使用顯式幀指針有幾個缺點。在某些處理器上,可用的寄存器相對較少。具有顯式幀指針會導致使用更多內存操作。生成的代碼速度較慢,因為幀指針必須位于寄存器中。具有顯式幀指針可能會限制編譯器可以生成的代碼,因為編譯器可能不會將函數序言和尾聲代碼與函數體混合。

編譯器的目標是盡可能生成快速代碼,因此編譯器通常會從生成的代碼中省略幀指針。正如 Phoronix 的基準測試 所示,保留幀指針會顯著降低性能。不過省略幀指針也有缺點,查找前一個調用函數的激活幀和返回地址不再是相對于幀指針的簡單偏移。

調用幀信息

為了幫助生成函數回溯,編譯器包含 DWARF 調用幀信息(CFI)來重建幀指針并查找返回地址。此補充信息存儲在執行的 .eh_frame 部分中。與傳統的函數和行位置信息的 debuginfo 不同,即使生成的可執行文件沒有調試信息,或者調試信息已從文件中刪除,.eh_frame 部分也位于可執行文件中。 調用幀信息對于 C++ 中的 throw-catch 等語言結構的操作至關重要。

CFI 的每個功能都有一個幀描述條目(FDE)。作為其步驟之一,回溯生成過程為當前正在檢查的激活幀找到適當的 FDE。將 FDE 視為一張表,每一行代表一個或多個指令,并具有以下列:

  • 規范幀地址(CFA),幀指針指向的位置
  • 返回地址
  • 有關其他寄存器的信息

FDE 的編碼旨在最大限度地減少所需的空間量。FDE 描述了行之間的變化,而不是完全指定每一行。為了進一步壓縮數據,多個 FDE 共有的起始信息被分解出來并放置在通用信息條目(CIE)中。 這使得 FDE 更加緊湊,但也需要更多的工作來計算實際的 CFA 并找到返回地址位置。該工具必須從未初始化狀態啟動。它逐步遍歷 CIE 中的條目以獲取函數條目的初始狀態,然后從 FDE 的第一個條目開始繼續處理 FDE,并處理操作,直到到達覆蓋當前正在分析的指令指針的行。

調用幀信息使用實例

從一個簡單的示例開始,其中包含將華氏溫度轉換為攝氏度的函數。 內聯函數在 CFI 中沒有條目,因此 f2c 函數的 attribute((noinline)) 確保編譯器將 f2c 保留為真實函數。

#include <stdio.h>
int __attribute__ ((noinline)) f2c(int f)
{
    int c;
    printf("converting\n");
    c = (f-32.0) * 5.0 /9.0;
    return c;
}
int main (int argc, char *argv[])
{
    int f;
    scanf("%d", &f);
    printf ("%d Fahrenheit = %d Celsius\n",
            f, f2c(f));
    return 0;
}

編譯代碼:

$ gcc -O2 -g -o f2c f2c.c

.eh_frame 部分展示如下:

$ eu-readelf -S f2c |grep eh_frame
[17] .eh_frame_hdr  PROGBITS   0000000000402058 00002058 00000034  0 A  0   0  4
[18] .eh_frame      PROGBITS   0000000000402090 00002090 000000a0  0 A  0   0  8

我們可以通過以下方式獲取 CFI 信息:

$ readelf --debug-dump=frames  f2c > f2c.cfi

生成 f2c 可執行文件的反匯編代碼,這樣你可以查找 f2c 和 main 函數:

$ objdump -d f2c > f2c.dis

在 f2c.dis 中找到以下信息來看看 f2c 和 main 函數的執行位置:

0000000000401060 <main>:
0000000000401190 <f2c>:

在許多情況下,二進制文件中的所有函數在執行函數的第一條指令之前都使用相同的 CIE 來定義初始條件。 在此示例中, f2c 和 main 都使用以下 CIE:

00000000 0000000000000014 00000000 CIE
  Version:                   1
  Augmentation:              "zR"
  Code alignment factor: 1
  Data alignment factor: -8
  Return address column: 16
  Augmentation data:         1b
  DW_CFA_def_cfa: r7 (rsp) ofs 8
  DW_CFA_offset: r16 (rip) at cfa-8
  DW_CFA_nop
  DW_CFA_nop

本示例中,不必擔心增強或增強數據條目。由于 x86_64 處理器具有 1 到 15 字節大小的可變長度指令,因此 “代碼對齊因子” 設置為 1。在只有 32 位(4 字節指令)的處理器上,“代碼對齊因子” 設置為 4,并且允許對一行狀態信息適用的字節數進行更緊湊的編碼。類似地,還有 “數據對齊因子” 來使 CFA 所在位置的調整更加緊湊。在 x86_64 上,堆棧槽的大小為 8 個字節。

虛擬表中保存返回地址的列是 16。這在 CIE 尾部的指令中使用。 有四個 DW_CFA 指令。第一條指令 DW_CFA_def_cfa 描述了如果代碼具有幀指針,如何計算幀指針將指向的規范幀地址(CFA)。 在這種情況下,CFA 是根據 r7 (rsp) 和 CFA=rsp+8 計算的。

第二條指令 DW_CFA_offset 定義從哪里獲取返回地址 CFA-8 。在這種情況下,返回地址當前由堆棧指針 (rsp+8)-8 指向。CFA 從堆棧返回地址的正上方開始。

CIE 末尾的 DW_CFA_nop 進行填充以保持 DWARF 信息的對齊。 FDE 還可以在末尾添加填充以進行對齊。

在 f2c.cfi 中找到 main 的 FDE,它涵蓋了從 0x40160 到(但不包括)0x401097 的 main 函數:

00000084 0000000000000014 00000088 FDE cie=00000000 pc=0000000000401060..0000000000401097
  DW_CFA_advance_loc: 4 to 0000000000401064
  DW_CFA_def_cfa_offset: 32
  DW_CFA_advance_loc: 50 to 0000000000401096
  DW_CFA_def_cfa_offset: 8
  DW_CFA_nop

在執行函數中的第一條指令之前,CIE 描述調用幀狀態。然而,當處理器執行函數中的指令時,細節將會改變。 首先,指令 DW_CFA_advance_loc 和 DW_CFA_def_cfa_offset 與 main 中 401060 處的第一條指令匹配。 這會將堆棧指針向下調整 0x18(24 個字節)。 CFA 沒有改變位置,但堆棧指針改變了,因此 CFA 在 401064 處的正確計算是 rsp+32。 這就是這段代碼中序言指令的范圍。 以下是 main 中的前幾條指令:

0000000000401060 <main>:
  401060:    48 83 ec 18      sub        $0x18,%rsp
  401064:    bf 1b 20 40 00   mov        $0x40201b,%edi

DW_CFA_advance_loc 使當前行應用于函數中接下來的 50 個字節的代碼,直到 401096。CFA 位于 rsp+32,直到 401092 處的堆棧調整指令完成執行。DW_CFA_def_cfa_offset 將 CFA 的計算更新為與函數入口相同。這是預期之中的,因為 401096 處的下一條指令是返回指令 ret,并將返回值從堆棧中彈出。

  401090:    31 c0        xor        %eax,%eax
  401092:    48 83 c4 18  add        $0x18,%rsp
  401096:    c3           ret

f2c 函數的 FDE 使用與 main 函數相同的 CIE,并覆蓋 0x41190 到 0x4011c3 的范圍:

00000068 0000000000000018 0000006c FDE cie=00000000 pc=0000000000401190..00000000004011c3
  DW_CFA_advance_loc: 1 to 0000000000401191
  DW_CFA_def_cfa_offset: 16
  DW_CFA_offset: r3 (rbx) at cfa-16
  DW_CFA_advance_loc: 29 to 00000000004011ae
  DW_CFA_def_cfa_offset: 8
  DW_CFA_nop
  DW_CFA_nop
  DW_CFA_nop

可執行文件中 f2c 函數的 objdump 輸出:

0000000000401190 <f2c>:
  401190:	53                   	push   %rbx
  401191:	89 fb                	mov    %edi,%ebx
  401193:	bf 10 20 40 00       	mov    $0x402010,%edi
  401198:	e8 93 fe ff ff       	call   401030 <puts@plt>
  40119d:	66 0f ef c0          	pxor   %xmm0,%xmm0
  4011a1:	f2 0f 2a c3          	cvtsi2sd %ebx,%xmm0
  4011a5:	f2 0f 5c 05 93 0e 00 	subsd  0xe93(%rip),%xmm0        # 402040 <__dso_handle+0x38>
  4011ac:	00 
  4011ad:	5b                   	pop    %rbx
  4011ae:	f2 0f 59 05 92 0e 00 	mulsd  0xe92(%rip),%xmm0        # 402048 <__dso_handle+0x40>
  4011b5:	00 
  4011b6:	f2 0f 5e 05 92 0e 00 	divsd  0xe92(%rip),%xmm0        # 402050 <__dso_handle+0x48>
  4011bd:	00 
  4011be:	f2 0f 2c c0          	cvttsd2si %xmm0,%eax
  4011c2:	c3                   	ret

在 f2c 的 FDE 中,函數開頭有一個帶有 DW_CFA_advance_loc 的單字節指令。在高級操作之后,還有兩個附加操作。DW_CFA_def_cfa_offset 將 CFA 更改為 %rsp+16,DW_CFA_offset 表示 %rbx 中的初始值現在位于 CFA-16(堆棧頂部)。

查看這個 fc2 反匯編代碼,可以看到 push 用于將 %rbx 保存到堆棧中。 在代碼生成中省略幀指針的優點之一是可以使用 push 和 pop 等緊湊指令在堆棧中存儲和檢索值。 在這種情況下,保存 %rbx 是因為 %rbx 用于向 printf 函數傳遞參數(實際上轉換為 puts 調用),但需要保存傳遞到函數中的 f 初始值以供后面的計算使用。4011ae 的 DW_CFA_advance_loc 29字節顯示了 pop %rbx 之后的下一個狀態變化,它恢復了 %rbx 的原始值。 DW_CFA_def_cfa_offset 指出 pop 將 CFA 更改為 %rsp+8。

GDB 使用調用幀信息

有了 CFI 信息,GNU 調試器(GDB) 和其他工具就可以生成準確的回溯。如果沒有 CFI 信息,GDB 將很難找到返回地址。如果在 f2c.c 的第 7 行設置斷點,可以看到 GDB 使用此信息。GDB在 f2c 函數中的 pop %rbx 完成且返回值不在棧頂之前放置了斷點。

GDB 能夠展開堆棧,并且作為額外收獲還能夠獲取當前保存在堆棧上的參數 f:

$ gdb f2c
[...]
(gdb) break f2c.c:7
Breakpoint 1 at 0x40119d: file f2c.c, line 7.
(gdb) run
Starting program: /home/wcohen/present/202207youarehere/f2c
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
98
converting
Breakpoint 1, f2c (f=98) at f2c.c:8
8            return c;
(gdb) where
#0  f2c (f=98) at f2c.c:8
#1  0x000000000040107e in main (argc=<optimized out>, argv=<optimized out>)
        at f2c.c:15

調用幀信息

DWARF 調用幀信息為編譯器提供了一種靈活的方式來包含用于準確展開堆棧的信息。這使得可以確定當前活動的函數調用。我在本文中提供了簡要介紹,但有關 DWARF 如何實現此機制的更多詳細信息,請參閱 DWARF 規范

責任編輯:趙寧寧 來源: Linux中國
相關推薦

2019-12-06 14:30:41

GNU調試器GDB修復代碼

2010-03-01 11:06:52

Python 調試器

2011-08-24 16:41:38

lua調試器

2020-03-16 10:05:13

EmacsGUDLinux

2009-12-14 10:57:34

Ruby調試器

2011-08-31 16:51:12

Lua調試器

2023-02-28 11:39:55

CMake腳本項目

2010-07-28 15:29:18

Flex函數

2010-01-28 13:35:41

調用C++函數

2010-02-24 09:32:24

Python 調試器

2011-08-24 11:08:09

Lua

2011-08-31 16:39:06

Lua調試器

2022-05-23 09:22:20

Go語言調試器Delve

2011-08-31 16:47:07

Lua調試器

2009-06-23 11:05:05

Mircosoft C

2013-12-13 15:54:32

Lua腳本語言

2010-04-16 11:03:02

Oracle存儲過程

2011-08-25 16:34:27

Lua調試器

2022-07-22 12:45:39

GNU

2023-03-13 00:21:21

調試器斷點開發者
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久精品a级毛片 | 成人在线视频网站 | 欧美日高清视频 | 国产精品成人在线播放 | 欧美一区二区三区在线观看 | 在线伊人网 | 精品视频在线一区 | 婷婷一级片 | 一区二区三区在线免费观看视频 | 日韩三区| 婷婷丁香在线视频 | 精品久久久av| 午夜影院在线观看版 | 国产综合视频 | 国产精品久久久久久久一区二区 | 一级黄色片毛片 | 国产又色又爽又黄又免费 | 欧美精品久久久 | 中文字幕在线三区 | 国产色视频网站 | 国产91丝袜在线播放 | 日本成人福利 | 欧洲视频一区二区 | 免费久久久 | 亚洲一卡二卡 | 国产精品久久久久久模特 | 综合激情av| 久久精品国产免费高清 | aaa国产大片 | 免费亚洲成人 | 亚洲欧美综合精品另类天天更新 | 精品久久ai电影 | 欧美一级片免费看 | 日韩在线中文字幕 | 国产一区二区三区精品久久久 | 国产精品99久久久久久www | 韩日一区二区 | 日韩一区二区三区视频在线播放 | 久久99久久| 四虎影视一区二区 | 国产午夜精品一区二区三区嫩草 |