setjmp与longjmp的用法请参考文章:http://blog.csdn.net/chenyiming_1990/article/details/8683413
函数的原语
Int setjmp(jmp_buf env)
将本函数的“调用上下文"保存到参数env中,同时该函数返回0。此函数的作用可以理解为“将当前位置(调用setjmp()函数的位置)设置成跳转目标”;
void longjmp(jmp_buf env,int val)
此函数实现跳转,跳转至setjmp()函数设置的“跳转目标”,其中参数env 就是setjmp()函数设置的 env。此函数从setjmp()函数中返回,参数val作为从setjmp()函数返回时的返回值。
一个函数可以“返回”两次,另一个函数则可以“借用”第一个函数的“返回地址”进行返回,这显然不是C语言的语法能办到的。关键时刻还得看汇编大显身手。这两个函数的实现与平台相关,android的libc库中,x86平台的实现在 \bionic\libc\arch-x86\bionic\setjmp.S文件中。
回顾下X86函数调用的规则:
1、函数参数通过栈传递,参数入栈顺序为从右至左依次入栈;返回值放到eax寄存器中(当返回64位的返回值时为eax和edx)。
2、子函数通过call指令进行调用,call指令会自动将函数的返回地址(即call指令的下一条指令的地址)入栈;函数运行过程中,会对栈进行扩展以容纳局部变量;再函数即将返回时,会回收栈,使栈恢复到刚进入函数时的状态,此时esp位置处存放的是函数的返回地址,调用ret指令即可返回到返回地址处。刚进入函数、函数运行中以及即将返回时栈的分布如下图(注意堆栈是向下扩展的):
可以这样讲:当执行ret指令时,esp中存放是什么地址,就返回到什么地址处。这就是longjmp的基本思路。
下面开始正式的代码分析
jmp_buf的定义在/bionic/libc/include/setjmp.h中(因为手上只有安卓的源码,所以就拿它分析了),jmp_buf其实是个数组:
#define _JBLEN 10 /* size, in longs, of a jmp_buf */ typedef long jmp_buf[_JBLEN];
setjmp.S中的汇编是AT&T格式的(linux中的汇编代码格式),这种格式与8086的汇编格式有些不同,为了便于理解,我们用IDA打开最终的libc库来查看函数的实现,IDA中是按8086格式显示的:
; =============== S U B R O U T I N E======================================= ;int setjmp(jmp_buf); public setjmp setjmp proc near arg_0 = dword ptr 4 push 0 ;准备sigblock函数的参数 call sigblock add esp, 4 ;恢复栈(恢复上面push引起的堆栈扩展) mov ecx, [esp+arg_0] ;入参到ecx,即jmp_buf mov edx, [esp+0] ;[esp + 0]存放的是setjmp的返回地址,赋给edx ;jmp_buf其实是long型数组,_JBLEN=10,一共10个元素,下面给该数组中元素赋值 mov [ecx], edx ;将setjmp的返回地址赋给第一元素 mov [ecx+4], ebx ;第二个元素 mov [ecx+8], esp mov [ecx+0Ch], ebp mov [ecx+10h], esi mov [ecx+14h], edi mov [ecx+18h], eax ;eax存放返回值,异或操作值为0,可见setjmp第一次返回时返回0 xor eax, eax retn setjmp endp ; =============== S U B R O U T I N E======================================= ; void longjmp(jmp_buf jmpbuf, int val); ; Attributes: noreturn public longjmp longjmp proc near arg_0 = dword ptr 4 arg_4 = dword ptr 8 mov edx, [esp+arg_0] ;第一个参数jmpbuf到edx push dword ptr [edx+18h] ;准备sigsetmask的参数 call sigsetmask ;调用sigsetmask add esp, 4 ;恢复堆栈(恢复上面push引起的堆栈扩展) mov edx, [esp+arg_0] ;取入参jmp_buf mov eax, [esp+arg_4] ;取入参val ;参考setjmp的代码,jmpbuf中的第一个元素为setjmp的返回地址,这里取出到ecx mov ecx, [edx] mov ebx, [edx+4] mov esp, [edx+8] mov ebp, [edx+0Ch] mov esi, [edx+10h] mov edi, [edx+14h] test eax, eax ;测试val是否为0 jnz short loc_1963D ;不为0则跳转 ;为0,则val + 1,可见从longjmp返回到setjmp,val一定为非0值 inc eax loc_1963D: ;[esp+0]指向函数的返回地址,这里修改函数的返回地址为setjmp的返回地址, ;执行retn指令时将从setjmp中返回 mov [esp+0], ecx retn longjmp endp
总结:简单来说,在setjmp()函数中,将setjmp()函数的返回地址以及其他一些寄存器存到jmp_buf中;而在longjmp函数中,将jmp_buf中保存的"setjmp()函数的返回地址"作为longjmp()函数自己的返回地址。于是执行ret指令之后,longjmp()函数返回到“调用setjmp()函数”处(准确来说是“调用setjmp()函数”的下一条指令的地址),并通过eax传回返回值。