C中堆栈帧的函数参数名和变量名?

时间:2016-09-16 18:03:49

标签: c linux gcc gdb elf

有没有办法从堆栈帧中获取函数参数和变量 例如

foo(int a){
    char z;
    // Can I know what is the name of my local variable and argument inside this function 
}

我在这个函数中想要的是参数和变量的名称。我也有兴趣了解他们的类型。 我也可以访问堆栈框架的函数指针。我可以从函数指针获取有关参数,变量名称及其数据类型的信息吗?

我可以使用-g或其他标志来编译它,以便在运行时在可执行文件中出现符号。

5 个答案:

答案 0 :(得分:4)

  

// Can i know what is the name of my local variable and arugment inside this function

如果你使用GDB打破函数内部,并且如果你用-g编译了代码,那么GDB info locals可以告诉你局部变量和参数的名称,所以很清楚信息 可用。

坏消息:您需要实现50%的调试程序来获取该信息,这是一项非常重要的任务。

所以答案是:是的,你可以,但需要付出很多努力,你真的应该寻找一个不同的解决方案来实现实际试图实现的目标。

答案 1 :(得分:1)

C语言本身并不提供此功能,但大多数可执行格式确实提供了启用此功能的方法,因为它是调试器所需的。

你经常可以通过使用像libunwind或libbacktrace这样的库从callstack中获取函数名,但它们并不总是可移植的,并且它们并不总是很容易执行(执行成本),并且它们需要使用可用的调试符号构建程序。

在任何一种情况下,只有在没有优化的情况下构建时,这才是可靠的。一旦涉及优化器,所有投注都会关闭。

例如,

if (pointer && pointer->sub_->something_) {
     pointer->sub_->action();  //1
     return nullptr;
}
/// ...
if (pointer) {
     pointer->sub_->action();  //2
     return nullptr;
}
/// ...

我实际上在生产崩溃错误中看到了这一点:编译器告诉我们我们在// 1处访问空指针,这显然是不可能的。我们无法在测试中重现崩溃,而且功能特别漫长而复杂。

发生了什么事情,编译器将所有pointer->sub_->action(); return nullptr折叠为一个来自 // 1的存根函数,而实际上是// 2的未经检查的调用是崩溃的根源。

在这样的优化,函数内联,整个程序优化等之间,准确地告诉正在运行的程序的机器状态相对于源代码的内容是非常困难的。

堆栈跟踪的另一个复杂因素是,在优化代码中,它们通常包含转发地址。考虑:

int f() {
    g();
    h();
}

如果要查看g中的callstack,很可能看起来就好像它是从h调用的那样:编译器可以操作堆栈所以当g返回时,它会直接转到h,而不是浪费地返回f只是为了获得另一次跳跃。

变量甚至更难 - 优化器努力完全消除它们,在寄存器中有效地将它们混乱,等等。

但你可以在理论上构建自己的简单反射系统,将变量包装在容器中。但这通常会变得笨拙。

用于跟踪调用堆栈:

#include <iostream>
#include <vector>

struct Callsite {
    const char* file_;
    size_t line_;

     static thread_local std::vector<Callsite*> callStack;

    Callsite(const char* file, size_t line) : file_(file), line_(line) {
        callStack.push_back(this);
    }
    ~Callsite() noexcept { callStack.pop_back(); }
};

thread_local std::vector<Callsite*> Callsite::callStack;

#define ENTER  Callsite __callsite_entry(__FILE__, __LINE__);

void f() {
    ENTER;

    for (auto&& stack: Callsite::callStack) {
        std::cout << stack->file_ << ":" << stack->line_ << "\n";
    }
}

int main() {
    ENTER;
    f();
}

现场演示:http://ideone.com/ZAUVib

答案 2 :(得分:0)

不,无法知道函数中变量的名称。这不是C的一部分。

您可以使用特殊标志编译代码,以生成包含有关生成代码的信息的各种其他文件。然后,您可以打开文件并查找变量名称。但这将高度依赖于编译器。

答案 3 :(得分:0)

即使代码未在启用调试器支持的情况下编译,您也可以通过查看关联的程序集列表来推断包括signedness在内的变量类型。这就是像IDA Pro这样的工具。

答案 4 :(得分:0)

前几天在gdb标记中只有question关于反射的内容。我在下面列出了我的答案。正如Employed Russian指出的那样,在C库中实现反射似乎是可能的,因为它是在gdb中完成的。

正如其他人所说,反射不是内置于C或C ++语言中。有各种各样的想法here

但是,可以在C / C ++中使用第三方库和可执行文件或外部文件中的调试符号进行反射。

dwarfdump可执行文件或多或少可以满足您的需求。使用DWARF信息详细信息可以使用函数,变量,类型等。以类似的方式,进程可以使用libdwarfdump功能来检查自身。

以下是一个简单的手动示例:

typedef struct somestruct 
{
   int i;
   int j;
} somestruct ;

int abc(int x, float y , struct somestruct z ){
    char a;
    int b ;
}


int main(int argc, char* argv[])
{

   struct somestruct z;
   abc(1,1.0f,z);
   return 0;
}

和dwarfdump的部分输出

< 1><0x00000055>    DW_TAG_subprogram
                      DW_AT_external              yes(1)
                      DW_AT_name                  "abc"
                      DW_AT_decl_file             0x00000001 /tmp/dwarf.c
                      DW_AT_decl_line             0x00000009
                      DW_AT_prototyped            yes(1)
                      DW_AT_type                  <0x0000004e>
                      DW_AT_low_pc                0x004004ed
                      DW_AT_high_pc               <offset-from-lowpc>18
                      DW_AT_frame_base            len 0x0001: 9c: DW_OP_call_frame_cfa
                      DW_AT_GNU_all_call_sites    yes(1)
                      DW_AT_sibling               <0x000000ad>
< 2><0x00000076>      DW_TAG_formal_parameter
                        DW_AT_name                  "x"
                        DW_AT_decl_file             0x00000001 /tmp/dwarf.c
                        DW_AT_decl_line             0x00000009
                        DW_AT_type                  <0x0000004e>
                        DW_AT_location              len 0x0002: 916c: DW_OP_fbreg -20
< 2><0x00000082>      DW_TAG_formal_parameter
                        DW_AT_name                  "y"
                        DW_AT_decl_file             0x00000001 /tmp/dwarf.c
                        DW_AT_decl_line             0x00000009
                        DW_AT_type                  <0x000000ad>
                        DW_AT_location              len 0x0002: 9168: DW_OP_fbreg -24
< 2><0x0000008e>      DW_TAG_formal_parameter
                        DW_AT_name                  "z"
                        DW_AT_decl_file             0x00000001 /tmp/dwarf.c
                        DW_AT_decl_line             0x00000009
                        DW_AT_type                  <0x0000002d>
                        DW_AT_location              len 0x0002: 9160:        DW_OP_fbreg -32

通过仔细研究,我们可以看到片段定义了函数&abc&#39;与争论x,y和z。

参数x的类型是与键0x4e的类型表的间接。

在输出的其他地方,我们可以看到类型0x4e的定义。类型0x2d是连接到参数z的somestruct。

< 1><0x0000002d>    DW_TAG_structure_type
                      DW_AT_name                  "somestruct"
                      DW_AT_byte_size             0x00000008
                      DW_AT_decl_file             0x00000001 /tmp/dwarf.c
                      DW_AT_decl_line             0x00000003
                      DW_AT_sibling               <0x0000004e>

< 1><0x0000004e>    DW_TAG_base_type
                      DW_AT_byte_size             0x00000004
                      DW_AT_encoding              DW_ATE_signed
                      DW_AT_name                  "int"

ptrace,ELF,DWARF和/ proc文件系统的组合允许gdb读取进程的静态和动态信息。另一个进程可以使用类似的功能来创建Reflection功能。

我使用此策略的变体来创建自定义调试器和内存泄漏检测器。我从未见过这种用于业务逻辑的策略。