C++学习------csetjmp头文件的源码学习

407 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情

引言

csetjmp是C++对setjmp.h头文件的封装,通过这个头文件提供的工具允许程序员通过提供执行跳转的方法来绕过正常的函数调用和返回规程,从而保留调用环境。其中定义了一些函数和宏定义来完成这项工作。

setjmp.h

代码参考:www.aospxref.com/android-13.…

变量定义sigjmp_buf和jmp_buf

根据不同平台定义了不同的数组别名sigjmp_buf和jmp_buf,其中保存的元素都是long,sigjmp_buf中的元素多1个。

45  #if defined(__aarch64__)
46  #define _JBLEN 32
47  #elif defined(__arm__)
48  #define _JBLEN 64
49  #elif defined(__i386__)
50  #define _JBLEN 10
51  #elif defined(__x86_64__)
52  #define _JBLEN 11
53  #endif
54  
55  typedef long sigjmp_buf[_JBLEN + 1];
56  typedef long jmp_buf[_JBLEN];

宏函数定义setjmp

68  #define setjmp(__env) setjmp(__env)

这里实际上调用的还是函数实现的setjmp

函数定义

定义了setjmp和longjmp函数,sigsetjmp和siglongjmp函数

62  int _setjmp(jmp_buf __env) __returns_twice;
63  __noreturn void _longjmp(jmp_buf __env, int __value);
64  
65  int setjmp(jmp_buf __env) __returns_twice;
66  __noreturn void longjmp(jmp_buf __env, int __value);
67  
68  #define setjmp(__env) setjmp(__env)
69  
70  int sigsetjmp(sigjmp_buf __env, int __save_signal_mask);
71  __noreturn void siglongjmp(sigjmp_buf __env, int __value);

setjmp函数---保存函数调用环境

代码参考:www.aospxref.com/android-13.… 可以看到setjmp函数入参为jmp_buf数组首地址,观察汇编代码,该入参存储在rdi中,依次将rbx,rbp,r12,r13,r14,r15,rsp+8,(%rsp)位置上的值都保存到该数组中,即保存函数调用环境。 setjmp第一次返回0,第二次(即被longjmp加载函数调用环境之后)返回值由longjmp的第二个输入参数决定。

1 /* Copyright 2011-2012 Nicholas J. Kain, licensed under standard MIT license */
2 .global __setjmp
3 .global _setjmp
4 .global setjmp
5 .type __setjmp,@function
6 .type _setjmp,@function
7 .type setjmp,@function
8 __setjmp:
9 _setjmp:
10 setjmp:
11 	mov %rbx,(%rdi)         /* rdi is jmp_buf, move registers onto it */
12 	mov %rbp,8(%rdi)
13 	mov %r12,16(%rdi)
14 	mov %r13,24(%rdi)
15 	mov %r14,32(%rdi)
16 	mov %r15,40(%rdi)
17 	lea 8(%rsp),%rdx        /* this is our rsp WITHOUT current ret addr */
18 	mov %rdx,48(%rdi)
19 	mov (%rsp),%rdx         /* save return addr ptr for new rip */
20 	mov %rdx,56(%rdi)
21 	xor %eax,%eax           /* always return 0 */
22 	ret

longjump---加载函数调用环境

代码参考:www.aospxref.com/android-13.… 可以看到,依次还原输入的jmp_buf,然后跳转到之前保存的rsp地址中;注意,这里还对输入参数int __value做了处理,保存在esi中,如果不等于0,则eax=__value,如果等于0则eax = 1.

1 /* Copyright 2011-2012 Nicholas J. Kain, licensed under standard MIT license */
2 .global _longjmp
3 .global longjmp
4 .type _longjmp,@function
5 .type longjmp,@function
6 _longjmp:
7 longjmp:
8 	xor %eax,%eax
9 	cmp $1,%esi             /* CF = val ? 0 : 1 */
10 	adc %esi,%eax           /* eax = val + !val */
11 	mov (%rdi),%rbx         /* rdi is the jmp_buf, restore regs from it */
12 	mov 8(%rdi),%rbp
13 	mov 16(%rdi),%r12
14 	mov 24(%rdi),%r13
15 	mov 32(%rdi),%r14
16 	mov 40(%rdi),%r15
17 	mov 48(%rdi),%rsp
18 	jmp *56(%rdi)           /* goto saved address without altering rsp */

有点类似于goto语句,又重新跳回某处执行,不过这里是将之前某处的指令执行现场保存下来,然后再加载执行。

sigsetjmp

代码参考:www.aospxref.com/android-13.… 入参是:int sigsetjmp(sigjmp_buf __env, int __save_signal_mask)分别存储在rdi和esi中 通过__save_signal_mask判断,如果等于0,就跳转到标号1的位置;否则就保存信息到sigjmp_buf数组中

1 .global sigsetjmp
2 .global __sigsetjmp
3 .type sigsetjmp,@function
4 .type __sigsetjmp,@function
5 sigsetjmp:
6 __sigsetjmp:
7 	test %esi,%esi
8 	jz 1f
9 
10 	popq 64(%rdi)
11 	mov %rbx,72+8(%rdi)
12 	mov %rdi,%rbx
13 
14 	call setjmp@PLT
15 
16 	pushq 64(%rbx)
17 	mov %rbx,%rdi
18 	mov %eax,%esi
19 	mov 72+8(%rbx),%rbx
20 
21 .hidden __sigsetjmp_tail
22 	jmp __sigsetjmp_tail
23 
24 1:	jmp setjmp@PLT

siglongjmp

代码参考:www.aospxref.com/android-13.… 借助longjmp进行实现

1  #include <setjmp.h>
2  #include <signal.h>
3  #include "syscall.h"
4  #include "pthread_impl.h"
5  
6  _Noreturn void siglongjmp(sigjmp_buf buf, int ret)
7  {
8  	longjmp(buf, ret);
9  }