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
", ++(*k));
10     longjmp(buf, 65536);
11 }
12 
13 void first(int* k)
14 {
15     printf("first times = %d
", ++(*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
", ret);
25         first(&times);
26     }
27     else
28     {
29         printf("2. ret is %d
", 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)
原文地址:https://www.cnblogs.com/ym65536/p/4984339.html