VBでEXEを作成する

後述のサンプルは、VBで作成した簡単なコンパイラの例である。

フォーム上には、ソースを入力するテキストボックスと、コンパイルを実行するコマンドボタンがある。テキストボックスに、以下のソースを入力してコマンドボタンを押下すると、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
(original text:1999/05/31 更新)

本ドキュメントの内容は保証しません。本ドキュメントによって生じた結果について、一切の責任を負いません。