🤖

アセンブラで引数取得

に公開

32bit(x86)モードのアセンブラに入門します
使用アセンブラはnasmgccです

引数の加算演算

二つの引数を受け取って加算した結果を返すプログラムを考えます
32bitモードでは引数は全てスタックに積まれます
C言語からこのルーチンを関数として呼び出し、戻り値を標準出力へ表示することを想定します

c.c
#include <stdio.h>

int add(int,int);

int  main(void){
        int x=add(1,2);
        printf("%d",x);
};

関数となるルーチンは、32bit下でgccの呼び出し規約に則り、次の制約を課されます

  • スタックポインタ(SP)の値を8の倍数に整える
  • スタックの確保時にリターンアドレスを用意する

32bitモードではCPUが一回で扱える最長のデータサイズもこれに制限されるため、ポインタや整数表現も4byteを最大として扱います
リターンアドレスもこの長さを持つため、スタックの確保時にはまずリターンアドレスに加え、4byte分の空のデータ領域の確保が必要です
尚リターンアドレスは暗黙的に確保されるため、実際にプログラム側で最初に確保するサイズは4byteで構いません

a.asm
bits 32

section .text
global add

add:
        sub esp,4
        mov eax,[esp+12]
        add eax,[esp+8]
        add esp,4
        ret

引数はリターンアドレスの手前に確保されます
今、スタックポインタを8の倍数に合わせるためにリターンアドレスと空の領域の計8byte分を事前消費しています
つまり、引数はesp+8で表されるアドレスに存在します
ちなみにここに格納される値は最後尾の引数なので、今回の場合は二つ目の実引数となります
そのため一つ目の引数が更にこの手前にあることとなり、そこはesp+12のアドレスであると分かります
このような引数専用の領域はシャドウスペースと呼ばれます
これを戻り値用途のレジスタであるeaxに格納したら、ルーチンの仕事は終わりです
最後にスタックの値をリターンアドレス領域の先頭アドレスに合わせ、retで完了します

c.c
#include <stdio.h>

int add(int,int);

int  main(void){
        int x=add(1,2);
        printf("%d",x);
};
a.asm
bits 32

section .text
global add

add:
        sub esp,4
        mov eax,[esp+12]
        add eax,[esp+8]
        add esp,4
        ret
3

引数を安全に取得するには、シャドウスペースの先頭アドレスを保存できればいいと言えます
そうすれば、このアドレスに対し4の倍数を減算することで、先頭の引数から順にアクセスできることになります
スタックポインタの状態を記録するためのレジスタとしてはベースポインタ(ebp)があります
アセンブラ内で自作のサブルーチンなどを呼び出す際は、このベースポインタを扱うことで引数を安全に管理できます
尚シャドウスペースにおいてプリミティブ型の実引数値には一個につき最長のデータサイズが一律で割り当てられるため、型ごとのデータサイズを考慮に含める必要はありません
そのため引数の場所がesp+4の倍数であると推論できます

Discussion