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]
プログラムは正常に動作します。なぜこれが起こるのか誰かが説明できますか?
これは、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