Как работает longjmp?

Мне нужно понять, КАК работает функция 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>

Может ли кто-нибудь вкратце объяснить мне код или указать, где я могу найти исходный код в системе?


person Aslan986    schedule 26.05.2011    source источник
comment
Вы должны смотреть на источник longjmp, а не siglongjmp. Последнее, вероятно, является скомпилированным кодом C для восстановления маски сигнала, за которым следует вызов или переход к реальному longjmp asm.   -  person R.. GitHub STOP HELPING ICE    schedule 27.05.2011


Ответы (6)


Вот код i386 для longjmp, в стандартном i386 ABI, без всяких сумасшедших расширений для взаимодействия с 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
person R.. GitHub STOP HELPING ICE    schedule 26.05.2011

В основном, он восстанавливает регистры и стек в том виде, в каком они были на момент соответствующего события setjmp(). Требуется некоторая дополнительная очистка (исправление обработки сигналов и раскручивание ожидающих обработчиков стека), а также возврат значения, отличного от кажущегося возвращаемого значения setjmp, но восстановление состояния является сутью операции.

Чтобы он работал, стек не может быть ниже точки, в которой был вызван setjmp. Longjmp — это грубый способ просто забыть обо всем, что было вызвано ниже него, вплоть до того же уровня в стеке вызовов (или последовательности вложенных вызовов функций), в основном путем простой установки указателя стека на тот же фрейм, который был при вызове setjmp.

Для правильной работы longjmp() вызывает все обработчики выхода для промежуточных функций, чтобы они могли удалять переменные и любую другую очистку, обычно выполняемую при возврате функции. Сброс стека в менее глубокую точку освобождает все переменные auto, но если одна из них является FILE *, файл необходимо закрыть, а также освободить буфер ввода-вывода.

person wallyk    schedule 26.05.2011
comment
Спасибо за ваш ответ. Это то, что он делает. способ.. - person Aslan986; 27.05.2011
comment
@ Asian986: Надеюсь, мой ответ с поправками касается этого. - person wallyk; 27.05.2011
comment
Функция C longjmp не вызывает обработчики выхода для промежуточных функций, которых нет в языке C. Этот ответ неверно подразумевает, что выделенные ресурсы в указанных промежуточных функциях будут освобождены, что совершенно невозможно в C. - person R.. GitHub STOP HELPING ICE; 27.05.2011
comment
@R: обработчики выхода сильно зависят от машины и реализованы многими компиляторами C в соответствии со стандартами различных платформ. Например, в VaxC 1980-х есть обработчики выхода из функций. ОП спросил, что делает функция, а не то, что говорит или не говорит стандарт C. Во всяком случае, setjmp() и longjmp() работают и в C++. Например, см. источник в glibc.sourcearchive.com/documentation/2.7-18lenny7. / (и его ссылки) о том, как longjmp() разворачивает кадры стека. - person wallyk; 27.05.2011
comment
И у них есть неопределенное поведение, если они перепрыгивают через любые деструкторы и т.д. - person R.. GitHub STOP HELPING ICE; 27.05.2011
comment
Разматывание кадров стека не является частью стандартного поведения longjmp. Это вредоносное расширение, которое делает операцию, которая должна быть O(1) внезапно O(n). Единственный способ раскрутить кадры стека — если платформа реализует свой стек вызовов как сложную структуру, а не просто увеличивает и уменьшает указатель стека. - person R.. GitHub STOP HELPING ICE; 27.05.2011
comment
Кроме того, я думаю, что вызов деструкторов может потенциально ограничить longjmp() полезность, например. если вы используете его как строительный блок для человека бедняка setcontext() (если это вообще выполнимо, я не думал об этом тщательно). - person ninjalj; 27.05.2011
comment
Посмотрите, как сопрограммы реализованы с использованием setjmp и longjmp, и, пожалуйста, скорректируйте свой ответ. - person Anne van Rossum; 18.10.2020

Я думаю, вам нужно ознакомиться с Записи активации процедуры и Стеки вызовов и Setjmp.h ' s jmp_buf структура.

Цитата из Expert C Programming: Deep C Secrets:

Setjmp сохраняет копию счетчика команд и текущий указатель на вершину стека. Это сохраняет некоторые начальные значения, если хотите. Затем longjmp восстанавливает эти значения, эффективно передавая управление и возвращая состояние обратно в то состояние, которое было при сохранении. Это называется «раскручивание стека», потому что вы разворачиваете записи активации из стека, пока не доберетесь до сохраненной.

Посмотрите также страницу 153 здесь.

Stackframe будет сильно зависеть от машины и исполняемого файла, но идея та же.

person phoxis    schedule 27.05.2011
comment
За исключением того, что он не разворачивает стек по одному кадру за раз, он просто устанавливает указатель стека на сохраненное значение. Он не запускает деструкторы на выходе, как это делает механизм обработки исключений. (Поэтому не используйте его в C++, если это проблема.) - person Peter Cordes; 08.03.2020

В 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
person smart master    schedule 07.03.2020
comment
Немного текста, объясняющего почему, было бы неплохо: это регистры, сохраненные при вызове, которые вы должны восстановить, чтобы setjmp выглядело так, как будто оно возвращалось во второй раз, когда вы вызываете longjmp из дочернего элемента этой функции. (Я не думаю, что вам нужно сохранять/восстанавливать MXCSR или регистр состояния FP; зависит от того, какую семантику вы хотите для изменений режима округления между setjmp и longjmp) - person Peter Cordes; 08.03.2020

Вы передаете setjmp() параметр буфера. Затем он сохраняет текущую информацию о регистре и т. д. в этом буфере. Затем вызов longjmp() восстанавливает эти значения из буфера. Кроме того, что сказал Уоллик.

person BjoernD    schedule 26.05.2011

Вот версии setmp и longjmp, которые я написал для небольшого подмножества clib (написано и протестировано в Visual Studio 2008). Ассемблерный код хранится в отдельном файле .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 целых чисел).

person Tim Ring    schedule 28.06.2019
comment
Вы можете затереть EAX, ECX и EDX в своих функциях; это упростило бы их по сравнению с использованием только EAX и одного из регистров, которые вы пытаетесь сохранить (EDI). - person Peter Cordes; 08.03.2020