如何通过查看堆栈值来生成回溯?

时间:2011-05-28 09:30:37

标签: linux executable debugging x86

测验:如何通过查看堆栈值来生成回溯?

0xf3e2de34 f3e2de70 c0135351 401ef021 00000000   p.bsQS...p......
0xf3e2de44 f3e2de81 00000021 f3e2c000 f7950924   ..bs......bs...w
0xf3e2de54 00000000 401ef000 00000246 00000246   .....p..F...F...
0xf3e2de64 00000001 00000001 f7950000 f3e2df18   ...........w..bs
0xf3e2de74 c02898b5 322d7875 23203235 00007820   5...ux.252...x..
0xf3e2de84 f3e2de98 00000000 f7950bd0 bffff0ec   ..bs....P..wlp..
0xf3e2de94 c70cf660 00000000 00000000 bffff0ec   .v.G........lp..
0xf3e2dea4 f3e2c000 f7950930 7fffffff 00000000   ..bs0..w........
0xf3e2deb4 00000000 00000001 00000000 bffff07b   .............p..
0xf3e2dec4 f5cfd880 bffff07b 00000000 f5f24740   .XOu.p.......Gru
0xf3e2ded4 c0124f87 00000000 00000000 00200200   .O..............
0xf3e2dee4 f3e2defc 067b3067 00000000 f5f24740   ..bsg0.......Gru
0xf3e2def4 c0124f87 f7950934 f7950934 c028331b   .O..4..w4..w.3..
0xf3e2df04 00000000 c01b0fe8 f5cfd880 f7950000   ....h....XOu...w
0xf3e2df14 fffffffb f3e2df50 c0283642 00000001   ....P.bsB6......
0xf3e2df24 00000001 00000001 f3e2c000 f6fe30d0   ..........bsP0.v

提示 - 当前功能始终可以从EIP确定

我在kernelnewbies/ABI文档中找到了这个问题 我真的不明白那里给出的提示?(也许是因为我对这件事情一无所知)。

有人可以解释我如何解决这些问题。

2 个答案:

答案 0 :(得分:4)

在这种情况下,你可以非常可靠地完成它,因为该代码是在启用帧指针的情况下编译的。

您需要了解的内容:

  • EIP是一个指向下一条执行指令的寄存器。
  • 调用函数时,参数然后EIP(所以被调用函数知道返回的位置)保存在堆栈中。

  • 当编译器被告知(显式或隐式)使用帧指针时,它会将帧指针(在EBP寄存器中)保存在堆栈上(因此它可以稍后将帧指针恢复为值)在调用函数上),然后将帧指针设置为指向堆栈的当前顶部。这允许从已知的参考点(帧指针)轻松访问参数和局部变量,并大大简化调试。

  • 然后,为局部变量保留空间,并执行该函数。
  • 从函数返回时,前一帧指针和指令指针将被恢复。

因此,要生成回溯,您应该遵循帧指针,查看相应的已保存的EIPS。所以:

current function was called from c0135351
follow f3e2de70 → was called from c02898b5
follow f3e2df18 → was called from c0283642

当然,这很容易。当你没有帧指针时,你必须猜测堆栈上的给定值是否对应于指令指针。

缺少的部分是如何将这些数字转换为函数名称。拥有未提取的vmlinux(请注意x,而不是z)文件是非常宝贵的。 System.map只包含一些符号,因此您通常只会知道相关函数位于函数A和函数B之间。

修改

x86上的函数调用类似于:

                                        ...
int main()                              add  $-0x8,%esp ; alignment
{                                       push $0x2       ; arg 2
        ...                             push $0x1       ; arg 1
        func(1, 2);                     call func       ; function call
        ...                             add  $0x10,%esp ; pop args from stack
}                                       ...

被调用的函数看起来像:

void func(int arg1, int arg2)           push %ebp       ;\
{                                       mov  %esp,%ebp  ;/ create stack frame
        int local1;                     sub  $0x18,%esp ; reserves space
        ...                             ...
}                                       mov  %ebp,%esp  ;\
                                        pop  %ebp       ;/ destroys frame
                                        ret             ; returns

因此,堆栈看起来类似于:

          :           :
          +-----------+
          : alignment :
          +-----------+
12(%ebp)  |   arg2    |
          +-----------+
 8(%ebp)  |   arg1    |
          +-----------+
 4(%ebp)  |    ret    | -----> return address
          +-----------+
  (%ebp)  |    ebp    | -----> previous ebp
          +-----------+
-4(%ebp)  |  local1   | -----> local vars
          +-----------+
          : alignment :
          +-----------+
          :           :

(ASCII地址较低的地址较低)

因此,如果您继续关注已保存的EBP指针,则可以获取保存的EIP指针(上面为ret),这些指针指向调用链中的指令(在返回中)链,准确地说。)

答案 1 :(得分:0)

EIP是我认为的指令指针 - 它保存了当前正在执行的指令的地址。