2016-09-16 226 views
0

有没有办法从堆栈帧 获取函数参数和变量例如函数参数名称和C中堆栈帧的变量名?

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

我想这个函数内部是参数和变量名。我也有兴趣知道他们的类型。 我也可以访问堆栈帧的函数指针。我可以从函数指针获取关于参数,变量名称及其数据类型的信息吗?

我很好地编译它与-g或另一个标志为在运行时可执行文件中存在的符号。

+1

局部变量的名字是'z'。不知道你的问题是什么。无论如何,没有必要使用标准堆栈。而在x86/x64/ARM等最新平台上,它很可能不会。而类型显然是'int' C不支持动态类型(这包括编译器扩展)。 – Olaf

+1

使用标准C?不,没有办法。对于GCC,您可能需要[阅读文档](https://gcc.gnu.org/onlinedocs/)以查看它具有哪些(非标准)功能来帮助您。 –

+0

[内省](https://en.wikipedia.org/wiki/Introspection_(computer_science))或[反思](https://en.wikipedia.org/wiki/Reflection_(computer_programming))是描述什么你想...... – gilez

回答

4

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

如果你打破带GDB的功能里面,如果你已经编译了代码-g,然后GDB info locals可以告诉你局部变量和参数的名称,以便清楚,信息可用。

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

所以答案是:是的,你可以,但它会花费很多努力,你真的应该寻找不同的解决方案,无论你是什么实际上试图实现。

0

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

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

0

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

0

gdb标签中,前几天只有question。我在下面列出了我的答案。正如俄罗斯就业指出的那样,在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' 与arguements 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一个用于读取过程的静态和动态信息。另一个进程可以使用类似的功能来创建反射功能。

我已经使用此策略的变体来创建自定义调试器和内存泄漏检测器。然而,我从来没有见过这种用于商业逻辑的策略。

1

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

通常可以通过使用像libunwind或libbacktrace这样的库从callstack获取函数名称,但它们并不总是可移植的,它们并不总是微不足道的执行(执行成本),并且它们要求您构建您的带有调试符号的程序可用。

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

例如,

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

我已经在生产崩溃的bug居然看到了这一点:编译器告诉我们,我们在// 1,这显然是不可能访问一个空指针。我们无法在测试中复制崩溃,功能特别长且复杂。

事情是这样的编译器崩溃所有pointer->sub_->action(); return nullptr s到其 // 1传来一个存根函数,它实际上是在// 2处未选中的调用,这是崩溃的源头。

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

堆栈跟踪的进一步复杂情况是,在优化的代码中,它们通常包含一个转发地址的。试想一下:

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

如果你要检查调用堆栈中g有一个很好的机会,它会看起来像被人从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