web-dev-qa-db-fra.com

Erreur de segmentation lors de l'éclatement de la pile x86

J'essaie de lier l'assemblage x86 et C.

Mon programme C:

extern int plus_10(int);

# include <stdio.h>

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

Mon programme d'assemblage:

[bits 32]

section .text

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

Je compile et lie les deux comme suit:

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

Cependant, lorsque j'exécute le fichier résultant, j'obtiens une erreur de segmentation.

Mais quand je remplace

pop edx

avec

mov edx, [esp + 4]

le programme fonctionne bien. Quelqu'un peut-il expliquer pourquoi cela se produit?

35
Susmit Agrawal

Ceci est un code d'assemblage possible de 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

Maintenant, lorsque vous appelez plus_10, l'adresse retadd est poussée sur la pile par l'instruction call. C'est en fait un Push + jmp, et ret est effectivement pop eip.

Donc, votre pile ressemble à ceci dans le plus_10 fonction:

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

ESP pointe vers un emplacement mémoire qui contient l'adresse de retour.

Maintenant, si vous utilisez pop edx l'adresse de retour va dans edx et la pile ressemble à ceci:

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

Maintenant, si vous exécutez ret à ce stade, le programme sautera en fait à l'adresse 40 et très probablement au défaut de segmentation ou se comportera d'une autre manière imprévisible.

Le code d'assembly réel généré par le compilateur peut être différent, mais cela illustre le problème.


BTW, une façon plus efficace d'écrire votre fonction est la suivante: c'est ce que la plupart des compilateurs feraient avec l'optimisation activée, pour une version non en ligne de cette petite fonction.

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

C'est plus petit et légèrement plus efficace que

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