【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

EXEを作る (4)

id:n7shi:20110421の続きです。リフレクションを用いて、クラスの中のクラスを追えるようにします。

Function writeAny%(image As Byte(), pos%, v As Object)
    If TypeOf v Is Byte Then
        image(pos) = CByte(v)
        Return pos + 1
    ElseIf TypeOf v Is UShort Then
        Return write16(image, pos, CUShort(v))
    ElseIf TypeOf v Is Integer Then
        Return write32(image, pos, CInt(v))
    ElseIf TypeOf v Is Byte() Then
        Return write8s(image, pos, CType(v, Byte()))
    ElseIf TypeOf v Is UShort() Then
        Return write16s(image, pos, CType(v, UShort()))
    ElseIf TypeOf v Is Integer() Then
        Return write32s(image, pos, CType(v, Integer()))
    Else
        Dim t = v.GetType
        If t.IsArray Then
            For Each obj In CType(v, Array)
                pos = writeAny(image, pos, obj)
            Next
            Return pos
        Else
            Dim fs = t.GetFields
            If fs.Length > 0 Then
                Return writeFields(image, pos, v)
            Else
                Throw New ArgumentException("不明な型: ", t.FullName)
            End If
        End If
    End If
End Function

Function writeFields%(image As Byte(), pos%, obj As Object)
    For Each f In obj.GetType.GetFields
        pos = writeAny(image, pos, f.GetValue(obj))
    Next
    Return pos
End Function

writeFieldsの中で、フィールド1つずつに対しwriteAnyを呼んでいます。もしそのフィールドの中にフィールドがあれば、再帰的にwriteFieldsを呼んで処理します。配列の場合は要素を1つずつwriteAnyで処理します。

これによりWin32の構造体をそのまま書き写せるようになりました。

Public Class IMAGE_NT_HEADERS32
    Public Signature%
    Public FileHeader As New IMAGE_FILE_HEADER
    Public OptionalHeader As New IMAGE_OPTIONAL_HEADER32
End Class

Public Class IMAGE_FILE_HEADER
    Public Machine, NumberOfSections As UShort
    Public TimeDateStamp, PointerToSymbolTable, NumberOfSymbols As Integer
    Public SizeOfOptionalHeader, Characteristics As UShort
End Class

Public Class IMAGE_OPTIONAL_HEADER32
    Public Magic As UShort
    Public MajorLinkerVersion, MinorLinkerVersion As Byte
    Public SizeOfCode, SizeOfInitializedData, SizeOfUninitializedData,
           AddressOfEntryPoint, BaseOfCode, BaseOfData, ImageBase,
           SectionAlignment, FileAlignment As Integer
    Public MajorOperatingSystemVersion, MinorOperatingSystemVersion,
           MajorImageVersion, MinorImageVersion,
           MajorSubsystemVersion, MinorSubsystemVersion As UShort
    Public Win32VersionValue, SizeOfImage, SizeOfHeaders, CheckSum As Integer
    Public Subsystem, DllCharacteristics As UShort
    Public SizeOfStackReserve, SizeOfStackCommit,
           SizeOfHeapReserve, SizeOfHeapCommit,
           LoaderFlags, NumberOfRvaAndSizes As Integer
    Public DataDirectory(15) As IMAGE_DATA_DIRECTORY
End Class

Public Class IMAGE_DATA_DIRECTORY
    Public VirtualAddress, Size As Integer
End Class

これでようやくC++のサンプル(id:n7shi:20110416)とほぼ同じ構成でEXEが出力できるようになりました。

Imports System.IO

Module Module1
    Sub Main()
        Dim image(&H3FF) As Byte

        Dim dosh = New IMAGE_DOS_HEADER With {
            .e_magic = conv16("MZ"),
            .e_cblp = &H90,
            .e_cp = 3,
            .e_cparhdr = 4,
            .e_maxalloc = &HFFFF,
            .e_sp = &HB8,
            .e_lfarlc = &H40,
            .e_lfanew = &H80}
        writeFields(image, 0, dosh)
        write8s(image, &H40, &HB8, 1, &H4C, &HCD, &H21)

        Dim peh = New IMAGE_NT_HEADERS32 With {
            .Signature = conv32("PE")}
        With peh.FileHeader
            .Machine = &H14C
            .NumberOfSections = 1
            .SizeOfOptionalHeader = &HE0
            .Characteristics = &H102
        End With
        With peh.OptionalHeader
            .Magic = &H10B
            .MajorLinkerVersion = 10
            .SizeOfCode = &H200
            .AddressOfEntryPoint = &H1000
            .BaseOfCode = &H1000
            .BaseOfData = &H2000
            .ImageBase = &H400000
            .SectionAlignment = &H1000
            .FileAlignment = &H200
            .MajorOperatingSystemVersion = 5
            .MinorOperatingSystemVersion = 1
            .MajorSubsystemVersion = 5
            .MinorSubsystemVersion = 1
            .SizeOfImage = &H2000
            .SizeOfHeaders = &H200
            .Subsystem = 2
            .DllCharacteristics = &H8500
            .SizeOfStackReserve = &H100000
            .SizeOfStackCommit = &H1000
            .SizeOfHeapReserve = &H100000
            .SizeOfHeapCommit = &H1000
            .NumberOfRvaAndSizes = 16
        End With
        writeFields(image, &H80, peh)

        Dim text = New IMAGE_SECTION_HEADER With {
            .Name = getBytes(".text", 8),
            .VirtualSize = 1,
            .VirtualAddress = &H1000,
            .SizeOfRawData = &H200,
            .PointerToRawData = &H200,
            .Characteristics = &H60000020}
        writeFields(image, &H178, text)

        image(&H200) = &HC3

        Using fs = New FileStream("output.exe", FileMode.Create)
            fs.Write(image, 0, image.Length)
        End Using
    End Sub
End Module

実質的にここがスタート地点です。今後は、第3回でうやむやなまま終わった.idataの生成処理について、コードでわかりやすく説明できないか考えていきます。