« Xbyakで始めるx86(IA-32)入門 | メイン | Xbyakで始めるx86(IA-32)入門(2-2) »

Xbyakで始めるx86(IA-32)入門(2-1)

前回言い忘れましたが,このシリーズの目標はLL魂2007デモコードのchaos.cpp程度のものを読めて理解できることを考えています.
具体的には

  • レジスタやスタックを理解する
  • 関数を作れる
  • SIMD命令の基礎を知る
  • JITのメリットを理解する

あたりを目指します.なお,JITアセンブラは通常のアセンブラに比べてワンクッション概念が必要になります.もしかしたらアセンブリ言語の本当の入門としては不適切かもしれませんが,まあご了承ください.

それでは,最初に他の言語と同様,"Hello Xbyak!"を表示してみましょう.

#include "xbyak/xbyak.h"
#include <stdio.h>
 
struct HelloGenerator : public Xbyak::CodeGenerator { // (A)
    HelloGenerator() // (B)
    {
        push((int)"Hello Xbyak!"); // (C1)
        call((int)puts); // (C2)
        add(esp, 4); // (C3)
        ret(); // (C4)
    }
};
 
int main()
{
    HelloGenerator hello; // (D)
    void (*code)() = (void (*)())hello.getCode(); // (E)
    code(); // (F)
    return 0;
}

VC系ではコンソールアプリとしてコンパイルしてください.
gccでは-fno-operator-namesをつけるのを忘れないでください.これはandやorを演算子ではなく関数として扱いたいためです.

> g++ t.cpp -g -fno-operator-names
> ./a.out
> Hello Xbyak!

# C++的には

#include <stdio.h>
int main(int argc, char *argv[])
{
    argc == 1 and puts("need option");
}

ってできたのご存じでした?

それはともかくコードの説明に入ります.Xbyakでは実行時に命令をアセンブルしてバイトコード(以下マシン語と呼ぶことにします)を生成します.それを生成するためのクラスがXbyak::CodeGeneratorであり,これを継承する(A)ことでそのクラス内でx86の殆どの命令を記述できるようになります.

今後も繰り返すことになるかと思いますが,コンパイル時ではなく実行時であることに注意してください.ここではHelloGeneratorのコンストラクタにXbyak命令(面倒なので以後asmと呼びます)を記述している(B)ので,main()内のインスタンスhelloが生成されたとき(D)にアセンブルされます.

アセンブルされたマシン語はgetCode()メソッドで取得できます(E).この返り値は生成されたマシン語へのポインタですので,適当な関数ポインタ(詳細は後述)にキャストし,その関数を呼び出すことでマシン語を実行します(F).

以上がXbyakを使った場合の大まかな流れです.

続いて実行時の挙動を追いかけてみましょう.

VC系ではDebugモードでコンパイルし,(F)のところにブレークポイントを置いてデバッグ実行します.停止したところでVC6なら[表示]→[デバッグウィンドウ]→[混合モード],VC8なら[デバッグ]→[ウィンドウ]→[逆アセンブル]で

19:       code();
00401696 8B F4                mov         esi,esp
00401698 FF 95 F4 FE FF FF    call        dword ptr [ebp-10Ch]
0040169E 3B F4                cmp         esi,esp
004016A0 E8 2B 14 01 00       call        __chkesp (00412ad0)

というようなウィンドウに入ってください.[F10]を一度押し,callのところで[F11]を押すと

003730A4 68 B0 01 44 00       push        offset string "Hello Xbyak!" (004401b0)
003730A9 E8 62 FA 09 00       call        puts (00412b10)
003730AE 83 C4 04             add         esp,4
003730B1 C3                   ret

が表示されると思います.左端の数値は違うかもしれません.意味も今は分からなくて結構です.ただ上記サンプルの(C1)~(C4)に対応していることを確認してください.

gccの場合はgdbを使っておっかけましょう.まず起動してgetCode()のところでブレークポイントを置きます.

>gdb ./a.out
>b HelloGenerator.getCode
Breakpoint 1 at 0x804b2b6: file xbyak/xbyak.h, line 1307.

実行した後,[n]と[Enter]でステップ実行し,code()の直前まで進みます.

(gdb) r
Starting program: /home/shigeo/Program/xbyak/a.out
Breakpoint 1, Xbyak::CodeGenerator::getCode (this=0xbffff2e0) at xbyak/xbyak.h:1307
1307                    assert(!hasUndefinedLabel());
(gdb) n
1309                    return top_;
(gdb)([Enter]を押す)
1310            } ([Enter]を押す)
(gdb) ([Enter]を押す)
main () at t.cpp:19
19              code();

ここでコードがどうなっているか見ます.その前にデバッグに見やすいaliasを作っておきます.

(gdb) define al
Type commands for definition of "al".
End with a line saying just "end".
>x/11i $pc
>end

と定義してみてください.

(gdb) al
0x8048904 <main+52>:    mov    eax,DWORD PTR [ebp-12]
0x8048907 <main+55>:    call   eax
0x8048909 <main+57>:    mov    ebx,0x0
0x804890e <main+62>:    lea    eax,[ebp-0x108]
0x8048914 <main+68>:    mov    DWORD PTR [esp],eax
0x8048917 <main+71>:    call   0x8049c2c <~HelloGenerator>
0x804891c <main+76>:    mov    DWORD PTR [ebp-0x10c],ebx
0x8048922 <main+82>:    jmp    0x8048952 <main+130>
0x8048924 <main+84>:    mov    DWORD PTR [ebp-0x110],eax
0x804892a <main+90>:    mov    ebx,DWORD PTR [ebp-0x110]
0x8048930 <main+96>:    lea    eax,[ebp-0x108]

siを使ってasmレベルでのステップ実行をします.

(gdb) si
0x08048907      19              code();
(gdb) ([Enter]を押す)
0x0804c190 in ?? ()

call eaxが実行された瞬間からXbyakが生成したマシン語に突入しています.alで確認しましょう.

(gdb) al
0x804c194:      push   0x804b44b
0x804c199:      call   0x8048734 <puts@plt>
0x804c19e:      add    esp,0x4
0x804c1a1:      ret

やはり上記サンプルの(C1)~(C4)に対応していることを確認してください.
うっかり行き過ぎたら,最初からやり直してマシン語を一行ずつ実行する感じをつかんでください.gdbを使うときはアセンブラで遊ぶ時に便利なgdb設定を参考にすると便利だと思います.上記alもこのリンク先の.gdbinitで定義されているのを拝借しました.

今回はXbyakの基本的な書き方と実行時に確認する方法の説明をしました.長くなりましたので一端区切ります.次はコードの説明に入ります.

コメント (2)

っき:

Intel Mac(Jan 2007) + Mac OS X 10.4.10 + Xcode Tools 2.4.1 の環境で試してみましたが、いくつかハマりましたので報告します。

1. サンプルのcalcのビルドで下記のエラーが発生。
calc.cpp:31:33: error: boost/spirit/core.hpp: No such file or directory
どうやら、別途ライブラリが必要な様子ですが、とりあえず無視しました。

2. alコマンドがない。
小一時間程調べて "x/11i $pc" のマクロらしい事は何となく分かりました。
差し障りがなければ、~/.gdbinit を公開して頂けると助かります。

3. 下記の行ですが、
(gdb)#([Enter]を押す)
1310 } ([Enter]を押す)
(gdb) ([Enter]を押す)
これは多分、
(gdb) ([Enter]を押す)
1310 }
(gdb) ([Enter]を押す)
ですよね?

以上、よろしくお願い致します。

へるみ:

>別途ライブラリが必要な様子ですが
calc.cppのコンパイルにはboostライブラリ(http://www.boost.org/)が必要です.書き忘れて申し訳ありません.
>2. alコマンドがない。
こちらもすいません.本文後半にある「アセンブラで遊ぶ時に便利なgdb設定」に書かれている.gdbinitをお使いください.

3. そうです.
ご指摘どうもありがとうございました.

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)