setjmp 和 longjmp 实现
setjmp and longjmp implementation
简而言之,基本上我的问题是我对 setjmp 和 longjmp 的实现
不起作用。我之所以不在(代码审查)中以这种形式提问,是因为我是汇编新手,我的背景知识很少,仍在学习,但仍然不确定代码(请阅读到最后)。
首先我用三个不同的编译器在两个平台上执行了代码
这就是为什么我确定我在汇编程序上做错了什么的原因。
平台:mac OS 10.12.5 x86_64 , ubuntu linux x86
编译器:Apple LLVM clang 8.0.0 x86_x64,clang 3.9.1 x86_x64,gcc 6.3 x86
我已经在所有平台上以 32 位模式编译了代码,因此在本示例中 linux 和 mac 上生成的 machine 代码是 32 位的。
我将 post 的代码是在 Apple clang 下编译的,没有使用 -m32 标志进行优化以生成 32 位 machine 代码
#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);
}
我正在学习本教程:http://vmresu.me/blog/2016/02/09/lets-understand-setjmp-slash-longjmp/
注意:我已经调试了源码问题出在:
jmp *%ecx
但我认为问题在于 setjmp 以及我存储上下文的方式
特别是那一行:
lea 4(%esp), %ecx # get previous value of esp, before call\n"
这也是我没有理解的代码部分。
我也知道我的编译器生成的用于调用和清理 setjmp 和 longjmp 堆栈的代码以及在我的案例中使用的调用约定 (CDECL)。
非常感谢您的帮助。
很多问题。正如 fuz 所说,你不应该像这样使用内联汇编。使用单独的 asm 文件,或者至少使用约束,最好不要依赖特定的堆栈布局。
无论如何,你的偏移量是错误的,参数是 正 偏移 ebp
而不是负数,第一个是 8(%ebp)
。另外你把 return 地址弄错了,它在 4(%esp)
因为 (%esp)
是保存的 ebp
。此外,由于函数序言保存了 ebp
,因此您保存的不是调用者的 ebp
,而是 esp
.
的副本
固定版本(仍然只在 32 位模式下使用堆栈参数调用约定):
查看整个函数的结果汇编 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");
}
如果您在全局范围内使用 asm
语句,则无需使用 __attribute__
东西来对抗编译器以确保它发出您期望的序言。您也可以跳过设置 EBP,这样您就可以直接获得呼叫者的 EBP。
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"
);
简而言之,基本上我的问题是我对 setjmp 和 longjmp 的实现 不起作用。我之所以不在(代码审查)中以这种形式提问,是因为我是汇编新手,我的背景知识很少,仍在学习,但仍然不确定代码(请阅读到最后)。
首先我用三个不同的编译器在两个平台上执行了代码 这就是为什么我确定我在汇编程序上做错了什么的原因。
平台:mac OS 10.12.5 x86_64 , ubuntu linux x86 编译器:Apple LLVM clang 8.0.0 x86_x64,clang 3.9.1 x86_x64,gcc 6.3 x86
我已经在所有平台上以 32 位模式编译了代码,因此在本示例中 linux 和 mac 上生成的 machine 代码是 32 位的。
我将 post 的代码是在 Apple clang 下编译的,没有使用 -m32 标志进行优化以生成 32 位 machine 代码
#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);
}
我正在学习本教程:http://vmresu.me/blog/2016/02/09/lets-understand-setjmp-slash-longjmp/
注意:我已经调试了源码问题出在:
jmp *%ecx
但我认为问题在于 setjmp 以及我存储上下文的方式 特别是那一行:
lea 4(%esp), %ecx # get previous value of esp, before call\n"
这也是我没有理解的代码部分。
我也知道我的编译器生成的用于调用和清理 setjmp 和 longjmp 堆栈的代码以及在我的案例中使用的调用约定 (CDECL)。
非常感谢您的帮助。
很多问题。正如 fuz 所说,你不应该像这样使用内联汇编。使用单独的 asm 文件,或者至少使用约束,最好不要依赖特定的堆栈布局。
无论如何,你的偏移量是错误的,参数是 正 偏移 ebp
而不是负数,第一个是 8(%ebp)
。另外你把 return 地址弄错了,它在 4(%esp)
因为 (%esp)
是保存的 ebp
。此外,由于函数序言保存了 ebp
,因此您保存的不是调用者的 ebp
,而是 esp
.
固定版本(仍然只在 32 位模式下使用堆栈参数调用约定):
查看整个函数的结果汇编 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");
}
如果您在全局范围内使用 asm
语句,则无需使用 __attribute__
东西来对抗编译器以确保它发出您期望的序言。您也可以跳过设置 EBP,这样您就可以直接获得呼叫者的 EBP。
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"
);