フォーム上には、ソースを入力するテキストボックスと、コンパイルを実行するコマンドボタンがある。テキストボックスに、以下のソースを入力してコマンドボタンを押下すると、test.exeという実行ファイルが作成される。
let a 1 let b 2 add a b disp a
このコンパイラが受け付ける言語は、英小文字一字の変数、数値、三つの命令からなる。let命令は、変数に値を代入する。add文は指定された一つ目の変数に二つ目の変数の値が加算される。disp文は、指定された変数の値をメッセージボックスで表示する。
上記のソースでは、まず変数aに1を、変数bに2を代入し、次にaにbの値を足す。従ってaの値は1+2=3となり、最後のdisp文でaの値である3を表示する。
作成されるexeは、コードセクション、初期化データセクション、インポートセクションを含む。インポートセクションでは、kernel32.dllからExitProcess、user32.dllからMessageBoxAとwsprintfAをインポートしている。初期化データセクションは、メッセージボックス用のタイトル、メッセージボックスに表示する際にwsprintf用に使用するフォーマット文字列、wsprintfの結果を受け取るバッファ、及び変数値の格納用エリアからなる。
'------------------------ Module1.bas ------------------------
Dim DataSize As Long
Public Sub MakeExe(CodeData As String)
Dim i As Long
Dim CodeSize As Long
Dim AlCodeSize As Long
Open App.Path & "\test.exe" For Binary Access Write As #2
CodeData = Trim(CodeData)
CodeSize = (Len(CodeData) + 1) / 3
AlCodeSize = ((CodeSize + &H1FF) \ &H200) * &H200
'stub(そこらのEXEのを適当に貼り付けただけ)
'stub1
PutData "4d 5a 50 00 02 00 00 00 04 00 0f 00 ff ff 00 00"
PutData "b8 00 00 00 00 00 00 00 40 00 1a 00 00 00 00 00"
PutData "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
PutData "00 00 00 00 00 00 00 00 00 00 00 00"
PutData "00 01 00 00" 'PEヘッダ位置 = 100h
'stub2
PutData "ba 10 00 0e 1f b4 09 cd 21 b8 01 4c cd 21 90 90"
PutData "54 68 69 73 20 70 72 6f 67 72 61 6d 20 6d 75 73"
PutData "74 20 62 65 20 72 75 6e 20 75 6e 64 65 72 20 57"
PutData "69 6e 33 32 0d 0a 24 37 00 00 00 00 00 00 00 00"
PutPadding &H100
'header
PutData "50 45 00 00" 'PEシグネチャ = "PE\0\0"
'標準COFFヘッダ
PutData "4c 01" 'マシンタイプ = I386
PutData "03 00" 'セクション数 = 3
PutData "00 00 00 00" '作成日時
PutData "00 00 00 00" 'COFFシンボルテーブル位置 = 0
PutData "00 00 00 00" 'シンボルテーブルエントリ数 = 0
PutData "E0 00" 'オプションヘッダサイズ = e0h
PutData "0F 03" '特性
'オプションヘッダ - 標準フィールド
PutData "0b 01" 'イメージファイル = 通常の実行ファイル
PutData "01 00" 'リンカのバージョン = 1.0
PutLong AlCodeSize 'コードセクションのサイズ = AlCodeSize
PutData "00 02 00 00" '初期化データセクションのサイズ = 200h
PutData "00 00 00 00" '未初期化セクションのサイズ = 0
PutData "00 00 01 00" '開始アドレス = 10000h
PutData "00 00 01 00" 'コードセクションのRVA = 10000h
PutData "00 00 00 00" 'データセクションのRVA = 30000h
'オプションヘッダ - Windows固有フィールド
PutData "00 00 40 00" 'イメージがメモリにロードされるアドレス = 400000h
PutData "00 00 01 00" 'メモリロード時のセクションのアラインメント = 10000h
PutData "00 02 00 00" 'ファイル中のセクションのアラインメント = 512
PutData "01 00 00 00" '必要なOSのバージョン = 1.0
PutData "00 00 00 00" 'イメージのバージョン = 0.0
PutData "04 00 00 00" 'サブシステムのバージョン = 4.0
PutData "00 00 00 00" 'Reserved
PutData "00 00 04 00" 'イメージのサイズ = 40000h
PutData "00 04 00 00" 'スタブ+PEヘッダ+セクションヘッダのサイズ = 400h
PutData "00 00 00 00" 'チェックサム = 0
PutData "02 00" 'サブシステム = GUI
PutData "00 00" 'DLL特性 = 0
PutData "00 00 10 00" '予約スタックサイズ = 100000h
PutData "00 20 00 00" 'コミットスタックサイズ = 2000h
PutData "00 00 10 00" '予約ヒープサイズ = 100000h
PutData "00 10 00 00" 'コミットヒープサイズ = 1000h
PutData "00 00 00 00" 'ローダフラグ = 0
PutData "10 00 00 00" 'データディクショナリエントリ数 = 16
'データディクショナリ
PutData "00 00 00 00 00 00 00 00" 'エクスポートテーブル無し
PutData "00 00 02 00 00 02 00 00" 'インポートテーブル = 20000h, Size = 200h
PutData "00 00 00 00 00 00 00 00" 'リソーステーブル無し
PutData "00 00 00 00 00 00 00 00" '例外テーブル無し
PutData "00 00 00 00 00 00 00 00" '属性認証テーブル無し
PutData "00 00 00 00 00 00 00 00" 'ベース再配置テーブル無し
PutData "00 00 00 00 00 00 00 00" 'デバッグデータ無し
PutData "00 00 00 00 00 00 00 00" 'アーキテクチャ固有データ無し
PutData "00 00 00 00 00 00 00 00" 'グローバルポインタレジスタ無し
PutData "00 00 00 00 00 00 00 00" 'スレッドローカルストレージテーブル無し
PutData "00 00 00 00 00 00 00 00" 'ロードコンフィギュレーションテーブル無し
PutData "00 00 00 00 00 00 00 00" 'バウンドインポートテーブル無し
PutData "00 00 00 00 00 00 00 00" 'インポートアドレステーブル無し
PutData "00 00 00 00 00 00 00 00" '遅延インポート記述子無し
PutData "00 00 00 00 00 00 00 00" 'Reserved
PutData "00 00 00 00 00 00 00 00" 'Reserved
'セクションテーブル - コードセクションヘッダ
PutData "2e 74 65 78 74 00 00 00" 'セクション名 = ".text"
PutLong AlCodeSize 'メモリ上でのセクションサイズ = AlCodeSize
PutData "00 00 01 00" 'メモリ上でのセクションの先頭のRVA = 10000h
PutLong AlCodeSize 'ファイル上のセクションサイズ = AlCodeSize
PutData "00 08 00 00" 'ファイル上のセクションの位置 = 800h
PutData "00 00 00 00" 'セクションの再配置エントリへのファイルポインタ = 実行イメージは無し
PutData "00 00 00 00" '行番号エントリ無し
PutData "00 00" 'セクション内の再配置エントリの数 = 実行イメージは無し
PutData "00 00" 'セクションの行番号エントリの数 = 0
PutData "20 00 00 60" ' 特性
'セクションテーブル - インポートセクションヘッダ
PutData "2E 69 64 61 74 61 00 00" 'セクション名 = ".idata"
PutData "9e 00 00 00" 'メモリ上でのセクションサイズ = 9eh
PutData "00 00 02 00" 'メモリ上でのセクションの先頭のRVA = 20000h
PutData "00 02 00 00" 'ファイル上のセクションサイズ = 200h
PutData "00 04 00 00" 'ファイル上のセクションの位置 = 400h
PutData "00 00 00 00" 'セクションの再配置エントリへのファイルポインタ = 実行イメージは無し
PutData "00 00 00 00" '行番号エントリ無し
PutData "00 00" 'セクション内の再配置エントリの数 = 実行イメージは無し
PutData "00 00" 'セクションの行番号エントリの数 = 0
PutData "40 00 00 C0" ' 特性
'セクションテーブル - データセクションヘッダ
PutData "2e 64 61 74 61 00 00 00" 'セクション名 = ".data"
PutData "00 02 00 00" 'メモリ上でのセクションサイズ = 200h
PutData "00 00 03 00" 'メモリ上でのセクションの先頭のRVA = 30000h
PutData "00 02 00 00" 'ファイル上のセクションサイズ = 200h
PutData "00 06 00 00" 'ファイル上のセクションの位置 = 600h
PutData "00 00 00 00" 'セクションの再配置エントリへのファイルポインタ = 実行イメージは無し
PutData "00 00 00 00" '行番号エントリ無し
PutData "00 00" 'セクション内の再配置エントリの数 = 実行イメージは無し
PutData "00 00" 'セクションの行番号エントリの数 = 0
PutData "40 00 00 C0" ' 特性
PutPadding &H400
'.idata
'インポートディレクトリテーブル - kernel32.dll
PutData "3C 00 02 00" 'インポートルックアップテーブルのRVA = 2003ch
PutData "00 00 00 00" '(結合後に使用される)
PutData "00 00 00 00" '最初のフォワーダ参照のインデックス = 0
PutData "50 00 02 00" 'DLL名のRVA = 20050h
PutData "70 00 02 00" 'インポートアドレステーブルのRVA = 20070h
'インポートディレクトリテーブル - user32.dll
PutData "44 00 02 00" 'インポートルックアップテーブルのRVA = 20044h
PutData "00 00 00 00" '(結合後に使用される)
PutData "00 00 00 00" '最初のフォワーダ参照のインデックス = 0
PutData "60 00 02 00" 'DLL名のRVA = 20060h
PutData "78 00 02 00" 'インポートアドレステーブルのRVA = 20078h
'ヌルディレクトリエントリ
PutData "00 00 00 00"
PutData "00 00 00 00"
PutData "00 00 00 00"
PutData "00 00 00 00"
PutData "00 00 00 00"
'kernel32 ルックアップテーブル
PutData "84 00 02 00" 'ExitProcessの名前のRVA = 20084h
PutData "00 00 00 00" '終端のNULL
'user32 ルックアップテーブル
PutData "92 00 02 00" 'MessageBoxAの名前のRVA = 20092h
PutData "a0 00 02 00" 'wsprintfAの名前RVA = 200a0h
PutData "00 00 00 00" '終端のNULL
'DLL名
PutData "4b 45 52 4e 45 4c 33 32 2e 64 6c 6c 00 00 00 00" 'kernel32名 = "KERNEL32.dll"
PutData "55 53 45 52 33 32 2e 64 6c 6c 00 00 00 00 00 00" 'user32名 = "USER32.dll"
'kernel32 インポートアドレステーブル
PutData "84 00 02 00" 'ExitProcessのヒント/名前のRVA = 20084h
PutData "00 00 00 00" '終端のNULL
'user32 インポートアドレステーブル
PutData "92 00 02 00" 'MessageBoxAのヒント/名前のRVA = 20092h
PutData "a0 00 02 00" 'wsprintfAのヒント/名前RVA = 200a0h
PutData "00 00 00 00" '終端のNULL
'kernel32 ヒント/名前テーブル(ドキュメントではヒント=4バイトとなっているが…)
PutData "71 00" 'ExitProcessのヒント = 71h
PutData "45 78 69 74 50 72 6F 63 65 73 73 00" 'ExitProcessの名前 = "ExitProcess"
PutData "76 01" 'MessageBoxAのヒント = 176h
PutData "4d 65 73 73 61 67 65 42 6f 78 41 00" 'MessageBoxAの名前 = "MessageBoxA"
PutData "2d 02" 'wsprintfAのヒント = 22dh
PutData "77 73 70 72 69 6e 74 66 41 00" 'wsprintfAの名前 = "wsprintfA"
PutPadding &H600
'.data
PutData "4d 65 73 73 61 67 65 00 00 00 00 00 00 00 00 00" '"Message"
PutData "25 64 00 00 00 00 00 00 00 00 00 00 00 00 00 00" '"%d"
PutData "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" 'wsprintf用バッファ
PutPadding &H800 '.data+100h以降は変数用とする
'.text
PutData CodeData
PutPadding &H800 + AlCodeSize
Close #2
End Sub
Public Function GetLongStr(L As Long) As String
Dim S As String
S = Right("00000000" + Hex(L), 8)
S = Mid(S, 7, 2) + " " + Mid(S, 5, 2) + " " + Mid(S, 3, 2) + " " + Mid(S, 1, 2)
GetLongStr = S
End Function
Public Sub PutData(S As String)
Dim i As Long
Dim B As Byte
For i = 1 To Len(S) Step 3
B = Val("&h" + Mid(S, i, 2))
Put #2, , B
DataSize = DataSize + 1
Next
End Sub
Public Sub PutLong(L As Long)
Dim S As String
PutData GetLongStr(L)
End Sub
Public Sub PutPadding(Offset As Long)
Dim i As Long
While DataSize < Offset
PutData "00"
Wend
End Sub
'------------------------ Form1.frm ------------------------
Public Function GetToken(S As String, ByRef P As Long) As String
Dim i As Long
If P > Len(S) Then
GetToken = ""
Exit Function
End If
While Mid(S, P, 1) = " " Or Mid(S, P, 1) = vbCr Or Mid(S, P, 1) = vbLf
P = P + 1
If P > Len(S) Then
GetToken = ""
Exit Function
End If
Wend
i = P
While Mid(S, P, 1) <> " " And Mid(S, P, 1) <> vbCr And Mid(S, P, 1) <> vbLf
P = P + 1
If P > Len(S) Then
GetToken = Mid(S, P)
Exit Function
End If
Wend
GetToken = Mid(S, i, P - i)
End Function
Private Sub Command1_Click()
Dim Src As String
Dim Token As String
Dim P As Long
Dim Code As String
Src = Text1.Text
P = 1
Do
Token = GetToken(Src, P)
If Token = "" Then Exit Do
Select Case Token
Case "let"
Code = Code + "ba " + GetLongStr(&H430100 + (Asc(GetToken(Src, P)) - Asc("a")) * 4) + " "
Code = Code + "c7 02 " + GetLongStr(CLng(GetToken(Src, P))) + " "
Case "add"
Code = Code + "ba " + GetLongStr(&H430100 + (Asc(GetToken(Src, P)) - Asc("a")) * 4) + " "
Code = Code + "a1 " + GetLongStr(&H430100 + (Asc(GetToken(Src, P)) - Asc("a")) * 4) + " "
Code = Code + "01 02 "
Case "disp"
Code = Code + "a1 " + GetLongStr(&H430100 + (Asc(GetToken(Src, P)) - Asc("a")) * 4) + " "
Code = Code + "50 "
Code = Code + "68 10 00 43 00 "
Code = Code + "68 20 00 43 00 "
Code = Code + "a1 7c 00 42 00 "
Code = Code + "ff d0 "
Code = Code + "83 c4 0c "
Code = Code + "68 00 00 00 00 "
Code = Code + "68 00 00 43 00 "
Code = Code + "68 20 00 43 00 "
Code = Code + "68 00 00 00 00 "
Code = Code + "a1 78 00 42 00 "
Code = Code + "ff d0 "
End Select
Loop
Code = Code + "68 00 00 00 00 "
Code = Code + "a1 70 00 42 00 "
Code = Code + "ff d0 "
MakeExe Code
MsgBox "Complete."
End Sub