setjmp/longjmp原理分析
1、使用
setjmp的man手册上可以看到使用方法:
1 #include <setjmp.h> 2 3 int setjmp(jmp_buf env);
setjmp() saves the stack context/environment in env for later use by longjmp(3)
RETURN VALUE
setjmp() return 0 if returning directly, and non-zero when returning from longjmp(3) using the saved context.
1 #include <setjmp.h> 2 3 void longjmp(jmp_buf env, int val);
longjmp() restores the environment saved by the last call of setjmp(3) with the corresponding env argument.
由此可知:
1、setjmp第一次返回0,第二次返回值是longjmp中的val参数。
2、调用longjmp后,返回到setjmp函数的返回地址处。
举例:
1 #include <stdio.h> 2 #include <setjmp.h> 3 4 jmp_buf buf; 5 int times = 0; 6 7 void second(int* k) 8 { 9 printf("second times = %d\n", ++(*k)); 10 longjmp(buf, 65536); 11 } 12 13 void first(int* k) 14 { 15 printf("first times = %d\n", ++(*k)); 16 second(k); 17 } 18 19 int main(void) 20 { 21 int ret = setjmp(buf); 22 if (ret == 0) 23 { 24 printf("1. ret is %d\n", ret); 25 first(×); 26 } 27 else 28 { 29 printf("2. ret is %d\n", ret); 30 } 31 }
运行结果:
1. ret is 0
first times = 1
second times = 2
2. ret is 65536
二、源码分析
(以https://android.googlesource.com/platform/bionic/+/6719500/libc/arch-x86/bionic/setjmp.S中的setjmp为例分析,其他类似)
jump_buf是个数组:
1 #define _JBLEN 10 /* size, in longs, of a jmp_buf */ 2 typedef long jmp_buf[_JBLEN];
以下是源码:
1 #include <machine/asm.h> 2 /* 3 * C library -- setjmp, longjmp 4 * 5 * longjmp(a,v) 6 * will generate a "return(v)" from the last call to 7 * setjmp(a) 8 * by restoring registers from the stack. 9 * The previous signal state is restored. 10 */ 11 ENTRY(setjmp)
# 12-20行是为了屏蔽信号,可不关注 12 PIC_PROLOGUE 13 pushl $0 # sigblock的参数 14 #ifdef PIC 15 call PIC_PLT(_C_LABEL(sigblock)) 16 #else 17 call _C_LABEL(sigblock) 18 #endif 19 addl $4,%esp # 返回上述 pushl $0时的压栈 20 PIC_EPILOGUE
# 以下是主要代码 21 movl 4(%esp),%ecx # 将env地址 -> ecx 22 movl 0(%esp),%edx # 返回地址 -> edx
# 23-29行 讲edx、ebx...eax的值保存到env中 23 movl %edx, 0(%ecx) 24 movl %ebx, 4(%ecx) 25 movl %esp, 8(%ecx) 26 movl %ebp,12(%ecx) 27 movl %esi,16(%ecx) 28 movl %edi,20(%ecx) 29 movl %eax,24(%ecx) # eax中保存的是sigblock的返回值
# setjmp返回0 30 xorl %eax,%eax 31 ret 32 END(setjmp)
# 到此时,env[0]保存返回地址, env[1]保存ebx的值, ..., env[6]保存eax的值
# 下面是longjmp的代码 33 ENTRY(longjmp) 34 movl 4(%esp),%edx # 将env的地址 -> edx 35 PIC_PROLOGUE 36 pushl 24(%edx) # env[6], 即setjmp中sigblock返回值压栈,当做sigsetmask的参数 37 #ifdef PIC 38 call PIC_PLT(_C_LABEL(sigsetmask)) 39 #else 40 call _C_LABEL(sigsetmask) 41 #endif 42 addl $4,%esp # 回复堆栈 43 PIC_EPILOGUE
# 上面 33-43行, 是为了恢复信号
# 下面是longjmp的主代码 44 movl 4(%esp),%edx # 将env地址 -> edx 45 movl 8(%esp),%eax # val -> eax 46 movl 0(%edx),%ecx # setjmp函数返回地址 -> ecx
# 下面是恢复ebx、esp ... edi等 47 movl 4(%edx),%ebx 48 movl 8(%edx),%esp 49 movl 12(%edx),%ebp 50 movl 16(%edx),%esi 51 movl 20(%edx),%edi
# 下面三行确保eax(即val, longjmp返回到setjmp时的返回值)不为零
# 如果eax为零则加一,否则保持原值 52 testl %eax,%eax 53 jnz 1f 54 incl %eax
# 这一步非常重要, 将ecx(setjmp的返回地址) -> longjmp的返回地址
# 这样,ret返回后,就跳转到了setjmp的返回地址 55 1: movl %ecx,0(%esp) 56 ret 57 END(longjmp)