LLVM を Windows で使う(EXE ファイル生成編)

2012年08月21日(火)23時58分

実行ファイルを得る

前回「LLVM を Windows で使う(インタプリタ編)」の続きです。
要約しますと、「オリジナルな自作プログラミング言語を作る」という妄想にリアリティを持たせるため、コンパイラ基盤である LLVM について調べています、という内容です。
で、前回は「LLVM アセンブリ言語で書かれた TEXT ファイルを i386 用の機械語バイナリである OBJECT ファイルに変換する」というところまですすめました。
今回は、その次の段階である「OBJECT ファイルとライブラリをリンクして EXE ファイルを生成する」という部分に着手します。
そして、この部部はどうやら現状の LLVM ではできないようなので、他のツール(GCC)の力を借りて行うことにしました。

ポータブル

前回の繰り返しにもなりますが、一応方針として、なるべく必要なファイルだけを抜き出し、一つのフォルダ配下に固めて、丸ごと持ち運べる(ポータブル)ような環境の構築を目指します。
…という予定ではありますが、実際のところ「LinuxGCC 使ったことがない人間」が四苦八苦し、「とにかく LLVM アセンブリを何とかして EXE ファイルにする」ところまではこぎつけた、というだけの記録ですので、間違いや問題のあるやり方を多く含むかもしれません。
少なくとも、そのあたりの知識を十分に持ち、「Win 上で LLVM の本格的な開発環境を構築・運用したい」というような方が読んで役に立つものでは、…多分ないと思います。

MinGW と ld.exe

ともかく「リンカ」と「ライブラリ」が必要なわけです。
Visual C++ をインストールしてあれば、それに付属の「link.exe」を使う手もあるのですが、それですと「ポータブル」になりませんので、今回は MinGW というツールに含まれる「ld.exe」を使用します。
MinGW というのは、GCC で Windows アプリケーションを開発するために作られたもので、リンカ「ld.exe」はもちろん、Win32API 用のヘッダファイルやライブラリなども含まれています。
そもそも、LLVM の Windows 版自体がこの MinGW との連携を前提に作られているため、「ld.exe」を使うのが正攻法?ともいえます。
ということで、以下しばらくは導入に関する説明が長々と続きますが、基本的には前回終了時の状態から、「U:\LLVM\bin」フォルダに「ld.exe」をコピーし、「U:\LLVM\lib」フォルダにライブラリ(拡張子「.a」のファイル群)を集める作業となっています。

MinGW とインストーラ

で、その MinGW ですが、それ自体「ポータブル」なツールの集合体であるものの、インストーラがあり、それを利用して一度インストールした方が圧倒的に楽、というより使わないと結構きつい作業になります。
というのも、MinGW の「Min」は「Minimalist 」の「Min」で、これは最小主義、つまり使う側が使いたいもののみを選択できるようなシステムとなっており、それは非常に自分の好みのスタイルでもあるのですが、そのために内部的に細かく分割されているのです。
そのため、インストーラなしで集めるとなると、自分に必要なファイルがどのパッケージに含まれるのかを調べ、それらを個別にダウンロードし、そこから必要なファイルを取り出し、それを適切なフォルダに格納していく…、という非常に骨の折れる作業になります。

MinGW をインストールする場合

ということで、まずは MinGW をインストールする場合について説明します。
公式サイトは「http://www.mingw.org/」で、ダウンロードは「http://sourceforge.net/projects/mingw/files/」からとなります。
「Installer」のフォルダを手繰っていかずとも、おそらくトップページに、最新バージョンのインストーラである「mingw-get-inst-XXXXXXXX.exe」の直リンクがあると思いますので、それをダウンロードして実行します。
インストール先は、今回の目的だけであればどこでも構わないのですが、後で述べる「clang」のことを考えると、できれば「C:\MinGW」がいいと思います(以下「C:\MinGW」にインストールしてという前提で話を進めます)。
インストールするパッケージ(Components 画面で選択するもの)は「C Compiler」と「C++ Compiler」の二つで十分です(「C Compiler」は標準で強制選択ですが)。

必要ファイルの収集

MinGW をインストールした場合、必要ファイルの収集作業は非常に簡単です。
まずは前回の作業で「U:\LLVM\bin」フォルダに「llvm-as.exe」「lli.exe」「llc.exe」の三つが入っている状態からのスタートと仮定します。
作業内容は「U:\LLVM\bin」フォルダに「C:\MinGW\bin」フォルダから「ld.exe」をコピーし、「U:\LLVM」フォルダに「C:\MinGW\lib」フォルダを丸ごと(「lib」フォルダごと)コピーするだけです。

MinGW をインストールしない場合

前述のとおり、MinGW をインストールしない場合には、リンカ「ld.exe」とライブラリ(拡張子「.a」のファイル群)が含まれるアーカイブを個別に集めます。
ということで、ますは「http://sourceforge.net/projects/mingw/files/」から、「MinGW → Base」と辿り、以下の6ファイルをダウンロードします。

  • binutils-2.22-1-mingw32-bin.tar.lzma
  • pthreads-w32-2.9.0-mingw32-pre-20110507-2-dev.tar.lzma
  • mingwrt-3.20-mingw32-dev.tar.gz
  • w32api-3.17-2-mingw32-dev.tar.lzma
  • gcc-c++-4.6.2-1-mingw32-bin.tar.lzma
  • gcc-core-4.6.2-1-mingw32-bin.tar.lzma

「.lzma」と「Lhaz」と「.7z」

前述の MinGW のサイトで配布されている圧縮ファイルは、拡張子が「.lzma」という、Windows ではあまり見かけないものです。
自分は「Lhaz」という圧縮解凍ツールを使っているのですが、この「.lzma」には対応していませんでした。
しかし、少し調べてみると「.lzma」は「7-Zip」に関係がある云々という情報がありましたので、ためしに拡張子を「.lzma」から「.7z」に変更してみると、「Lhaz」でも解凍(展開)できました。
ということで、もしかすると他の解凍ツールでも、「.7z」に対応しているものであれば、拡張子変更でうまくいくかもしれません。

必要ファイルの収集

ということで、とりあえず前回の続き、「U:\LLVM\bin」に「llvm-as.exe」「lli.exe」「llc.exe」の三ファイルがある状態から、今回必要になるファイルを追加収集します。
まず、はじめに「U:\LLVM\lib\gcc\mingw32\4.6.2」フォルダを作っておきます。
あとは前項「MinGW をインストールしない場合」で集めた圧縮ファイルの中から、以下の必要ファイルを抜き出してコピーします。

binutils-2.22-1-mingw32-bin.tar.lzma

「U:\LLVM\bin」に「bin\ld.exe」をコピー。
「U:\LLVM\lib」に「lib」フォルダ内のファイル群をコピー。

pthreads-w32-2.9.0-mingw32-pre-20110507-2-dev.tar.lzma

「U:\LLVM\lib」に「lib」フォルダ内のファイル群をコピー。

mingwrt-3.20-mingw32-dev.tar.gz

「U:\LLVM\lib」に「lib」フォルダ内のファイル群をコピー。

w32api-3.17-2-mingw32-dev.tar.lzma

「U:\LLVM\lib」に「lib」フォルダ内のファイル群をコピー。

gcc-core-4.6.2-1-mingw32-bin.tar.lzma

「U:\LLVM\lib\gcc\mingw32\4.6.2」に「lib\gcc\mingw32\4.6.2」フォルダ内のフォルダとファイル群をコピー。

gcc-c++-4.6.2-1-mingw32-bin.tar.lzma

「U:\LLVM\lib\gcc\mingw32\4.6.2」に「lib\gcc\mingw32\4.6.2」フォルダ内の「debug」フォルダとファイル群をコピー。

バージョンに注意

これでやっと準備が整い、このあと本題である実行ファイルの生成を行うわけですが、その際、コマンドラインに上記作業で収集したファイルやフォルダを指定することがあります。
ただ、GCC はバージョンを混在できるようにするためか、フォルダ名にバージョン番号が含まれているのです。
つまり、今回の内容はバージョン 4.6.2 の GCC を前提に書いているため、フォルダ名に「4.6.2」という文字列が出現することになりますが、使用する GCC のバージョンが違う場合には、この部分を対象バージョンの文字列に読み替える必要があります。

hello world

ということで、まずは前回最初に作った「hello.s」を実行ファイル化してみます。
「hello.s」が「U:\LLVM\home」フォルダにある場合、以下の内容の「hello.bat」を実行すれば、「hello.exe」が生成され「hello world」が表示されます。

set PATH=..\bin
llc -filetype=obj -o hello.o hello.s
ld -L../lib -L../lib/gcc/mingw32/4.6.2 -o hello.exe ../lib/crt2.o ../lib/gcc/mingw32/4.6.2/crtbegin.o hello.o -lmingw32 -lgcc_eh -lgcc -lmoldname -lmingwex -lmsvcrt -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc_eh -lgcc -lmoldname -lmingwex -lmsvcrt ../lib/gcc/mingw32/4.6.2/crtend.o
hello.exe
pause

BAT ファイル二行目「llc -filetype=obj -o hello.o hello.s」 は、前回最後の説明のとおり、LLVM アセンブリ用コンパイラ「llc.exe」で i386 用機械語バイナリ「hello.o」を作っています。
三行目が、今回用意したリンカ「ld.exe」の使用部分です。 
まず、「-L」オプションでライブラリが置いてある場所を指定しています(二か所)。
次の「-o」オプションは出力ファイル名の指定です。
その次に「crt2.o」というファイルを指定していますが、これは多分、C言語の標準的な機能を詰め込んだCランタイムと呼ばれるものだと思います。
続く「crtbegin.o」は、おそらく末尾につける「crtend.o」とセットで、これからリンクする各種ライブラリの初期化処理と後始末を担当するような役割があるのではないかと思うのですが、付けなくとも何事もなく成功しますので、実はよくわかりません(GCC に自動でリンクを任せたときに付いていたのでそのままつけています)。
その後、今回作った「hello.o」を指定し、うしろに「-l」オプションでリンクするライブラリを大量に指定しています。
ただし、たかだか「hello world」を表示する程度のプログラムに、こんなにたくさんのライブラリは本来必要ないようで、実際には「-lmingw32」「-lkernel32」「-lmsvcrt」の三つを指定すれば動きます。
ということで、三行目は「ld -L../lib -L../lib/gcc/mingw32/4.6.2 -o hello.exe ../lib/crt2.o hello.o -lmingw32 -lkernel32 -lmsvcrt」にしても大丈夫でした。

「-l」オプションと「lib~.a」ファイル

「-l」オプションは、例えば「-lmingw32」とした場合、オプション文字列である「-l」を除くと「mingw32」を指定したことになるわけですが、この場合「ld.exe」は、指定された文字列の頭に「lib」、末尾に「.a」をつけた「libmingw32.a」というファイル名のライブラリを、「-L」オプションで指定されたフォルダから探してリンクするそうです。
余談ですが、そういえば MeCab の DLL が、なぜ「mecab.dll」ではなく「libmecab.dll」なのかと不思議に思っていたのですが、こういう事情があったのかと納得しました。

MassageBox

続いて Win32API を使って MessageBox を出すだけのプログラムです。
前回は、実行のために必要だった LLVM インタプリタ実行環境「lli.exe」の仕様に合わせて main 関数を置いていましたが、今回はその必要がないため WinMain 関数を使用しています。
その結果、生成された「MessageBox.exe」は、実行時にコンソール画面を出しません。
元になったC言語でのコードは以下になります。

#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) {
  MessageBox(NULL,"Text","Caption",MB_OK);
  return 0;
}

これを LLVM アセンブリにすると以下のようになります。

%struct.HINSTANCE__ = type { i32 }
%struct.HWND__ = type { i32 }
@.str = private unnamed_addr constant [5 x i8] c"Text\00", align 1
@.str1 = private unnamed_addr constant [8 x i8] c"Caption\00", align 1
define x86_stdcallcc i32 @WinMain(%struct.HINSTANCE__* %hInstance, %struct.HINSTANCE__* %hPrevInstance, i8* %lpCmdLine, i32 %nCmdShow) nounwind {
entry:
  %hInstance.addr = alloca %struct.HINSTANCE__*, align 4
  %hPrevInstance.addr = alloca %struct.HINSTANCE__*, align 4
  %lpCmdLine.addr = alloca i8*, align 4
  %nCmdShow.addr = alloca i32, align 4
  store %struct.HINSTANCE__* %hInstance, %struct.HINSTANCE__** %hInstance.addr, align 4
  store %struct.HINSTANCE__* %hPrevInstance, %struct.HINSTANCE__** %hPrevInstance.addr, align 4
  store i8* %lpCmdLine, i8** %lpCmdLine.addr, align 4
  store i32 %nCmdShow, i32* %nCmdShow.addr, align 4
  %call = call x86_stdcallcc i32 @MessageBoxA(%struct.HWND__* null, i8* getelementptr inbounds ([5 x i8]* @.str, i32 0, i32 0), i8* getelementptr inbounds ([8 x i8]* @.str1, i32 0, i32 0), i32 0)
  ret i32 0
}
declare x86_stdcallcc i32 @MessageBoxA(%struct.HWND__*, i8*, i8*, i32)

上記を「MessageBox.s」として保存し、以下の内容の「MessageBox.bat」を実行すると、「MessageBox.exe」が生成され、メッセージボックスが表示さるはずです。

set path=..\bin
llc -filetype=obj -o MessageBox.o MessageBox.s
ld --subsystem windows -L../lib -L../lib/gcc/mingw32/4.6.2 -o MessageBox.exe ../lib/crt2.o ../lib/gcc/mingw32/4.6.2/crtbegin.o MessageBox.o -lmingw32 -lgcc_eh -lgcc -lmoldname -lmingwex -lmsvcrt -lgdi32 -lcomdlg32 -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc_eh -lgcc -lmoldname -lmingwex -lmsvcrt ../lib/gcc/mingw32/4.6.2/crtend.o
MessageBox.exe
pause

BAT ファイルの内容は、前述「hello world」とほぼ同様で、「-l」オプションで付けている大量のライブラリ指定も、同じくかなり削り込めます。
キモは「--subsystem windows」で、これを指定することで実行時にコンソールが生成されなくなり、アプリケーションが WinMain 関数から開始されるようになります。

Window

次は何もない空のウインドウを一つだけ生成するサンプルです。
前回との違いは、前述 MassageBox と同じく、main 関数を WinMain 関数に置き換えたことです。
元になったC言語でのコードは以下の通り。

#include <windows.h>
LRESULT CALLBACK WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) {
  switch(msg) {
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return(DefWindowProc(hWnd,msg,wParam,lParam));
  }
  return 0L;
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) {
  HWND hWnd;
  WNDCLASS wc;
  MSG msg;
  wc.style=CS_HREDRAW|CS_VREDRAW;
  wc.lpfnWndProc=WndProc;
  wc.cbClsExtra=0;
  wc.cbWndExtra=0;
  wc.hInstance=hInstance;
  wc.hIcon=NULL;
  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
  wc.hbrBackground=GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName=NULL;
  wc.lpszClassName="ClassName";
  if(RegisterClass(&wc)==0)return FALSE;
  hWnd=CreateWindow(wc.lpszClassName,"WindowName",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);
  ShowWindow(hWnd,nCmdShow);
  UpdateWindow(hWnd);
  while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);}
  return (int)msg.wParam;
}

これを LLVM アセンブリにしたものが以下です。

%struct.HWND__ = type { i32 }
%struct.HINSTANCE__ = type { i32 }
%struct._WNDCLASSA = type { i32, i32 (%struct.HWND__*, i32, i32, i32)*, i32, i32, %struct.HINSTANCE__*, %struct.HICON__*, %struct.HICON__*, %struct.HBRUSH__*, i8*, i8* }
%struct.HICON__ = type { i32 }
%struct.HBRUSH__ = type { i32 }
%struct.tagMSG = type { %struct.HWND__*, i32, i32, i32, i32, %struct.tagPOINT }
%struct.tagPOINT = type { i32, i32 }
%struct.HMENU__ = type { i32 }
@.str = private unnamed_addr constant [10 x i8] c"ClassName\00", align 1
@.str1 = private unnamed_addr constant [11 x i8] c"WindowName\00", align 1
define x86_stdcallcc i32 @WndProc(%struct.HWND__* %hWnd, i32 %msg, i32 %wParam, i32 %lParam) nounwind {
entry:
  %retval = alloca i32, align 4
  %hWnd.addr = alloca %struct.HWND__*, align 4
  %msg.addr = alloca i32, align 4
  %wParam.addr = alloca i32, align 4
  %lParam.addr = alloca i32, align 4
  store %struct.HWND__* %hWnd, %struct.HWND__** %hWnd.addr, align 4
  store i32 %msg, i32* %msg.addr, align 4
  store i32 %wParam, i32* %wParam.addr, align 4
  store i32 %lParam, i32* %lParam.addr, align 4
  %0 = load i32* %msg.addr, align 4
  switch i32 %0, label %sw.default [
    i32 2, label %sw.bb
  ]
sw.bb:                                            ; preds = %entry
  call x86_stdcallcc void @PostQuitMessage(i32 0)
  br label %sw.epilog
sw.default:                                       ; preds = %entry
  %1 = load %struct.HWND__** %hWnd.addr, align 4
  %2 = load i32* %msg.addr, align 4
  %3 = load i32* %wParam.addr, align 4
  %4 = load i32* %lParam.addr, align 4
  %call = call x86_stdcallcc i32 @DefWindowProcA(%struct.HWND__* %1, i32 %2, i32 %3, i32 %4)
  store i32 %call, i32* %retval
  br label %return
sw.epilog:                                        ; preds = %sw.bb
  store i32 0, i32* %retval
  br label %return
return:                                           ; preds = %sw.epilog, %sw.default
  %5 = load i32* %retval
  ret i32 %5
}
declare x86_stdcallcc void @PostQuitMessage(i32)
declare x86_stdcallcc i32 @DefWindowProcA(%struct.HWND__*, i32, i32, i32)
define x86_stdcallcc i32 @WinMain(%struct.HINSTANCE__* %hInstance, %struct.HINSTANCE__* %hPrevInstance, i8* %lpCmdLine, i32 %nCmdShow) nounwind {
entry:
  %retval = alloca i32, align 4
  %hInstance.addr = alloca %struct.HINSTANCE__*, align 4
  %hPrevInstance.addr = alloca %struct.HINSTANCE__*, align 4
  %lpCmdLine.addr = alloca i8*, align 4
  %nCmdShow.addr = alloca i32, align 4
  %hWnd = alloca %struct.HWND__*, align 4
  %wc = alloca %struct._WNDCLASSA, align 4
  %msg = alloca %struct.tagMSG, align 4
  store %struct.HINSTANCE__* %hInstance, %struct.HINSTANCE__** %hInstance.addr, align 4
  store %struct.HINSTANCE__* %hPrevInstance, %struct.HINSTANCE__** %hPrevInstance.addr, align 4
  store i8* %lpCmdLine, i8** %lpCmdLine.addr, align 4
  store i32 %nCmdShow, i32* %nCmdShow.addr, align 4
  %style = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 0
  store i32 3, i32* %style, align 4
  %lpfnWndProc = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 1
  store i32 (%struct.HWND__*, i32, i32, i32)* @WndProc, i32 (%struct.HWND__*, i32, i32, i32)** %lpfnWndProc, align 4
  %cbClsExtra = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 2
  store i32 0, i32* %cbClsExtra, align 4
  %cbWndExtra = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 3
  store i32 0, i32* %cbWndExtra, align 4
  %0 = load %struct.HINSTANCE__** %hInstance.addr, align 4
  %hInstance1 = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 4
  store %struct.HINSTANCE__* %0, %struct.HINSTANCE__** %hInstance1, align 4
  %hIcon = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 5
  store %struct.HICON__* null, %struct.HICON__** %hIcon, align 4
  %call = call x86_stdcallcc %struct.HICON__* @LoadCursorA(%struct.HINSTANCE__* null, i8* inttoptr (i32 32512 to i8*))
  %hCursor = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 6
  store %struct.HICON__* %call, %struct.HICON__** %hCursor, align 4
  %call2 = call x86_stdcallcc i8* @GetStockObject(i32 0)
  %1 = bitcast i8* %call2 to %struct.HBRUSH__*
  %hbrBackground = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 7
  store %struct.HBRUSH__* %1, %struct.HBRUSH__** %hbrBackground, align 4
  %lpszMenuName = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 8
  store i8* null, i8** %lpszMenuName, align 4
  %lpszClassName = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 9
  store i8* getelementptr inbounds ([10 x i8]* @.str, i32 0, i32 0), i8** %lpszClassName, align 4
  %call3 = call x86_stdcallcc zeroext i16 @RegisterClassA(%struct._WNDCLASSA* %wc)
  %conv = zext i16 %call3 to i32
  %cmp = icmp eq i32 %conv, 0
  br i1 %cmp, label %if.then, label %if.end
if.then:                                          ; preds = %entry
  store i32 0, i32* %retval
  br label %return
if.end:                                           ; preds = %entry
  %lpszClassName5 = getelementptr inbounds %struct._WNDCLASSA* %wc, i32 0, i32 9
  %2 = load i8** %lpszClassName5, align 4
  %3 = load %struct.HINSTANCE__** %hInstance.addr, align 4
  %call6 = call x86_stdcallcc %struct.HWND__* @CreateWindowExA(i32 0, i8* %2, i8* getelementptr inbounds ([11 x i8]* @.str1, i32 0, i32 0), i32 13565952, i32 -2147483648, i32 -2147483648, i32 -2147483648, i32 -2147483648, %struct.HWND__* null, %struct.HMENU__* null, %struct.HINSTANCE__* %3, i8* null)
  store %struct.HWND__* %call6, %struct.HWND__** %hWnd, align 4
  %4 = load %struct.HWND__** %hWnd, align 4
  %5 = load i32* %nCmdShow.addr, align 4
  %call7 = call x86_stdcallcc i32 @ShowWindow(%struct.HWND__* %4, i32 %5)
  %6 = load %struct.HWND__** %hWnd, align 4
  %call8 = call x86_stdcallcc i32 @UpdateWindow(%struct.HWND__* %6)
  br label %while.cond
while.cond:                                       ; preds = %while.body, %if.end
  %call9 = call x86_stdcallcc i32 @GetMessageA(%struct.tagMSG* %msg, %struct.HWND__* null, i32 0, i32 0)
  %tobool = icmp ne i32 %call9, 0
  br i1 %tobool, label %while.body, label %while.end
while.body:                                       ; preds = %while.cond
  %call10 = call x86_stdcallcc i32 @TranslateMessage(%struct.tagMSG* %msg)
  %call11 = call x86_stdcallcc i32 @DispatchMessageA(%struct.tagMSG* %msg)
  br label %while.cond
while.end:                                        ; preds = %while.cond
  %wParam = getelementptr inbounds %struct.tagMSG* %msg, i32 0, i32 2
  %7 = load i32* %wParam, align 4
  store i32 %7, i32* %retval
  br label %return
return:                                           ; preds = %while.end, %if.then
  %8 = load i32* %retval
  ret i32 %8
}
declare x86_stdcallcc %struct.HICON__* @LoadCursorA(%struct.HINSTANCE__*, i8*)
declare x86_stdcallcc i8* @GetStockObject(i32)
declare x86_stdcallcc zeroext i16 @RegisterClassA(%struct._WNDCLASSA*)
declare x86_stdcallcc %struct.HWND__* @CreateWindowExA(i32, i8*, i8*, i32, i32, i32, i32, i32, %struct.HWND__*, %struct.HMENU__*, %struct.HINSTANCE__*, i8*)
declare x86_stdcallcc i32 @ShowWindow(%struct.HWND__*, i32)
declare x86_stdcallcc i32 @UpdateWindow(%struct.HWND__*)
declare x86_stdcallcc i32 @GetMessageA(%struct.tagMSG*, %struct.HWND__*, i32, i32)
declare x86_stdcallcc i32 @TranslateMessage(%struct.tagMSG*)
declare x86_stdcallcc i32 @DispatchMessageA(%struct.tagMSG*)

上記を「Window.s」として保存し、以下の内容の「Window.bat」を実行すると、「Window.exe」が生成され、空のウインドウが表示さるはずです。

set path=..\bin
llc -filetype=obj -o Window.o Window.s
ld --subsystem windows -L../lib -L../lib/gcc/mingw32/4.6.2 -o Window.exe ../lib/crt2.o ../lib/gcc/mingw32/4.6.2/crtbegin.o Window.o -lmingw32 -lgcc_eh -lgcc -lmoldname -lmingwex -lmsvcrt -lgdi32 -lcomdlg32 -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc_eh -lgcc -lmoldname -lmingwex -lmsvcrt ../lib/gcc/mingw32/4.6.2/crtend.o
Window.exe
pause

省サイズ EXE ファイル

Windows アプリケーションは、Win32API のみを使用して(あるいはC標準関数を使用せず)記述することで、Cランタイムのリンクを不要にし、劇的に小さいサイズの実行ファイルを作ることができます。
Visual C++ で「/NODEFAULTLIB」オプション、GCC で「-nostdlib」オプションを指定してできるもので、リンカ「ld.exe」に必要最低限のライブラリのみを指定することで作ります。
ここでは前述 MessageBox に相当する「Cランタイム不使用版」を作って比較してみます。
まずは元になったC言語のコードから。

#include <windows.h>
void WinMainCRTStartup() {
  MessageBox(NULL,"Text","Caption",MB_OK);
  ExitProcess(0);
}

これを以下のような LLVM アセンブリにします。

%struct.HWND__ = type { i32 }
@.str = private unnamed_addr constant [5 x i8] c"Text\00", align 1
@.str1 = private unnamed_addr constant [8 x i8] c"Caption\00", align 1
define void @WinMainCRTStartup() nounwind {
entry:
  %call = call x86_stdcallcc i32 @MessageBoxA(%struct.HWND__* null, i8* getelementptr inbounds ([5 x i8]* @.str, i32 0, i32 0), i8* getelementptr inbounds ([8 x i8]* @.str1, i32 0, i32 0), i32 0)
  call x86_stdcallcc void @ExitProcess(i32 0) noreturn
  unreachable
return:                                           ; No predecessors!
  ret void
}
declare x86_stdcallcc i32 @MessageBoxA(%struct.HWND__*, i8*, i8*, i32)
declare x86_stdcallcc void @ExitProcess(i32) noreturn

上記を「NoStdLib.s」として保存し、以下の内容の「NoStdLib.bat」を実行すると、MessageBox のときと同様に、メッセージボックスが表示さるはずです。

set path=..\bin
llc -filetype=obj -o NoStdLib.o NoStdLib.s
ld --subsystem windows --entry _WinMainCRTStartup -L../lib -L../lib/gcc/mingw32/4.6.2 -o NoStdLib.exe NoStdLib.o -lkernel32 -luser32
NoStdLib.exe
pause

ポイントは「--entry _WinMainCRTStartup」で、もともと「プログラムは main(または WinMain)関数から始まる」というのは、Cランタイムがその関数を探して最初に実行することによって実現されていますので、これを使用しない場合は、このようにして自分で開始関数を指定する必要があります。
なお、これで生成される「NoStdLib.exe」は 5KB 程度で、同じ機能の「MessageBox.exe」が 50KB ほどありますから、十分の一までダイエットできたことになります。
もっとも、本体の規模が大きくなってくればCランタイムのサイズなど誤差程度になってきますし、浮動小数点の扱いに難があったりと思わぬところに罠があったりもしますので、本当に小さな単機能のツールぐらいにしかおすすめはできませんが。

関連記事

【同じタグを付けた記事の一覧】
ソースコード プログラミング ポータブル C++ LLVM

スポンサーサイト

コメントの投稿

非公開コメント

最新記事
最新コメント
Amazonおまかせリンク
カテゴリ
タグクラウド
Amazonお買い得ウィジェット
カレンダー
04 | 2017/05 | 06
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31 - - -
月別アーカイブ
プロフィール

電脳太助

Author:電脳太助
Website:電脳スピーチ web

RSSリンクの表示
メールフォーム

名前:
メール:
件名:
本文:

サイト内検索
Ads by Google
FC2アクセスランキング
Ads by Google
FC2拍手ランキング
ユーザータグ

音楽管理(66)
ポータブル(57)
ソフト紹介(44)
プログラミング(42)
音声技術(41)
自作ソフト(35)
サイト運営(32)
FC2(31)
ブログ(30)
iTunes(27)
Windows(25)
LISMO(24)
音声合成(23)
音声認識(22)
x-アプリ(22)
電子ブック(22)
eラーニング(20)
バックアップ(19)
語学学習(19)
foobar2000(18)
ソースコード(18)
WindowsLiveWriter(15)
画像管理(15)
C++(14)
アフィリエイト(10)
DnspTools(10)
fi-6130(9)
FLAC(9)
JavaScript(9)
ウォークマン(9)
英語音読学習計画(8)
Gracenote(8)
Prolog(8)
ベクター(8)
雑記(8)
CodeBlocks(7)
SyntaxHighlighter(7)
TraConv(7)
spcbght(7)
wxWidgets(7)
VirtualBox(6)
W63CA(6)
DCP-J552N(6)
WinRT(6)
WindowsLiveMesh(6)
iGoinLM(6)
英語発音矯正実験(6)
ExactAudioCopy(6)
MP3Gain(6)
LAME(5)
音楽技術(5)
Mery(5)
楽器演奏(5)
GalateaTalk(4)
nLite(4)
WindowsLiveSkyDrive(4)
ホームページ(4)
GalateaProject(4)
MIDI(4)
LLVM(4)
PC-98(3)
カウンター(3)
AACGain(3)
iTCDini(3)
OverCutChecker(3)
拍手(3)
PK-513L(3)
UniversalExtractor(3)
アクセスランキング(3)
ImageCompositeEditor(2)
アクセス解析(2)
OCR(2)
qtaacenc(2)
資格試験(1)
AquesTalk(1)
AquesCmdDl(1)

FC2アクセスランキング
最新トラックバック
アクセスランキング
[ジャンルランキング]
コンピュータ
90位
アクセスランキングを見る>>

[サブジャンルランキング]
ソフトウェア
6位
アクセスランキングを見る>>
FC2カウンター