longjmp如何运作?

时间:2011-05-26 21:14:51

标签: c linux security assembly

我需要了解longjmp函数的工作原理;我知道它的作用,但我需要知道它是如何做到的。

我试图取消gdb中的代码,但我无法理解一些步骤。代码是:

0xb7ead420 <siglongjmp+0>:      push   %ebp
0xb7ead421 <siglongjmp+1>:      mov    %esp,%ebp
0xb7ead423 <siglongjmp+3>:      sub    $0x18,%esp
0xb7ead426 <siglongjmp+6>:      mov    %ebx,-0xc(%ebp)
0xb7ead429 <siglongjmp+9>:      call   0xb7e9828f <_Unwind_Find_FDE@plt+119>
0xb7ead42e <siglongjmp+14>:     add    $0x12bbc6,%ebx
0xb7ead434 <siglongjmp+20>:     mov    %esi,-0x8(%ebp)
0xb7ead437 <siglongjmp+23>:     mov    0xc(%ebp),%esi
0xb7ead43a <siglongjmp+26>:     mov    %edi,-0x4(%ebp)
0xb7ead43d <siglongjmp+29>:     mov    0x8(%ebp),%edi
0xb7ead440 <siglongjmp+32>:     mov    %esi,0x4(%esp)
0xb7ead444 <siglongjmp+36>:     mov    %edi,(%esp)
0xb7ead447 <siglongjmp+39>:     call   0xb7ead4d0
0xb7ead44c <siglongjmp+44>:     mov    0x18(%edi),%eax
0xb7ead44f <siglongjmp+47>:     test   %eax,%eax
0xb7ead451 <siglongjmp+49>:     jne    0xb7ead470 <siglongjmp+80>
0xb7ead453 <siglongjmp+51>:     test   %esi,%esi
0xb7ead455 <siglongjmp+53>:     mov    $0x1,%eax
0xb7ead45a <siglongjmp+58>:     cmove  %eax,%esi
0xb7ead45d <siglongjmp+61>:     mov    %esi,0x4(%esp)
0xb7ead461 <siglongjmp+65>:     mov    %edi,(%esp)
0xb7ead464 <siglongjmp+68>:     call   0xb7ead490
0xb7ead469 <siglongjmp+73>:     lea    0x0(%esi,%eiz,1),%esi
0xb7ead470 <siglongjmp+80>:     lea    0x1c(%edi),%eax
0xb7ead473 <siglongjmp+83>:     movl   $0x0,0x8(%esp)
0xb7ead47b <siglongjmp+91>:     mov    %eax,0x4(%esp)
0xb7ead47f <siglongjmp+95>:     movl   $0x2,(%esp)
0xb7ead486 <siglongjmp+102>:    call   0xb7ead890 <sigprocmask>
0xb7ead48b <siglongjmp+107>:    jmp    0xb7ead453 <siglongjmp+51>

有人可以简单地向我解释一下代码,或者说明我可以在系统中找到源代码的位置吗?

6 个答案:

答案 0 :(得分:5)

大多数情况下,它会恢复寄存器和堆栈,就像它们在相应的setjmp()时一样。需要一些额外的清理(修复信号处理和展开待处理的堆栈处理程序),以及返回一个不同的值作为 setjmp 的表观返回值,但恢复状态是操作的本质。

要使其工作,堆栈不能低于调用setjmp的点。 Longjmp是一种野蛮的方法,只是将调用堆栈(或函数调用嵌套序列)中调用的所有内容调用到同一级别,通常只需将堆栈指针设置为调用setjmp时的相同帧即可。 / p>

为了使它干净地工作,longjmp()调用所有的中间函数的退出处理程序,因此它们可以删除变量,以及在函数返回时通常进行的任何其他清理。将堆栈重置为深度较低的点将释放所有auto个变量,但如果其中一个变量为FILE *,则需要关闭该文件并释放i / o缓冲区。

答案 1 :(得分:5)

以下是标准i386 ABI中longjmp的i386代码,没有与C ++交互,异常,清理函数,信号掩码等的任何疯狂扩展:

    mov 4(%esp),%edx
    mov 8(%esp),%eax
    test %eax,%eax
    jnz 1f
    inc %eax
1:
    mov (%edx),%ebx
    mov 4(%edx),%esi
    mov 8(%edx),%edi
    mov 12(%edx),%ebp
    mov 16(%edx),%ecx
    mov %ecx,%esp
    mov 20(%edx),%ecx
    jmp *%ecx

答案 2 :(得分:2)

我认为您需要看到Procedure Activation RecordsCall Stacks以及Setjmp.hjmp_buf结构。

引自Expert C编程:Deep C Secrets:

  

Setjmp将程序计数器的副本和当前指针保存到堆栈顶部。如果您愿意,这可以节省一些初始值。然后longjmp恢复这些值,有效地转移控制并将状态重置回您执行保存时的位置。它被称为“展开堆栈”,因为您从堆栈中取消激活记录,直到您找到保存的记录。

Have a look at page 153 also here.

堆栈框架将高度依赖于机器和可执行文件,但想法是一样的。

答案 3 :(得分:1)

在Windows X64 MASM中

.code

my_jmp_buf STRUCT

        _Frame QWORD ?;
        _Rbx QWORD ?;
        _Rsp QWORD ?;
        _Rbp QWORD ?;
        _Rsi QWORD ?;
        _Rdi QWORD ?;
        _R12 QWORD ?;
        _R13 QWORD ?;
        _R14 QWORD ?;
        _R15 QWORD ?;
        _Rip QWORD ?;
        _MxCsr DWORD ?;
        _FpCsr WORD ?;
        _Spare WORD ?;
        _Xmm6 XMMWORD ?;
        _Xmm7 XMMWORD ?;
        _Xmm8 XMMWORD ?;
        _Xmm9 XMMWORD ?;
        _Xmm10 XMMWORD ?;
        _Xmm11 XMMWORD ?;
        _Xmm12 XMMWORD ?;
        _Xmm13 XMMWORD ?;
        _Xmm14 XMMWORD ?;
        _Xmm15 XMMWORD ?;

my_jmp_buf ENDS


;extern "C" int my_setjmp(jmp_buf env);
public my_setjmp

my_setjmp PROC

    mov rax, [rsp] ;save ip 
    mov (my_jmp_buf ptr[rcx])._Rip, rax

    lea rax, [rsp + 8] ;save sp before call this function
    mov (my_jmp_buf ptr[rcx])._Rsp, rax
    mov (my_jmp_buf ptr[rcx])._Frame, rax

    ;save gprs
    mov (my_jmp_buf ptr[rcx])._Rbx,rbx  
    mov (my_jmp_buf ptr[rcx])._Rbp,rbp  
    mov (my_jmp_buf ptr[rcx])._Rsi,rsi  
    mov (my_jmp_buf ptr[rcx])._Rdi,rdi  
    mov (my_jmp_buf ptr[rcx])._R12,r12  
    mov (my_jmp_buf ptr[rcx])._R13,r13  
    mov (my_jmp_buf ptr[rcx])._R14,r14  
    mov (my_jmp_buf ptr[rcx])._R15,r15  

    ;save fp and xmm
    stmxcsr     (my_jmp_buf ptr[rcx])._MxCsr
    fnstcw      (my_jmp_buf ptr[rcx])._FpCsr
    movdqa      (my_jmp_buf ptr[rcx])._Xmm6,xmm6  
    movdqa      (my_jmp_buf ptr[rcx])._Xmm7,xmm7  
    movdqa      (my_jmp_buf ptr[rcx])._Xmm8,xmm8  
    movdqa      (my_jmp_buf ptr[rcx])._Xmm9,xmm9  
    movdqa      (my_jmp_buf ptr[rcx])._Xmm10,xmm10
    movdqa      (my_jmp_buf ptr[rcx])._Xmm11,xmm11
    movdqa      (my_jmp_buf ptr[rcx])._Xmm12,xmm12
    movdqa      (my_jmp_buf ptr[rcx])._Xmm13,xmm13
    movdqa      (my_jmp_buf ptr[rcx])._Xmm14,xmm14
    movdqa      (my_jmp_buf ptr[rcx])._Xmm15,xmm15

    xor         rax,rax  
    ret  

my_setjmp ENDP


;extern "C" void my_longjmp(jmp_buf env,  int value);

public my_longjmp

my_longjmp PROC

    ;restore fp and xmm
    movdqa      xmm15,(my_jmp_buf ptr[rcx])._Xmm15
    movdqa      xmm14,(my_jmp_buf ptr[rcx])._Xmm14
    movdqa      xmm13,(my_jmp_buf ptr[rcx])._Xmm13
    movdqa      xmm12,(my_jmp_buf ptr[rcx])._Xmm12
    movdqa      xmm11,(my_jmp_buf ptr[rcx])._Xmm11
    movdqa      xmm10,(my_jmp_buf ptr[rcx])._Xmm10
    movdqa      xmm9,(my_jmp_buf ptr[rcx])._Xmm9
    movdqa      xmm8,(my_jmp_buf ptr[rcx])._Xmm8
    movdqa      xmm7,(my_jmp_buf ptr[rcx])._Xmm7
    movdqa      xmm6,(my_jmp_buf ptr[rcx])._Xmm6

    fldcw      (my_jmp_buf ptr[rcx])._FpCsr
    ldmxcsr    (my_jmp_buf ptr[rcx])._MxCsr


    ;restore gprs
    mov r15, (my_jmp_buf ptr[rcx])._R15 
    mov r14, (my_jmp_buf ptr[rcx])._R14
    mov r13, (my_jmp_buf ptr[rcx])._R13  
    mov r12, (my_jmp_buf ptr[rcx])._R12  
    mov rdi, (my_jmp_buf ptr[rcx])._Rdi  
    mov rsi, (my_jmp_buf ptr[rcx])._Rsi  
    mov rbp, (my_jmp_buf ptr[rcx])._Rbp  
    mov rbx, (my_jmp_buf ptr[rcx])._Rbx



    ;retore sp
    mov rsp, (my_jmp_buf ptr[rcx])._Rsp

    ;restore ip
    mov rcx, (my_jmp_buf ptr[rcx])._Rip; must be the last instruction as rcx modified

    ;return value
    mov rax, rdx 

   jmp rcx
my_longjmp ENDP

END

答案 4 :(得分:0)

传递setjmp()一个缓冲区参数。然后它将当前寄存器信息等存储到该缓冲区中。然后调用longjmp()然后从缓冲区恢复这些值。此外,wallyk说了什么。

答案 5 :(得分:0)

这里是我为一个小的clib子集(在Visual Studio 2008中编写和测试)编写的setmp和longjmp版本。汇编代码存储在单独的.asm文件中。

.586
.MODEL FLAT, C  ; Flat memory model, C calling conventions.
;.STACK         ; Not required for this example.
;.DATA          ; Not required for this example.
.code


; Simple version of setjmp (x86-32 bit).
;
; Saves ebp, ebx, edi, esi, esp and eip in that order.
; 
setjmp_t proc
    push ebp
    mov ebp, esp
    push edi

    mov edi, [ebp+8]    ; Pointer to jmpbuf struct.

    mov eax, [ebp]      ; Save ebp, note we are saving the stored version on the stack.
    mov [edi], eax

    mov [edi+4], ebx    ; Save ebx

    mov eax, [ebp-4]
    mov [edi+8], eax    ; Save edi, note we are saving the stored verion on the stack.

    mov [edi+12], esi   ; Save esi 

    mov eax, ebp;
    add eax, 8
    mov [edi+16], eax   ; Save sp, note saving sp pointing to last item on stack just before call to setjmp.

    mov eax, [ebp+4]
    mov [edi+20], eax   ; Save return address (will be used as jump address in longjmp().

    xor eax, eax        ; return 0;

    pop edi
    pop ebp
    ret
setjmp_t endp


; Simple version of longjmp (x86-32 bit).
;
; Restores ebp, ebx, edi, esi, esp and eip.
; 
longjmp_t proc
    mov edi, [esp+4]    ; Pointer to jmpbuf struct.
    mov eax, [esp+8]    ; Get return value (value passed to longjmp).

    mov ebp, [edi]      ; Restore ebp.
    mov ebx, [edi+4]    ; Restore ebx.
    mov esi, [edi+12]   ; Restore esi.
    mov esp, [edi+16]   ; Restore stack pointer.
    mov ecx, [edi+20]   ; Original return address to setjmp. 

    mov edi, [edi+8]    ; Restore edi, note, done last as we were using edi up to this point.
    jmp ecx             ; Wing and a prayer...
longjmp_t endp

end

一个C代码片段对其进行测试:

extern "C" int setjmp_t( int *p);
extern "C" int longjmp_t( int *p, int n);
jmp_buf test2_buff;

void DoTest2()
{
    int x;

    x = setjmp_t( test2_buff);
    printf( "setjmp_t return - %d\n", x);

    switch (x)
    {
        case 0:
            printf( "About to do long jump...\n");
            longjmp_t( test2_buff, 99);
            break;
        default:
            printf( "Here becauuse of long jump...\n");
            break;
    }

    printf( "Test2 passed!\n");
}

请注意,我使用了'setjmp.h'中的声明作为缓冲区,但是如果您愿意,可以使用一个整数数组(最少6个整数)。