0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MSVCx64関数呼び出し規約のメモ

Last updated at Posted at 2021-06-13

MSVCが/Odで吐き出すアセンブリの話です。
最適化をかけると呼出し自体が消滅(inline)したり、余分なバックアップがなくなったりします。

x86では__cdecl__stdcallなどいろいろな呼び出し規約がありましたが、x64では__fastcallに一本化(もはや区別する意味が無い)されました。

レジスタ

引数

引数は先頭4つまでがレジスタ渡しになりうる。この4つというのは整数、浮動小数合わせたもの。

0番目引数は、整数ならrcx浮動小数ならxmm0
1番目引数は、整数ならrbx浮動小数ならxmm1
2番目引数は、整数ならr8浮動小数ならxmm2
3番目引数は、整数ならr9浮動小数ならxmm3

例えばf(int,int,float,float)のようなときはf(rxc,rbx,xmm2,xmm3)となる。
f(rxc,rbx,xmm0,xmm1)ではない点に注意。
また、f(int,int,int,int,float,float)では4,5番目は浮動小数レジスタが余っているが引数に利用されずスタック渡しとなる。

引数

戻り値は、整数ならrax、浮動小数ならxmm0
整数の場合は引数で用いるレジスタとは別だが、浮動小数では戻り値と0番目引数が同じくxmm0を用いる。排他ではなく呼び出し直前と直後で解釈が変わるということ。

バックアップ

下記レジスタの状態をバックアップするのは呼び出された側の役目。(そもそも使わない場合はバックアップもスキップされるので注意)
それ以外のレジスタは呼び出され側で書き換え自由なので、ある意味呼び出し側で(必要に応じて)バックアップが必要ということになる。

R12~R15(汎用整数)、RBX,RDI,RSI(一応用途はあるけど実質汎用整数)、
XMM6~XMM15(汎用浮動小数)、
RBP(基本的にベースポインタ?)

スタック

引数にスタックを使わない場合でも、呼び出し前に40Bを自由に使える領域として確保しておく必要があります。
40Bの内訳は、アラインメント8+レジスタ引数バックアップ用8*4で40とのこと。
アラインメントが揃っているなら8は不要ですが、スタックのアラインメントは16Bで関数呼び出し時に戻りアドレスのバックアップで8B積まれているので、アライメントが崩れています。
http://herumi.in.coocan.jp/prog/x64.html

sub rsp 40

実際にMSVCが吐くアセンブリでは、スタックの確保の回数はなるべく少なくするようになっているようで、関数の初めにローカル変数や引数用の分とまとめて確保(rspを減算)しているようです。

また、呼び出され側で引数を受け取った場合はとりあえず、呼び出し側が確保してくれた引数用スタックの32Bに放り込んでいます。(引数有関数では冒頭は以下のような決まり文句になっています)

f:
mov DWORD PTR [rsp+32], r9d ; 3番目整数引数
mov DWORD PTR [rsp+24], r8d ; 2番目整数引数
mov DWORD PTR [rsp+16], edx ; 1番目整数引数
mov DWORD PTR [rsp+8], ecx  ; 0番目整数引数
// push ?? ; レジスタのバックアップ
// push ??
sub rsp, ?? ; スタックの確保

これを一見すると、スタック確保前にスタック使っているように見えるのですが、movしている先のスタックは呼び出し元の関数のスタック領域です。(rspより下なので確保済み領域ですね)

アドレス 内容 確保した関数 使う関数
0x00 ローカル変数 Callee Callee
0x08 レジスタのバックアップ Callee(push) Callee
0x10 ReturnAddress Caller(jmp) Callee
0x18 アラインメント Caller -
0x20 5番目引数 Caller Callee
0x28 4番目引数 Caller Callee
0x30 3番目引数バックアップ用 Caller Callee
0x38 2番目引数バックアップ用 Caller Callee
0x40 1番目引数バックアップ用 Caller Callee
0x48 0番目引数バックアップ用 Caller Callee
0x50 ローカル変数 Caller Caller

ベースポインタとRBP

rbpはベースポインタということで、上の表で言うならCalleeに居る時、rbp=0xXX38と期待されると思うのですが、実際に吐かれるアセンブリではその限りではないようです。
というのもスタックの確保は基本的に関数の先頭でまとめてsub rspで済ませてしまうため、ローカル変数や引数の基準点はrspからでも計算できますし、それでなくとも基準点さえ固定であれば何もReturnAddressが入っているアドレスを基準にする必要もありません。
なので微妙な位置にrbpをセットすることもあるようです。特に、rspは先の関数呼び出しのために確保してあげる40B+αも含めたものになっているので、そこから40B+αを足した分(量的には引いた分)をrbpにセットしているようです。

おまけ

これ便利

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?