如何使用gdb stacktrace和运行时生成的机器代码?

我为GNU /
Linux继承了一些聪明的x64机器代码,它为c函数调用创建了一个机器代码包装器.我想在更高级的语言中,代码可能被称为装饰器或闭包.代码运行良好,但是有了不幸的假象,当调用包装器时,它会吞噬gdb中的堆栈跟踪.

从我从net 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调用,我们可以通过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
识别堆栈帧?

最佳答案 在某些体系结构中,您可以使框架具有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编写一个unwinder.在my JIT unwinder上工作时,我选择了这种方法,因为它易于调试,易于部署,相当灵活,并且不需要对劣质进行任何特殊更改.

这些方法都是documented in the gdb manual.但是在某些情况下,我认为文档有点不尽如人意.您可能必须找到一些示例代码或深入了解gdb源代码才能真正理解它应该如何工作.

点赞