2016-01-22 16 views
4

我爲GNU/Linux繼承了一些聰明的x64機器代碼,它創建了圍繞c函數調用的機器代碼包裝。我猜想用更高級的語言來說,代碼可能被稱爲裝飾者或封閉。該代碼運行良好,但有一個不幸的神器,當包裝被調用時,它會吞噬gdb中的堆棧跟蹤。如何將gdb stacktrace與運行時生成的機器碼一起使用?

從我從網絡gdb中學到的知識使用https://en.wikipedia.org/wiki/DWARF作爲分離堆棧中堆棧幀的指南。這適用於靜態代碼,但顯然在運行時生成並調用的代碼未在DWARF框架中註冊。

我的問題是在這種情況下是否有任何方法來解救堆棧跟蹤?

下面是一些類似的c代碼,顯示問題。

typedef int (*ftype)(int x); 
int wuz(int x) { return x + 7; } 
int wbar(int x) { return wuz(x)+5; } 
int main(int argc, char **argv) 
{ 
    const unsigned char wbarcode[] = { 
    0x55 ,       // push %rbp 
    0x48,0x89,0xe5 ,     // mov %rsp,%rbp 
    0x48,0x83,0xec,0x08 ,    // sub $0x8,%rsp 
    0x89,0x7d,0xfc ,     // mov %edi,-0x4(%rbp) 
    0x8b,0x45,0xfc ,     // mov -0x4(%rbp),%eax 
    0x89,0xc7 ,      // mov %eax,%edi 
    0x48,0xc7,0xc0,0xf6,0x04,0x40,00, // mov $0x4004f6,%rax 
    0xff,0xd0,      // callq *%rax 
    0x83,0xc0,0x05 ,     // add $0x5,%eax 
    0xc9 ,       // leaveq 
    0xc3        // retq 
    }; 

    int wb = wbar(5); 
    ftype wf = (ftype)wbarcode; 
    int fwb = wf(5); 
} 

通過編譯:

gcc -g -o mcode mcode.c 
execstack -s mcode 

,並在gdb的運行它:

$ gdb mcode 
(gdb) break wuz 

如果我們反wbar我們得到非常相似的字節序列wbarcode[]東西。唯一的區別是我改變了調用wuz()的調用約定。

(gdb) disas/r wbar 
Dump of assembler code for function wbar: 
    0x0000000000400505 <+0>: 55  push %rbp 
    0x0000000000400506 <+1>: 48 89 e5  mov %rsp,%rbp 
    0x0000000000400509 <+4>: 48 83 ec 08  sub $0x8,%rsp 
    0x000000000040050d <+8>: 89 7d fc  mov %edi,-0x4(%rbp) 
    0x0000000000400510 <+11>:  8b 45 fc  mov -0x4(%rbp),%eax 
    0x0000000000400513 <+14>:  89 c7 mov %eax,%edi 
    0x0000000000400515 <+16>:  e8 dc ff ff ff callq 0x4004f6 <wuz> 
    0x000000000040051a <+21>:  83 c0 05  add $0x5,%eax 
    0x000000000040051d <+24>:  c9  leaveq 
    0x000000000040051e <+25>:  c3  retq 
End of assembler dump. 

如果我們現在運行該程序,它將在wuz()中停止兩次。第一次通過我們的c-call調用 ,我們可以通過bt請求堆棧跟蹤。

Breakpoint 3, wuz (x=5) at mcode.c:2 
=> 0x00000000004004fd <wuz+7>: 8b 45 fc mov -0x4(%rbp),%eax 
    0x0000000000400500 <wuz+10>: 83 c0 07 add $0x7,%eax 
    0x0000000000400503 <wuz+13>: 5d pop %rbp 
    0x0000000000400504 <wuz+14>: c3 retq 
(gdb) bt 
#0 wuz (x=5) at mcode.c:2 
#1 0x000000000040051a in wbar (x=5) at mcode.c:3 
#2 0x00000000004005b0 in main (argc=1, argv=0x7fffffffe528) at mcode.c:20 

這是展示我們從main()wbar()wuz()有一個正常的堆棧跟蹤。

但是,如果我們現在繼續我們到達wuz()第二次,我們再次 請求堆棧跟蹤:

(gdb) c 
Continuing. 

Breakpoint 3, wuz (x=5) at mcode.c:2 
=> 0x00000000004004fd <wuz+7>: 8b 45 fc mov -0x4(%rbp),%eax 
    0x0000000000400500 <wuz+10>: 83 c0 07 add $0x7,%eax 
    0x0000000000400503 <wuz+13>: 5d pop %rbp 
    0x0000000000400504 <wuz+14>: c3 retq 
(gdb) bt 
#0 wuz (x=5) at mcode.c:2 
#1 0x00007fffffffe419 in ??() 
#2 0x0000000500000001 in ??() 
#3 0x00007fffffffe440 in ??() 
#4 0x00000000004005c6 in main (argc=0, argv=0xffffffff) at mcode.c:22 
Backtrace stopped: frame did not save the PC 

儘管我們也做了相同的兩個層次的來電,我們得到一個 堆棧跟蹤包含錯誤的幀。在我原來的繼承 包裝代碼的情況甚至更糟,因爲後5幀與具有頂級地址0

所以問題又是堆棧跟蹤 結束,有沒有可以添加任何額外的代碼到 wbarcode[]這將導致gdb輸出一個有效的堆棧跟蹤?或者是 有沒有其他的運行時技術可以用來使gdb 識別棧幀?

+0

嗯,所述'wbarcode'動態生成的函數確實使一個傳統的堆棧幀(推動'%rbp')。如果調用者使用默認的'-fomit-frame-pointer'編譯,我想這並不會起作用,因爲'wbarcode'的調用者可能不會保留''rsp'-on-entry' %rbp'。 **如果使用'-fno-omit-frame-pointer' **編譯整個內容會發生什麼? IDK,如果仍然存在'.eh_frame_hdr'部分gdb仍然想使用... –

+0

不確定這是否是巧妙的代碼,因爲它在編碼的彙編器中使用硬編碼地址。 –

+0

@MichaelPetch:他說這只是一個MCVE,並不是說它實際上就是他的實際動態生成函數。 –

回答

2

在某些體系結構上,您可以使該框架具有gdb對該端口的默認解除綁定器所期望的佈局。但是,這不適用於所有架構。讀取x86-64端口(請參閱gdb/amd64-tdep.c,特別是函數amd64_frame_cache_1),我想在這裏gdb想知道函數範圍,因此它可以嘗試分析序言。但是,函數邊界來自(ELF)符號表,所以你在那裏運氣不好。

儘管如此,還是有一種方法。由於最近(以gdb的方式)JIT編譯器的崛起,gdb提供了三種其他方式來解決這個問題。

的一種方法是,你的程序可以在內存中散發出特殊的ELF對象(實際的任何對象格式GDB瞭解到,IIRC),並調用運行時鉤告知其存在的GDB。 gdb將讀取這個對象,包括它包含的任何調試信息。這種做法是相當沉重的,但可以訪問大多數的GDB的能力 - 你可以指定不只是平倉,而且類型,局部變量等

的第二種方式有些類似。你的程序仍然調用一個特殊的鉤子。但是,您還提供了一個由gdb加載的插件。這個插件可以從下層讀取符號和其他信息,但在這種情況下,符號和展開信息不必以任何特定的格式。

最後一種方法(在gdb 7.10新)是你可以用Python語言編寫的開卷。在my JIT unwinder上工作時,我選擇了這種方法,因爲它調試簡單,部署簡單,相當靈活,並且不需要對劣勢進行任何特定更改。

這些方法都是documented in the gdb manual。但是,在某些情況下,我認爲文檔有點不盡人意。你可能不得不找到一些示例代碼或挖掘到gdb源,以真正瞭解它應該如何工作。

相關問題