同じx64でもWindowsとLinuxとで呼び出し規約が微妙に違った話

はじめに

最近、WindowsとLinuxの双方をターゲットに、Reversingをしている。AMD64はともにfastcallを標準の呼び出し規約として採用しているが、ディスアセンブル結果を見比べたら微妙に挙動が異なっていて、解析対象が変わると見るべきレジスタも変わるので、備忘録としてまとめる。

自分が把握するべき範囲内の理解を優先するため、正確性はだいぶ犠牲にしていると思う。その点は留意されたい。

Application Binary Interface: ABI

ABIとは、関数の呼び出し規約、バイナリフォーマット(PEやELFなどのこと)など低レイヤの共通のルールを定め、OSとアプリケーションの互換性を維持する技術仕様のこと。WindowsではWindows ABI、LinuxやBSDなどはSystem V ABIを採用しているとのこと。

スタックアライメントや浮動小数点数の扱いなどもABIに含まれているが、今回は整数値の引数の扱いにのみ着目する。

ソースコード

以下のソースコードを共通して用いる。コンパイラは、WindowsではVisual Studio 2026付属のもの、LinuxではUbuntuのGCC 13.3.0を用いる。いずれの場合もコンパイラの最適化は無効にし、func関数の呼び出しの様子を比較する。

#include <stdio.h>

void func (
    int arg1, int arg2, int arg3, int arg4,
    int arg5, int arg6, int arg7, int arg8
);

int main (void) {
	func(1, 2, 3, 4, 5, 6, 7, 8);
	return 0;
}

void func (
    int arg1, int arg2, int arg3, int arg4,
    int arg5, int arg6, int arg7, int arg8
) {
	printf("%d\n", arg1);
	printf("%d\n", arg2);
	printf("%d\n", arg3);
	printf("%d\n", arg4);
	printf("%d\n", arg5);
	printf("%d\n", arg6);
	printf("%d\n", arg7);
	printf("%d\n", arg8);
	return;
}

Windows

Windowsでコンパイルし、生成した実行形式のfunc関数呼び出し部分をディスアセンブルした結果は以下の通り。スタックが必要以上に確保されているように見えるが、呼び出し先(今回はfunc関数)のプロローグでレジスタの値をスタックに退避する分も含めて確保されていた。shadow storeと呼ぶらしい。

レジスタやスタックに格納される値を見ると、第1引数はrcxレジスタ、第2引数はrdxレジスタ、第3引数はr8レジスタ、第4引数はr9レジスタ、第5引数以降はスタックに積まれることがわかる。

sub	rsp,0x48
mov	dword ptr [rsp+0x38], 0x8
mov	dword ptr [rsp+0x30], 0x7
mov	dword ptr [rsp+0x28], 0x6
mov	dword ptr [rsp+0x20], 0x5
mov	r9d, 0x4
mov	r8d, 0x3
mov	edx, 0x2
mov	ecx, 0x1
call	func

これらはMicrosoft公式の仕様通りの挙動である。

System V(Linux)

Linuxでコンパイルし、生成した実行形式の関数呼び出し部分をディスアセンブルした結果は以下の通り。同じようにレジスタやスタックに格納される値を見ると、第1引数はrdiレジスタ、第2引数はrsiレジスタ、第3引数はrdxレジスタ、第4引数はrcxレジスタ、第5引数はr8レジスタ、第6引数はr9レジスタ、第7引数以降はスタックに積まれることがわかる。

push	0x8
push	0x7
mov	r9d, 0x6
mov	r8d, 0x5
mov	ecx, 0x4
mov	edx, 0x3
mov	esi, 0x2
mov	edi, 0x1
call	func

これも仕様通りの挙動である。ちなみに、func関数内でレジスタに渡されていた引数がスタックに退避されていた。System Vでも引数は全てスタックに退避されると考えても良いかは要検証もしくは要出典。

まとめ

関数が呼び出される際の引数は以下の通りに格納されることがわかった。見るべきレジスタが変わるのもそうだし、Linuxはrdx→rcxの順番なので、これも要注意ポイント。

ABIWindowsSystem V
第1引数rcxレジスタrdiレジスタ
第2引数rdxレジスタrsiレジスタ
第3引数r8レジスタrdxレジスタ
第4引数r9レジスタrcxレジスタ
第5引数スタックr8レジスタ
第6引数スタックr9レジスタ
第7引数以降スタックスタック

今後のreversingやpwnではこのことを意識して取り組みたいと思う。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。