Intel x86 Assembly Language & Microarchitecture
呼び出し規約
サーチ…
備考
リソース
概要/比較: Agner Fogの素晴らしいコーリングコンベンションガイド 。また、 x86 ABI(wikipedia) :x86-64 WindowsおよびSystem V(Linux)を含む関数の呼び出し規約。
SystemV x86-64 ABI(公式の標準) 。 Windows以外のすべてのOSで使用されます。 ( このgithub wikiページは 、HJ Luの最新版で 、32bit、64bit、x32へのリンクがあります。また、ABIのメンテナー/コントリビュータの公式フォーラムへのリンクもあります) 。clang / gccのsign / zeroは、書面によるABIはそれを必要としないにもかかわらず、 32bit 。 Clang生成コードはそれに依存します。
SystemV 32ビット(i386)ABI(公式標準) .LinuxとUnixで使用されています。 ( 旧バージョン )。
OS X 32ビットx86呼び出し規約、他のものへのリンク 。 64bit呼び出し規約はSystem Vです.AppleのサイトはそのためのFreeBSD pdfにリンクしています。
Windows
__vectorcall:32ビット版と64ビット版のドキュメントWindows 32bit
__stdcall:Win32 API関数の呼び出しに使用されます。そのページは他の呼び出し規約にリンクしています(例__cdecl)。なぜWindows64はx86-64上の他のすべてのOSとは異なる呼び出し規約を使用していますか?いくつかの興味深い歴史、特に。メーリングリストのアーカイブが公開されているSysV ABIのために、AMDが最初のシリコンをリリースする前に戻ってくる。
32ビットのcdecl
cdeclはWindows 32ビット関数呼び出し規約であり、多くのPOSIXオペレーティングシステムで使用されている呼び出し規約( i386 System V ABIに記載されている)とよく似ています。違いの1つは小さな構造体を返すことです。
パラメーター
パラメータはスタックに渡され、最初の引数は呼び出し時にスタックの最下位アドレスに格納されます(最後にプッシュされるため、関数へのエントリ時にリターンアドレスのすぐ上にあります)。呼び出し側は、コール後にスタックからパラメータをポップバックする責任があります。
戻り値
スカラ型の戻り値の場合、戻り値はEAX、または64ビット整数の場合はEDX:EAXに格納されます。浮動小数点型はst0(x87)で返されます。構造体のようなより大きな型を返すことは、暗黙的な第1引数としてポインタを渡して参照することによって行われます。 (このポインタはEAXで返されるので、呼び出し元は渡された内容を覚える必要はありません)。
保存された、およびClobberedのレジスタ
EBX、EDI、ESI、EBP、およびESP(およびFP / SSE丸めモードの設定)は、呼び出し元が呼び出しによって変更されていないレジスタに依存できるように、呼び出し先によって保持される必要があります。
他のすべてのレジスタ(EAX、ECX、EDX、FLAGS(DF以外)、x87、ベクトルレジスタ)は、呼び出し先によって自由に変更できます。呼び出し元が関数呼び出しの前後で値を保持したい場合は、保存されたレジスタの1つまたはスタック上のように、別の場所に値を保存する必要があります。
64ビットシステムV
これは、多くのPOSIXオペレーティングシステム上の64ビットアプリケーションのデフォルトの呼び出し規約です。
パラメーター
最初の8つのスカラパラメータは、RDI、RSI、RDX、RCX、R8、R9、R10、R11の順に(順番に)渡されます。最初の8つを超えるパラメータはスタックに配置され、以前のパラメータはスタックの最上位に近くなります。呼び出し側は、呼び出しの後にスタックからこれらの値をポップする必要がなくなった場合にそれをポップする責任があります。
戻り値
スカラー型の場合、戻り値はRAXに格納されます。構造体のようなより大きな型を返すことは、関数のシグネチャを概念的に変更して、戻り値を配置する場所へのポインタであるパラメータリストの先頭にパラメータを追加することによって行われます。
保存された、およびClobberedのレジスタ
RBP、RBX、およびR12〜R15は、呼び出し先によって保持されます。他のすべてのレジスタは呼び出し先によって変更される可能性があり、呼び出し元はその値を後で使用したい場合はレジスタの値自体(スタックなど)を保持する必要があります。
32ビットstdcall
stdcallは32ビットWindows API呼び出しに使用されます。
パラメーター
パラメータはスタックに渡され、最初のパラメータはスタックの最上位に最も近くなります。呼び出し先は、戻す前にスタックからこれらの値をポップします。
戻り値
スカラの戻り値はEAXに格納されます。
保存された、およびClobberedのレジスタ
EAX、ECX、およびEDXは、呼び出し先によって自由に変更することができ、必要に応じて呼び出し元が保存する必要があります。 EBX、ESI、EDI、およびEBPは、変更された場合には呼び出し先によって保存され、返されると元の値に復元されなければなりません。
32ビット、cdecl - 整数の扱い
パラメータ(8,16,32ビット)として、
8,16,32ビットの整数は、スタック上で、常にフルサイズの32ビット値1として渡されます。
拡張子は署名されているかゼロになっている必要はありません。
呼び出し先は、全幅の値の下位部分を使用します。
//C prototype of the callee
void __attribute__((cdecl)) foo(char a, short b, int c, long d);
foo(-1, 2, -3, 4);
;Call to foo in assembly
push DWORD 4 ;d, long is 32 bits, nothing special here
push DWORD 0fffffffdh ;c, int is 32 bits, nothing special here
push DWORD 0badb0002h ;b, short is 16 bits, higher WORD can be any value
push DWORD 0badbadffh ;a, char is 8 bits, higher three bytes can be any value
call foo
add esp, 10h ;Clean up the stack
パラメータ(64ビット)として、
64ビットの値は2回のプッシュを使用してスタックに渡され、リテール・エンディアンの規約2を尊重し、最初に上位32ビット、次に下位32ビットをプッシュします。
//C prototype of the callee
void __attribute__((cdecl)) foo(char a, short b, int c, long d);
foo(0x0123456789abcdefLL);
;Call to foo in assembly
push DWORD 89abcdefh ;Higher DWORD of 0123456789abcdef
push DWORD 01234567h ;Lower DWORD of 0123456789abcdef
call foo
add esp, 08h
戻り値として
8ビットの整数がALで返され、最終的には全体のeax破壊します。
16ビットの整数がAXで返され、最終的に全体のeax破棄します。
32ビットの整数がEAX返されEAX 。
64ビット整数がEDX:EAX返されEAXは下位32ビットを保持し、 EDXは上位ビットを保持します。
//C
char foo() { return -1; }
;Assembly
mov al, 0ffh
ret
//C
unsigned short foo() { return 2; }
;Assembly
mov ax, 2
ret
//C
int foo() { return -3; }
;Assembly
mov eax, 0fffffffdh
ret
//C
int foo() { return 4; }
;Assembly
xor edx, edx ;EDX = 0
mov eax, 4 ;EAX = 4
ret
1これはスタックを自然なワードサイズである4バイトに整列させたままにします。また、x86 CPUは、ロングモードでないときには2バイトまたは4バイトしかプッシュできません。
2下位アドレスの下位DWORD
32ビット、cdecl - 浮動小数点の扱い
パラメータ(float、double)として、
浮動小数点は32ビットのサイズで、スタックに自然に渡されます。
ダブルスのサイズは64ビットで、リトルエンディアンのコンベンション1を尊重し、上位32ビットと下位32ビットを押します。
//C prototype of callee
double foo(double a, float b);
foo(3.1457, 0.241);
;Assembly call
;3.1457 is 0x40092A64C2F837B5ULL
;0.241 is 0x3e76c8b4
push DWORD 3e76c8b4h ;b, is 32 bits, nothing special here
push DWORD 0c2f837b5h ;a, is 64 bits, Higher part of 3.1457
push DWORD 40092a64h ;a, is 64 bits, Lower part of 3.1457
call foo
add esp, 0ch
;Call, using the FPU
;ST(0) = a, ST(1) = b
sub esp, 0ch
fstp QWORD PTR [esp] ;Storing a as a QWORD on the stack
fstp DWORD PTR [esp+08h] ;Storing b as a DWORD on the stack
call foo
add esp, 0ch
パラメータ(long double)として、
ロング・ダブルは80ビット2ワイドであり、スタック上ではTBYTEを2つの32ビット・プッシュと1つの16ビット・プッシュ(4 + 4 + 2 = 10)に格納してスタックを4バイトに整列させておくことができます。 12バイト、したがって3つの32ビットプッシュを使用する。
リトルエンディアンの慣例を考慮すると、ビット79-64が最初に3 、次いでビット63-32の後にビット31-0がプッシュされる。
//C prototype of the callee
void __attribute__((cdecl)) foo(long double a);
foo(3.1457);
;Call to foo in assembly
;3.1457 is 0x4000c9532617c1bda800
push DWORD 4000h ;Bits 79-64, as 32 bits push
push DWORD 0c9532617h ;Bits 63-32
push DWORD 0c1bda800h ;Bits 31-0
call foo
add esp, 0ch
;Call to foo, using the FPU
;ST(0) = a
sub esp, 0ch
fstp TBYTE PTR [esp] ;Store a as ten byte on the stack
call foo
add esp, 0ch
戻り値として
浮動小数点値は、そのサイズに関係なく、 ST(0) 4で返されます。
//C
float one() { return 1; }
;Assembly
fld1 ;ST(0) = 1
ret
//C
double zero() { return 0; }
;Assembly
fldz ;ST(0) = 0
ret
//C
long double pi() { return PI; }
;Assembly
fldpi ;ST(0) = PI
ret
1下位アドレスのDWORDを下げます。
2 10バイトからのTBYTEとして知られています。
3任意の拡張子の全角プッシュを使用すると、上位のワードは使用されません。
4どれがTBYEであるかは、整数に反して、FPは常により正確に返されることに注意してください。
64ビットWindows
パラメーター
最初の4つのパラメータは、(順番に)RCX、RDX、R8、R9で渡されます。 XMM0〜XMM3は、浮動小数点パラメータを渡すために使用されます。
それ以上のパラメータはスタックに渡されます。
64ビットより大きいパラメータはアドレスで渡されます。
スピルスペース
関数が4つ未満のパラメータを使用しても、呼び出し側はスタック上に4つのQWORDサイズのパラメータのための領域を常に提供します。呼び出し先は任意の目的に自由に使用できますが、別の呼び出しでそのパラメータが書き込まれた場合は、そこにパラメータをコピーするのが一般的です。
戻り値
スカラー型の場合、戻り値はRAXに格納されます。戻り値の型が64ビットより大きい場合(例えば、構造体の場合)、RAXはそのポインタです。
保存された、およびClobberedのレジスタ
パラメータパッシング(RCX、RDX、R8、R9、XMM0〜XMM3)、RAX、R10、R11、XMM4、およびXMM5で使用されるすべてのレジスタは、呼び出し先によって流出する可能性があります。他のすべてのレジスタは、呼び出し元によって保持される必要があります(スタックなど)。
スタックアライメント
スタックは16バイトの境界線を保つ必要があります。 "call"命令は8バイトのリターンアドレスをプッシュするので、16バイトのアラインメントを復元するためには、リーフ以外のすべての関数が16n + 8という形式の値でスタックを調整することになります。
コール後にスタックをクリーニングするのは、発信者の仕事です。
出典: 呼び出し規約の歴史、パート5:amd64 Raymond Chen
32ビット、cdecl - 構造体を扱う
パディング
構造体のメンバは、自然な境界に整列するように、通常埋め込まれています。
struct t
{
int a, b, c, d; // a is at offset 0, b at 4, c at 8, d at 0ch
char e; // e is at 10h
short f; // f is at 12h (naturally aligned)
long g; // g is at 14h
char h; // h is at 18h
long i; // i is at 1ch (naturally aligned)
};
パラメータ(参照渡し)として、
参照渡し時には、メモリ内の構造体へのポインタがスタックの最初の引数として渡されます。これは、自然サイズ(32ビット)の整数値を渡すことと同じです。詳細については、 32ビットのcdeclを参照してください。
パラメータ(値渡し)として、
値渡しで渡されると、元のメモリレイアウトを尊重して構造体がスタックに完全にコピーされます( つまり 、最初のメンバーは下位アドレスになります)。
int __attribute__((cdecl)) foo(struct t a);
struct t s = {0, -1, 2, -3, -4, 5, -6, 7, -8};
foo(s);
; Assembly call
push DWORD 0fffffff8h ; i (-8)
push DWORD 0badbad07h ; h (7), pushed as DWORD to naturally align i, upper bytes can be garbage
push DWORD 0fffffffah ; g (-6)
push WORD 5 ; f (5)
push WORD 033fch ; e (-4), pushed as WORD to naturally align f, upper byte can be garbage
push DWORD 0fffffffdh ; d (-3)
push DWORD 2 ; c (2)
push DWORD 0ffffffffh ; b (-1)
push DWORD 0 ; a (0)
call foo
add esp, 20h
戻り値として
彼らは1些細でない限り、構造体を返す前に、呼び出し側が提供するバッファにコピーされます。これは隠された最初のパラメータstruct S *retval ( struct Sはstruct Sの型です)を持つのと同じです。
関数は、このポインタとともにeax戻り値に戻る必要があります。呼び出し元はポインタを戻り値に保持しているeaxに依存することができcall 。戻り値はcall直前にプッシュしcall 。
struct S
{
unsigned char a, b, c;
};
struct S foo(); // compiled as struct S* foo(struct S* _out)
隠しパラメータは、呼び出し先が処理する必要があるため、スタッククリーンアップの目的でパラメータカウントに追加されません。
sub esp, 04h ; allocate space for the struct
; call to foo
push esp ; pointer to the output buffer
call foo
add esp, 00h ; still as no parameters have been passed
上記の例では、構造体はスタックの最上部に保存されます。
struct S foo()
{
struct S s;
s.a = 1; s.b = -2; s.c = 3;
return s;
}
; Assembly code
push ebx
mov eax, DWORD PTR [esp+08h] ; access hidden parameter, it is a pointer to a buffer
mov ebx, 03fe01h ; struct value, can be held in a register
mov DWORD [eax], ebx ; copy the structure into the output buffer
pop ebx
ret 04h ; remove the hidden parameter from the stack
; EAX = pointer to the output buffer
1 "単純な"構造体とは、構造体ではない非配列型(最大32ビット)のメンバのみを含む構造体です。このような構造体の場合、そのメンバの値は単にeaxレジスタに返されます。 (この動作はLinuxをターゲットとするGCCで観察されています)
cdeclのWindows版は、System V ABIの呼び出し規約とは異なります。「単純な」構造体には、構造体ではない非配列型(最大32ビット)のメンバーを2つまで含めることができます。これらの値は、64ビット整数のようにeaxとedxで返されます。 (この動作は、Win32をターゲットとするMSVCとClangで観察されています)。