When the core of our program is dropped, it is very advantageous to locate the problem if the function call stack gets to the core. You can use the SEH mechanism under Windows, and use the Coredump file with GDB under Linux.
However, sometimes the stack is destroyed due to some errors, and the call stack is not taken.
Some basic preparatory knowledge this article is no longer detailed, you can refer to the following articles:
- Analysis of acquiring principle of function call stack
- Registers, function calls, and stack frames
Information you need to know:
- The corresponding instruction of a function call
call
is essentially pressing the address of the next instruction into the stack and then jumping to the destination function address
- The function return instruction
ret
is to take an address out of the stack and then jump to that address
- The EBP register always points to the position in the stack where the information about the current executing function (local variables) is located, and ESP always points to the top of the stack
- Each function entry will hold the caller's EBP value, and the EBP value will be reset at the exit to enable the field preservation of the function call and on-site recovery.
- The 64-bit machine adds a lot of registers so that the parameters of the function call can be passed through the register most of the time, while the register name changes, for example, EBP becomes RBP
A description of the stack in the function call is available:
To map the code together:
void G () { int *p = 0; Long a = 0x1234; printf ("%p%x\n", &a, a); printf ("%p%x\n", &p, p); f (); *p = 1;} void B (int argc, char **argv) { printf ("%p%p\n", &ARGC, &ARGV); g ();} int main (int argc, char **argv) { B (argc, argv); return 0;}
At the function g()
break point, look at the contents of the stack (64-bit machine):
(GDB) p $RBP $ = (void *) 0x7fffffffe370 (GDB) p &p$3 = (int * *) 0x7fffffffe368 (GDB) p $rsp $4 = (void *) 0x7fffffffe360 (gdb) X/8ag $rbp -160x7fffffffe360:0x1234 0x00x7fffffffe370:0x7fffffffe390 0x400631 <b (int, char**) +43 >0x7fffffffe380:0x7fffffffe498 0x1a561cbc00x7fffffffe390:0x7fffffffe3b0 0x40064f <main (int, Char * *) +27>
The corresponding stack diagram:
You can look at 0x400631 <b(int, char**)+43>
the code in the example and in 0x40064f <main(int, char**)+27>
:
(GDB) Disassemble 0x400631 ... 0x0000000000400627 <b (int, char**) +33>: callq 0x400468 <[email protected]>0x000000000040062c <b ( int, char**) +38>: Callq 0x4005ae <g () >0x0000000000400631 <b (int, char**) +43>: Leaveq # Call's next instruction ... (GDB) Disassemble 0x40064f ... 0x000000000040063f <main (int, char**) +11>: mov %rsi,-0x10 (%RBP) 0x0000000000400643 <main (int, Char * *) +15>: mov -0x10 (%RBP),%rsi0x0000000000400647 <main (int, char**) +19> : mov -0x4 (%RBP) ,%edi0x000000000040064a <main (int, char**) +22>: callq 0x400606 <b (int, char**) > 0x000000000040064f <main (int, char**) +27>: mov $0x0,%eax # Call's next instruction ...
Incidentally, for each function entry and exit, the corresponding setting RBP code is:
(GDB) disassemble g ... 0x00000000004005ae <g () +0>: push %RBP # Saves the caller's RBP to the stack 0x00000000004005af <g () +1>: mov %RSP,%RBP # Set your own RBP ... 0x0000000000400603 <g () +85>: leaveq # equivalent to: Movq%rbp,%rsp # POPQ%rbp0x0000000000400604 <g ( ) +86>: retq
As seen above, the RBP of all functions in the call stack can be found through the current RSP or RBP, and the function address can be found RBP . Because, at any time, the RBP point to the stack position is the RBP of the previous function, and at any time the previous position in the RBP stack is the function return address.
Thus we can build ourselves an example that will result in gdb not getting the call stack:
void F () { long *p = 0; p = (long*) (&p + 1); Get G () RBP *p = 0; Destroy G () rbp}void g () { int *p = 0; Long a = 0x1234; printf ("%p%x\n", &a, a); printf ("%p%x\n", &p, p); f (); *p = 1; Writing 0 addresses results in one core}void b (int argc, char **argv) { printf ("%p%p\n", &ARGC, &ARGV); g ();} int main (int argc, char **argv) { B (argc, argv); return 0;}
Run the program using GDB:
Program received signal SIGSEGV, segmentation fault.g () at ebp.c:3737 *p = 1; (GDB) Btcannot access memory at address 0x8 (GDB) p $RBP $ = (void *) 0x0
bt
Unable to get the stack, in the function g()
RBP is rewritten as 0,gdb from 0 offset an address length that is 0x8, try to get the address of the function from the 0x8 memory location, and then prompt Cannot access memory at address 0x8
.
RBP There is a problem, we can get the call stack manually via RSP. because RSP is not compromised, getting the call stack via RSP requires offsetting some of the space occupied by local variables:
(GDB) p $RSP $ = (void *) 0x7fffffffe360 (gdb) x/8ag $rsp +16 # g () Local variables accounted for 16 bytes 0x7fffffffe370:0x7fffffffe390 0x400631 <b (int, char**) +43>0x7fffffffe380:0x7fffffffe498 0x1a561cbc00x7fffffffe390:0x7fffffffe3b0 0x40064f <main (int, char**) +27>0x7fffffffe3a0:0x7fffffffe498 0x100000000
Based on the above, you can manually find the call stack:
G () 0x400631 <b (int, char**) +43>0x40064f <main (int, char**) +27>
The above example essentially destroys the stack, and only destroys the saved RBP. In the real situation, the stack can be destroyed more, which can lead to manual positioning is also more difficult.
A broken stack can also cause more problems, such as overwriting a function return address, which can cause RIP errors, such as a stack imbalance. There are also many reasons for the stack being destroyed, such as a partial array out of bounds, an object on the Delete/free stack, and so on.
Omit-frame-pointer
It is relatively easy to get the call stack using RBP. But now the compiler can set it up without using RBP (GCC uses-fomit-frame-pointer,msvc with/oy), and not setting its RBP for functions means you can save several instructions. Inside the function, the offset of the RSP is used to locate the local variables, including the local variables in the nested scope, even if the program is actually running without entering the scope.
For example:
void F2 () { int a = 0x1234; if (a > 0) { int b = 0xFF; b = A; }}
The generated code used in GCC -fomit-frame-pointer
is:
(GDB) disassemble f2dump of assembler code for function F2:0x00000000004004a5 <f2+0>: movl $0x1234,-0x8 (% RSP) # int a = 0X12340X00000000004004AD <f2+8>: cmpl $0x0,-0x8 (%RSP) 0x00000000004004b2 <f2 +13>: jle 0x4004c4 <f2+31> 0x00000000004004b4 <f2+15>: movl $0xff,-0x4 (%RSP) # int b = 0XFF0X00000000004004BC <f2+23>: mov -0x8 (%RSP),%eax0x00000000004004c0 <f2+27>: mov %eax,-0x4 (%RSP) 0x00000000004004c4 <f2+31>: retq
You can find f2()
RBP
instructions such as no operations.
Original address: http://codemacro.com/2014/09/02/stack-frame/
Written by Kevin Lynx posted athttp://codemacro.com
Manually fetch the call stack in C + +