時代はアセンブラだ!?

C 言語には「インライン・アセンブラ」なる機能があり、これを使えば任意のアセンブリコードを C のソースコードに挿入することができるらしい…!なんということでしょう。マシンコードを poke していた人達もこれで楽ができますね。僕は漢字 Talk 7 とか Windows 95 の世代なので、先史時代のことはよく分かりませんが。

ということで、gcc で x64 アセンブリにチャレンジしてみました。インラインアセンブラを書くには、__asm__ という関数?演算子?何だか良く分かりませんが、とにかく __asm__ を使います。

まず C の変数に値を書き込む例です (__asm__ や AT&T 記法の詳細は http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html を見てね。僕は読んでないけど)。

#include <stdio.h>

int main(int argc, char *argv[])
{
  int res = 0;

  /*
   * - Mac では AT&T 記法しか使えない。
   * - インラインアセンブラでは %%rax のようにレジスタ名に
   *   '%' を2つ付ける。
   */

  /* 変数への書き込み */
  __asm__ volatile("movq $0xffffffff0000007b,%%rax"  /* 64 ビットデータを mov する */
                  :"=a"(res));                       /* 最後に rax の値を res 変数 (32 ビット) に代入する */
  printf("%d\n", res);

  return 0;
}

これをコンパイルして実行すると、ターミナルに “123” と表示されます。すごい。res 変数には rax の下位 32 ビットが入るようです。

なお、gcc に ーS オプションを渡すと、完成したアセンブリコードを出力してくれます。拡張子は .s です。

次はポインタを使ってみます。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  int *resp;
  resp = (int*)malloc(sizeof(int));

  /* ポインタの参照先への書き込み */
  __asm__ volatile("movl $123,(%%rax)"  /* 32 ビットデータを mov する (レジスタ間接) */
                  :                     /* 出力オペランドはございません */
                  :"a"(resp));          /* 最初に resp 変数の値 (つまりメモリアドレス)
                                           を rax に代入する */
  printf("%d\n", *resp);

  return 0;
}

次は stos を実行してみました。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  char *str;
  str  = (char *)malloc(sizeof(char) * 100);

  /*
   * インラインアセンブラの内容はコンパイルされたソーフファイルにほぼそのまま
   * 挿入されるので、あらかじめ改行とタブを適当に付け足しておきます。
   */

  /* stos (STOre String) 命令のテスト */
  __asm__ volatile(        "movb    $65, %%al    \n"  /* 'A' == 65 */
                   "        movw    $26, %%cx    \n"  /* 26 回繰り返す */
                   "lp:     stosb                \n"  /* stos は rdi をインクリメントする */
                   "        inc     %%al         \n"
                   "        dec     %%cx         \n"
                   "        jnz     lp           \n"  /* Zero フラグがリセットならジャンプ */
                   "        movb    $0, (%%rdi)"      /* C の文字列は Null 終端なので */
                  :                                   /* 出力オペランドはございません */
                  :"D"(str)                           /* 最初に str 変数の値 (つまりメモリの
                                                         アドレス) を rdi に代入する */
                  :"%rax","%rcx","%rdi");             /* このインラインアセンブラが rax, rcx,
                                                         rdi の内容を破壊することを明示します */
  printf("%s\n", str);

  return 0;
}

次は printf をアセンブラから呼び出してみました。

/* Mac 用どす */
#include <stdio.h>

int main(int argc, char *argv[])
{
  char *format = "%s\n";
  char *hello  = "Hello, world!";

  __asm__ volatile("call    _printf"
                  :
                  :"D" (format), "S" (hello));
  return 0;
}

なんと!x64 アーキテクチャでは、C の関数呼び出しの規約 (ABI) が x86 とは大幅に異なるのです!x64 では r8 から r15 という8つの汎用レジスタが追加されたので、Linux や Mac OS X が採用している System V AMD64 ABI においては引数をレジスタに入れておくことになったのです。第一引数から順に rdi, rsi, 云云かんぬんとなっているようです (http://homepage1.nifty.com/herumi/prog/x64.html#GCC64). x86 では引数をスタックに置いてました (CDECL)。call から戻ってきたらスタックポインタを元に戻す操作が必要でしたが、x64 では必要ないんですね。すごい!

汎用レジスタがたくさんあるって素敵なことですね。x64 を見た後に 6502 とかを改めて見ると衝撃的です。

(コウヅ)

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中