在顯存位址上設定斷點似乎不錯,但它並沒有提供最方便使用者的工具。我們希望還能在原始碼行和函數入口位址上設定斷點,以便我們可以在與程式碼相同的具象層級中進行偵錯。
這篇文章將會加入源碼級斷點到我們的偵錯器中。透過所有我們早已支援的功能,這比起最初聽起來容易得多。我們還將添加一個命令來獲取符號的類型和地址,這對於定位代碼或資料以及理解連結概念十分有用。
系列索引
隨著前面文章的發布,這種連結會漸漸生效。
打算環境斷點暫存器和顯存Elves和dwarves源碼和訊息源碼級逐漸執行源碼級斷點調用棧讀取變數以後步驟
斷點
DWARF
Elves和dwarves這篇文章,描述了DWARF調試資訊是怎樣運作的,以及怎樣用它來將機器碼映射到高層原始碼中。回想一下,DWARF包含了函數的位址範圍和一個容許你在具象層之間轉換程式碼位置的行表。我們將使用這種功能來實現我們的斷點。
#函數入口
假如你考慮重載、成員函數等等linux作業系統原理,這麼在函數名稱上設定斷點可能有點複雜,而且我們將遍歷所有的編譯單元,並蒐索與我們正在尋找的名稱匹配的函數。 DWARF資訊如下所示:
DW_TAG_compile_unit DW_AT_producerclang version 3.9.1 (tags/RELEASE_391/final) DW_AT_languageDW_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_pc0x00400670 DW_AT_high_pc 0x0040069c LOCAL_SYMBOLS: DW_TAG_subprogram DW_AT_low_pc0x00400670 DW_AT_high_pc 0x0040069c DW_AT_namefoo ... ... DW_TAG_subprogram DW_AT_low_pc0x00400700 DW_AT_high_pc 0x004007a0 DW_AT_namebar ...
我們想要匹配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 [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_suffixhack,这样你可以输入c.cpp代表a/b/c.cpp。其实你实际上应当使用大小写敏感路径处理库或则其它东西,而且我比较懒。entry.is_stmt是检测行表入口是否被标记为一个句子的开头,这是由编译器按照它觉得是断点的最佳目标的地址设置的。
符号查找
当我们在对象文件层时,符号是王者。函数用符号命名红旗linux系统,全局变量用符号命名,你得到一个符号,我们得到一个符号,每位人都得到一个符号。在给定的对象文件中linux vector 头文件,一些符号可能引用其他对象文件或共享库,链接器将从符号引用创建一个可执行程序。
可以在正确命名的符号表中查找符号,它储存在二补码文件的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 debugger::lookup_symbol(const std::string& name) { std::vector 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风格的插口linux vector 头文件,其中断点类型是通过你传递的参数推论的,而不用要求显式切换:
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; } }
测试一下
在一个简单的二补码文件上启动调试器,并设置源代码级别的断点。在一些foo函数上设置一个断点,见到我的调试器停在它前面是我这个项目最有价值的时刻之一。
符号查找可以通过在程序中添加一些函数或全局变量并查找它们的名称来进行测试。请注意,假若你正在编译C++代码,你还须要考虑名称重整。
以上是準備環境暫存器和記憶體Elves和dwarves源碼和訊號源碼級逐步執行的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Linux系統依靠防火牆來保護未經授權的網絡訪問。 這些軟件障礙控製網絡流量,允許基於預定義的規則來阻止數據包。 他們主要在網絡層操作,他們管理

確定Linux系統是台式機還是筆記本電腦對於系統優化至關重要。本指南概述了簡單的命令以識別您的系統類型。 hostnamectl命令:此命令提供了一種檢查系統機箱的簡潔方法

Linux服務器TCP/IP連接數限制調整指南 Linux系統常用於服務器和網絡應用,管理員經常會遇到TCP/IP連接數達到上限的問題,導致用戶連接錯誤。本文將指導您如何提升Linux系統中的最大TCP/IP連接數。 TCP/IP連接數理解 TCP/IP (傳輸控制協議/互聯網協議)是互聯網的基本通信協議。每個TCP連接都需要係統資源。當活動連接過多時,系統可能會拒絕新的連接或速度變慢。 通過增加允許的最大連接數,可以提高服務器性能並處理更多並髮用戶。 檢查當前Linux連接數限制 在更改設置之

SVG(可擴展的矢量圖形)文件是徽標和插圖的理想選擇,因為它們的可重複性而沒有質量損失。 但是,PNG(便攜式網絡圖形)格式通常可以更好地與網站和應用程序兼容。本指南d

Livecode:跨平台發展革命 LiveCode是一種編程語言,於1993年首次亮相,簡化了每個人的應用程序開發。 它的高級,類似英語的語法和動態鍵入使得可以輕鬆地創建強大的應用程序

本指南提供了一個分步過程,用於通過Linux命令行重置故障USB設備。 使用這些命令簡化了對無響應或斷開USB驅動器的故障排除。 步驟1:識別您的USB設備 首先,我

在Linux上暫時設置靜態IP地址對於網絡故障排除或特定的會話配置是無價的。 本指南詳細介紹瞭如何使用命令行工具來實現此目的,並指出更改並非跨重啟

Linux以其強大的命令行工具集而聞名,這些工具允許用戶高效地與系統交互。雖然許多Linux用戶熟悉諸如ls、cd或grep之類的常用命令,但還有一些鮮為人知但極其有用的命令和快捷方式可以簡化並提高生產力。 我們很高興分享我們關於“鮮為人知的Linux命令”的最新五篇文章,其中包含50多個你可能不知道的命令。 您可能也喜歡: 11個鮮為人知的實用Linux命令——第一部分 10個鮮為人知的Linux命令——第二部分 10個鮮為人知的Linux命令——第三部分 10個鮮為人知的有效Linux命令


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

記事本++7.3.1
好用且免費的程式碼編輯器

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。