自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

開(kāi)發(fā)一個(gè)Linux調(diào)試器(八):堆棧展開(kāi)

系統(tǒng) Linux
有時(shí)你需要知道的最重要的信息是什么,你當(dāng)前的程序狀態(tài)是如何到達(dá)那里的。有一個(gè) backtrace 命令,它給你提供了程序當(dāng)前的函數(shù)調(diào)用鏈。這篇文章將向你展示如何在 x86_64 上實(shí)現(xiàn)堆棧展開(kāi)以生成這樣的回溯。

[[205544]]

有時(shí)你需要知道的最重要的信息是什么,你當(dāng)前的程序狀態(tài)是如何到達(dá)那里的。有一個(gè) backtrace 命令,它給你提供了程序當(dāng)前的函數(shù)調(diào)用鏈。這篇文章將向你展示如何在 x86_64 上實(shí)現(xiàn)堆棧展開(kāi)以生成這樣的回溯。

系列索引

這些鏈接將會(huì)隨著其他帖子的發(fā)布而上線。

  1. 準(zhǔn)備環(huán)境
  2. 斷點(diǎn)
  3. 寄存器和內(nèi)存
  4. ELF 和 DWARF
  5. 源碼和信號(hào)
  6. 源碼級(jí)逐步執(zhí)行
  7. 源碼級(jí)斷點(diǎn)
  8. 堆棧展開(kāi)
  9. 讀取變量
  10. 之后步驟

用下面的程序作為例子:

  1. void a() { 
  2.     //stopped here 
  3. void b() { 
  4.      a(); 
  5. void c() { 
  6.      a(); 
  7. int main() { 
  8.     b(); 
  9.     c(); 

如果調(diào)試器停在 //stopped here' 這行,那么有兩種方法可以達(dá)到:main->b->a或main->c->a`。如果我們用 LLDB 設(shè)置一個(gè)斷點(diǎn),繼續(xù)執(zhí)行并請(qǐng)求一個(gè)回溯,那么我們將得到以下內(nèi)容:

  1. * frame #0: 0x00000000004004da a.out`a() + 4 at bt.cpp:3 
  2.   frame #1: 0x00000000004004e6 a.out`b() + 9 at bt.cpp:6 
  3.   frame #2: 0x00000000004004fe a.out`main + 9 at bt.cpp:14 
  4.   frame #3: 0x00007ffff7a2e830 libc.so.6`__libc_start_main + 240 at libc-start.c:291 
  5.   frame #4: 0x0000000000400409 a.out`_start + 41 

這說(shuō)明我們目前在函數(shù) a 中,a 從函數(shù) b 中跳轉(zhuǎn),b 從 main 中跳轉(zhuǎn)等等。***兩個(gè)幀是編譯器如何引導(dǎo) main 函數(shù)的。

現(xiàn)在的問(wèn)題是我們?nèi)绾卧?x86_64 上實(shí)現(xiàn)。最穩(wěn)健的方法是解析 ELF 文件的 .eh_frame 部分,并解決如何從那里展開(kāi)堆棧,但這會(huì)很痛苦。你可以使用 libunwind 或類似的來(lái)做,但這很無(wú)聊。相反,我們假設(shè)編譯器以某種方式設(shè)置了堆棧,我們將手動(dòng)遍歷它。為了做到這一點(diǎn),我們首先需要了解堆棧的布局。

  1.     High 
  2. |   ...   | 
  3. +---------+ 
  4. |  Arg 1  | 
  5. +---------+ 
  6. |  Arg 2  | 
  7. +---------+ 
  8. Return  | 
  9. +---------+ 
  10. |Saved EBP| 
  11. +---------+ 
  12. |  Var 1  | 
  13. +---------+ 
  14. |  Var 2  | 
  15. +---------+ 
  16. |   ...   | 
  17.     Low 

如你所見(jiàn),***一個(gè)堆棧幀的幀指針存儲(chǔ)在當(dāng)前堆棧幀的開(kāi)始處,創(chuàng)建一個(gè)鏈接的指針列表。堆棧依據(jù)這個(gè)鏈表解開(kāi)。我們可以通過(guò)查找 DWARF 信息中的返回地址來(lái)找出列表中下一幀的函數(shù)。一些編譯器將忽略跟蹤 EBP 的幀基址,因?yàn)檫@可以表示為 ESP 的偏移量,并可以釋放一個(gè)額外的寄存器。即使啟用了優(yōu)化,傳遞 -fno-omit-frame-pointer 到 GCC 或 Clang 會(huì)強(qiáng)制它遵循我們依賴的約定。

我們將在 print_backtrace 函數(shù)中完成所有的工作:

  1. void debugger::print_backtrace() { 

首先要決定的是使用什么格式打印出幀信息。我用了一個(gè) lambda 來(lái)推出這個(gè)方法:

  1. auto output_frame = [frame_number = 0] (auto&& func) mutable { 
  2.     std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func) 
  3.               << ' ' << dwarf::at_name(func) << std::endl; 
  4. }; 

打印輸出的***幀是當(dāng)前正在執(zhí)行的幀。我們可以通過(guò)查找 DWARF 中的當(dāng)前程序計(jì)數(shù)器來(lái)獲取此幀的信息:

  1. auto current_func = get_function_from_pc(get_pc()); 
  2.     output_frame(current_func); 

接下來(lái)我們需要獲取當(dāng)前函數(shù)的幀指針和返回地址。幀指針存儲(chǔ)在 rbp 寄存器中,返回地址是從幀指針堆棧起的 8 字節(jié)。

  1. auto frame_pointer = get_register_value(m_pid, reg::rbp); 
  2. auto return_address = read_memory(frame_pointer+8); 

現(xiàn)在我們擁有了展開(kāi)堆棧所需的所有信息。我只需要繼續(xù)展開(kāi),直到調(diào)試器*** main,但是當(dāng)幀指針為 0x0 時(shí),你也可以選擇停止,這些是你在調(diào)用 main 函數(shù)之前調(diào)用的函數(shù)。我們將從每幀抓取幀指針和返回地址,并打印出信息。

  1. while (dwarf::at_name(current_func) != "main") { 
  2.         current_func = get_function_from_pc(return_address); 
  3.         output_frame(current_func); 
  4.         frame_pointer = read_memory(frame_pointer); 
  5.         return_address = read_memory(frame_pointer+8); 
  6.     } 

就是這樣!以下是整個(gè)函數(shù):

  1. void debugger::print_backtrace() { 
  2.     auto output_frame = [frame_number = 0] (auto&& func) mutable { 
  3.         std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func) 
  4.                   << ' ' << dwarf::at_name(func) << std::endl; 
  5.     }; 
  6.     auto current_func = get_function_from_pc(get_pc()); 
  7.     output_frame(current_func); 
  8.     auto frame_pointer = get_register_value(m_pid, reg::rbp); 
  9.     auto return_address = read_memory(frame_pointer+8); 
  10.     while (dwarf::at_name(current_func) != "main") { 
  11.         current_func = get_function_from_pc(return_address); 
  12.         output_frame(current_func); 
  13.         frame_pointer = read_memory(frame_pointer); 
  14.         return_address = read_memory(frame_pointer+8); 
  15.     } 

添加命令

當(dāng)然,我們必須向用戶公開(kāi)這個(gè)命令。

  1. else if(is_prefix(command, "backtrace")) { 
  2.     print_backtrace(); 

測(cè)試

測(cè)試此功能的一個(gè)方法是通過(guò)編寫(xiě)一個(gè)測(cè)試程序與一堆互相調(diào)用的小函數(shù)。設(shè)置幾個(gè)斷點(diǎn),跳到代碼附近,并確保你的回溯是準(zhǔn)確的。

我們已經(jīng)從一個(gè)只能產(chǎn)生并附加到其他程序的程序走了很長(zhǎng)的路。本系列的倒數(shù)第二篇文章將通過(guò)支持讀寫(xiě)變量來(lái)完成調(diào)試器的實(shí)現(xiàn)。在此之前,你可以在這里找到這個(gè)帖子的代碼。 

責(zé)任編輯:龐桂玉 來(lái)源: Linux中國(guó)
相關(guān)推薦

2017-06-28 14:21:22

Linux調(diào)試器斷點(diǎn)

2017-06-22 10:44:55

Linux調(diào)試器準(zhǔn)備環(huán)境

2017-10-09 10:56:49

Linux調(diào)試器處理變量

2017-10-12 18:20:44

Linux調(diào)試器高級(jí)主題

2017-07-25 10:30:32

Linux調(diào)試器Elves和dwarv

2017-09-25 08:04:31

Linux調(diào)試器源碼級(jí)斷點(diǎn)

2017-08-28 14:40:57

Linux調(diào)試器源碼和信號(hào)

2017-07-05 14:37:07

Linux調(diào)試器寄存器和內(nèi)存

2017-08-28 15:29:19

Linux調(diào)試器源碼級(jí)逐步執(zhí)行

2024-03-13 08:00:00

Linux調(diào)試器應(yīng)用程序

2017-04-19 21:35:38

Linux調(diào)試器工作原理

2011-08-25 16:34:27

Lua調(diào)試器

2010-03-01 11:06:52

Python 調(diào)試器

2020-03-16 10:05:13

EmacsGUDLinux

2009-12-14 10:57:34

Ruby調(diào)試器

2011-08-31 16:51:12

Lua調(diào)試器

2016-09-21 12:34:10

Chrome瀏覽器插件

2019-12-06 14:30:41

GNU調(diào)試器GDB修復(fù)代碼

2023-02-28 11:39:55

CMake腳本項(xiàng)目

2009-06-23 11:05:05

Mircosoft C
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)