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

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

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

[[201417]]

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

系列文章索引

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

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

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

我們正在超越了自我。首先讓我們通過用戶接口揭秘指令級單步執(zhí)行。我決定將它切分為能被其它部分代碼利用的 single_step_instruction 和確保是否啟用了某個斷點的 single_step_instruction_with_breakpoint_check 兩個函數(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.     //首先,檢查我們是否需要停用或者啟用某個斷點 
  6.     if (m_breakpoints.count(get_pc())) { 
  7.         step_over_breakpoint(); 
  8.     } 
  9.     else { 
  10.         single_step_instruction(); 
  11.     } 

正如以往,另一個命令被集成到我們的 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ù)我們可以開始實現(xiàn)我們的源碼級逐步執(zhí)行函數(shù)。

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

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

對于跳出 step_out,我們只是在函數(shù)的返回地址處設(shè)一個斷點然后繼續(xù)執(zhí)行。我暫時還不想考慮調(diào)用棧展開的細節(jié) - 這些都會在后面的部分介紹 - 但可以說返回地址就保存在棧幀開始的后 8 個字節(jié)中。因此我們會讀取棧指針然后在內(nèi)存相對應(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 是一個小的幫助函數(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。一個簡單的算法是繼續(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 對于我們來說是三個中最難的。理論上,解決方法就是在下一行源碼中設(shè)置一個斷點,但下一行源碼是什么呢?它可能不是當前行后續(xù)的那一行,因為我們可能處于一個循環(huán)、或者某種條件結(jié)構(gòu)之中。真正的調(diào)試器一般會檢查當前正在執(zhí)行什么指令然后計算出所有可能的分支目標,然后在所有分支目標中設(shè)置斷點。對于一個小的項目,我不打算實現(xiàn)或者集成一個 x86 指令模擬器,因此我們要想一個更簡單的解決辦法。有幾個可怕的選擇,一個是一直逐步執(zhí)行直到當前函數(shù)新的一行,或者在當前函數(shù)的每一行都設(shè)置一個斷點。如果我們是要跳過一個函數(shù)調(diào)用,前者將會相當?shù)牡托В驗槲覀冃枰鸩綀?zhí)行那個調(diào)用圖中的每個指令,因此我會采用第二種方法。

  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.     } 

這個函數(shù)有一點復(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 信息條目的最小和***程序計數(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; 

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

  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.     } 

這里我們在函數(shù)的返回地址處設(shè)置一個斷點,正如跳出 step_out。

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

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

它并不美觀,但暫時先這樣吧。

當然,我們還需要將這個新功能添加到用戶界面:

  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(); 

測試

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

  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è)置一個斷點,然后在整個程序中跳入、跳過、跳出函數(shù)。如果你嘗試跳出 main 函數(shù)或者跳入任何動態(tài)鏈接庫,就會出現(xiàn)意料之外的事情。

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

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

2017-09-25 08:04:31

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

2017-08-28 14:40:57

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

2017-06-28 14:21:22

Linux調(diào)試器斷點

2017-06-22 10:44:55

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

2017-10-09 10:56:49

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

2017-10-12 18:20:44

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

2017-10-09 10:26:01

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)存

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ù)代碼

2023-02-28 11:39:55

CMake腳本項目

2024-03-13 08:00:00

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

2010-02-24 09:32:24

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

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

主站蜘蛛池模板: 久久久亚洲| 青青草视频网 | 一呦二呦三呦国产精品 | 一级在线视频 | 一区二区三区欧美在线观看 | 亚洲一区二区三区免费在线观看 | 亚洲一区二区日韩 | 国产一区二区三区精品久久久 | 一区二区在线看 | 日本不卡一区二区三区在线观看 | 99精品久久| 亚洲第1页| 九九热在线观看视频 | 一区二区高清 | 久久日韩粉嫩一区二区三区 | 亚洲不卡在线观看 | 亚洲国产成人av好男人在线观看 | 成人免费福利视频 | 欧美色综合网 | 在线成人一区 | 日韩欧美在线一区 | 久久久久久99 | 久久最新精品视频 | www.youjizz.com日韩 | 日本黄视频在线观看 | 99久久精品免费看国产小宝寻花 | 亚洲一区二区三区视频免费观看 | 亚洲精品一区二三区不卡 | 久久久成人免费一区二区 | 日韩综合| 日日日日日日bbbbb视频 | 黄在线免费观看 | 免费观看成人性生生活片 | 久久精品一区二区三区四区 | 日本中文字幕一区 | 欧美啪啪| 97视频成人 | 国产男女视频 | 亚洲精品黄色 | 成人午夜精品 | 九色在线观看 |