2016-09-16 212 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