问题
basically my problem in short is my implementation for setjmp and longjmp doesn't work. the reason why i'm asking in this form not in (code review) is that i'm new to assembly i've little background and still learning but still not sure about the code (please read until the end).
first i'v executed the code on two platforms with three different compilers and that's the reason why i'm sure that i'm doing something wrong with the assembler.
platforms: mac OS 10.12.5 x86_64 , ubuntu linux x86 compilers: Apple LLVM clang 8.0.0 x86_x64 ,clang 3.9.1 x86_x64 ,gcc 6.3 x86
i've compiled the code in 32bit mode on all platforms so the generated machine code on linux and mac with this example is 32bit.
the code i will post here is the was compiled under Apple clang with no optimization using -m32 flag to generate 32bit machine code
#include <cstdio>
typedef unsigned long jmp_buf[6];
int Setjmp(jmp_buf var){
__asm__(
" mov -4(%ebp), %eax # get pointer to jmp_buf, passed as argument on stack\n"
" mov %ebx, (%eax) # jmp_buf[0] = ebx\n"
" mov %esi, 4(%eax) # jmp_buf[1] = esi\n"
" mov %edi, 8(%eax) # jmp_buf[2] = edi\n"
" mov %ebp, 12(%eax) # jmp_buf[3] = ebp\n"
" lea 4(%esp), %ecx # get previous value of esp, before call\n"
" mov %ecx, 16(%eax) # jmp_buf[4] = esp before call\n"
" mov (%esp), %ecx # get saved caller eip from top of stack\n"
" mov %ecx, 20(%eax) #jmp_buf[5] = saved eip\n"
" xor %eax, %eax #eax = 0\n"
);
return 0;
}
void Longjmp(jmp_buf var,int m){
__asm__(" mov -4(%ebp),%edx # get pointer to jmp_buf, passed as argument 1 on stack\n"
" mov -8(%ebp),%eax #get int val in eax, passed as argument 2 on stack\n"
" test %eax,%eax # is int val == 0?\n"
" jnz 1f\n"
" inc %eax # if so, eax++\n"
"1:\n"
" mov (%edx),%ebx # ebx = jmp_buf[0]\n"
" mov 4(%edx),%esi # esi = jmp_buf[1]\n"
" mov 8(%edx),%edi #edi = jmp_buf[2]\n"
" mov 12(%edx),%ebp # ebp = jmp_buf[3]\n"
" mov 16(%edx),%ecx # ecx = jmp_buf[4]\n"
" mov %ecx,%esp # esp = ecx\n"
" mov 20(%edx),%ecx # ecx = jmp_buf[5]\n"
" jmp *%ecx # eip = ecx");
}
void fancy_func(jmp_buf env);
int main() {
jmp_buf env;
int ret = Setjmp(env);
if (ret == 0) {
puts("just returning from setjmp!");
fancy_func(env);
} else {
puts("now returning from longjmp and exiting!");
}
}
void fancy_func(jmp_buf env) {
puts("doing fancy stuff");
Longjmp(env, 1);
}
i was following this tutorial : http://vmresu.me/blog/2016/02/09/lets-understand-setjmp-slash-longjmp/
Note: I've debugged the source code the problem comes from the:
jmp *%ecx
but i think the problem is with setjmp and the way i'm storing the context and specially that line:
lea 4(%esp), %ecx # get previous value of esp, before call\n"
which is also the part of the code that i'm not getting it.
i'm also aware of the code that was generated by my compiler for calling and cleaning the stack of setjmp and longjmp and the calling convention that was used (CDECL) in my case.
much thanks for any help .
回答1:
Many problems with this. As fuz said, you should not use inline assembly like this. Use separate asm file, or at least constraints and better not rely on a particular stack layout.
Anyway you got the offsets wrong, arguments are at positive offset from ebp
not negative, with first being 8(%ebp)
. Also you got the return address wrong, it's at 4(%esp)
since (%esp)
is the saved ebp
. Furthermore, since the function prologue saved ebp
you are not saving the caller's ebp
but a copy of esp
.
The fixed version (still only works in 32-bit mode for calling conventions with stack args):
See the resulting asm for the whole function on the Godbolt compiler explorer
// optimize("no-omit-frame-pointer") doesn't seem to work
// we still don't get a frame-point unless we force -O0 for the function with optimize(0)
__attribute__((noinline, noclone, returns_twice, optimize(0)))
int Setjmp(jmp_buf var){
// relies on the compiler to make a stack-frame
// because we're using inline asm inside a function instead of at global scope
__asm__(
" mov 8(%ebp), %eax # get pointer to jmp_buf, passed as argument on stack\n"
" mov %ebx, (%eax) # jmp_buf[0] = ebx\n"
" mov %esi, 4(%eax) # jmp_buf[1] = esi\n"
" mov %edi, 8(%eax) # jmp_buf[2] = edi\n"
" mov (%ebp), %ecx\n"
" mov %ecx, 12(%eax) # jmp_buf[3] = ebp\n"
" lea 8(%ebp), %ecx # get previous value of esp, before call\n"
" mov %ecx, 16(%eax) # jmp_buf[4] = esp before call\n"
" mov 4(%ebp), %ecx # get saved caller eip from top of stack\n"
" mov %ecx, 20(%eax) #jmp_buf[5] = saved eip\n"
" xor %eax, %eax #eax = 0\n"
);
return 0;
}
__attribute__((noinline, noclone, optimize(0)))
void Longjmp(jmp_buf var,int m){
__asm__(" mov 8(%ebp),%edx # get pointer to jmp_buf, passed as argument 1 on stack\n"
" mov 12(%ebp),%eax #get int val in eax, passed as argument 2 on stack\n"
" test %eax,%eax # is int val == 0?\n"
" jnz 1f\n"
" inc %eax # if so, eax++\n"
"1:\n"
" mov (%edx),%ebx # ebx = jmp_buf[0]\n"
" mov 4(%edx),%esi # esi = jmp_buf[1]\n"
" mov 8(%edx),%edi #edi = jmp_buf[2]\n"
" mov 12(%edx),%ebp # ebp = jmp_buf[3]\n"
" mov 16(%edx),%ecx # ecx = jmp_buf[4]\n"
" mov %ecx,%esp # esp = ecx\n"
" mov 20(%edx),%ecx # ecx = jmp_buf[5]\n"
" jmp *%ecx # eip = ecx");
}
If you used an asm
statement at global scope, you wouldn't need to fight against the compiler with __attribute__
stuff to make sure it emits the prologue you expect. You could also skip setting up EBP so you'd have the caller's EBP directly.
asm(".globl SetJmp \n"
"SetJmp: \n\t"
" push %ebp \n\t"
" mov %esp, %ebp \n\t"
"... your current implementation \n\t"
" xor %eax,%eax \n\t"
" pop %ebp \n\t"
" ret \n\t"
);
来源:https://stackoverflow.com/questions/46094444/setjmp-and-longjmp-implementation