久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

Linux中如何實現源碼級斷點

173次閱讀
沒有評論

共計 9217 個字符,預計需要花費 24 分鐘才能閱讀完成。

這篇文章主要為大家展示了“Linux 中如何實現源碼級斷點”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓丸趣 TV 小編帶領大家一起研究并學習一下“Linux 中如何實現源碼級斷點”這篇文章吧。

斷點

DWARF

Elves 和 dwarves 這篇文章,描述了 DWARF 調試信息是如何工作的,以及如何用它來將機器碼映射到高層源碼中。回想一下,DWARF   包含了函數的地址范圍和一個允許你在抽象層之間轉換代碼位置的行表。我們將使用這些功能來實現我們的斷點。

函數入口

如果你考慮重載、成員函數等等,那么在函數名上設置斷點可能有點復雜,但是我們將遍歷所有的編譯單元,并搜索與我們正在尋找的名稱匹配的函數。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(函數的起始地址)來設置我們的斷點。

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

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

源碼行

要在高層源碼行上設置一個斷點,我們要將這個行號轉換成 DWARF   中的一個地址。我們將遍歷編譯單元,尋找一個名稱與給定文件匹配的編譯單元,然后查找與給定行對應的入口。

DWARF 看上去有點像這樣:

.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 的第五行設置一個斷點,我們將查找與行 (0x004004e3) 相關的入口并設置一個斷點。

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  。當然你實際上應該使用大小寫敏感路徑處理庫或者其它東西,但是我比較懶。entry.is_stmt   是檢查行表入口是否被標記為一個語句的開頭,這是由編譯器根據它認為是斷點的 *** 目標的地址設置的。

符號查找

當我們在對象文件層時,符號是王者。函數用符號命名,全局變量用符號命名,你得到一個符號,我們得到一個符號,每個人都得到一個符號。  在給定的對象文件中,一些符號可能引用其他對象文件或共享庫,鏈接器將從符號引用創建一個可執行程序。

可以在正確命名的符號表中查找符號,它存儲在二進制文件的 ELF 部分中。幸運的是,libelfin   有一個不錯的接口來做這件事,所以我們不需要自己處理所有的 ELF 的事情。為了讓你知道我們在處理什么,下面是一個二進制文件的 .symtab 部分的轉儲,它由  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

你可以在對象文件中看到用于設置環境的很多符號,*** 還可以看到 main 符號。

我們對符號的類型、名稱和值 (地址) 感興趣。我們有一個該類型的 symbol_type 枚舉,并使用一個 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   獲得的符號類型映射到我們的枚舉,因為我們不希望依賴關系破環這個接口。幸運的是,我為所有的東西選了同樣的名字,所以這樣很簡單:

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

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

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

添加命令

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

0x hexadecimal – 斷點地址

line : filename – 斷點行號

anything else – 斷點函數名

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]); } }

對于符號,我們將查找符號并打印出我們發現的任何匹配項:

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 中如何實現源碼級斷點”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注丸趣 TV 行業資訊頻道!

正文完
 
丸趣
版權聲明:本站原創文章,由 丸趣 2023-08-25發表,共計9217字。
轉載說明:除特殊說明外本站除技術相關以外文章皆由網絡搜集發布,轉載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 新疆| 临沭县| 通许县| 顺义区| 固始县| 洪泽县| 德昌县| 揭东县| 遵义县| 靖西县| 齐齐哈尔市| 临城县| 中宁县| 灌阳县| 松阳县| 阿拉尔市| 林周县| 婺源县| 巴马| 安远县| 邯郸县| 安义县| 循化| 光泽县| 盐源县| 库尔勒市| 六盘水市| 宝山区| 岳阳县| 聂荣县| 肇庆市| 云龙县| 易门县| 富川| 元朗区| 剑河县| 昭觉县| 南澳县| 庆云县| 北辰区| 若羌县|