You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C/C++代码中实现类似GDB的带参数栈回溯打印

实现带参数的C/C++调用栈回溯

我正在C/C++程序中实现调用栈打印功能,已找到依赖libdw、execinfo库的实现示例,该示例可输出栈帧地址、函数名及对应代码位置,但无法打印函数参数。现需要实现类似GDB的bt命令效果,输出包含函数参数的完整栈回溯。

现有示例代码

#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <iomanip>

#include <cxxabi.h> // __cxa_demangle
#include <elfutils/libdwfl.h> // Dwfl*
#include <execinfo.h> // backtrace
#include <unistd.h> // getpid

using namespace std;

// 解析C++ mangled函数名
std::string demangle(const char* name) {
    int status = -4;
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };
    return (status==0) ? res.get() : name ;
}

std::string debug_info(Dwfl* dwfl, void* ip) {

    uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
    Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
    char const* name = dwfl_module_addrname(module, ip2);
    std::string function_name = name ? demangle(name) : "<unknown>";

    int line_num = -1;
    char const* file_name;
    if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
        Dwarf_Addr addr;
        file_name = dwfl_lineinfo(dwfl_line, &addr, &line_num, nullptr, nullptr, nullptr);
    }
    std::stringstream ss;
    ss << std::setw(16)<<std::setfill('0') <<ip << ' ' << function_name;
    if (file_name)
        ss << " at " << file_name << ':' << line_num;
    ss << std::endl;
    return ss.str();
}

std::string gen_framestack_backtrace() {
    // 初始化Dwfl
    Dwfl* dwfl = nullptr;
    {
        Dwfl_Callbacks callbacks = {};
        char* debuginfo_path = nullptr;
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;
        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);
        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    // 遍历栈帧
    std::stringstream ss;
    {
        void* stack[512];
        int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
        for (int i = 0; i < stack_size; ++i) {
            ss << i << ": ";
            ss << debug_info(dwfl, stack[i]);
        }
    }
    dwfl_end(dwfl);
    return ss.str();
}

void my_func_2() {
    std::cout << gen_framestack_backtrace() << std::endl;
    std::cout.flush();
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);   // line 122
        my_func_1(2.0); // line 123
    }
}

编译运行命令

$ sudo apt install libdw-dev libunwind-dev
$ g++ -fno-pie -ggdb3 -O0 -no-pie -o a.out -std=c++11 -Wall -Wextra -pedantic-errors test.cpp -ldw -lunwind -ggdb
$ ./a.out

现有运行结果

0: 000000000x401ab1 stacktrace[abi:cxx11]() at /home/wxq/test/test7.cpp:71
1: 000000000x401c11 my_func_2() at /home/wxq/test/test7.cpp:106
2: 000000000x401ca2 my_func_1(int) at /home/wxq/test/test7.cpp:117
3: 000000000x401d01 main at /home/wxq/test/test7.cpp:128
4: 000x7f3e4ee4dbf6 __libc_start_main at ../csu/libc-start.c:310
5: 000000000x401479 _start at ../csu/libc-start.c:-1

0: 000000000x401ab1 stacktrace[abi:cxx11]() at /home/wxq/test/test7.cpp:71
1: 000000000x401c11 my_func_2() at /home/wxq/test/test7.cpp:106
2: 000000000x401c8f my_func_1(double) at /home/wxq/test/test7.cpp:112
3: 000000000x401d16 main at /home/wxq/test/test7.cpp:129
4: 000x7f3e4ee4dbf6 __libc_start_main at ../csu/libc-start.c:310
5: 000000000x401479 _start at ../csu/libc-start.c:-1

期望输出效果(类似GDB的bt命令)

#0  createObj1 (handle=0x5555559291c0, shimHandle=0x55555595a850) at /home/wxq/setup.cpp:983
#1  0x00007ffff60a3763 in initialize (this=0x55555595a850, config=...)at /home/wxq/test.cpp:197
#2  0x00007ffff60a24f2 in create_extended (setup=0x5555559291c0) at /home/wxq/test.cpp:509
#3  0x0000555555555538 in main (argc=5, argv=0x7fffffffe0e8) at /home/wxq/core_model.cpp:145

解决方案:添加函数参数打印功能

要实现参数打印,需要解析DWARF调试信息获取函数参数的定义,再结合栈帧布局读取参数值。以下是基于现有代码的修改方案:

核心思路

  1. 获取函数DWARF调试信息:通过dwfl_module_getdwarf获取模块的Dwarf对象,再根据指令地址定位到对应的函数调试条目(DIE)。
  2. 解析参数元信息:遍历函数DIE的子条目,提取参数名称、类型以及参数在栈/寄存器中的位置。
  3. 读取栈中参数值:根据目标架构的调用约定(如x86-64 System V ABI),从当前栈帧中读取参数的实际值。

修改后的核心代码

扩展debug_info函数,添加参数解析逻辑:

#include <elfutils/dwarf.h>

// 解析函数参数并返回参数字符串
std::string get_function_args(Dwfl* dwfl, void* ip, uintptr_t frame_ptr) {
    uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
    Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
    if (!module) return "";

    Dwarf_Die die_mem;
    Dwarf_Die* func_die = dwfl_module_addrdie(module, ip2, &die_mem);
    if (!func_die) return "";

    std::stringstream args_ss;
    args_ss << "(";
    bool first_arg = true;

    // 遍历函数的参数DIE
    Dwarf_Die child;
    if (dwarf_child(func_die, &child) == 0) {
        do {
            if (dwarf_tag(&child) == DW_TAG_formal_parameter) {
                const char* arg_name = dwarf_name(&child);
                if (!arg_name) arg_name = "<unknown>";

                // 这里简化处理:假设参数在栈上,基于x86-64 System V ABI
                // 实际需要根据DWARF的location信息判断参数位置(栈/寄存器)
                uintptr_t arg_value = 0;
                // 示例:从栈帧指针偏移处读取参数值(仅作演示,需根据实际调用约定调整)
                if (frame_ptr != 0) {
                    // 跳过返回地址,第一个栈上参数在rbp+16位置(x86-64)
                    static int arg_idx = 0;
                    arg_value = *(uintptr_t*)(frame_ptr + 16 + arg_idx * sizeof(uintptr_t));
                    arg_idx++;
                }

                if (!first_arg) args_ss << ", ";
                args_ss << arg_name << "=0x" << std::hex << arg_value;
                first_arg = false;
            }
        } while (dwarf_siblingof(&child, &child) == 0);
    }

    args_ss << ")";
    return args_ss.str();
}

// 修改后的debug_info函数
std::string debug_info(Dwfl* dwfl, void* ip, uintptr_t frame_ptr) {
    uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
    Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
    char const* name = dwfl_module_addrname(module, ip2);
    std::string function_name = name ? demangle(name) : "<unknown>";

    // 添加参数打印
    std::string args_str = get_function_args(dwfl, ip, frame_ptr);

    int line_num = -1;
    char const* file_name;
    if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
        Dwarf_Addr addr;
        file_name = dwfl_lineinfo(dwfl_line, &addr, &line_num, nullptr, nullptr, nullptr);
    }
    std::stringstream ss;
    ss << std::setw(16)<<std::setfill('0') <<ip << ' ' << function_name << args_str;
    if (file_name)
        ss << " at " << file_name << ':' << line_num;
    ss << std::endl;
    return ss.str();
}

注意事项

  • 调用约定适配:不同架构(x86、x86-64、ARM)的调用约定不同,参数可能存放在寄存器或栈上,需要根据DWARF的DW_AT_location信息精准定位参数位置。
  • 调试信息完整性:必须用-ggdb3编译,保留完整DWARF调试信息;开启优化(如-O1及以上)可能导致参数被优化或栈帧布局变化,无法正确读取参数。
  • 栈帧指针获取:需要结合libunwind获取栈帧指针(RBP/x86-64),替换原代码中backtrace仅获取指令地址的逻辑,才能准确读取栈上参数。

内容的提问来源于stack exchange,提问作者xiaoqiang-wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 11:05:30