GAS_関数

GAS(GNU アセンブラ)における関数処理

関数呼び出し

関数を呼び出す場合には、call命令を使います。

call命令は次に実行する地点である「命令ポインタ(EIP)」をスタックに保存しておき、指定したラベルへジャンプする動作となります。


call function = pushl %eip; jmp function

関数からの復帰

関数から復帰するときにはret命令を使います。

ret命令はスタックから戻り先アドレス(命令ポインタ)をポップして復帰する動作となります。


ret = popl %eip

GAS(GNU アセンブラ)における関数定義

関数定義の基本形

関数呼び出しを行う場合には、必ず呼び出した時のレジスタの値と関数処理が終了した時のレジスタの値を一致させる必要があります。

関数に入った(call命令を実行した)時点で、スタックの先頭には関数から帰るべきアドレス(命令ポインタ)が格納されています。

espが変更されては、関数から復帰する際に戻るべき場所が判らなくなってしまいますので、必ずレジスタを復元する処理が必要となります。


一般的に、関数処理時のレジスタルールは以下のものがあります。

  • 関数に入る時と出る時で変化してはいけないレジスタ: EBP, ESP, EDI, ESI, EBX
  • 戻り値を格納するレジスタ: EAX


function:
    push %ebp        # ベースポインタをスタックに保存
    movl %esp, %ebp  # 現時点でのスタックポインタの位置をベースポインタに設定

    ~関数の処理~

    movl %ebp, %esp  # スタックポインタを復元する
    popl %ebp        # ベースポインタを復元する
    ret

関数定義はpushとpopをペアで使用することで、ebpとespが関数に入る時と出る時で同じ値を保つようにします。

また、関数に入った時点のスタックポインタ位置(espの値)をebpに記憶しておくことで、スタックの基準位置(ベース)を関数に合わせることができます。


leave命令

leave命令は、関数からの復帰処理をまとめた命令です。

関数定義は以下の形式で記述することが一般的です。


function:
    push %ebp
    movl %esp, %ebp

    ~関数の処理~

    leave
    ret

引数について

引数を扱う場合、関数呼び出し側では引数をスタックにプッシュしてcall命令を実行します。

そして、関数側で関数に入った時点のスタックポインタ位置(espの値)をebpに記憶しておきます。(関数の基本操作)

こうすることで、ベースポインタ(ebp)からのオフセットで引数を参照することができます。

第一引数は常に8(%ebp)、第二引数なら12(%ebp)となります。


    pushl %ebx
    call function
    ...
function:
    pushl %ebp
    movl %esp, %ebp
    movl 8(%ebp), %eax
    ...

関連ページ