Improved Windows support

This commit is contained in:
Eduard Urbach 2024-08-13 19:34:54 +02:00
parent 7b1a293cd0
commit e818e5b907
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
16 changed files with 142 additions and 57 deletions

3
lib/sys/sys_windows.q Normal file
View File

@ -0,0 +1,3 @@
write(_ Int, _ Pointer, _ Int) -> Int {
return 0
}

View File

@ -9,6 +9,9 @@ import (
"git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/arch/x64"
"git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/os/common" "git.akyoto.dev/cli/q/src/os/common"
"git.akyoto.dev/cli/q/src/os/linux/elf"
"git.akyoto.dev/cli/q/src/os/mac/macho"
"git.akyoto.dev/cli/q/src/os/windows/pe"
"git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/sizeof"
) )
@ -337,8 +340,26 @@ restart:
} }
data, dataLabels = a.Data.Finalize() data, dataLabels = a.Data.Finalize()
dataStart := config.BaseAddress + config.CodeOffset + Address(len(code))
dataStart += int32(common.Padding(int64(dataStart), config.Align)) var (
codeOffset Address
align Address
)
switch config.TargetOS {
case "linux":
codeOffset = elf.CodeOffset
align = elf.Align
case "mac":
codeOffset = macho.CodeOffset
align = macho.Align
case "windows":
codeOffset = pe.CodeOffset
align = pe.Align
}
dataStart := Address(config.BaseAddress) + codeOffset + Address(len(code))
dataStart += int32(common.Padding(dataStart, align))
for _, pointer := range dataPointers { for _, pointer := range dataPointers {
address := dataStart + pointer.Resolve() address := dataStart + pointer.Resolve()

View File

@ -8,12 +8,6 @@ const (
// The base address is the virtual address for our ELF file. // The base address is the virtual address for our ELF file.
BaseAddress = 0x40 * MinAddress BaseAddress = 0x40 * MinAddress
// The code offset is the offset of the executable machine code within the file.
CodeOffset = 64 + 56 + 56
// Align decides the alignment of the sections and it must be a multiple of the page size.
Align = 0x1000
) )
var ( var (
@ -45,3 +39,5 @@ func Reset() {
TargetOS = "mac" TargetOS = "mac"
} }
} }
//

View File

@ -1,5 +1,13 @@
package elf package elf
const (
// Align decides the alignment of the sections and it must be a multiple of the page size.
Align = 0x1000
// The code offset is the offset of the executable machine code within the file.
CodeOffset = HeaderSize + ProgramHeaderSize*2
)
const ( const (
LittleEndian = 1 LittleEndian = 1
TypeExecutable = 2 TypeExecutable = 2

View File

@ -22,8 +22,8 @@ type ELF struct {
// New creates a new ELF binary. // New creates a new ELF binary.
func New(code []byte, data []byte) *ELF { func New(code []byte, data []byte) *ELF {
dataOffset := config.CodeOffset + int64(len(code)) dataOffset := CodeOffset + int64(len(code))
dataPadding := common.Padding(dataOffset, config.Align) dataPadding := common.Padding(dataOffset, Align)
dataOffset += dataPadding dataOffset += dataPadding
return &ELF{ return &ELF{
@ -37,7 +37,7 @@ func New(code []byte, data []byte) *ELF {
Type: TypeExecutable, Type: TypeExecutable,
Architecture: ArchitectureAMD64, Architecture: ArchitectureAMD64,
FileVersion: 1, FileVersion: 1,
EntryPointInMemory: config.BaseAddress + config.CodeOffset, EntryPointInMemory: config.BaseAddress + CodeOffset,
ProgramHeaderOffset: HeaderSize, ProgramHeaderOffset: HeaderSize,
SectionHeaderOffset: 0, SectionHeaderOffset: 0,
Flags: 0, Flags: 0,
@ -51,12 +51,12 @@ func New(code []byte, data []byte) *ELF {
CodeHeader: ProgramHeader{ CodeHeader: ProgramHeader{
Type: ProgramTypeLOAD, Type: ProgramTypeLOAD,
Flags: ProgramFlagsExecutable | ProgramFlagsReadable, Flags: ProgramFlagsExecutable | ProgramFlagsReadable,
Offset: config.CodeOffset, Offset: CodeOffset,
VirtualAddress: config.BaseAddress + config.CodeOffset, VirtualAddress: config.BaseAddress + CodeOffset,
PhysicalAddress: config.BaseAddress + config.CodeOffset, PhysicalAddress: config.BaseAddress + CodeOffset,
SizeInFile: int64(len(code)), SizeInFile: int64(len(code)),
SizeInMemory: int64(len(code)), SizeInMemory: int64(len(code)),
Align: config.Align, Align: Align,
}, },
DataHeader: ProgramHeader{ DataHeader: ProgramHeader{
Type: ProgramTypeLOAD, Type: ProgramTypeLOAD,
@ -66,7 +66,7 @@ func New(code []byte, data []byte) *ELF {
PhysicalAddress: config.BaseAddress + dataOffset, PhysicalAddress: config.BaseAddress + dataOffset,
SizeInFile: int64(len(data)), SizeInFile: int64(len(data)),
SizeInMemory: int64(len(data)), SizeInMemory: int64(len(data)),
Align: config.Align, Align: Align,
}, },
CodePadding: nil, CodePadding: nil,
Code: code, Code: code,

View File

@ -1,5 +1,13 @@
package macho package macho
const (
// Align decides the alignment of the sections and it must be a multiple of the page size.
Align = 0x1000
// The code offset is the offset of the executable machine code within the file.
CodeOffset = 32 + 0x48*3 + 184
)
type CPU uint32 type CPU uint32
const ( const (

View File

@ -25,7 +25,7 @@ func New(code []byte, data []byte) *MachO {
MicroArchitecture: 3 | 0x80000000, MicroArchitecture: 3 | 0x80000000,
Type: TypeExecute, Type: TypeExecute,
NumCommands: 4, NumCommands: 4,
SizeCommands: 0x48*3 + 184, SizeCommands: 72*3 + 184,
Flags: FlagNoUndefs, Flags: FlagNoUndefs,
Reserved: 0, Reserved: 0,
}, },
@ -40,7 +40,7 @@ func (m *MachO) Write(writer io.Writer) {
binary.Write(writer, binary.LittleEndian, &Segment64{ binary.Write(writer, binary.LittleEndian, &Segment64{
LoadCommand: LcSegment64, LoadCommand: LcSegment64,
Length: 0x48, Length: 72,
Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'},
Address: 0, Address: 0,
SizeInMemory: config.BaseAddress, SizeInMemory: config.BaseAddress,
@ -55,12 +55,12 @@ func (m *MachO) Write(writer io.Writer) {
codeStart := uint64(32 + m.Header.SizeCommands) codeStart := uint64(32 + m.Header.SizeCommands)
codeLength := uint64(len(m.Code)) codeLength := uint64(len(m.Code))
codeEnd := codeStart + codeLength codeEnd := codeStart + codeLength
dataPadding := common.Padding(codeEnd, config.Align) dataPadding := common.Padding(codeEnd, Align)
dataStart := codeEnd + dataPadding dataStart := codeEnd + dataPadding
binary.Write(writer, binary.LittleEndian, &Segment64{ binary.Write(writer, binary.LittleEndian, &Segment64{
LoadCommand: LcSegment64, LoadCommand: LcSegment64,
Length: 0x48, Length: 72,
Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'},
Address: config.BaseAddress + codeStart, Address: config.BaseAddress + codeStart,
SizeInMemory: codeLength, SizeInMemory: codeLength,
@ -74,7 +74,7 @@ func (m *MachO) Write(writer io.Writer) {
binary.Write(writer, binary.LittleEndian, &Segment64{ binary.Write(writer, binary.LittleEndian, &Segment64{
LoadCommand: LcSegment64, LoadCommand: LcSegment64,
Length: 0x48, Length: 72,
Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'},
Address: config.BaseAddress + dataStart, Address: config.BaseAddress + dataStart,
SizeInMemory: uint64(len(m.Data)), SizeInMemory: uint64(len(m.Data)),

View File

@ -1,5 +1,13 @@
package pe package pe
const (
// Align decides the alignment of the sections.
Align = 0x200
// The code offset is the offset of the executable machine code within the file.
CodeOffset = Align
)
// CPU // CPU
const ( const (
IMAGE_FILE_MACHINE_AMD64 = 0x8664 IMAGE_FILE_MACHINE_AMD64 = 0x8664

View File

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

View File

@ -1,6 +1,7 @@
package pe package pe
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"io" "io"
@ -8,53 +9,58 @@ import (
"git.akyoto.dev/cli/q/src/os/common" "git.akyoto.dev/cli/q/src/os/common"
) )
const NumSections = 2
// EXE is the portable executable format used on Windows. // EXE is the portable executable format used on Windows.
type EXE struct { type EXE struct {
DOSHeader DOSHeader
NTHeader PEHeader
OptionalHeader64 OptionalHeader64
CodeHeader SectionHeader Sections [NumSections]SectionHeader
Code []byte CodePadding []byte
Data []byte Code []byte
DataPadding []byte
Data []byte
} }
// New creates a new EXE file. // New creates a new EXE file.
func New(code []byte, data []byte) *EXE { func New(code []byte, data []byte) *EXE {
const codeStart = 0x170 codeStart := uint32(DOSHeaderSize + PEHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections)
const optHeaderSize = 0xF0 codePadding := common.Padding(codeStart, Align)
codeStart += codePadding
codeSize := uint32(len(code)) dataStart := codeStart + uint32(len(code))
headerSize := uint32(codeStart) dataPadding := common.Padding(dataStart, Align)
sectionAlign := uint32(0x10) dataStart += dataPadding
fileAlign := uint32(0x10)
imageSize := uint32(codeStart + len(code)) imageSize := uint32(dataStart + uint32(len(data)))
imageSize += common.Padding(imageSize, sectionAlign) imageSize += common.Padding(imageSize, Align)
return &EXE{ return &EXE{
DOSHeader: DOSHeader{ DOSHeader: DOSHeader{
Magic: [4]byte{'M', 'Z', 0, 0}, Magic: [4]byte{'M', 'Z', 0, 0},
NTHeaderOffset: 0x40, PEHeaderOffset: 0x40,
}, },
NTHeader: NTHeader{ PEHeader: PEHeader{
Signature: [4]byte{'P', 'E', 0, 0}, Signature: [4]byte{'P', 'E', 0, 0},
Machine: IMAGE_FILE_MACHINE_AMD64, Machine: IMAGE_FILE_MACHINE_AMD64,
NumberOfSections: 1, NumberOfSections: NumSections,
SizeOfOptionalHeader: optHeaderSize, SizeOfOptionalHeader: OptionalHeader64Size,
Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE,
}, },
OptionalHeader64: OptionalHeader64{ OptionalHeader64: OptionalHeader64{
Magic: 0x020B, // PE32+ executable Magic: 0x020B, // PE32+ executable
MajorLinkerVersion: 0x0E, MajorLinkerVersion: 0x0E,
MinorLinkerVersion: 0x16, MinorLinkerVersion: 0x16,
SizeOfCode: codeSize, SizeOfCode: uint32(len(code)),
AddressOfEntryPoint: codeStart, AddressOfEntryPoint: codeStart,
ImageBase: config.BaseAddress, ImageBase: config.BaseAddress,
SectionAlignment: sectionAlign, // power of 2, must be greater than or equal to FileAlignment SectionAlignment: Align, // power of 2, must be greater than or equal to FileAlignment
FileAlignment: fileAlign, // power of 2 FileAlignment: Align, // power of 2
MajorOperatingSystemVersion: 0x06, MajorOperatingSystemVersion: 0x06,
MajorSubsystemVersion: 0x06, MajorSubsystemVersion: 0x06,
SizeOfImage: imageSize, SizeOfImage: imageSize,
SizeOfHeaders: headerSize, SizeOfHeaders: codeStart, // section bodies begin here
Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI,
DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE,
SizeOfStackReserve: 0x100000, SizeOfStackReserve: 0x100000,
@ -81,25 +87,39 @@ func New(code []byte, data []byte) *EXE {
{VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0},
}, },
}, },
CodeHeader: SectionHeader{ Sections: [NumSections]SectionHeader{
Name: [8]byte{'.', 't', 'e', 'x', 't'}, {
VirtualSize: uint32(len(code)), Name: [8]byte{'.', 'c', 'o', 'd', 'e'},
VirtualAddress: codeStart, VirtualSize: uint32(len(code)),
RawSize: uint32(len(code)), // must be a multiple of FileAlignment VirtualAddress: codeStart,
RawAddress: codeStart, // must be a multiple of FileAlignment RawSize: uint32(len(code)), // must be a multiple of FileAlignment
Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, RawAddress: codeStart, // must be a multiple of FileAlignment
Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ,
},
{
Name: [8]byte{'.', 'd', 'a', 't', 'a'},
VirtualSize: uint32(len(data)),
VirtualAddress: dataStart,
RawSize: uint32(len(data)), // must be a multiple of FileAlignment
RawAddress: dataStart, // must be a multiple of FileAlignment
Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
},
}, },
Code: code, CodePadding: bytes.Repeat([]byte{0}, int(codePadding)),
// Data: data, Code: code,
DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)),
Data: data,
} }
} }
// Write writes the EXE file to the given writer. // Write writes the EXE file to the given writer.
func (pe *EXE) Write(writer io.Writer) { func (pe *EXE) Write(writer io.Writer) {
binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) binary.Write(writer, binary.LittleEndian, &pe.DOSHeader)
binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.PEHeader)
binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64)
binary.Write(writer, binary.LittleEndian, &pe.CodeHeader) binary.Write(writer, binary.LittleEndian, &pe.Sections)
binary.Write(writer, binary.LittleEndian, &pe.CodePadding)
binary.Write(writer, binary.LittleEndian, &pe.Code) binary.Write(writer, binary.LittleEndian, &pe.Code)
// binary.Write(writer, binary.LittleEndian, &pe.Data) binary.Write(writer, binary.LittleEndian, &pe.DataPadding)
binary.Write(writer, binary.LittleEndian, &pe.Data)
} }

View File

@ -0,0 +1,9 @@
package pe
type ImportDirectory struct {
OriginalFirstThunk uint32
TimeDateStamp uint32
ForwarderChain uint32
Name uint32
FirstThunk uint32
}

View File

@ -1,5 +1,7 @@
package pe package pe
const OptionalHeader64Size = 240
type OptionalHeader64 struct { type OptionalHeader64 struct {
Magic uint16 Magic uint16
MajorLinkerVersion uint8 MajorLinkerVersion uint8

View File

@ -1,6 +1,8 @@
package pe package pe
type NTHeader struct { const PEHeaderSize = 24
type PEHeader struct {
Signature [4]byte Signature [4]byte
Machine uint16 Machine uint16
NumberOfSections uint16 NumberOfSections uint16

View File

@ -1,5 +1,7 @@
package pe package pe
const SectionHeaderSize = 40
type SectionHeader struct { type SectionHeader struct {
Name [8]byte Name [8]byte
VirtualSize uint32 VirtualSize uint32

View File

@ -29,6 +29,10 @@ func (s *Scanner) queueDirectory(directory string, pkg string) {
return return
} }
if strings.HasSuffix(name, "_windows.q") && config.TargetOS != "windows" {
return
}
fullPath := filepath.Join(directory, name) fullPath := filepath.Join(directory, name)
s.queueFile(fullPath, pkg) s.queueFile(fullPath, pkg)
}) })

View File

@ -249,7 +249,7 @@ func (s *Scanner) scanFile(path string, pkg string) error {
register := x64.InputRegisters[count] register := x64.InputRegisters[count]
uses := token.Count(function.Body, contents, token.Identifier, name) uses := token.Count(function.Body, contents, token.Identifier, name)
if uses == 0 { if uses == 0 && name != "_" {
return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position)
} }