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

開發(fā)一個(gè)Linux調(diào)試器(六):源碼級(jí)逐步執(zhí)行

系統(tǒng) Linux
在前幾篇博文中我們學(xué)習(xí)了 DWARF 信息以及它如何使我們將機(jī)器碼和上層源碼聯(lián)系起來。這一次我們通過為我們的調(diào)試器添加源碼級(jí)逐步調(diào)試將該知識(shí)應(yīng)用于實(shí)際。

[[201417]]

在前幾篇博文中我們學(xué)習(xí)了 DWARF 信息以及它如何使我們將機(jī)器碼和上層源碼聯(lián)系起來。這一次我們通過為我們的調(diào)試器添加源碼級(jí)逐步調(diào)試將該知識(shí)應(yīng)用于實(shí)際。

系列文章索引

隨著后面文章的發(fā)布,這些鏈接會(huì)逐漸生效。

  1. 準(zhǔn)備環(huán)境
  2. 斷點(diǎn)
  3. 寄存器和內(nèi)存
  4. Elves 和 dwarves
  5. 源碼和信號(hào)
  6. 源碼級(jí)逐步執(zhí)行
  7. 源碼級(jí)斷點(diǎn)
  8. 調(diào)用棧展開
  9. 讀取變量
  10. 下一步

揭秘指令級(jí)逐步執(zhí)行

我們正在超越了自我。首先讓我們通過用戶接口揭秘指令級(jí)單步執(zhí)行。我決定將它切分為能被其它部分代碼利用的 single_step_instruction 和確保是否啟用了某個(gè)斷點(diǎn)的 single_step_instruction_with_breakpoint_check 兩個(gè)函數(shù)。

  1. void debugger::single_step_instruction() { 
  2.     ptrace(PTRACE_SINGLESTEP, m_pid, nullptr, nullptr); 
  3.     wait_for_signal(); 
  4. void debugger::single_step_instruction_with_breakpoint_check() { 
  5.     //首先,檢查我們是否需要停用或者啟用某個(gè)斷點(diǎn) 
  6.     if (m_breakpoints.count(get_pc())) { 
  7.         step_over_breakpoint(); 
  8.     } 
  9.     else { 
  10.         single_step_instruction(); 
  11.     } 

正如以往,另一個(gè)命令被集成到我們的 handle_command 函數(shù):

  1. else if(is_prefix(command, "stepi")) { 
  2.     single_step_instruction_with_breakpoint_check(); 
  3.     auto line_entry = get_line_entry_from_pc(get_pc()); 
  4.     print_source(line_entry->file->path, line_entry->line); 
  5.  } 

利用新增的這些函數(shù)我們可以開始實(shí)現(xiàn)我們的源碼級(jí)逐步執(zhí)行函數(shù)。

實(shí)現(xiàn)逐步執(zhí)行

我們打算編寫這些函數(shù)非常簡(jiǎn)單的版本,但真正的調(diào)試器有 thread plan 的概念,它封裝了所有的單步信息。例如,調(diào)試器可能有一些復(fù)雜的邏輯去決定斷點(diǎn)的位置,然后有一些回調(diào)函數(shù)用于判斷單步操作是否完成。這其中有非常多的基礎(chǔ)設(shè)施,我們只采用一種樸素的方法。我們可能會(huì)意外地跳過斷點(diǎn),但如果你愿意的話,你可以花一些時(shí)間把所有的細(xì)節(jié)都處理好。

對(duì)于跳出 step_out,我們只是在函數(shù)的返回地址處設(shè)一個(gè)斷點(diǎn)然后繼續(xù)執(zhí)行。我暫時(shí)還不想考慮調(diào)用棧展開的細(xì)節(jié) - 這些都會(huì)在后面的部分介紹 - 但可以說返回地址就保存在棧幀開始的后 8 個(gè)字節(jié)中。因此我們會(huì)讀取棧指針然后在內(nèi)存相對(duì)應(yīng)的地址讀取值:

  1. void debugger::step_out() { 
  2.     auto frame_pointer = get_register_value(m_pid, reg::rbp); 
  3.     auto return_address = read_memory(frame_pointer+8); 
  4.     bool should_remove_breakpoint = false
  5.     if (!m_breakpoints.count(return_address)) { 
  6.         set_breakpoint_at_address(return_address); 
  7.         should_remove_breakpoint = true
  8.     } 
  9.     continue_execution(); 
  10.     if (should_remove_breakpoint) { 
  11.         remove_breakpoint(return_address); 
  12.     } 

remove_breakpoint 是一個(gè)小的幫助函數(shù):

  1. void debugger::remove_breakpoint(std::intptr_t addr) { 
  2.     if (m_breakpoints.at(addr).is_enabled()) { 
  3.         m_breakpoints.at(addr).disable(); 
  4.     } 
  5.     m_breakpoints.erase(addr); 

接下來是跳入 step_in。一個(gè)簡(jiǎn)單的算法是繼續(xù)逐步執(zhí)行指令直到新的一行。

  1. void debugger::step_in() { 
  2.    auto line = get_line_entry_from_pc(get_pc())->line; 
  3.     while (get_line_entry_from_pc(get_pc())->line == line) { 
  4.         single_step_instruction_with_breakpoint_check(); 
  5.     } 
  6.     auto line_entry = get_line_entry_from_pc(get_pc()); 
  7.     print_source(line_entry->file->path, line_entry->line); 

跳過 step_over 對(duì)于我們來說是三個(gè)中最難的。理論上,解決方法就是在下一行源碼中設(shè)置一個(gè)斷點(diǎn),但下一行源碼是什么呢?它可能不是當(dāng)前行后續(xù)的那一行,因?yàn)槲覀兛赡芴幱谝粋€(gè)循環(huán)、或者某種條件結(jié)構(gòu)之中。真正的調(diào)試器一般會(huì)檢查當(dāng)前正在執(zhí)行什么指令然后計(jì)算出所有可能的分支目標(biāo),然后在所有分支目標(biāo)中設(shè)置斷點(diǎn)。對(duì)于一個(gè)小的項(xiàng)目,我不打算實(shí)現(xiàn)或者集成一個(gè) x86 指令模擬器,因此我們要想一個(gè)更簡(jiǎn)單的解決辦法。有幾個(gè)可怕的選擇,一個(gè)是一直逐步執(zhí)行直到當(dāng)前函數(shù)新的一行,或者在當(dāng)前函數(shù)的每一行都設(shè)置一個(gè)斷點(diǎn)。如果我們是要跳過一個(gè)函數(shù)調(diào)用,前者將會(huì)相當(dāng)?shù)牡托?,因?yàn)槲覀冃枰鸩綀?zhí)行那個(gè)調(diào)用圖中的每個(gè)指令,因此我會(huì)采用第二種方法。

  1. void debugger::step_over() { 
  2.     auto func = get_function_from_pc(get_pc()); 
  3.     auto func_entry = at_low_pc(func); 
  4.     auto func_end = at_high_pc(func); 
  5.     auto line = get_line_entry_from_pc(func_entry); 
  6.     auto start_line = get_line_entry_from_pc(get_pc()); 
  7.     std::vector<std::intptr_t> to_delete{}; 
  8.     while (line->address < func_end) { 
  9.         if (line->address != start_line->address && !m_breakpoints.count(line->address)) { 
  10.             set_breakpoint_at_address(line->address); 
  11.             to_delete.push_back(line->address); 
  12.         } 
  13.         ++line; 
  14.     } 
  15.     auto frame_pointer = get_register_value(m_pid, reg::rbp); 
  16.     auto return_address = read_memory(frame_pointer+8); 
  17.     if (!m_breakpoints.count(return_address)) { 
  18.         set_breakpoint_at_address(return_address); 
  19.         to_delete.push_back(return_address); 
  20.     } 
  21.     continue_execution(); 
  22.     for (auto addr : to_delete) { 
  23.         remove_breakpoint(addr); 
  24.     } 

這個(gè)函數(shù)有一點(diǎn)復(fù)雜,我們將它拆開來看。

  1. auto func = get_function_from_pc(get_pc()); 
  2. auto func_entry = at_low_pc(func); 
  3. auto func_end = at_high_pc(func); 

at_low_pc 和 at_high_pc 是 libelfin 中的函數(shù),它們能給我們指定函數(shù) DWARF 信息條目的最小和***程序計(jì)數(shù)器值。

  1. auto line = get_line_entry_from_pc(func_entry); 
  2. auto start_line = get_line_entry_from_pc(get_pc()); 
  3. std::vector<std::intptr_t> breakpoints_to_remove{}; 
  4. while (line->address < func_end) { 
  5.     if (line->address != start_line->address && !m_breakpoints.count(line->address)) { 
  6.         set_breakpoint_at_address(line->address); 
  7.         breakpoints_to_remove.push_back(line->address); 
  8.     } 
  9.     ++line; 

我們需要移除我們?cè)O(shè)置的所有斷點(diǎn),以便不會(huì)泄露出我們的逐步執(zhí)行函數(shù),為此我們把它們保存到一個(gè) std::vector 中。為了設(shè)置所有斷點(diǎn),我們循環(huán)遍歷行表?xiàng)l目直到找到一個(gè)不在我們函數(shù)范圍內(nèi)的。對(duì)于每一個(gè),我們都要確保它不是我們當(dāng)前所在的行,而且在這個(gè)位置還沒有設(shè)置任何斷點(diǎn)。

  1. auto frame_pointer = get_register_value(m_pid, reg::rbp); 
  2.     auto return_address = read_memory(frame_pointer+8); 
  3.     if (!m_breakpoints.count(return_address)) { 
  4.         set_breakpoint_at_address(return_address); 
  5.         to_delete.push_back(return_address); 
  6.     } 

這里我們?cè)诤瘮?shù)的返回地址處設(shè)置一個(gè)斷點(diǎn),正如跳出 step_out。

  1. continue_execution(); 
  2. for (auto addr : to_delete) { 
  3.     remove_breakpoint(addr); 

***,我們繼續(xù)執(zhí)行直到***它們中的其中一個(gè)斷點(diǎn),然后移除所有我們?cè)O(shè)置的臨時(shí)斷點(diǎn)。

它并不美觀,但暫時(shí)先這樣吧。

當(dāng)然,我們還需要將這個(gè)新功能添加到用戶界面:

  1. else if(is_prefix(command, "step")) { 
  2.     step_in(); 
  3. else if(is_prefix(command, "next")) { 
  4.     step_over(); 
  5. else if(is_prefix(command, "finish")) { 
  6.     step_out(); 

測(cè)試

我通過實(shí)現(xiàn)一個(gè)調(diào)用一系列不同函數(shù)的簡(jiǎn)單函數(shù)來進(jìn)行測(cè)試:

  1. void a() { 
  2.     int foo = 1; 
  3. void b() { 
  4.     int foo = 2; 
  5.     a(); 
  6. void c() { 
  7.     int foo = 3; 
  8.     b(); 
  9. void d() { 
  10.     int foo = 4; 
  11.     c(); 
  12. void e() { 
  13.     int foo = 5; 
  14.     d(); 
  15. void f() { 
  16.     int foo = 6; 
  17.     e(); 
  18. int main() { 
  19.     f(); 

你應(yīng)該可以在 main 地址處設(shè)置一個(gè)斷點(diǎn),然后在整個(gè)程序中跳入、跳過、跳出函數(shù)。如果你嘗試跳出 main 函數(shù)或者跳入任何動(dòng)態(tài)鏈接庫,就會(huì)出現(xiàn)意料之外的事情。

你可以在這里找到這篇博文的相關(guān)代碼。下次我們會(huì)利用我們新的 DWARF 技巧來實(shí)現(xiàn)源碼級(jí)斷點(diǎn)。 

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

2017-09-25 08:04:31

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

2017-08-28 14:40:57

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

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:26:01

Linux調(diào)試器堆棧展開

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-07-05 14:37:07

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

2022-05-23 09:22:20

Go語言調(diào)試器Delve

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)試器

2019-12-06 14:30:41

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

2024-03-13 08:00:00

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

2023-02-28 11:39:55

CMake腳本項(xiàng)目

2009-06-23 11:05:05

Mircosoft C
點(diǎn)贊
收藏

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