Top / CompilerLib

コンパイラ作成を支援するライブラリです。ライブラリはC#のマネージドコードですが、出力するのはネイティブバイナリです。(関連日記

コンパイラの内部処理用ライブラリを想定しているため、言語処理系を含んでいません。テキストではなくオブジェクトを渡します。アセンブリをコードで組み立てて出力するというイメージです。

以下のC言語のソースと同等のものを作成します。

#include <windows.h>

int main()
{
    HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE);
    const char *hello = "Hello, World!\r\n";
    DWORD dummy;
    WriteConsole(stdout, hello, strlen(hello), &dummy, NULL);
    return 0;
}

CompilerLibを利用したコードは以下のようになります。

   Module module = new Module();
   List<OpCode> c = new List<OpCode>();

   const int STD_OUTPUT_HANDLE = -11;
   Function GetStdHandle = module.GetFunction(CallType.Std, "kernel32.dll", "GetStdHandle");
   Function WriteConsole = module.GetFunction(CallType.Std, "kernel32.dll", "WriteConsoleW");
   Function ExitProcess = module.GetFunction(CallType.Std, "kernel32.dll", "ExitProcess");

   // HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE);
   IntAddr32 stdout = new IntAddr32(module.GetInt32("stdout"));
   c.AddRange(GetStdHandle.Invoke(STD_OUTPUT_HANDLE));
   c.Add(I386.Mov(stdout, Reg32.EAX));

   Ref<uint> dummy = module.GetInt32("dummy");
   string hello = "Hello, World!\r\n";
   c.AddRange(WriteConsole.Invoke(stdout, hello, hello.Length, dummy, 0));
   c.AddRange(ExitProcess.Invoke(0));

   module.Text.OpCodes = c.ToArray();
   module.Link("output.exe");

変数

Moduleインスタンスからグローバル変数のポインタを取得するのがModule.GetInt32()です。ポインタが指す値はIntAddr32クラスを通して扱います。C++と対比させたイメージを示します。

【C++】
extern int g_abc;
int *p_abc = &g_abc;
int &abc = *p_abc;

【CompilerLib】
Ref<uint> p_abc = module.GetInt32("g_abc");
IntAddr32 abc = new IntAddr32(p_abc);

アセンブラレベルでは、p_abcがEDXに割り当てられた場合、abcは[EDX]に相当します。アセンブリの[]で括ったアドレッシングがnew IntAddr32()に相当します。

【アセンブリ】 EDX → [EDX]
【C言語】 edx → *edx
【CompilerLib】 edx → new IntAddr32(edx)

この操作はCompilerLibでオブジェクト化されたアセンブリに反映されます。

// mov EAX, [0x12345678]
I386.Mov(Reg32.EAX, new IntAddr32(0x12345678));
// mov EDX, 0x12345678
I386.Mov(Reg32.EDX, 0x12345678);
// mov EAX, [EDX]
I386.Mov(Reg32.EAX, RegAddr32.EDX);

最後の例で[EDX]がnew IntAddr32(Reg32.EDX)のように対称形になっていないのは、オペランドに許されている組み合わせがレジスタ経由と即値では異なるためです。別の型になっていればI386.Mov()のオーバーロードで表現することができます。たとえばmov [0x12345678], 0x12345678のような組み合わせをコンパイル時に排除する一方で、mov [EDX], 0x12345678のような組み合わせを許容することができます。

今後の課題

  1. アセンブラの充実
    • サンプルで使っているニーモニックとオペランドの組み合わせしかサポートされていません。まともに使うには整備する必要があります。
  2. ラベル
    • CompilerLibではアセンブリのラベルが扱えないため、条件分岐やループができません。オブジェクトベースで表現できるようにする必要があります。

コメント



トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS