Ошибка сегментации при выталкивании стека x86

Я пытаюсь связать сборку x86 и C.

Моя C программа:

extern int plus_10(int);

# include <stdio.h>

int main() {
    int x = plus_10(40);
    printf("%d\n", x);
    return 0;
}

Моя программа сборки:

[bits 32]

section .text

global plus_10
plus_10:
    pop edx
    mov eax, 10
    add eax, edx
    ret

Я компилирую и связываю их следующим образом:

gcc -c prog.c -o prog_c.o -m32
nasm -f elf32 prog.asm -o prog_asm.o
gcc prog_c.o prog_asm.o -m32

Однако, когда я запускаю полученный файл, я получаю ошибку сегментации.

Но когда я заменяю

поп-эдкс

с

mov edx, [esp + 4]

программа работает отлично. Может кто-нибудь объяснить, почему это происходит?

Ответ 1

Это возможный код сборки int x = plus_10(40);

        push    40                      ; push argument
        call    plus_10                 ; call function
retadd: add     esp, 4                  ; clean up stack (dummy pop)
        ; result of the function call is in EAX, per the calling convention

        ; if compiled without optimization, the caller might just store it:
        mov     DWORD PTR [ebp-x], eax  ; store return value
                                        ; (in eax) in x

Теперь, когда вы вызываете plus_10, plus_10 адрес retadd в стек инструкцией call. Это эффективно push + jmp, а ret эффективно pop eip.

Итак, ваш стек выглядит так в функции plus_10 :

|  ...   |
+--------+
|   40   |  <- ESP+4 points here (the function argument)
+--------+
| retadd |  <- ESP points here
+--------+

ESP указывает на область памяти, которая содержит адрес возврата.

Теперь, если вы используете pop edx адрес возврата переходит в edx и стек выглядит так:

|  ...   |
+--------+
|   40   |  <- ESP points here
+--------+

Теперь, если вы выполните ret в этот момент, программа фактически перейдет к адресу 40 и, скорее всего, к segfault или будет вести себя каким-то другим непредсказуемым образом.

Фактический код сборки, сгенерированный компилятором, может отличаться, но это иллюстрирует проблему.


Кстати, более эффективный способ написания вашей функции заключается в следующем: это то, что большинство компиляторов будет делать с включенной оптимизацией для не встроенной версии этой крошечной функции.

global plus_10
plus_10:
    mov   eax,  [esp+4]    ; retval = first arg
    add   eax,  10         ; retval += 10
    ret

Это меньше и немного более эффективно, чем

    mov   eax,  10
    add   eax,  [esp+4]        ; decode to a load + add.
    ret