setjmp与longjmp原理分析

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指令即可返回到返回地址处。刚进入函数、函数运行中以及即将返回时栈的分布如下图(注意堆栈是向下扩展的):

setjmp与longjmp原理分析_第1张图片

可以这样讲:当执行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传回返回值。

 



你可能感兴趣的:(逆向工程)