溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Linux中如何實(shí)現(xiàn)源碼級(jí)斷點(diǎn)

發(fā)布時(shí)間:2021-10-28 17:14:42 來源:億速云 閱讀:146 作者:小新 欄目:系統(tǒng)運(yùn)維

這篇文章主要為大家展示了“Linux中如何實(shí)現(xiàn)源碼級(jí)斷點(diǎn)”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Linux中如何實(shí)現(xiàn)源碼級(jí)斷點(diǎn)”這篇文章吧。

斷點(diǎn)

DWARF

Elves 和 dwarves 這篇文章,描述了 DWARF 調(diào)試信息是如何工作的,以及如何用它來將機(jī)器碼映射到高層源碼中?;叵胍幌?,DWARF  包含了函數(shù)的地址范圍和一個(gè)允許你在抽象層之間轉(zhuǎn)換代碼位置的行表。我們將使用這些功能來實(shí)現(xiàn)我們的斷點(diǎn)。

函數(shù)入口

如果你考慮重載、成員函數(shù)等等,那么在函數(shù)名上設(shè)置斷點(diǎn)可能有點(diǎn)復(fù)雜,但是我們將遍歷所有的編譯單元,并搜索與我們正在尋找的名稱匹配的函數(shù)。DWARF  信息如下所示:

< 0><0x0000000b>  DW_TAG_compile_unit                     DW_AT_producer              clang version 3.9.1 (tags/RELEASE_391/final)                     DW_AT_language              DW_LANG_C_plus_plus                     DW_AT_name                  /super/secret/path/MiniDbg/examples/variable.cpp                     DW_AT_stmt_list             0x00000000                     DW_AT_comp_dir              /super/secret/path/MiniDbg/build                     DW_AT_low_pc                0x00400670                     DW_AT_high_pc               0x0040069c LOCAL_SYMBOLS: < 1><0x0000002e>    DW_TAG_subprogram                       DW_AT_low_pc                0x00400670                       DW_AT_high_pc               0x0040069c                       DW_AT_name                  foo                       ... ... <14><0x000000b0>    DW_TAG_subprogram                       DW_AT_low_pc                0x00400700                       DW_AT_high_pc               0x004007a0                       DW_AT_name                  bar                       ...

我們想要匹配 DW_AT_name 并使用 DW_AT_low_pc(函數(shù)的起始地址)來設(shè)置我們的斷點(diǎn)。

void debugger::set_breakpoint_at_function(const std::string& name) {     for (const auto& cu : m_dwarf.compilation_units()) {         for (const auto& die : cu.root()) {             if (die.has(dwarf::DW_AT::name) && at_name(die) == name) {                 auto low_pc = at_low_pc(die);                 auto entry = get_line_entry_from_pc(low_pc);                 ++entry; //skip prologue                 set_breakpoint_at_address(entry->address);             }         }     } }

這代碼看起來有點(diǎn)奇怪的唯一一點(diǎn)是 ++entry。 問題是函數(shù)的 DW_AT_low_pc 不指向該函數(shù)的用戶代碼的起始地址,它指向 prologue  的開始。編譯器通常會(huì)輸出一個(gè)函數(shù)的 prologue 和  epilogue,它們用于執(zhí)行保存和恢復(fù)堆棧、操作堆棧指針等。這對(duì)我們來說不是很有用,所以我們將入口行加一來獲取用戶代碼的***行而不是  prologue。DWARF 行表實(shí)際上具有一些功能,用于將入口標(biāo)記為函數(shù) prologue  之后的***行,但并不是所有編譯器都輸出它,因此我采用了原始的方法。

源碼行

要在高層源碼行上設(shè)置一個(gè)斷點(diǎn),我們要將這個(gè)行號(hào)轉(zhuǎn)換成 DWARF  中的一個(gè)地址。我們將遍歷編譯單元,尋找一個(gè)名稱與給定文件匹配的編譯單元,然后查找與給定行對(duì)應(yīng)的入口。

DWARF 看上去有點(diǎn)像這樣:

.debug_line: line number info for a single cu Source lines (from CU-DIE at .debug_info offset 0x0000000b): NS new statement, BB new basic block, ET end of text sequence PE prologue end, EB epilogue begin IS=val ISA number, DI=val discriminator value <pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath" 0x004004a7  [   1, 0] NS uri: "/super/secret/path/a.hpp" 0x004004ab  [   2, 0] NS 0x004004b2  [   3, 0] NS 0x004004b9  [   4, 0] NS 0x004004c1  [   5, 0] NS 0x004004c3  [   1, 0] NS uri: "/super/secret/path/b.hpp" 0x004004c7  [   2, 0] NS 0x004004ce  [   3, 0] NS 0x004004d5  [   4, 0] NS 0x004004dd  [   5, 0] NS 0x004004df  [   4, 0] NS uri: "/super/secret/path/ab.cpp" 0x004004e3  [   5, 0] NS 0x004004e8  [   6, 0] NS 0x004004ed  [   7, 0] NS 0x004004f4  [   7, 0] NS ET

所以如果我們想要在 ab.cpp 的第五行設(shè)置一個(gè)斷點(diǎn),我們將查找與行 (0x004004e3) 相關(guān)的入口并設(shè)置一個(gè)斷點(diǎn)。

void debugger::set_breakpoint_at_source_line(const std::string& file, unsigned line) {     for (const auto& cu : m_dwarf.compilation_units()) {         if (is_suffix(file, at_name(cu.root()))) {             const auto& lt = cu.get_line_table();             for (const auto& entry : lt) {                 if (entry.is_stmt && entry.line == line) {                     set_breakpoint_at_address(entry.address);                     return;                 }             }         }     } }

我這里做了 is_suffix hack,這樣你可以輸入 c.cpp 代表 a/b/c.cpp  。當(dāng)然你實(shí)際上應(yīng)該使用大小寫敏感路徑處理庫或者其它東西,但是我比較懶。entry.is_stmt  是檢查行表入口是否被標(biāo)記為一個(gè)語句的開頭,這是由編譯器根據(jù)它認(rèn)為是斷點(diǎn)的***目標(biāo)的地址設(shè)置的。

符號(hào)查找

當(dāng)我們?cè)趯?duì)象文件層時(shí),符號(hào)是王者。函數(shù)用符號(hào)命名,全局變量用符號(hào)命名,你得到一個(gè)符號(hào),我們得到一個(gè)符號(hào),每個(gè)人都得到一個(gè)符號(hào)。  在給定的對(duì)象文件中,一些符號(hào)可能引用其他對(duì)象文件或共享庫,鏈接器將從符號(hào)引用創(chuàng)建一個(gè)可執(zhí)行程序。

可以在正確命名的符號(hào)表中查找符號(hào),它存儲(chǔ)在二進(jìn)制文件的 ELF 部分中。幸運(yùn)的是,libelfin  有一個(gè)不錯(cuò)的接口來做這件事,所以我們不需要自己處理所有的 ELF 的事情。為了讓你知道我們?cè)谔幚硎裁?,下面是一個(gè)二進(jìn)制文件的 .symtab 部分的轉(zhuǎn)儲(chǔ),它由  readelf 生成:

Num:    Value          Size Type    Bind   Vis      Ndx Name  0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND  1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1  2: 0000000000400254     0 SECTION LOCAL  DEFAULT    2  3: 0000000000400278     0 SECTION LOCAL  DEFAULT    3  4: 00000000004002c8     0 SECTION LOCAL  DEFAULT    4  5: 0000000000400430     0 SECTION LOCAL  DEFAULT    5  6: 00000000004004e4     0 SECTION LOCAL  DEFAULT    6  7: 0000000000400508     0 SECTION LOCAL  DEFAULT    7  8: 0000000000400528     0 SECTION LOCAL  DEFAULT    8  9: 0000000000400558     0 SECTION LOCAL  DEFAULT    9 10: 0000000000400570     0 SECTION LOCAL  DEFAULT   10 11: 0000000000400714     0 SECTION LOCAL  DEFAULT   11 12: 0000000000400720     0 SECTION LOCAL  DEFAULT   12 13: 0000000000400724     0 SECTION LOCAL  DEFAULT   13 14: 0000000000400750     0 SECTION LOCAL  DEFAULT   14 15: 0000000000600e18     0 SECTION LOCAL  DEFAULT   15 16: 0000000000600e20     0 SECTION LOCAL  DEFAULT   16 17: 0000000000600e28     0 SECTION LOCAL  DEFAULT   17 18: 0000000000600e30     0 SECTION LOCAL  DEFAULT   18 19: 0000000000600ff0     0 SECTION LOCAL  DEFAULT   19 20: 0000000000601000     0 SECTION LOCAL  DEFAULT   20 21: 0000000000601018     0 SECTION LOCAL  DEFAULT   21 22: 0000000000601028     0 SECTION LOCAL  DEFAULT   22 23: 0000000000000000     0 SECTION LOCAL  DEFAULT   23 24: 0000000000000000     0 SECTION LOCAL  DEFAULT   24 25: 0000000000000000     0 SECTION LOCAL  DEFAULT   25 26: 0000000000000000     0 SECTION LOCAL  DEFAULT   26 27: 0000000000000000     0 SECTION LOCAL  DEFAULT   27 28: 0000000000000000     0 SECTION LOCAL  DEFAULT   28 29: 0000000000000000     0 SECTION LOCAL  DEFAULT   29 30: 0000000000000000     0 SECTION LOCAL  DEFAULT   30 31: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS init.c 32: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c 33: 0000000000600e28     0 OBJECT  LOCAL  DEFAULT   17 __JCR_LIST__ 34: 00000000004005a0     0 FUNC    LOCAL  DEFAULT   10 deregister_tm_clones 35: 00000000004005e0     0 FUNC    LOCAL  DEFAULT   10 register_tm_clones 36: 0000000000400620     0 FUNC    LOCAL  DEFAULT   10 __do_global_dtors_aux 37: 0000000000601028     1 OBJECT  LOCAL  DEFAULT   22 completed.6917 38: 0000000000600e20     0 OBJECT  LOCAL  DEFAULT   16 __do_global_dtors_aux_fin 39: 0000000000400640     0 FUNC    LOCAL  DEFAULT   10 frame_dummy 40: 0000000000600e18     0 OBJECT  LOCAL  DEFAULT   15 __frame_dummy_init_array_ 41: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS /super/secret/path/MiniDbg/ 42: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c 43: 0000000000400818     0 OBJECT  LOCAL  DEFAULT   14 __FRAME_END__ 44: 0000000000600e28     0 OBJECT  LOCAL  DEFAULT   17 __JCR_END__ 45: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 46: 0000000000400724     0 NOTYPE  LOCAL  DEFAULT   13 __GNU_EH_FRAME_HDR 47: 0000000000601000     0 OBJECT  LOCAL  DEFAULT   20 _GLOBAL_OFFSET_TABLE_ 48: 0000000000601028     0 OBJECT  LOCAL  DEFAULT   21 __TMC_END__ 49: 0000000000601020     0 OBJECT  LOCAL  DEFAULT   21 __dso_handle 50: 0000000000600e20     0 NOTYPE  LOCAL  DEFAULT   15 __init_array_end 51: 0000000000600e18     0 NOTYPE  LOCAL  DEFAULT   15 __init_array_start 52: 0000000000600e30     0 OBJECT  LOCAL  DEFAULT   18 _DYNAMIC 53: 0000000000601018     0 NOTYPE  WEAK   DEFAULT   21 data_start 54: 0000000000400710     2 FUNC    GLOBAL DEFAULT   10 __libc_csu_fini 55: 0000000000400570    43 FUNC    GLOBAL DEFAULT   10 _start 56: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__ 57: 0000000000400714     0 FUNC    GLOBAL DEFAULT   11 _fini 58: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_ 59: 0000000000400720     4 OBJECT  GLOBAL DEFAULT   12 _IO_stdin_used 60: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT   21 __data_start 61: 00000000004006a0   101 FUNC    GLOBAL DEFAULT   10 __libc_csu_init 62: 0000000000601028     0 NOTYPE  GLOBAL DEFAULT   22 __bss_start 63: 0000000000601030     0 NOTYPE  GLOBAL DEFAULT   22 _end 64: 0000000000601028     0 NOTYPE  GLOBAL DEFAULT   21 _edata 65: 0000000000400670    44 FUNC    GLOBAL DEFAULT   10 main 66: 0000000000400558     0 FUNC    GLOBAL DEFAULT    9 _init

你可以在對(duì)象文件中看到用于設(shè)置環(huán)境的很多符號(hào),***還可以看到 main 符號(hào)。

我們對(duì)符號(hào)的類型、名稱和值(地址)感興趣。我們有一個(gè)該類型的 symbol_type 枚舉,并使用一個(gè) std::string  作為名稱,std::uintptr_t 作為地址:

enum class symbol_type {     notype,            // No type (e.g., absolute symbol)     object,            // Data object     func,              // Function entry point     section,           // Symbol is associated with a section     file,              // Source file associated with the };                     // object file std::string to_string (symbol_type st) {     switch (st) {     case symbol_type::notype: return "notype";     case symbol_type::object: return "object";     case symbol_type::func: return "func";     case symbol_type::section: return "section";     case symbol_type::file: return "file";     } } struct symbol {     symbol_type type;     std::string name;     std::uintptr_t addr; };

我們需要將從 libelfin  獲得的符號(hào)類型映射到我們的枚舉,因?yàn)槲覀儾幌M蕾囮P(guān)系破環(huán)這個(gè)接口。幸運(yùn)的是,我為所有的東西選了同樣的名字,所以這樣很簡(jiǎn)單:

symbol_type to_symbol_type(elf::stt sym) {     switch (sym) {     case elf::stt::notype: return symbol_type::notype;     case elf::stt::object: return symbol_type::object;     case elf::stt::func: return symbol_type::func;     case elf::stt::section: return symbol_type::section;     case elf::stt::file: return symbol_type::file;     default: return symbol_type::notype;     } };

***我們要查找符號(hào)。為了說明的目的,我循環(huán)查找符號(hào)表的 ELF 部分,然后收集我在其中找到的任意符號(hào)到 std::vector  中。更智能的實(shí)現(xiàn)可以建立從名稱到符號(hào)的映射,這樣你只需要查看一次數(shù)據(jù)就行了。

std::vector<symbol> debugger::lookup_symbol(const std::string& name) {     std::vector<symbol> syms;     for (auto &sec : m_elf.sections()) {         if (sec.get_hdr().type != elf::sht::symtab && sec.get_hdr().type != elf::sht::dynsym)             continue;         for (auto sym : sec.as_symtab()) {             if (sym.get_name() == name) {                 auto &d = sym.get_data();                 syms.push_back(symbol{to_symbol_type(d.type()), sym.get_name(), d.value});             }         }     }     return syms; }

添加命令

一如往常,我們需要添加一些更多的命令來向用戶暴露功能。對(duì)于斷點(diǎn),我使用 GDB  風(fēng)格的接口,其中斷點(diǎn)類型是通過你傳遞的參數(shù)推斷的,而不用要求顯式切換:

  • 0x<hexadecimal> -> 斷點(diǎn)地址

  • <line>:<filename> -> 斷點(diǎn)行號(hào)

  • <anything else> -> 斷點(diǎn)函數(shù)名

else if(is_prefix(command, "break")) {     if (args[1][0] == '0' && args[1][1] == 'x') {         std::string addr {args[1], 2};         set_breakpoint_at_address(std::stol(addr, 0, 16));     }     else if (args[1].find(':') != std::string::npos) {         auto file_and_line = split(args[1], ':');         set_breakpoint_at_source_line(file_and_line[0], std::stoi(file_and_line[1]));     }     else {         set_breakpoint_at_function(args[1]);     } }

對(duì)于符號(hào),我們將查找符號(hào)并打印出我們發(fā)現(xiàn)的任何匹配項(xiàng):

else if(is_prefix(command, "symbol")) {     auto syms = lookup_symbol(args[1]);     for (auto&& s : syms) {         std::cout << s.name << ' ' << to_string(s.type) << " 0x" << std::hex << s.addr << std::endl;     } }

以上是“Linux中如何實(shí)現(xiàn)源碼級(jí)斷點(diǎn)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI