Added Windows PE support

This commit is contained in:
Eduard Urbach 2024-08-13 14:07:40 +02:00
parent b90ee62b98
commit 7b1a293cd0
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
20 changed files with 373 additions and 103 deletions

View File

@ -8,7 +8,7 @@ import (
"git.akyoto.dev/cli/q/src/arch/x64"
"git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/os/linux/elf"
"git.akyoto.dev/cli/q/src/os/common"
"git.akyoto.dev/cli/q/src/sizeof"
)
@ -338,7 +338,7 @@ restart:
data, dataLabels = a.Data.Finalize()
dataStart := config.BaseAddress + config.CodeOffset + Address(len(code))
dataStart += int32(elf.Padding(int64(dataStart), config.Align))
dataStart += int32(common.Padding(int64(dataStart), config.Align))
for _, pointer := range dataPointers {
address := dataStart + pointer.Resolve()

View File

@ -14,6 +14,7 @@ import (
"git.akyoto.dev/cli/q/src/os/linux/elf"
"git.akyoto.dev/cli/q/src/os/mac"
"git.akyoto.dev/cli/q/src/os/mac/macho"
"git.akyoto.dev/cli/q/src/os/windows/pe"
)
// Result contains all the compiled functions in a build.
@ -35,20 +36,22 @@ func (r *Result) finalize() ([]byte, []byte) {
Data: make(map[string][]byte, r.DataCount),
}
sysExit := 0
final.Call("main.main")
switch config.TargetOS {
case "linux":
sysExit = linux.Exit
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit)
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0)
final.Syscall()
case "mac":
sysExit = mac.Exit
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit)
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0)
final.Syscall()
case "windows":
final.RegisterNumber(asm.MOVE, x64.RAX, 0)
final.Return()
}
final.Call("main.main")
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit)
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0)
final.Syscall()
// This will place the main function immediately after the entry point
// and also add everything the main function calls recursively.
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {
@ -56,9 +59,20 @@ func (r *Result) finalize() ([]byte, []byte) {
})
final.Label(asm.LABEL, "_crash")
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit)
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1)
final.Syscall()
switch config.TargetOS {
case "linux":
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit)
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1)
final.Syscall()
case "mac":
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit)
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1)
final.Syscall()
case "windows":
final.RegisterNumber(asm.MOVE, x64.RAX, 1)
final.Return()
}
code, data := final.Finalize()
return code, data
@ -138,6 +152,9 @@ func write(writer io.Writer, code []byte, data []byte) error {
case "mac":
exe := macho.New(code, data)
exe.Write(buffer)
case "windows":
exe := pe.New(code, data)
exe.Write(buffer)
default:
return fmt.Errorf("unsupported platform '%s'", config.TargetOS)
}

View File

@ -1,6 +1,6 @@
package elf
package common
// Padding calculates the padding needed to align `n` bytes with the specified alignment.
func Padding[T int | int32 | int64 | uint | uint32 | uint64](n T, align T) T {
func Padding[T int64 | uint64 | int32 | uint32](n T, align T) T {
return align - (n % align)
}

View File

@ -0,0 +1,55 @@
package elf
const (
LittleEndian = 1
TypeExecutable = 2
ArchitectureAMD64 = 0x3E
)
type ProgramType int32
const (
ProgramTypeNULL ProgramType = 0
ProgramTypeLOAD ProgramType = 1
ProgramTypeDYNAMIC ProgramType = 2
ProgramTypeINTERP ProgramType = 3
ProgramTypeNOTE ProgramType = 4
ProgramTypeSHLIB ProgramType = 5
ProgramTypePHDR ProgramType = 6
ProgramTypeTLS ProgramType = 7
)
type ProgramFlags int32
const (
ProgramFlagsExecutable ProgramFlags = 0x1
ProgramFlagsWritable ProgramFlags = 0x2
ProgramFlagsReadable ProgramFlags = 0x4
)
type SectionType int32
const (
SectionTypeNULL SectionType = 0
SectionTypePROGBITS SectionType = 1
SectionTypeSYMTAB SectionType = 2
SectionTypeSTRTAB SectionType = 3
SectionTypeRELA SectionType = 4
SectionTypeHASH SectionType = 5
SectionTypeDYNAMIC SectionType = 6
SectionTypeNOTE SectionType = 7
SectionTypeNOBITS SectionType = 8
SectionTypeREL SectionType = 9
SectionTypeSHLIB SectionType = 10
SectionTypeDYNSYM SectionType = 11
)
type SectionFlags int64
const (
SectionFlagsWritable SectionFlags = 1 << 0
SectionFlagsAllocate SectionFlags = 1 << 1
SectionFlagsExecutable SectionFlags = 1 << 2
SectionFlagsStrings SectionFlags = 1 << 5
SectionFlagsTLS SectionFlags = 1 << 10
)

View File

@ -6,6 +6,7 @@ import (
"io"
"git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/os/common"
)
// ELF represents an ELF file.
@ -22,7 +23,7 @@ type ELF struct {
// New creates a new ELF binary.
func New(code []byte, data []byte) *ELF {
dataOffset := config.CodeOffset + int64(len(code))
dataPadding := Padding(dataOffset, config.Align)
dataPadding := common.Padding(dataOffset, config.Align)
dataOffset += dataPadding
return &ELF{

View File

@ -1,11 +1,7 @@
package elf
const (
LittleEndian = 1
TypeExecutable = 2
ArchitectureAMD64 = 0x3E
HeaderSize = 64
)
// HeaderSize is equal to the size of a header in bytes.
const HeaderSize = 64
// Header contains general information.
type Header struct {

View File

@ -14,24 +14,3 @@ type ProgramHeader struct {
SizeInMemory int64
Align int64
}
type ProgramType int32
const (
ProgramTypeNULL ProgramType = 0
ProgramTypeLOAD ProgramType = 1
ProgramTypeDYNAMIC ProgramType = 2
ProgramTypeINTERP ProgramType = 3
ProgramTypeNOTE ProgramType = 4
ProgramTypeSHLIB ProgramType = 5
ProgramTypePHDR ProgramType = 6
ProgramTypeTLS ProgramType = 7
)
type ProgramFlags int32
const (
ProgramFlagsExecutable ProgramFlags = 0x1
ProgramFlagsWritable ProgramFlags = 0x2
ProgramFlagsReadable ProgramFlags = 0x4
)

View File

@ -16,30 +16,3 @@ type SectionHeader struct {
Align int64
EntrySize int64
}
type SectionType int32
const (
SectionTypeNULL SectionType = 0
SectionTypePROGBITS SectionType = 1
SectionTypeSYMTAB SectionType = 2
SectionTypeSTRTAB SectionType = 3
SectionTypeRELA SectionType = 4
SectionTypeHASH SectionType = 5
SectionTypeDYNAMIC SectionType = 6
SectionTypeNOTE SectionType = 7
SectionTypeNOBITS SectionType = 8
SectionTypeREL SectionType = 9
SectionTypeSHLIB SectionType = 10
SectionTypeDYNSYM SectionType = 11
)
type SectionFlags int64
const (
SectionFlagsWritable SectionFlags = 1 << 0
SectionFlagsAllocate SectionFlags = 1 << 1
SectionFlagsExecutable SectionFlags = 1 << 2
SectionFlagsStrings SectionFlags = 1 << 5
SectionFlagsTLS SectionFlags = 1 << 10
)

View File

@ -1,10 +0,0 @@
package macho
type CPU uint32
const (
CPU_X86 CPU = 7
CPU_X86_64 CPU = CPU_X86 | 0x01000000
CPU_ARM CPU = 12
CPU_ARM_64 CPU = CPU_ARM | 0x01000000
)

View File

@ -1,5 +1,22 @@
package macho
type CPU uint32
const (
CPU_X86 CPU = 7
CPU_X86_64 CPU = CPU_X86 | 0x01000000
CPU_ARM CPU = 12
CPU_ARM_64 CPU = CPU_ARM | 0x01000000
)
type Prot uint32
const (
ProtReadable Prot = 0x1
ProtWritable Prot = 0x2
ProtExecutable Prot = 0x4
)
type HeaderFlags uint32
const (
@ -30,3 +47,14 @@ const (
FlagNoHeapExecution HeaderFlags = 0x1000000
FlagAppExtensionSafe HeaderFlags = 0x2000000
)
type HeaderType uint32
const (
TypeObject HeaderType = 0x1
TypeExecute HeaderType = 0x2
TypeCore HeaderType = 0x4
TypeDylib HeaderType = 0x6
TypeBundle HeaderType = 0x8
TypeDsym HeaderType = 0xA
)

View File

@ -1,12 +0,0 @@
package macho
type HeaderType uint32
const (
TypeObject HeaderType = 0x1
TypeExecute HeaderType = 0x2
TypeCore HeaderType = 0x4
TypeDylib HeaderType = 0x6
TypeBundle HeaderType = 0x8
TypeDsym HeaderType = 0xA
)

View File

@ -6,7 +6,7 @@ import (
"io"
"git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/os/linux/elf"
"git.akyoto.dev/cli/q/src/os/common"
)
// MachO is the executable format used on MacOS.
@ -55,7 +55,7 @@ func (m *MachO) Write(writer io.Writer) {
codeStart := uint64(32 + m.Header.SizeCommands)
codeLength := uint64(len(m.Code))
codeEnd := codeStart + codeLength
dataPadding := elf.Padding(codeEnd, 4096)
dataPadding := common.Padding(codeEnd, config.Align)
dataStart := codeEnd + dataPadding
binary.Write(writer, binary.LittleEndian, &Segment64{

View File

@ -1,9 +0,0 @@
package macho
type Prot uint32
const (
ProtReadable Prot = 0x1
ProtWritable Prot = 0x2
ProtExecutable Prot = 0x4
)

View File

@ -0,0 +1,72 @@
package pe
// CPU
const (
IMAGE_FILE_MACHINE_AMD64 = 0x8664
IMAGE_FILE_MACHINE_ARM64 = 0xAA64
IMAGE_FILE_MACHINE_RISCV64 = 0x5064
)
// Subsystems
const (
IMAGE_SUBSYSTEM_UNKNOWN = 0
IMAGE_SUBSYSTEM_NATIVE = 1
IMAGE_SUBSYSTEM_WINDOWS_GUI = 2
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3
IMAGE_SUBSYSTEM_OS2_CUI = 5
IMAGE_SUBSYSTEM_POSIX_CUI = 7
IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9
IMAGE_SUBSYSTEM_EFI_APPLICATION = 10
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12
IMAGE_SUBSYSTEM_EFI_ROM = 13
IMAGE_SUBSYSTEM_XBOX = 14
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16
)
// Characteristics
const (
IMAGE_FILE_RELOCS_STRIPPED = 0x0001
IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002
IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004
IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008
IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010
IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020
IMAGE_FILE_BYTES_REVERSED_LO = 0x0080
IMAGE_FILE_32BIT_MACHINE = 0x0100
IMAGE_FILE_DEBUG_STRIPPED = 0x0200
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400
IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800
IMAGE_FILE_SYSTEM = 0x1000
IMAGE_FILE_DLL = 0x2000
IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000
IMAGE_FILE_BYTES_REVERSED_HI = 0x8000
)
// DLL characteristics
const (
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080
IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200
IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400
IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800
IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000
IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000
)
// Section characteristics
const (
IMAGE_SCN_CNT_CODE = 0x00000020
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
IMAGE_SCN_LNK_COMDAT = 0x00001000
IMAGE_SCN_MEM_DISCARDABLE = 0x02000000
IMAGE_SCN_MEM_EXECUTE = 0x20000000
IMAGE_SCN_MEM_READ = 0x40000000
IMAGE_SCN_MEM_WRITE = 0x80000000
)

View File

@ -0,0 +1,9 @@
package pe
// DOSHeader is at the beginning of each EXE file and nowadays just points
// to the NTHeader using an absolute file offset.
type DOSHeader struct {
Magic [4]byte
_ [56]byte
NTHeaderOffset uint32
}

View File

@ -0,0 +1,6 @@
package pe
type DataDirectory struct {
VirtualAddress uint32
Size uint32
}

105
src/os/windows/pe/EXE.go Normal file
View File

@ -0,0 +1,105 @@
package pe
import (
"encoding/binary"
"io"
"git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/os/common"
)
// EXE is the portable executable format used on Windows.
type EXE struct {
DOSHeader
NTHeader
OptionalHeader64
CodeHeader SectionHeader
Code []byte
Data []byte
}
// New creates a new EXE file.
func New(code []byte, data []byte) *EXE {
const codeStart = 0x170
const optHeaderSize = 0xF0
codeSize := uint32(len(code))
headerSize := uint32(codeStart)
sectionAlign := uint32(0x10)
fileAlign := uint32(0x10)
imageSize := uint32(codeStart + len(code))
imageSize += common.Padding(imageSize, sectionAlign)
return &EXE{
DOSHeader: DOSHeader{
Magic: [4]byte{'M', 'Z', 0, 0},
NTHeaderOffset: 0x40,
},
NTHeader: NTHeader{
Signature: [4]byte{'P', 'E', 0, 0},
Machine: IMAGE_FILE_MACHINE_AMD64,
NumberOfSections: 1,
SizeOfOptionalHeader: optHeaderSize,
Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE,
},
OptionalHeader64: OptionalHeader64{
Magic: 0x020B, // PE32+ executable
MajorLinkerVersion: 0x0E,
MinorLinkerVersion: 0x16,
SizeOfCode: codeSize,
AddressOfEntryPoint: codeStart,
ImageBase: config.BaseAddress,
SectionAlignment: sectionAlign, // power of 2, must be greater than or equal to FileAlignment
FileAlignment: fileAlign, // power of 2
MajorOperatingSystemVersion: 0x06,
MajorSubsystemVersion: 0x06,
SizeOfImage: imageSize,
SizeOfHeaders: headerSize,
Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI,
DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE,
SizeOfStackReserve: 0x100000,
SizeOfStackCommit: 0x1000,
SizeOfHeapReserve: 0x100000,
SizeOfHeapCommit: 0x1000,
NumberOfRvaAndSizes: 16,
DataDirectory: [16]DataDirectory{
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
},
},
CodeHeader: SectionHeader{
Name: [8]byte{'.', 't', 'e', 'x', 't'},
VirtualSize: uint32(len(code)),
VirtualAddress: codeStart,
RawSize: uint32(len(code)), // must be a multiple of FileAlignment
RawAddress: codeStart, // must be a multiple of FileAlignment
Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ,
},
Code: code,
// Data: data,
}
}
// Write writes the EXE file to the given writer.
func (pe *EXE) Write(writer io.Writer) {
binary.Write(writer, binary.LittleEndian, &pe.DOSHeader)
binary.Write(writer, binary.LittleEndian, &pe.NTHeader)
binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64)
binary.Write(writer, binary.LittleEndian, &pe.CodeHeader)
binary.Write(writer, binary.LittleEndian, &pe.Code)
// binary.Write(writer, binary.LittleEndian, &pe.Data)
}

View File

@ -0,0 +1,12 @@
package pe
type NTHeader struct {
Signature [4]byte
Machine uint16
NumberOfSections uint16
TimeDateStamp uint32
PointerToSymbolTable uint32
NumberOfSymbols uint32
SizeOfOptionalHeader uint16
Characteristics uint16
}

View File

@ -0,0 +1,34 @@
package pe
type OptionalHeader64 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
ImageBase uint64
SectionAlignment uint32
FileAlignment uint32
MajorOperatingSystemVersion uint16
MinorOperatingSystemVersion uint16
MajorImageVersion uint16
MinorImageVersion uint16
MajorSubsystemVersion uint16
MinorSubsystemVersion uint16
Win32VersionValue uint32
SizeOfImage uint32
SizeOfHeaders uint32
CheckSum uint32
Subsystem uint16
DllCharacteristics uint16
SizeOfStackReserve uint64
SizeOfStackCommit uint64
SizeOfHeapReserve uint64
SizeOfHeapCommit uint64
LoaderFlags uint32
NumberOfRvaAndSizes uint32
DataDirectory [16]DataDirectory
}

View File

@ -0,0 +1,14 @@
package pe
type SectionHeader struct {
Name [8]byte
VirtualSize uint32
VirtualAddress uint32
RawSize uint32
RawAddress uint32
PointerToRelocations uint32
PointerToLineNumbers uint32
NumberOfRelocations uint16
NumberOfLineNumbers uint16
Characteristics uint32
}