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

開發(fā)一個(gè)Linux調(diào)試器(五):源碼和信號(hào)

系統(tǒng) Linux
在上一部分我們學(xué)習(xí)了關(guān)于 DWARF 的信息,以及它如何被用于讀取變量和將被執(zhí)行的機(jī)器碼與我們的高級(jí)語言的源碼聯(lián)系起來。在這一部分,我們將進(jìn)入實(shí)踐,實(shí)現(xiàn)一些我們調(diào)試器后面會(huì)使用的 DWARF 原語。我們也會(huì)利用這個(gè)機(jī)會(huì),使我們的調(diào)試器可以在命中一個(gè)斷點(diǎn)時(shí)打印出當(dāng)前的源碼上下文。

[[201385]]

在上一部分我們學(xué)習(xí)了關(guān)于 DWARF 的信息,以及它如何被用于讀取變量和將被執(zhí)行的機(jī)器碼與我們的高級(jí)語言的源碼聯(lián)系起來。在這一部分,我們將進(jìn)入實(shí)踐,實(shí)現(xiàn)一些我們調(diào)試器后面會(huì)使用的 DWARF 原語。我們也會(huì)利用這個(gè)機(jī)會(huì),使我們的調(diào)試器可以在***一個(gè)斷點(diǎn)時(shí)打印出當(dāng)前的源碼上下文。

系列文章索引

隨著后面文章的發(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. 下一步

設(shè)置我們的 DWARF 解析器

正如我在這系列文章開始時(shí)備注的,我們會(huì)使用 libelfin 來處理我們的 DWARF 信息。希望你已經(jīng)在***部分設(shè)置好了這些,如果沒有的話,現(xiàn)在做吧,確保你使用我倉庫的 fbreg 分支。

一旦你構(gòu)建好了 libelfin,就可以把它添加到我們的調(diào)試器。***步是解析我們的 ELF 可執(zhí)行程序并從中提取 DWARF 信息。使用 libelfin 可以輕易實(shí)現(xiàn),只需要對(duì)調(diào)試器作以下更改:

  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.         auto fd = open(m_prog_name.c_str(), O_RDONLY); 
  6.         m_elf = elf::elf{elf::create_mmap_loader(fd)}; 
  7.         m_dwarf = dwarf::dwarf{dwarf::elf::create_loader(m_elf)}; 
  8.     } 
  9.     //... 
  10. private: 
  11.     //... 
  12.     dwarf::dwarf m_dwarf; 
  13.     elf::elf m_elf; 
  14. }; 

我們使用了 open 而不是 std::ifstream,因?yàn)?elf 加載器需要傳遞一個(gè) UNIX 文件描述符給 mmap,從而可以將文件映射到內(nèi)存而不是每次讀取一部分。

調(diào)試信息原語

下一步我們可以實(shí)現(xiàn)從程序計(jì)數(shù)器的值中提取行條目(line entry)以及函數(shù) DWARF 信息條目(function DIE)的函數(shù)。我們從 get_function_from_pc 開始:

  1. dwarf::die debugger::get_function_from_pc(uint64_t pc) { 
  2.     for (auto &cu : m_dwarf.compilation_units()) { 
  3.         if (die_pc_range(cu.root()).contains(pc)) { 
  4.             for (const auto& die : cu.root()) { 
  5.                 if (die.tag == dwarf::DW_TAG::subprogram) { 
  6.                     if (die_pc_range(die).contains(pc)) { 
  7.                         return die; 
  8.                     } 
  9.                 } 
  10.             } 
  11.         } 
  12.     } 
  13.     throw std::out_of_range{"Cannot find function"}; 

這里我采用了樸素的方法,迭代遍歷編譯單元直到找到一個(gè)包含程序計(jì)數(shù)器的,然后迭代遍歷它的子節(jié)點(diǎn)直到我們找到相關(guān)函數(shù)(DW_TAG_subprogram)。正如我在上一篇中提到的,如果你想要的話你可以處理類似的成員函數(shù)或者內(nèi)聯(lián)等情況。

接下來是 get_line_entry_from_pc:

  1. dwarf::line_table::iterator debugger::get_line_entry_from_pc(uint64_t pc) { 
  2.     for (auto &cu : m_dwarf.compilation_units()) { 
  3.         if (die_pc_range(cu.root()).contains(pc)) { 
  4.             auto &lt = cu.get_line_table(); 
  5.             auto it = lt.find_address(pc); 
  6.             if (it == lt.end()) { 
  7.                 throw std::out_of_range{"Cannot find line entry"}; 
  8.             } 
  9.             else { 
  10.                 return it; 
  11.             } 
  12.         } 
  13.     } 
  14.     throw std::out_of_range{"Cannot find line entry"}; 

同樣,我們可以簡單地找到正確的編譯單元,然后查詢行表獲取相關(guān)的條目。

打印源碼

當(dāng)我們***一個(gè)斷點(diǎn)或者逐步執(zhí)行我們的代碼時(shí),我們會(huì)想知道處于源碼中的什么位置。

  1. void debugger::print_source(const std::string& file_name, unsigned line, unsigned n_lines_context) { 
  2.     std::ifstream file {file_name}; 
  3.     //獲得一個(gè)所需行附近的窗口 
  4.     auto start_line = line <= n_lines_context ? 1 : line - n_lines_context; 
  5.     auto end_line = line + n_lines_context + (line < n_lines_context ? n_lines_context - line : 0) + 1; 
  6.     char c{}; 
  7.     auto current_line = 1u; 
  8.     //跳過 start_line 之前的行 
  9.     while (current_line != start_line && file.get(c)) { 
  10.         if (c == '\n') { 
  11.             ++current_line; 
  12.         } 
  13.     } 
  14.     //如果我們?cè)诋?dāng)前行則輸出光標(biāo) 
  15.     std::cout << (current_line==line ? "> " : "  "); 
  16.     //輸出行直到 end_line 
  17.     while (current_line <= end_line && file.get(c)) { 
  18.         std::cout << c; 
  19.         if (c == '\n') { 
  20.             ++current_line; 
  21.             //如果我們?cè)诋?dāng)前行則輸出光標(biāo) 
  22.             std::cout << (current_line==line ? "> " : "  "); 
  23.         } 
  24.     } 
  25.     //輸出換行確保恰當(dāng)?shù)厍蹇樟肆?nbsp;
  26.     std::cout << std::endl; 

現(xiàn)在我們可以打印出源碼了,我們需要將這些通過鉤子添加到我們的調(diào)試器。實(shí)現(xiàn)這個(gè)的一個(gè)好地方是當(dāng)調(diào)試器從一個(gè)斷點(diǎn)或者(最終)逐步執(zhí)行得到一個(gè)信號(hào)時(shí)。到了這里,我們可能想要給我們的調(diào)試器添加一些更好的信號(hào)處理。

更好的信號(hào)處理

我們希望能夠得知什么信號(hào)被發(fā)送給了進(jìn)程,同樣我們也想知道它是如何產(chǎn)生的。例如,我們希望能夠得知是否由于***了一個(gè)斷點(diǎn)從而獲得一個(gè) SIGTRAP,還是由于逐步執(zhí)行完成、或者是產(chǎn)生了一個(gè)新線程等等導(dǎo)致的。幸運(yùn)的是,我們可以再一次使用 ptrace。可以給 ptrace 的一個(gè)命令是 PTRACE_GETSIGINFO,它會(huì)給你被發(fā)送給進(jìn)程的***一個(gè)信號(hào)的信息。我們類似這樣使用它:

  1. siginfo_t debugger::get_signal_info() { 
  2.     siginfo_t info; 
  3.     ptrace(PTRACE_GETSIGINFO, m_pid, nullptr, &info); 
  4.     return info; 

這會(huì)給我們一個(gè) siginfo_t 對(duì)象,它能提供以下信息:

  1. siginfo_t { 
  2.     int      si_signo;     /* 信號(hào)編號(hào) */ 
  3.     int      si_errno;     /* errno 值 */ 
  4.     int      si_code;      /* 信號(hào)代碼 */ 
  5.     int      si_trapno;    /* 導(dǎo)致生成硬件信號(hào)的陷阱編號(hào) 
  6.                               (大部分架構(gòu)中都沒有使用) */ 
  7.     pid_t    si_pid;       /* 發(fā)送信號(hào)的進(jìn)程 ID */ 
  8.     uid_t    si_uid;       /* 發(fā)送信號(hào)進(jìn)程的用戶 ID */ 
  9.     int      si_status;    /* 退出值或信號(hào) */ 
  10.     clock_t  si_utime;     /* 消耗的用戶時(shí)間 */ 
  11.     clock_t  si_stime;     /* 消耗的系統(tǒng)時(shí)間 */ 
  12.     sigval_t si_value;     /* 信號(hào)值 */ 
  13.     int      si_int;       /* POSIX.1b 信號(hào) */ 
  14.     void    *si_ptr;       /* POSIX.1b 信號(hào) */ 
  15.     int      si_overrun;   /* 計(jì)時(shí)器 overrun 計(jì)數(shù); 
  16.                               POSIX.1b 計(jì)時(shí)器 */ 
  17.     int      si_timerid;   /* 計(jì)時(shí)器 ID; POSIX.1b 計(jì)時(shí)器 */ 
  18.     void    *si_addr;      /* 導(dǎo)致錯(cuò)誤的內(nèi)存地址 */ 
  19.     long     si_band;      /* Band event (在 glibc 2.3.2 和之前版本中是 int 類型) */ 
  20.     int      si_fd;        /* 文件描述符 */ 
  21.     short    si_addr_lsb;  /* 地址的最不重要位 
  22.                               (自 Linux 2.6.32) */ 
  23.     void    *si_lower;     /* 出現(xiàn)地址違規(guī)的下限 (自 Linux 3.19) */ 
  24.     void    *si_upper;     /* 出現(xiàn)地址違規(guī)的上限 (自 Linux 3.19) */ 
  25.     int      si_pkey;      /* PTE 上導(dǎo)致錯(cuò)誤的保護(hù)鍵 (自 Linux 4.6) */ 
  26.     void    *si_call_addr; /* 系統(tǒng)調(diào)用指令的地址 
  27.                               (自 Linux 3.5) */ 
  28.     int      si_syscall;   /* 系統(tǒng)調(diào)用嘗試次數(shù) 
  29.                               (自 Linux 3.5) */ 
  30.     unsigned int si_arch;  /* 嘗試系統(tǒng)調(diào)用的架構(gòu) 
  31.                               (自 Linux 3.5) */ 

我只需要使用 si_signo 就可以找到被發(fā)送的信號(hào),使用 si_code 來獲取更多關(guān)于信號(hào)的信息。放置這些代碼的***位置是我們的 wait_for_signal 函數(shù):

  1. void debugger::wait_for_signal() { 
  2.     int wait_status; 
  3.     auto options = 0; 
  4.     waitpid(m_pid, &wait_status, options); 
  5.     auto siginfo = get_signal_info(); 
  6.     switch (siginfo.si_signo) { 
  7.     case SIGTRAP: 
  8.         handle_sigtrap(siginfo); 
  9.         break; 
  10.     case SIGSEGV: 
  11.         std::cout << "Yay, segfault. Reason: " << siginfo.si_code << std::endl; 
  12.         break; 
  13.     default
  14.         std::cout << "Got signal " << strsignal(siginfo.si_signo) << std::endl; 
  15.     } 

現(xiàn)在再來處理 SIGTRAP。知道當(dāng)***一個(gè)斷點(diǎn)時(shí)會(huì)發(fā)送 SI_KERNEL 或 TRAP_BRKPT,而逐步執(zhí)行結(jié)束時(shí)會(huì)發(fā)送 TRAP_TRACE 就足夠了:

  1. void debugger::handle_sigtrap(siginfo_t info) { 
  2.     switch (info.si_code) { 
  3.     //如果***了一個(gè)斷點(diǎn)其中的一個(gè)會(huì)被設(shè)置 
  4.     case SI_KERNEL: 
  5.     case TRAP_BRKPT: 
  6.     { 
  7.         set_pc(get_pc()-1); //將程序計(jì)數(shù)器的值設(shè)置為它應(yīng)該指向的地方 
  8.         std::cout << "Hit breakpoint at address 0x" << std::hex << get_pc() << std::endl; 
  9.         auto line_entry = get_line_entry_from_pc(get_pc()); 
  10.         print_source(line_entry->file->path, line_entry->line); 
  11.         return
  12.     } 
  13.     //如果信號(hào)是由逐步執(zhí)行發(fā)送的,這會(huì)被設(shè)置 
  14.     case TRAP_TRACE: 
  15.         return
  16.     default
  17.         std::cout << "Unknown SIGTRAP code " << info.si_code << std::endl; 
  18.         return
  19.     } 

這里有一大堆不同風(fēng)格的信號(hào)你可以處理。查看 man sigaction 獲取更多信息。

由于當(dāng)我們收到 SIGTRAP 信號(hào)時(shí)我們已經(jīng)修正了程序計(jì)數(shù)器的值,我們可以從 step_over_breakpoint 中移除這些代碼,現(xiàn)在它看起來類似:

  1. void debugger::step_over_breakpoint() { 
  2.     if (m_breakpoints.count(get_pc())) { 
  3.         auto& bp = m_breakpoints[get_pc()]; 
  4.         if (bp.is_enabled()) { 
  5.             bp.disable(); 
  6.             ptrace(PTRACE_SINGLESTEP, m_pid, nullptr, nullptr); 
  7.             wait_for_signal(); 
  8.             bp.enable(); 
  9.         } 
  10.     } 

測試

現(xiàn)在你應(yīng)該可以在某個(gè)地址設(shè)置斷點(diǎn),運(yùn)行程序然后看到打印出了源碼,而且正在被執(zhí)行的行被光標(biāo)標(biāo)記了出來。

后面我們會(huì)添加設(shè)置源碼級(jí)別斷點(diǎn)的功能。同時(shí),你可以從這里獲取該博文的代碼。 

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

2017-09-25 08:04:31

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

2017-08-28 15:29:19

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

2017-06-28 14:21:22

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

2017-07-25 10:30:32

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

2017-06-22 10:44:55

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

2017-07-05 14:37:07

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

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í)主題

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

2010-12-21 10:16:53

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

2010-05-28 14:14:37

Linux開發(fā)工具

2011-08-31 16:51:12

Lua調(diào)試器

2010-03-02 13:23:02

VNC Linux服務(wù)

2019-12-06 14:30:41

GNU調(diào)試器GDB修復(fù)代碼
點(diǎn)贊
收藏

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