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

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

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

[[205544]]

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

系列索引

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

  1. 準(zhǔn)備環(huán)境
  2. 斷點
  3. 寄存器和內(nèi)存
  4. ELF 和 DWARF
  5. 源碼和信號
  6. 源碼級逐步執(zhí)行
  7. 源碼級斷點
  8. 堆棧展開
  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' 這行,那么有兩種方法可以達到:main->b->a或main->c->a`。如果我們用 LLDB 設(shè)置一個斷點,繼續(xù)執(zhí)行并請求一個回溯,那么我們將得到以下內(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 

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

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

  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 

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

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

  1. void debugger::print_backtrace() { 

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

  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í)行的幀。我們可以通過查找 DWARF 中的當(dāng)前程序計數(shù)器來獲取此幀的信息:

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

接下來我們需要獲取當(dāng)前函數(shù)的幀指針和返回地址。幀指針存儲在 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)在我們擁有了展開堆棧所需的所有信息。我只需要繼續(xù)展開,直到調(diào)試器*** main,但是當(dāng)幀指針為 0x0 時,你也可以選擇停止,這些是你在調(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.     } 

就是這樣!以下是整個函數(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)然,我們必須向用戶公開這個命令。

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

測試

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

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

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

2017-06-28 14:21:22

Linux調(diào)試器斷點

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)試器高級主題

2017-08-28 14:40:57

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

2017-09-25 08:04:31

Linux調(diào)試器源碼級斷點

2017-07-25 10:30:32

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

2017-07-05 14:37:07

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

2017-08-28 15:29:19

Linux調(diào)試器源碼級逐步執(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)試器

2020-03-16 10:05:13

EmacsGUDLinux

2010-03-01 11:06:52

Python 調(diào)試器

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腳本項目

2010-02-24 09:32:24

Python 調(diào)試器
點贊
收藏

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

主站蜘蛛池模板: 日日噜噜夜夜爽爽狠狠 | 国产精品永久免费视频 | 草久久免费视频 | 精品福利在线 | 国产目拍亚洲精品99久久精品 | 伊人二区 | 日日骚av| 99视频免费在线 | 久久久91| 一区二区三区韩国 | 国产91视频播放 | 国产99免费视频 | 久久99成人| 国产日韩一区二区三区 | 国产成人小视频 | 欧美精品在线观看 | 国产精品69毛片高清亚洲 | 求毛片 | 日本福利视频免费观看 | 91免费在线看 | www国产成人免费观看视频,深夜成人网 | 久久久久免费精品国产小说色大师 | 婷婷激情五月网 | 亚洲在线一区 | 永久免费av | 91在线播 | av一区二区三区四区 | 欧美久久久久久久久 | 日韩有码一区 | 欧美成人一区二区 | 99re在线视频 | 国产激情片在线观看 | 午夜国产一级 | wwwxxx国产| 视频一区二区在线 | 在线成人免费视频 | 一区二区三区小视频 | 欧美一级大片 | 日韩一区在线播放 | 精品一区二区三区在线观看 | 国产午夜精品一区二区 |