千家信息网

Linux中堆栈的示例分析

发表于:2025-12-03 作者:千家信息网编辑
千家信息网最后更新 2025年12月03日,这篇文章给大家分享的是有关Linux中堆栈的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。用下面的程序作为例子:void a() { //stopped h
千家信息网最后更新 2025年12月03日Linux中堆栈的示例分析

这篇文章给大家分享的是有关Linux中堆栈的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

用下面的程序作为例子:

void a() {     //stopped here } void b() {      a(); } void c() {      a(); } int main() {     b();     c(); }

如果调试器停在 //stopped here' 这行,那么有两种方法可以达到:main->b->a或main->c->a`。如果我们用 LLDB 设置一个断点,继续执行并请求一个回溯,那么我们将得到以下内容:

* frame #0: 0x00000000004004da a.out`a() + 4 at bt.cpp:3   frame #1: 0x00000000004004e6 a.out`b() + 9 at bt.cpp:6   frame #2: 0x00000000004004fe a.out`main + 9 at bt.cpp:14   frame #3: 0x00007ffff7a2e830 libc.so.6`__libc_start_main + 240 at libc-start.c:291   frame #4: 0x0000000000400409 a.out`_start + 41

这说明我们目前在函数 a 中,a 从函数 b 中跳转,b 从 main 中跳转等等。***两个帧是编译器如何引导 main 函数的。

现在的问题是我们如何在 x86_64 上实现。最稳健的方法是解析 ELF 文件的 .eh_frame 部分,并解决如何从那里展开堆栈,但这会很痛苦。你可以使用 libunwind 或类似的来做,但这很无聊。相反,我们假设编译器以某种方式设置了堆栈,我们将手动遍历它。为了做到这一点,我们首先需要了解堆栈的布局。

    High |   ...   | +---------+ |  Arg 1  | +---------+ |  Arg 2  | +---------+ | Return  | +---------+ |Saved EBP| +---------+ |  Var 1  | +---------+ |  Var 2  | +---------+ |   ...   |     Low

如你所见,***一个堆栈帧的帧指针存储在当前堆栈帧的开始处,创建一个链接的指针列表。堆栈依据这个链表解开。我们可以通过查找 DWARF 信息中的返回地址来找出列表中下一帧的函数。一些编译器将忽略跟踪 EBP 的帧基址,因为这可以表示为 ESP 的偏移量,并可以释放一个额外的寄存器。即使启用了优化,传递 -fno-omit-frame-pointer 到 GCC 或 Clang 会强制它遵循我们依赖的约定。

我们将在 print_backtrace 函数中完成所有的工作:

void debugger::print_backtrace() {

首先要决定的是使用什么格式打印出帧信息。我用了一个 lambda 来推出这个方法:

auto output_frame = [frame_number = 0] (auto&& func) mutable {     std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func)               << ' ' << dwarf::at_name(func) << std::endl; };

打印输出的***帧是当前正在执行的帧。我们可以通过查找 DWARF 中的当前程序计数器来获取此帧的信息:

auto current_func = get_function_from_pc(get_pc());     output_frame(current_func);

接下来我们需要获取当前函数的帧指针和返回地址。帧指针存储在 rbp 寄存器中,返回地址是从帧指针堆栈起的 8 字节。

auto frame_pointer = get_register_value(m_pid, reg::rbp); auto return_address = read_memory(frame_pointer+8);

现在我们拥有了展开堆栈所需的所有信息。我只需要继续展开,直到调试器*** main,但是当帧指针为 0x0 时,你也可以选择停止,这些是你在调用 main 函数之前调用的函数。我们将从每帧抓取帧指针和返回地址,并打印出信息。

while (dwarf::at_name(current_func) != "main") {         current_func = get_function_from_pc(return_address);         output_frame(current_func);         frame_pointer = read_memory(frame_pointer);         return_address = read_memory(frame_pointer+8);     } }

就是这样!以下是整个函数:

void debugger::print_backtrace() {     auto output_frame = [frame_number = 0] (auto&& func) mutable {         std::cout << "frame #" << frame_number++ << ": 0x" << dwarf::at_low_pc(func)                   << ' ' << dwarf::at_name(func) << std::endl;     };     auto current_func = get_function_from_pc(get_pc());     output_frame(current_func);     auto frame_pointer = get_register_value(m_pid, reg::rbp);     auto return_address = read_memory(frame_pointer+8);     while (dwarf::at_name(current_func) != "main") {         current_func = get_function_from_pc(return_address);         output_frame(current_func);         frame_pointer = read_memory(frame_pointer);         return_address = read_memory(frame_pointer+8);     } }

添加命令

当然,我们必须向用户公开这个命令。

else if(is_prefix(command, "backtrace")) {     print_backtrace(); }

感谢各位的阅读!关于"Linux中堆栈的示例分析"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

堆栈 函数 指针 信息 地址 内容 方法 编译器 编译 示例 分析 可以通过 命令 寄存器 更多 程序 篇文章 调试器 存储 不错 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 计算产品线的ai软件开发 ibm服务器初始密码 渭南软件开发哪家强 网络安全宣传座谈 十堰良好软件开发市场 互联网科技金融产品 dna打拐数据库保存多久 计算机网络技术要有什么基础 中国软件开发有限公司招聘 安徽智能还款软件开发 稳定dns服务器 放射治疗公共数据库 ar绘图卡 软件开发 咸宁市中小学生同上网络安全课 厦门网络安全技术应用示范项目 本地连接服务器创建实例 沛县正规软件开发答疑解惑 软件开发搜索关键词推荐怎么做 内丘法院对网络安全开展自查 辽宁上门软件开发条件 湖北信息化软件开发价钱 迷你世界ice服务器的账号 信息管理软件开发的过程 交罚款服务器维护中 企家有道网络技术与薪人薪事 云服务器公用安全性保障 40岁软件开发能找到工作吗 软件开发公司找项目 奉贤区品质软件开发服务电话 一个服务器插2个阵列卡
0