web-dev-qa-db-ja.com

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

次のように2つをコンパイルしてリンクします。

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

しかし、結果のファイルを実行すると、セグメンテーション違反が発生します。

しかし、私が交換するとき

ポップedx

mov edx、[esp + 4]

プログラムは正常に動作します。なぜこれが起こるのか誰かが説明できますか?

35
Susmit Agrawal

これは、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を呼び出すと、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
31
Jabberwocky