17Th
Developer Camp

【G6】ライトニングトーク

呼出規約の違いをアセンブラレベルで比較する

東洋テクニカルシステム株式会社
システム開発部
福士 光

1

17Th
Developer Camp

アジェンダ

2

アジェンダ• 呼出規約とは• Windows(x86)の呼出規約の比較• Windows(x86)の呼出規約によるコード生成の比較• クラスのメソッドの扱い• Windows(x64)のコード生成• 詳しい説明は後でセッション資料をダウンロードしてご確認ください。

3

17Th
Developer Camp

1

基本知識

4

基本知識～呼出規約とは？
• 呼出規約とは？
– サブルーチンとのインタフェースに関する決まりごと– 呼出元(caller)と呼出先(callee)で一致していないと正しく動作しない– 一般には• パラメータと戻値をどこに置くか• パラメータをどの順序で渡すか• クリーンアップの責任分担• レジスタの使い方• 名前の修飾(name mangling)
• など
5

17Th
Developer Camp

2

Windows(x86)の呼出規約

6Windows(x86)の呼出規約(1)• Windows(x86)+Delphi上の呼出規約とその主な違い

呼出規約

パラメータをスタック上の渡す順序
パラメータの削除

パラメータの引き渡しにレジスタを使用

register

左から右

呼出先

あり

pascal

左から右

呼出先

なし

cdecl

右から左

呼出元なし

stdcall

右から左呼出先なし

safecall

右から左呼出先なし

7

Windows(x86)の呼出規約(2)• パラメータを渡す順序が「右から左」であれば可変長引数が容易(Cのprintf関数のように)– レジスタまたはスタックの既知の位置にあるパラメータから残りのパラメータの数とサイズを推定できる• スタック上のパラメータの削除を呼出元で行うと、呼出毎にパラメータのクリーンアップコードが必要– 可変長引数では呼出先でクリーンアップすることは困難• スタック上のパラメータの削除を呼出先で行うと、パラメータのクリーンアップコードが1箇所で済む– 16bit Windowsのころはメモリが潤沢ではなかった

8

Windows(x86)の呼出規約(3)• Delphiのregister規約では最大3つの32bit値をレジスタ経由で渡すことができる– EAX/EDX/ECXレジスタを使用– この範囲であればスタックフレームを作成しなくてすむ– それ以上のパラメータはスタック経由• stdcall規約はWin32 APIあるいは標準的なDLLの呼び出しに使用する• safecall規約はstdcallに加えて例外「ファイアウォール」を実装している– COMのエラーを例外に変換する
9

Windows(x86)の呼出規約(4)• 順序型の戻値はAL(8bit)/AX(16bit)/EAX(32bit)/
EDX:EAX(64bit)に格納される• 実数型の戻値はFPUのスタックトップレジスタST(0)に格納される• 詳細はヘルプで(参考文献を参照)

10

Windows(x86)の呼出規約(5)• アセンブルリストはデバッグ実行中に以下の方法で表示– メインメニュー→表示→デバッグ→CPUウィンドウ→CPU全体– ショートカットキーは[Ctrl]+[Alt]+[C]• アセンブルリスト上での自動変数領域– [ebp-$xx]はローカル変数(x64では[rbp-$xx])– [ebp+$xx]は仮引数(x64では[rbp+$xx])

11

Windows(x86)の呼出規約(6)• サンプルregister規約でもスタックを使用させるため– 引数はInteger ｘ 4、戻値もInteger– 呼出規約を変化させて呼出元、呼出先の生成コードを確認してみる– デバッグビルド(x86、x64とも)
function Foo(Param1: Integer; Param2: Integer; Param3: Integer; Param4: Integer): Integer;
begin
Result := Param1 + Param2 + Param3 + Param4;
end;
var
RetVal: Integer;
begin
RetVal := Foo(1,2,3,4);
Writeln(RetVal);
end.

12

Windows(x86)の呼出規約 - register規約
CC_Register.dpr.15: RetVal := Foo(1,2,3,4);004060D4 6A04
push $04
004060D6 B903000000
mov ecx,$00000003
004060DB BA02000000
mov edx,$00000002
004060E0 B801000000
mov eax,$00000001
004060E5 E8E6F1FFFF
call Foo
004060EA A39CAB4000
mov [$0040ab9c],eax

第4パラメータだけがスタックに積まれ、残りはEAX/EDX/ECXに格納

戻値を格納

CC_Register.dpr.8: begin
004052D0 55
push ebp
004052D1 8BEC
mov ebp,esp
004052D3 83C4F0
add esp,-$10004052D6 894DF4
mov [ebp-$0c],ecx
004052D9 8955F8
mov [ebp-$08],edx
004052DC 8945FC
mov [ebp-$04],eax
CC_Register.dpr.9: Result := Param1 + Param2 + Param3 + Param4;004052DF 8B45FC
mov eax,[ebp-$04]
004052E2 0345F8
add eax,[ebp-$08]
004052E5 0345F4
add eax,[ebp-$0c]
004052E8 034508
add eax,[ebp+$08]
004052EB 8945F0
mov [ebp-$10],eax
CC_Register.dpr.10: end;
004052EE 8B45F0
mov eax,[ebp-$10]004052F1 8BE5
mov esp,ebp
スタックから4バイトをクリアしてリターン
004052F3 5D
pop ebp004052F4 C20400
ret $0004
13

Windows(x86)の呼出規約 - pascal規約
CC_Pascal.dpr.15: RetVal := Foo(1,2,3,4);004060D4 6A01
push $01
004060D6 6A02
push $02
004060D8 6A03
push $03
004060DA 6A04
push $04
004060DC E8EFF1FFFF
call Foo
004060E1 A39CAB4000
mov [$0040ab9c],eax

全てのパラメータがスタックに積まれる(順番に注意)

戻値を格納

CC_Pascal.dpr.8: begin
004052D0 55
push ebp
004052D1 8BEC
mov ebp,esp
004052D3 51
push ecx
CC_Pascal.dpr.9: Result := Param1 + Param2 + Param3 + Param4;004052D4 8B4514
mov eax,[ebp+$14]
004052D7 034510
add eax,[ebp+$10]
004052DA 03450C
add eax,[ebp+$0c]004052DD 034508
add eax,[ebp+$08]
004052E0 8945FC
mov [ebp-$04],eax
CC_Pascal.dpr.10: end;
004052E3 8B45FC
mov eax,[ebp-$04]
004052E6 59
pop ecx
スタックから16バイトをクリアしてリターン
004052E7 5D
pop ebp004052E8 C21000
ret $0010

14

Windows(x86)の呼出規約 - cdecl規約
CC_Cdecl.dpr.15: RetVal := Foo(1,2,3,4);004060D4 6A04
push $04
004060D6 6A03
push $03
004060D8 6A02
push $02
004060DA 6A01
push $01
004060DC E8EFF1FFFF
call Foo
004060E1 83C410
add esp,$10004060E4 A39CAB4000
mov [$0040ab9c],eax

全てのパラメータがスタックに積まれる

スタックから16バイトをクリア
戻値を格納

CC_Cdecl.dpr.8: begin
004052D0 55
push ebp
004052D1 8BEC
mov ebp,esp
004052D3 51
push ecx
CC_Cdecl.dpr.9: Result := Param1 + Param2 + Param3 + Param4;004052D4 8B4508
mov eax,[ebp+$08]
004052D7 03450C
add eax,[ebp+$0c]004052DA 034510
add eax,[ebp+$10]
004052DD 034514
add eax,[ebp+$14]
004052E0 8945FC
mov [ebp-$04],eax
CC_Cdecl.dpr.10: end;
004052E3 8B45FC
mov eax,[ebp-$04]
004052E6 59
pop ecx
単純にリターン004052E7 5D
pop ebp004052E8 C3
ret15

Windows(x86)の呼出規約 - stdcall規約
CC_Stdcall.dpr.15: RetVal := Foo(1,2,3,4);004060D4 6A04
push $04
004060D6 6A03
push $03
004060D8 6A02
push $02
004060DA 6A01
push $01
004060DC E8EFF1FFFF
call Foo
004060E1 A39CAB4000
mov [$0040ab9c],eax

全てのパラメータがスタックに積まれる

戻値を格納

CC_Stdcall.dpr.8: begin
004052D0 55
push ebp
004052D1 8BEC
mov ebp,esp
004052D3 51
push ecx
CC_Stdcall.dpr.9: Result := Param1 + Param2 + Param3 + Param4;004052D4 8B4508
mov eax,[ebp+$08]
004052D7 03450C
add eax,[ebp+$0c]
004052DA 034510
add eax,[ebp+$10]004052DD 034514
add eax,[ebp+$14]
004052E0 8945FC
mov [ebp-$04],eax
CC_Stdcall.dpr.10: end;
004052E3 8B45FC
mov eax,[ebp-$04]
004052E6 59
pop ecx
スタックから16バイトをクリアしてリターン
004052E7 5D
pop ebp004052E8 C21000
ret $0010

16Windows(x86)の呼出規約 - safecall規約 (1)CC_Safecall.dpr.15: RetVal := Foo(1,2,3,4);004060D4 8D45EC
lea eax,[ebp-$14]
004060D7 50
push eax
004060D8 6A04
push $04
004060DA 6A03
push $03
004060DC 6A02
push $02
004060DE 6A01
push $01
004060E0 E8E3F2FFFF
call Foo
004060E5 E85EEAFFFF
call @CheckAutoResult
004060EA 8B45EC
mov eax,[ebp-$14]004060ED A3A0AB4000
mov [$0040aba0],eax
CC_Safecall.dpr.8: begin
004053C8 55004053C9 8BEC004053CB 53004053CC 56004053CD 57004053CE 6A00004053D0 33C0004053D2 55004053D3 68FC534000
004053D8 64FF30004053DB 648920

戻値を格納するアドレス(隠しパラメータ)
全てのパラメータがスタックに積まれる

push ebp
mov ebp,esp
push ebx
push esi
push edi
push $00
xor eax,eax
push ebp
push $004053fc
push dword ptr fs:[eax]mov fs:[eax],esp

エラーがあったかどうかを検査
戻値を格納

スタック上に例外処理構造体を構築してFS:[0]に登録

17Windows(x86)の呼出規約 - safecall規約 (2)CC_Safecall.dpr.9: Result := Param1 + Param2 + Param3 + Param4;004053DE 8B4508
mov eax,[ebp+$08]
004053E1 03450C
add eax,[ebp+$0c]
004053E4 034510
add eax,[ebp+$10]
004053E7 034514
add eax,[ebp+$14]
004053EA 8B5518
mov edx,[ebp+$18]
戻値を隠しパラメータの場所に格納
004053ED 8902
mov [edx],eax
CC_Safecall.dpr.10: end;
004053EF 33C0
xor eax,eax
004053F1 5A
pop edx
スタック上の例外処理構造体を004053F2 59
pop ecx
クリーンアップ
004053F3 59
pop ecx
004053F4 648910
mov fs:[eax],edx
004053F7 58
pop eax
エラー発生時はここから004053F8 33DB
xor ebx,ebx
004053FA EB05
jmp $00405401エラーハンドラにジャンプ
004053FC E90FEAFFFF
jmp @HandleAutoException
00405401 8BC3
mov eax,ebx
00405403 5F
pop edi00405404 5E
pop esi
00405405 5B
pop ebx
スタックから20バイトをクリアしてリターン
00405406 5D
pop ebp00405407 C21400
ret $0014

18

17Th
Developer Camp

3

クラスのメソッドの扱い

19

クラスのメソッドの扱い(1)• 暗黙でSelfが渡される– インスタンスメソッドではインスタンスへのポインタ– クラスメソッドではクラス参照型へのポインタ• コンストラクタおよびデストラクタでは追加で呼出コンテキストのフラグ(8bit)が渡される– True(0x01)は直接の(最上位の)コンストラクタ/デストラクタ呼出を意味する(インスタンス領域の確保/解放が行われる)– False(0x00)はinheritedによる間接的なコンストラクタ/デストラクタ呼出を意味する(通常のメソッドと同様)

20

クラスのメソッドの扱い(2)• サンプル
typeTTest = class(TObject)publicconstructor Create;end;constructor TTest.Create;
begininherited;end;var
Test: TTest;
begin
Test := TTest.Create;
Test.Free;
end.

21

クラスのメソッドの扱い(3)• コンストラクタ(呼出元)
CC_OBJECT.dpr.24: Test := TTest.Create;0041B394 B201
mov dl,$010041B396 A108914100
mov eax,[$00419108]
0041B39B E824DEFFFF
call TTest.Create
0041B3A0 A3D82E4200
mov [$00422ed8],eax

DLに0x01(True)を格納
EAXにTTestのクラス参照を格納
戻値を格納

22

クラスのメソッドの扱い(3)• コンストラクタ(呼出先)
CC_OBJECT.dpr.17: begin
004191C4 55
push ebp
004191C5 8BEC
mov ebp,esp
004191C7 83C4F8
add esp,-$08004191CA 84D2
test dl,dl
呼出コンテキストフラグ(DL)が0x00で004191CC 7408
jz $004191d6
なければClassCreateを呼び出す
004191CE 83C4F0
add esp,-$10004191D1 E83EB7FEFF
call @ClassCreate
004191D6 8855FB
mov [ebp-$05],dl004191D9 8945FC
mov [ebp-$04],eax
CC_OBJECT.dpr.18: inherited;
DLをクリアしてから継承元の004191DC 33D2
xor edx,edx004191DE 8B45FC
mov eax,[ebp-$04]
コンストラクタを呼び出す
004191E1 E872B1FEFF
call TObject.Create
CC_OBJECT.dpr.19: end;
004191E6 8B45FC
mov eax,[ebp-$04]
004191E9 807DFB00
cmp byte ptr [ebp-$05],$00
呼出コンテキストフラグが0x00でな
004191ED 740F
jz $004191fe
004191EF E878B7FEFF
call @AfterConstruction
ければAfterConstructionを004191F4 648F0500000000
pop dword ptr fs:[$00000000]
呼び出す
004191FB 83C40C
add esp,$0c
004191FE 8B45FC
mov eax,[ebp-$04]
00419201 59
pop ecx
00419202 59
pop ecx00419203 5D
pop ebp2300419204 C3
ret

17Th
Developer Camp

4Windows(x64)の呼出規約

24

Windows(x64)の呼出規約(1)• Windows(x64) の呼出規約は一つしかない– ただしsafecallは例外ファイアウォールを持つので別扱い• 最大4つの64bit値をレジスタで渡すことができる– RCX/RDX/R8/R9レジスタを使用– 実数型はXMM0L/XMM1L/XMM2L/XMM3Lを使用– スタック上にもレジスタ経由の領域を確保する• 戻値はRAXレジスタに格納される• メモリ上のレイアウトは8バイト(QWORD)単位25

Windows(x64)の呼出規約(2)• サンプル

スタックを使用させるため– 5つのパラメータを渡してみますfunction Foo(Param1: Integer; Param2: Integer; Param3: Integer; Param4: Integer;
Param5: Integer): Integer;
begin
Result := Param1 + Param2 + Param3 + Param4 + Param5;
end;
var
RetVal: Integer;
begin
RetVal := Foo(1,2,3,4,5);
Writeln(RetVal);
end.

26

Windows(x64)の呼出規約(3)CC_x64.dpr.15: RetVal := Foo(1,2,3,4,5);000000000040B6AF C7C101000000
mov ecx,$00000001
000000000040B6B5 C7C202000000
mov edx,$00000002
000000000040B6BB 41C7C003000000
mov r8d,$00000003
000000000040B6C2 41C7C104000000
mov r9d,$00000004
000000000040B6C9 C744242005000000 mov [rsp+$20],$00000005
000000000040B6D1 E8CAF7FFFF
call Foo000000000040B6D6 8905F4750000
mov [rel $000075f4],eax

第5パラメータだけがスタックに積まれ、残りはレジスタに格納
戻値を格納

CC_x64.dpr.8: begin
000000000040AEA0 55
push rbp
000000000040AEA1 4883EC10
sub rsp,$10
パラメータをスタック上の000000000040AEA5 488BEC
mov rbp,rsp
000000000040AEA8 894D20
mov [rbp+$20],ecx
予約領域(spill)にもコピー
000000000040AEAB 895528
mov [rbp+$28],edx(コンサバティブな実装)
000000000040AEAE 44894530
mov [rbp+$30],r8d000000000040AEB2 44894D38
mov [rbp+$38],r9d
CC_x64.dpr.9: Result := Param1 + Param2 + Param3 + Param4 + Param5;000000000040AEB6 8B4520
mov eax,[rbp+$20]
000000000040AEB9 034528
add eax,[rbp+$28]
000000000040AEBC 034530
add eax,[rbp+$30]
000000000040AEBF 034538
add eax,[rbp+$38]
000000000040AEC2 034540
add eax,[rbp+$40]
000000000040AEC5 89450C
mov [rbp+$0c],eax
CC_x64.dpr.10: end;
単純にリターン
000000000040AEC8 8B450C
mov eax,[rbp+$0c]
000000000040AECB 488D6510
lea rsp,[rbp+$10]
000000000040AECF 5D
pop rbp000000000040AED0 C3ret27

17Th
Developer Camp5

参考文献

28参考文献 (1)• RAD Studioのヘルプ– Delphi リファレンス→Delphi 言語ガイド→
プログラムの制御• http://docwiki.embarcadero.com/RADStudio/
ja/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E
3%83%A0%E3%81%AE%E5%88%B6%E5%BE%A1– Delphi リファレンス→Delphi 言語ガイド→
手続きと関数→手続きと関数• http://docwiki.embarcadero.com/RADStudio/
ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E3%81%A8%E
9%96%A2%E6%95%B029

参考文献 (2)• RAD Studioのヘルプ– IDEリファレンス→RAD Studioのダイアログとコマンド→［表示］メニュー→デバッグ ウィンドウ→CPU ウィンドウ• http://docwiki.embarcadero.com/RADStudio/
ja/%EF%BC%BB%E9%80%86%E3%82%A2%E3%82%BB%E
3%83%B3%E3%83%96%E3%83%AB%EF%BC%BD%E3%83%
9A%E3%82%A4%E3%83%B330

参考文献 (3)• MSDN– 呼出規約• http://msdn.microsoft.com/jajp/library/9b372w95.aspx– Calling Conventions• http://msdn.microsoft.com/enus/library/k2b2ssfy.aspx– x64 呼び出し規約の概要• http://msdn.microsoft.com/jajp/library/ms235286

31参考文献 (4)• The Old New Thing (Raymond Chenさんのblog)• http://blogs.msdn.com/b/oldnewthing/– The history of calling conventions, part 1~5• http://blogs.msdn.com/b/oldnewthing/archive/
2004/01/02/47184.aspx• http://blogs.msdn.com/b/oldnewthing/archive/
2004/01/07/48303.aspx• http://blogs.msdn.com/b/oldnewthing/archive/
2004/01/08/48616.aspx• http://blogs.msdn.com/b/oldnewthing/archive/
2004/01/13/58199.aspx• http://blogs.msdn.com/b/oldnewthing/archive/
2004/01/14/58579.aspx
32

参考文献 (5)• Intel– 日本語技術資料のダウンロード• http://www.intel.com/jp/download/index.htm– IA-32 インテルアーキテクチャ
ソフトウェア・デベロッパーズ・マニュアル• 中巻A：命令セット・リファレンスA-M– http://download.intel.com/jp/developer/jpdoc/I
A32_Arh_Dev_Man_Vol2A_i.pdf• 中巻B：命令セット・リファレンスN-Z– http://download.intel.com/jp/developer/jpdoc/I
A32_Arh_Dev_Man_Vol2B_i.pdf

33参考文献 (6)• Intel– インテル エクステンデッド・メモリ 64 テクノロジ・
ソフトウェア・デベロッパーズ・ガイド• 第1巻– http://download.intel.com/jp/developer/jpdoc/E
M64T_VOL1_30083402_i.pdf• 第2巻– http://download.intel.com/jp/developer/jpdoc/E
M64T_VOL2_30083502_i.pdf

34

17Th
Developer Camp

Q&A

35

Q&A
• 不明な点は公式フォーラムかDelphi-mlで– Embarcadero Discussion Forums: Delphi• https://forums.embarcadero.com/forum.jspa?forumID=14– Delphi｜freeml• http://www.freeml.com/delphi-users

36

17Th
Developer Camp

ありがとうございました37