我爲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 識別棧幀?
嗯,所述'wbarcode'動態生成的函數確實使一個傳統的堆棧幀(推動'%rbp')。如果調用者使用默認的'-fomit-frame-pointer'編譯,我想這並不會起作用,因爲'wbarcode'的調用者可能不會保留''rsp'-on-entry' %rbp'。 **如果使用'-fno-omit-frame-pointer' **編譯整個內容會發生什麼? IDK,如果仍然存在'.eh_frame_hdr'部分gdb仍然想使用... –
不確定這是否是巧妙的代碼,因爲它在編碼的彙編器中使用硬編碼地址。 –
@MichaelPetch:他說這只是一個MCVE,並不是說它實際上就是他的實際動態生成函數。 –