C++とアセンブラー③

Visual StudioC++からアセンブリコードで記述した関数を紹介したが、レジスターの退避が十分とは言えませんので、破壊してはならないレジスターを保護する例を紹介する。

ほとんどのレジスターを保護

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.comdocs.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ファイルへ分離したものを示す。

呼び出し側の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;
}

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