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

開發(fā)一個Linux調(diào)試器(一):準備環(huán)境

系統(tǒng) Linux
在本項目中我會將重點放在 C 和 C++,但對于那些將源碼編譯為機器碼并輸出標準 DWARE 調(diào)試信息的語言也應該能起作用(如果你還不知道這些東西是什么,別擔心,馬上就會介紹到啦)。另外,我只關(guān)注如何將程序運行起來并在大部分情況下能正常工作,為了簡便,會避開類似健壯錯誤處理方面的東西。

[[194747]]

任何寫過比 hello world 復雜一些的程序的人都應該使用過調(diào)試器(如果你還沒有,那就停下手頭的工作先學習一下吧)。但是,盡管這些工具已經(jīng)得到了廣泛的使用,卻并沒有太多的資源告訴你它們的工作原理以及如何開發(fā),尤其是和其它那些比如編譯器等工具鏈技術(shù)相比而言。

此處有一些其它的資源可以參考:

  • http://eli.thegreenplace.net/2011/01/23/how-debuggers-work-part-1
  • https://t-a-w.blogspot.co.uk/2007/03/how-to-code-debuggers.html
  • https://www.codeproject.com/Articles/43682/Writing-a-basic-Windows-debugger
  • http://system.joekain.com/debugger/

我們將會支持以下功能:

  • 啟動、暫停、繼續(xù)執(zhí)行
  • 在不同地方設置斷點
    • 內(nèi)存地址
    • 源代碼行
    • 函數(shù)入口
  • 讀寫寄存器和內(nèi)存
  • 單步執(zhí)行
    • 指令
    • 進入函數(shù)
    • 跳出函數(shù)
    • 跳過函數(shù)
  • 打印當前代碼地址
  • 打印函數(shù)調(diào)用棧
  • 打印簡單變量的值

在***一部分,我還會大概介紹如何給你的調(diào)試器添加下面的功能:

  • 遠程調(diào)試
  • 共享庫和動態(tài)庫支持
  • 表達式計算
  • 多線程調(diào)試支持

在本項目中我會將重點放在 C 和 C++,但對于那些將源碼編譯為機器碼并輸出標準 DWARE 調(diào)試信息的語言也應該能起作用(如果你還不知道這些東西是什么,別擔心,馬上就會介紹到啦)。另外,我只關(guān)注如何將程序運行起來并在大部分情況下能正常工作,為了簡便,會避開類似健壯錯誤處理方面的東西。

系列文章索引

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

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

LCTT 譯注:ELF —— 可執(zhí)行文件格式(Executable and Linkable Format);DWARF(一種廣泛使用的調(diào)試數(shù)據(jù)格式,參考 WIKI)。

準備環(huán)境

在我們正式開始之前,我們首先要設置環(huán)境。在這篇文章中我會依賴兩個工具:Linenoise 用于處理命令行輸入,libelfin 用于解析調(diào)試信息。你也可以使用更傳統(tǒng)的 libdwarf 而不是 libelfin,但是界面沒有那么友好,另外 libelfin 還提供了基本完整的 DWARF 表達式求值器,當你想讀取變量的值時這能幫你節(jié)省很多時間。確認你使用的是 libelfin 我的 fbreg 分支,因為它提供 x86 上讀取變量的額外支持。

一旦你在系統(tǒng)上安裝或者使用你喜歡的編譯系統(tǒng)編譯好了這些依賴工具,就可以開始啦。我在 CMake 文件中把它們設置為和我其余的代碼一起編譯。

啟動可執(zhí)行程序

在真正調(diào)試任何程序之前,我們需要啟動被調(diào)試的程序。我們會使用經(jīng)典的 fork/exec 模式。

  1. int main(int argc, char* argv[]) { 
  2.     if (argc < 2) { 
  3.         std::cerr << "Program name not specified"
  4.         return -1; 
  5.     } 
  6.     auto prog = argv[1]; 
  7.     auto pid = fork(); 
  8.     if (pid == 0) { 
  9.         //we're in the child process 
  10.         //execute debugee 
  11.     } 
  12.     else if (pid >= 1)  { 
  13.         //we're in the parent process 
  14.         //execute debugger 
  15.     } 

我們調(diào)用 fork 把我們的程序分成兩個進程。如果我們是在子進程,fork 返回 0,如果我們是在父進程,它會返回子進程的進程 ID。

如果我們是在子進程,我們要用希望調(diào)試的程序替換正在執(zhí)行的程序。

  1. ptrace(PTRACE_TRACEME, 0, nullptr, nullptr); 
  2. execl(prog.c_str(), prog.c_str(), nullptr); 

這里我們***次遇到了 ptrace,它會在我們編寫調(diào)試器的時候經(jīng)常遇到。ptrace 通過讀取寄存器、內(nèi)存、逐步調(diào)試等讓我們觀察和控制另一個進程的執(zhí)行。其 API 非常簡單;你需要給這個簡單函數(shù)提供一個枚舉值指定你想要進行的操作,然后是一些取決于你所提供的值可能會被使用也可能會被忽略的參數(shù)。函數(shù)原型看起來類似:

  1. long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); 

request 是我們想對被跟蹤進程進行的操作;pid 是被跟蹤進程的進程 ID;addr 是一個內(nèi)存地址,用于在一些調(diào)用中指定被跟蹤程序的地址;data 是 request 相應的資源。返回值通常是一些錯誤信息,因此在你實際的代碼中你也許應該檢查返回值;為了簡潔我這里就省略了。你可以查看 man 手冊獲取更多(關(guān)于 ptrace)的信息。

上面代碼中我們發(fā)送的請求 PTRACE_TRACEME 表示這個進程應該允許父進程跟蹤它。所有其它參數(shù)都會被忽略,因為 API 設計并不是很重要,哈哈。

下一步,我們會調(diào)用 execl,這是很多諸多的 exec 函數(shù)格式之一。我們執(zhí)行指定的程序,通過命令行參數(shù)傳遞它的名稱,然后用一個 nullptr 終止列表。如果你愿意,你還可以傳遞其它執(zhí)行你的程序所需的參數(shù)。

在完成這些后,我們就會和子進程一起結(jié)束;在我們結(jié)束它之前它會一直執(zhí)行。

添加調(diào)試循環(huán)

現(xiàn)在我們已經(jīng)啟動了子進程,我們想要能夠和它進行交互。為此,我們會創(chuàng)建一個 debugger 類,循環(huán)監(jiān)聽用戶輸入,然后在我們父進程的 main 函數(shù)中啟動它。

  1. else if (pid >= 1)  { 
  2.     //parent 
  3.     debugger dbg{prog, pid}; 
  4.     dbg.run(); 
  5.  
  1. class debugger { 
  2. public
  3.     debugger (std::string prog_name, pid_t pid) 
  4.         : m_prog_name{std::move(prog_name)}, m_pid{pid} {} 
  5.     void run(); 
  6. private: 
  7.     std::string m_prog_name; 
  8.     pid_t m_pid; 
  9. }; 

在 run 函數(shù)中,我們需要等待,直到子進程完成啟動,然后一直從 linenoise 獲取輸入直到收到 EOF(CTRL+D)。

  1. void debugger::run() { 
  2.     int wait_status; 
  3.     auto options = 0; 
  4.     waitpid(m_pid, &wait_status, options); 
  5.     char* line = nullptr; 
  6.     while((line = linenoise("minidbg> ")) != nullptr) { 
  7.         handle_command(line); 
  8.         linenoiseHistoryAdd(line); 
  9.         linenoiseFree(line); 
  10.     } 

當被跟蹤的進程啟動時,會發(fā)送一個 SIGTRAP 信號給它,這是一個跟蹤或者斷點中斷。我們可以使用 waitpid 函數(shù)等待這個信號發(fā)送。

當我們知道進程可以被調(diào)試之后,我們監(jiān)聽用戶輸入。linenoise 函數(shù)它自己會用一個窗口顯示和處理用戶輸入。這意味著我們不需要做太多的工作就會有一個支持歷史記錄和導航命令的命令行。當我們獲取到輸入時,我們把命令發(fā)給我們寫的小程序 handle_command,然后我們把這個命令添加到 linenoise 歷史并釋放資源。

處理輸入

我們的命令類似 gdb 以及 lldb 的格式。要繼續(xù)執(zhí)行程序,用戶需要輸入 continue 或 cont 甚至只需 c。如果他們想在一個地址中設置斷點,他們會輸入 break 0xDEADBEEF,其中 0xDEADBEEF 就是所需地址的 16 進制格式。讓我們來增加對這些命令的支持吧。

  1. void debugger::handle_command(const std::string& line) { 
  2.     auto args = split(line,' '); 
  3.     auto command = args[0]; 
  4.     if (is_prefix(command, "continue")) { 
  5.         continue_execution(); 
  6.     } 
  7.     else { 
  8.         std::cerr << "Unknown command\n"
  9.     } 

split 和 is_prefix 是一對有用的小程序:

  1. std::vector<std::string> split(const std::string &s, char delimiter) { 
  2.     std::vector<std::string> out{}; 
  3.     std::stringstream ss {s}; 
  4.     std::string item; 
  5.     while (std::getline(ss,item,delimiter)) { 
  6.         out.push_back(item); 
  7.     } 
  8.     return out
  9. bool is_prefix(const std::string& s, const std::string& of) { 
  10.     if (s.size() > of.size()) return false
  11.     return std::equal(s.begin(), s.end(), of.begin()); 

我們會把 continue_execution 函數(shù)添加到 debuger 類。

  1. void debugger::continue_execution() { 
  2.     ptrace(PTRACE_CONT, m_pid, nullptr, nullptr); 
  3.     int wait_status; 
  4.     auto options = 0; 
  5.     waitpid(m_pid, &wait_status, options); 

現(xiàn)在我們的 continue_execution 函數(shù)會用 ptrace 告訴進程繼續(xù)執(zhí)行,然后用 waitpid 等待直到收到信號。

總結(jié)

現(xiàn)在你應該編譯一些 C 或者 C++ 程序,然后用你的調(diào)試器運行它們,看它是否能在函數(shù)入口暫停、從調(diào)試器中繼續(xù)執(zhí)行。在下一篇文章中,我們會學習如何讓我們的調(diào)試器設置斷點。如果你遇到了任何問題,在下面的評論框中告訴我吧!

你可以在這里找到該項目的代碼。 

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

2017-06-28 14:21:22

Linux調(diào)試器斷點

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

2017-07-25 10:30:32

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

2017-09-25 08:04:31

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

2017-08-28 14:40:57

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

2017-07-05 14:37:07

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

2017-08-28 15:29:19

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

2017-04-19 21:35:38

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

2020-10-10 06:32:43

IDEA插件環(huán)境

2020-03-16 09:31:10

Linux系統(tǒng)CentOS

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

2016-08-04 14:08:57

前端javascripthtml

2011-08-31 16:51:12

Lua調(diào)試器

2019-12-06 14:30:41

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

2024-03-13 08:00:00

Linux調(diào)試器應用程序
點贊
收藏

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