How to generate the backtrace by looking at the stack values?

半城伤御伤魂 提交于 2019-12-06 15:27:50

In this case, you can do it quite reliably, since that code has been compiled with frame pointers enabled.

What you need to know:

  • EIP is a register that points at the next instruction to execute.
  • When calling a function, the arguments and then EIP (so the called function knows where to return to) are saved on the stack.

  • When the compiler has been told (explicitly or implicitly) to use frame pointers, it then saves the frame pointer (in the EBP register) on the stack (so it can later restore the frame pointer to the value it had on the calling function), and then sets the frame pointer to point to the current top of the stack. This allows accessing easily arguments and local variables from a known point of reference (the frame pointer), and greatly simplifies debugging.

  • Then, space is reserved for local variables, and the function is executed.
  • When returning from the function, the previous frame pointer and instruction pointer are restored.

So, to generate a backtrace, you would follow the frame pointers, looking at the corresponding saved EIPS. So:

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

Of course, this was the easy case. When you don't have frame pointers, you have to guess whether a given value on the stack corresponds to an instruction pointer.

The missing part is how to translate this numbers to function names. Having an unstripped vmlinux (note the x, not a z) file is invaluable. System.map just contains some symbols, so very often you'll only end up knowing that the relevant function was between function A and function B.

Edit:

A function call on x86 looks something like:

                                        ...
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
}                                       ...

And the called function looks something like:

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

So, the stack will look similar to:

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

(Lower addresses are lower on the ASCII-art)

So, if you keep following the saved EBP pointers, you can get the saved EIP pointers (ret above), which point to instructions in the call chain (in the return chain, to be precise).

The EIP is the instruction pointer I think - it holds the address of the instruction being executed at the moment.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!