Visual Studio のC++からアセンブリコードで記述した関数を紹介したが、レジスターの退避が十分とは言えませんので、破壊してはならないレジスターを保護する例を紹介する。
ほとんどのレジスターを保護
arques.hatenablog.com
で紹介したプログラムは、壊して良いレジスターを一時退避に使い保護していた。ここでは、スタックに領域を確保し、ほとんどのレジスターを保護する例を紹介する。以前のプログラムと同様なので必要ないと思うが、まず呼び出し側のC++ソースコードを示す。
#include <iostream> using namespace std; extern "C" void myCpuid(int*, int); // assembler function int main() { int regs[4]; char CPUString[(sizeof(int)) * 3 + 1]{}; myCpuid(regs, 0); *((int*)(CPUString + 0)) = regs[1]; *((int*)(CPUString + 4)) = regs[3]; *((int*)(CPUString + 8)) = regs[2]; CPUString[sizeof CPUString - 1] = '\0'; cout << "Vender Id: " << CPUString << endl; }
次に、アセンブリ言語で記述したソースファイルを示す。
include ksamd64.inc _TEXT segment ; data structure to push on the stack stkF struct xmm6sv xmmword ? xmm7sv xmmword ? xmm8sv xmmword ? xmm9sv xmmword ? xmm10sv xmmword ? xmm11sv xmmword ? xmm12sv xmmword ? xmm13sv xmmword ? xmm14sv xmmword ? xmm15sv xmmword ? rbxsv dq ? rsisv dq ? rdisv dq ? rbpsv dq ? r12sv dq ? r13sv dq ? r14sv dq ? r15sv dq ? dq ? ; padding stkF ends ; error, if size is not a 16 multiples and plus 8 .erre (size stkF mod 16) eq 8, <the size of stack structure must be 16n+8> ; ; rcx = stringの先頭アドレス。 ; rdx = function # ; public myCpuid align 16 myCpuid proc frame ; prologを使う時はproc frame alloc_stack(size stkF) ; prolog save_xmm128 xmm6, stkF.xmm6sv save_xmm128 xmm7, stkF.xmm7sv save_xmm128 xmm8, stkF.xmm8sv save_xmm128 xmm9, stkF.xmm9sv save_xmm128 xmm10, stkF.xmm10sv save_xmm128 xmm11, stkF.xmm11sv save_xmm128 xmm12, stkF.xmm12sv save_xmm128 xmm13, stkF.xmm13sv save_xmm128 xmm14, stkF.xmm14sv save_xmm128 xmm15, stkF.xmm14sv save_reg rbx, stkF.rbxsv save_reg rsi, stkF.rsisv save_reg rdi, stkF.rdisv save_reg rbp, stkF.rbpsv save_reg r12, stkF.r12sv save_reg r13, stkF.r13sv save_reg r14, stkF.r14sv save_reg r15, stkF.r15sv .endprolog ; end of prolog mov r9, rcx ; save param addr mov eax, edx cpuid mov [r9+0], eax mov [r9+4], ebx mov [r9+8], ecx mov [r9+12], edx movaps xmm6, [rsp + stkF.xmm6sv] ; epilog, restore movaps xmm7, [rsp + stkF.xmm7sv] movaps xmm8, [rsp + stkF.xmm8sv] movaps xmm9, [rsp + stkF.xmm9sv] movaps xmm10, [rsp + stkF.xmm10sv] movaps xmm11, [rsp + stkF.xmm11sv] movaps xmm12, [rsp + stkF.xmm12sv] movaps xmm13, [rsp + stkF.xmm13sv] movaps xmm14, [rsp + stkF.xmm14sv] movaps xmm15, [rsp + stkF.xmm14sv] mov rbx, [rsp + stkF.rbxsv] mov rsi, [rsp + stkF.rsisv] mov rdi, [rsp + stkF.rdisv] mov rbp, [rsp + stkF.rbpsv] mov r12, [rsp + stkF.r12sv] mov r13, [rsp + stkF.r13sv] mov r14, [rsp + stkF.r14sv] mov r15, [rsp + stkF.r15sv] add rsp, size stkF ; end of epilog ret myCpuid endp _TEXT ends end
すでに汎用レジスターをスタックへ退避・復旧するプログラムは紹介したが、ここではSIMDレジスターも退避したいので、スタックを構造体で宣言し、push/popではなくmov/ movaps命令、そしてマクロなどを使って退避・復旧する。xmmレジスターなどは、push/popできないので先にalloc_stackでスタックにエリアを確保し、そこに保存する。スタックにエリアを確保するので、汎用レジスターも同様な手法を採用する。
スタックは構造体で宣言する、ここではstkFという名前で宣言する。保存するレジスターは、xmm6~xmm15、rbx~r15とする。保存するレジスターについてはソースコードを参照すること。
なお、確保するスタックのサイズは16の整数倍+8バイトでなければならない。このため、アセンブル時に適切なサイズであるか、16の剰余が8であるかチェックするようにした。これらの詳細について詳しく知りたいときは、docs.microsoft.comやdocs.microsoft.comを読むと良いだろう。また、アセンブラーのマクロなどについては、docs.microsoft.comを読むと良いだろう。
save_xmm128マクロ、「save_xmm128 xmm6, stkF.xmm6sv」は、「movaps stkF.xmm6sv[rsp], xmm6」と、翻訳される。アライメントを求めるmovapsが使われることに留意しておくと良いだろう。構造体の作り方次第で、要求アライメントを保証できない場合はmovupsを使用する必要があるが、構造体の先頭にxmmレジスター保存領域を確保するのであれば特に問題は起きないだろう。
実行結果:Vender Id: GenuineIntel
コマンドライン[x64 Native Tools Command Prompt for VS 20xx]を使って、ビルド・実行した例も示す。
マクロをincファイルへ分離
このままでは、asmファイルが長くなるので、マクロや構造体の定義などをincファイルへ分離したものを示す。
#include <iostream> using namespace std; extern "C" void myCpuid(int*, int); // assembler function int main() { int regs[4]; char CPUString[(sizeof(int)) * 3 + 1]{}; myCpuid(regs, 0); *((int*)(CPUString + 0)) = regs[1]; *((int*)(CPUString + 4)) = regs[3]; *((int*)(CPUString + 8)) = regs[2]; CPUString[sizeof CPUString - 1] = '\0'; cout << "Vender Id: " << CPUString << endl; }
incのソースコード
; prolog PROLOG macro stack alloc_stack(size stack) ; prolog save_xmm128 xmm6, stack.xmm6sv save_xmm128 xmm7, stack.xmm7sv save_xmm128 xmm8, stack.xmm8sv save_xmm128 xmm9, stack.xmm9sv save_xmm128 xmm10, stack.xmm10sv save_xmm128 xmm11, stack.xmm11sv save_xmm128 xmm12, stack.xmm12sv save_xmm128 xmm13, stack.xmm13sv save_xmm128 xmm14, stack.xmm14sv save_xmm128 xmm15, stack.xmm14sv save_reg rbx, stack.rbxsv save_reg rsi, stack.rsisv save_reg rdi, stack.rdisv save_reg rbp, stack.rbpsv save_reg r12, stack.r12sv save_reg r13, stack.r13sv save_reg r14, stack.r14sv save_reg r15, stack.r15sv .endprolog ; end of prolog endm ; epilog EPILOG macro stack movaps xmm6, stack.xmm6sv[rsp] ; epilog, restore movaps xmm7, stack.xmm7sv[rsp] movaps xmm8, stack.xmm8sv[rsp] movaps xmm9, stack.xmm9sv[rsp] movaps xmm10, stack.xmm10sv[rsp] movaps xmm11, stack.xmm11sv[rsp] movaps xmm12, stack.xmm12sv[rsp] movaps xmm13, stack.xmm13sv[rsp] movaps xmm14, stack.xmm14sv[rsp] movaps xmm15, stack.xmm14sv[rsp] mov rbx, stack.rbxsv[rsp] mov rsi, stack.rsisv[rsp] mov rdi, stack.rdisv[rsp] mov rbp, stack.rbpsv[rsp] mov r12, stack.r12sv[rsp] mov r13, stack.r13sv[rsp] mov r14, stack.r14sv[rsp] mov r15, stack.r15sv[rsp] add rsp, size stack ; end of epilog ret endm ; data structure to push on the stack stkF struct xmm6sv xmmword ? xmm7sv xmmword ? xmm8sv xmmword ? xmm9sv xmmword ? xmm10sv xmmword ? xmm11sv xmmword ? xmm12sv xmmword ? xmm13sv xmmword ? xmm14sv xmmword ? xmm15sv xmmword ? rbxsv dq ? rsisv dq ? rdisv dq ? rbpsv dq ? r12sv dq ? r13sv dq ? r14sv dq ? r15sv dq ? dq ? ; padding stkF ends ; error, if size is not a 16 multiples and plus 8 .erre (size stkF mod 16) eq 8, <the size of stack structure must be 16n+8>
include ksamd64.inc include reg128.inc _TEXT segment ; rcx = stringの先頭アドレス。 ; rdx = function # ; public myCpuid align 16 myCpuid proc frame PROLOG stkF ; prolog mov r9, rcx ; save param addr mov eax, edx cpuid mov [r9+0], eax mov [r9+4], ebx mov [r9+8], ecx mov [r9+12], edx EPILOG stkF ; epilog myCpuid endp _TEXT ends end