7

I need to understand HOW longjmp function works; I know what it does, but I need to know how it does it.

I tried to disas the code in gdb but I can't understand some steps. The code is:

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>

Can someone briefly explain me the code, or indicate where I can find the source code in the system?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Aslan986
  • 9,984
  • 11
  • 44
  • 75
  • 2
    You should look at the source to `longjmp`, not `siglongjmp`. The latter is probably compiled C code to restore the signal mask, followed by a call or jump to the actual `longjmp` asm. – R.. GitHub STOP HELPING ICE May 26 '11 at 21:49

6 Answers6

6

Here is the i386 code for longjmp, in the standard i386 ABI, without any crazy extensions for interaction with C++, exceptions, cleanup functions, signal mask, etc.:

    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
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • @R: Which i386 code? That resembles the implementation used in Unix V6 which had all kinds of problems, like not cleaning up. The i386 code for gcc is much better behaved. See the source at http://glibc.sourcearchive.com/documentation/2.7-18lenny7/setjmp_2longjmp_8c_source.html – wallyk May 26 '11 at 22:03
  • 8
    That's not the code for gcc, but for glibc. And there is no "cleanup" to be done in C. The `longjmp` function is specified as a pure jump. If you jump over anything that would require cleanup, then at best your program has a resource leak and at worst it invokes undefined behavior. This is the C language. If you don't like it, don't use `longjmp`. Don't insist that `longjmp` be changed to meet your requirements based on another language. – R.. GitHub STOP HELPING ICE May 26 '11 at 22:28
4

Mostly, it restores the registers and stack as they were at the time of the corresponding setjmp(). There is some additional cleanup required (fixing signal handling and unwinding pending stack handlers), as well as returning a different value as the apparent return value of setjmp, but restoring the state is the essence of the operation.

For it to work, the stack cannot be below the point at which setjmp was called. Longjmp is a brutish way to just forget everything that has been called below it down to the same level in the call stack (or function call nesting sequence) mostly by simply setting the stack pointer to the same frame it was when setjmp was called.

For it to work cleanly, longjmp() calls all the exit handlers for intermediate functions, so they can delete variables, and whatever other cleanup is normally done when a function returns. Resetting the stack to a point less deep releases all the auto variables but if one of those is a FILE *, the file needs to be closed and the i/o buffer freed too.

wallyk
  • 56,922
  • 16
  • 83
  • 148
  • Thank for your answer.. That is what it does..i need to understand in the detail HOW it does...i mean, it does not simply copies the saved register in the actual register...but did it in a different way.. – Aslan986 May 26 '11 at 21:24
  • @Asian986: Hopefully my amended answer addresses it. – wallyk May 26 '11 at 21:29
  • 6
    The C `longjmp` function does not call "exit handlers for intermediate functions", which do not exist in the C language. This answer incorrectly implies that allocated resources in said "intermediate functions" will be de-allocated, which is completely impossible in C. – R.. GitHub STOP HELPING ICE May 26 '11 at 21:50
  • @R: Exit handlers are highly machine-specific and implemented by many C compilers according to the standards of the various platforms. 1980s VaxC has function exit handlers, for example. The OP asked "what does the function do", not what the C standard says or doesn't say. Anyway, `setjmp()` and `longjmp()` work in C++ too. For example, see the source in http://glibc.sourcearchive.com/documentation/2.7-18lenny7/setjmp_2longjmp_8c_source.html (and its links) for how `longjmp()` unwinds the stack frames. – wallyk May 26 '11 at 21:55
  • 3
    And they have undefined behavior if they jump over any destructors, etc. – R.. GitHub STOP HELPING ICE May 26 '11 at 21:57
  • 3
    Unwinding stack frames is not part of the standard behavior of `longjmp`. It's a harmful extension that makes an operation that should be `O(1)` suddenly `O(n)`. The only way it would need to unwind stack frames is if the platform implements its call stack as a complex structure rather than simply incrementing and decrementing a stack pointer. – R.. GitHub STOP HELPING ICE May 26 '11 at 22:00
  • 3
    Furthermore, I would think calling destructors could potentially limit `longjmp()` usefulness, e.g. if you are using it as a building block for a poor's man `setcontext()` (if that's doable at all, I haven't thought carefully about it). – ninjalj May 26 '11 at 22:22
  • Take a look on how coroutines are implemented using setjmp and longjmp and please adjust your answer. – Anne van Rossum Oct 18 '20 at 18:23
3

I think you need to see Procedure Activation Records and Call Stacks and Setjmp.h 's jmp_buf's structure.

Quoted from Expert C Programming: Deep C Secrets:

Setjmp saves a copy of the program counter and the current pointer to the top of the stack. This saves some initial values, if you like. Then longjmp restores these values effectively transferring control and resetting the state back to where you were when you did the save. It's termed "unwinding the stack", because you unroll activation records from the stack until you get to the saved one.

Have a look at page 153 also here.

The stackframe will be highly dependent on the machine and the executable, but the idea is the same.

Nick Westgate
  • 3,088
  • 2
  • 34
  • 41
phoxis
  • 60,131
  • 14
  • 81
  • 117
  • Except it doesn't unwind the stack one frame at a time, it just sets the stack pointer to the saved value. It doesn't run destructors on the way out the way exception-handling machinery does. (So don't use it in C++ if that's a problem.) – Peter Cordes Mar 08 '20 at 03:58
1

In 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
  • 1
    Some text explaining *why* would be good: these are the call-preserved registers that you have to restore to make `setjmp` look like it returned a second time when you call `longjmp` from a child of that function. (I don't think you *need* to save/restore MXCSR or the FP status register; depends what semantics you want for rounding-mode changes between setjmp and longjmp) – Peter Cordes Mar 08 '20 at 04:00
0

Here are versions of setmp and longjmp I wrote for a small clib subset (written and tested in Visual Studio 2008). The assembly code is stored in a separate .asm file.

.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

A C code snippet to test it:

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");
}

Note that I used the declaration from 'setjmp.h' for the buffer but if you want you can use an array of ints (minimum of 6 ints).

Tim Ring
  • 1,845
  • 1
  • 20
  • 27
  • You can clobber EAX, ECX, and EDX in your functions; that would simplify them vs. using only EAX and one of the registers you're trying to save (EDI). – Peter Cordes Mar 08 '20 at 04:02
0

You pass setjmp() a buffer parameter. It then stores the current register info etc. into this buffer. The call to longjmp() then restores these values from the buffer. Furthermore, what wallyk said.

BjoernD
  • 4,720
  • 27
  • 32