Modify the Stack Frame of C
For a long time, I’m not so clearly on the process of function call of c. Recently days, when implementing an user level preempt multi-thread library I need to change the stack. Because it begin as an project of OS course, must use setjmp and longjmp but not getcontext, setcontext. Here just to write down what I have learned.
The Structure of stack and calling process
I thought there are lots of material about the stack, here just do a briefly introduce. Here is the structure of x86-64, in fact, because I didn’t contain much detail in here, the different only is the name of register which under x86 is ebp, esp and the length of address is 4 byte but not 8 byte.
|+++++++++
| parameter
| ...
| parameter
|++++++++ +
| return address <--- rbp + 8 under x86 is ebp+4
|+++++++++
| previous rbp address <--- rbp
|+++++++++
| local variable
| local variable
| ... <--- rsp
when we use “info frame” and “info register” under gdb, we can get those information. Under gdb, the return address named as saved rip. Because when return from current function, the program will jump back to return address which means the rip should point to there.
Before call a function, the caller should:
-
push the parameters into stack, may also use some of the register too.
-
execute call instruction which will push the return address(the address just after the call) and jump to the destination function. The called function will:
-
push the rbp into stack
-
move the current rbp point to rsp
-
create the space for local variables. When return from the called function, it will:
-
move the result to the register
-
move the rsp let it point to the rbp (let it jump the space of local variables, it do that by sub the space of the variables but not using mov %rbp, %rsp) [these can be done with leave instruction(equal to mov %rbp, %esp; pop %rbp; but when i disassemble the code, it didn’t use leave]
-
pop rbp
-
execute ret instruction (pop the address and jump)
Custom the stack
My plant is to modify the rsp. Normally when people try to do that, they will copy the saved stack to the default place but not modify the rsp.(At least the coded that I have read is this approach.) Because modify the rsp will cause many problem, to fix them need also modify the rbp, recove the return address and parameters and local variables in current function. (It really take me lots time to figure out the problem, but it makes me think more and learn more.)
Modify the rsp, will not casing any problem before return from this function( of course, i think, it’s should affect the local variables, but haven’t approve that ). Yes you can not return, because we lost the rsp information. To solve that need:
__asm__ ("mov 0, %rsp;": :"r"(p)); // set the rsp
__asm__ ("mov 8(%rbp), %rax; mov %rax, (%rsp);"); // save the return address
__asm__ ("push (%rbp);"); // push the current rbp
__asm__ ("mov %rsp, %rbp;"); // move the rsp to the be gaining of the stack
__asm__ ("sub $0x10,%rsp;"); // make the space for the local variables, here is an fix space which just for test, it should be change in really satiation.
In fact we may also need to restore the parameters to let it works perfectly.
Finally, I found there is an easy way to do that. Save the rsp and rbp and then restore them.
__asm__("mov %%rsp, %0; mov %%rbp, %1;" :"=r"(rsp),"=r"(rbp):); // save the rbp and rsp to pointer
__asm__("mov %0, %%rsp;": :"r"(p));
//do something
__asm__("mov %0, %%rsp; mov %1, %%rbp;" : :"r"(rsp),"r"(rbp)); // restore them
References: