首页 系统教程 操作系统 准备环境寄存器和内存Elves和dwarves源码和信号源码级逐步执行

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

Jun 02, 2024 am 09:10 AM

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

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

系列索引

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

打算环境断点寄存器和显存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中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1666
14
CakePHP 教程
1425
52
Laravel 教程
1328
25
PHP教程
1273
29
C# 教程
1253
24
互联网在Linux上运行吗? 互联网在Linux上运行吗? Apr 14, 2025 am 12:03 AM

互联网运行不依赖单一操作系统,但Linux在其中扮演重要角色。Linux广泛应用于服务器和网络设备,因其稳定性、安全性和可扩展性受欢迎。

Linux操作是什么? Linux操作是什么? Apr 13, 2025 am 12:20 AM

Linux操作系统的核心是其命令行界面,通过命令行可以执行各种操作。1.文件和目录操作使用ls、cd、mkdir、rm等命令管理文件和目录。2.用户和权限管理通过useradd、passwd、chmod等命令确保系统安全和资源分配。3.进程管理使用ps、kill等命令监控和控制系统进程。4.网络操作包括ping、ifconfig、ssh等命令配置和管理网络连接。5.系统监控和维护通过top、df、du等命令了解系统运行状态和资源使用情况。

Linux管理员的薪水是多少? Linux管理员的薪水是多少? Apr 17, 2025 am 12:24 AM

Linux管理员的平均年薪在美国为75,000至95,000美元,欧洲为40,000至60,000欧元。提升薪资可以通过:1.持续学习新技术,如云计算和容器技术;2.积累项目经验并建立Portfolio;3.建立职业网络,拓展人脉。

Linux系统管理员的主要任务是什么? Linux系统管理员的主要任务是什么? Apr 19, 2025 am 12:23 AM

Linux系统管理员的主要任务包括系统监控与性能调优、用户管理、软件包管理、安全管理与备份、故障排查与解决、性能优化与最佳实践。1.使用top、htop等工具监控系统性能,并进行调优。2.通过useradd等命令管理用户账户和权限。3.利用apt、yum管理软件包,确保系统更新和安全。4.配置防火墙、监控日志、进行数据备份以确保系统安全。5.通过日志分析和工具使用进行故障排查和解决。6.优化内核参数和应用配置,遵循最佳实践提升系统性能和稳定性。

Linux的主要目的是什么? Linux的主要目的是什么? Apr 16, 2025 am 12:19 AM

Linux的主要用途包括:1.服务器操作系统,2.嵌入式系统,3.桌面操作系统,4.开发和测试环境。Linux在这些领域表现出色,提供了稳定性、安全性和高效的开发工具。

Linux和Windows之间虚拟化支持有哪些差异? Linux和Windows之间虚拟化支持有哪些差异? Apr 22, 2025 pm 06:09 PM

Linux和Windows在虚拟化支持上的主要区别在于:1)Linux提供KVM和Xen,性能和灵活性突出,适合高定制环境;2)Windows通过Hyper-V支持虚拟化,界面友好,与Microsoft生态系统紧密集成,适合依赖Microsoft软件的企业。

很难学习Linux吗? 很难学习Linux吗? Apr 18, 2025 am 12:23 AM

学习Linux并不难。1.Linux是一个开源操作系统,基于Unix,广泛应用于服务器、嵌入式系统和个人电脑。2.理解文件系统和权限管理是关键,文件系统是层次化的,权限包括读、写和执行。3.包管理系统如apt和dnf使得软件管理方便。4.进程管理通过ps和top命令实现。5.从基本命令如mkdir、cd、touch和nano开始学习,再尝试高级用法如shell脚本和文本处理。6.常见错误如权限问题可以通过sudo和chmod解决。7.性能优化建议包括使用htop监控资源、清理不必要文件和使用sy

Linux软件的未来:Flatpak和Snap会替换本机桌面应用程序吗? Linux软件的未来:Flatpak和Snap会替换本机桌面应用程序吗? Apr 25, 2025 am 09:10 AM

多年来,Linux软件分布依赖于DEB和RPM等本地格式,并深深地根深蒂固。 但是,Flatpak和Snap已经出现,有望成为应用程序包装的通用方法。 本文考试

See all articles