准备环境寄存器和内存Elves和dwarves源码和信号源码级逐步执行

WBOY
发布: 2024-06-02 09:10:42
原创
659 人浏览过

在显存地址上设置断点似乎不错,但它并没有提供最便捷用户的工具。我们希望还能在源代码行和函数入口地址上设置断点,便于我们可以在与代码相同的具象级别中进行调试。

这篇文章将会添加源码级断点到我们的调试器中。通过所有我们早已支持的功能,这要比起最初听上去容易得多。我们还将添加一个命令来获取符号的类型和地址,这对于定位代码或数据以及理解链接概念十分有用。

系列索引

随着前面文章的发布,这种链接会渐渐生效。

打算环境断点寄存器和显存Elves和dwarves源码和讯号源码级逐渐执行源码级断点调用栈读取变量以后步骤

断点

DWARF

Elves和dwarves这篇文章,描述了DWARF调试信息是怎样工作的,以及怎样用它来将机器码映射到高层源码中。回想一下,DWARF包含了函数的地址范围和一个容许你在具象层之间转换代码位置的行表。我们将使用这种功能来实现我们的断点。

linux vector 头文件_头文件vector报错_头文件vector

函数入口

假如你考虑重载、成员函数等等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以后的第一行,但并不是所有编译器都输出它,因而我采用了原始的方式。

源码行

头文件vector报错_头文件vector_linux vector 头文件

要在高层源码行上设置一个断点,我们要将这个行号转换成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是检测行表入口是否被标记为一个句子的开头,这是由编译器按照它觉得是断点的最佳目标的地址设置的。

符号查找

头文件vector报错_头文件vector_linux vector 头文件

当我们在对象文件层时,符号是王者。函数用符号命名红旗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 << &#039; &#039; << to_string(s.type) << " 0x" << std::hex << s.addr << std::endl;
}
}
登录后复制

测试一下

在一个简单的二补码文件上启动调试器,并设置源代码级别的断点。在一些foo函数上设置一个断点,见到我的调试器停在它前面是我这个项目最有价值的时刻之一。

符号查找可以通过在程序中添加一些函数或全局变量并查找它们的名称来进行测试。请注意,假若你正在编译C++代码,你还须要考虑名称重整。

以上是准备环境寄存器和内存Elves和dwarves源码和信号源码级逐步执行的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:itcool.net
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!