From e818e5b907cb4d75a5f8f8a1c65909906649bc81 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 13 Aug 2024 19:34:54 +0200 Subject: [PATCH] Improved Windows support --- lib/sys/sys_windows.q | 3 + src/asm/Finalize.go | 25 +++++- src/config/config.go | 8 +- src/os/linux/elf/Constants.go | 8 ++ src/os/linux/elf/ELF.go | 16 ++-- src/os/mac/macho/Constants.go | 8 ++ src/os/mac/macho/MachO.go | 10 +-- src/os/windows/pe/Constants.go | 8 ++ src/os/windows/pe/DOSHeader.go | 6 +- src/os/windows/pe/EXE.go | 84 ++++++++++++------- src/os/windows/pe/ImportDirectory.go | 9 ++ src/os/windows/pe/OptionalHeader64.go | 2 + .../windows/pe/{NTHeader.go => PEHeader.go} | 4 +- src/os/windows/pe/SectionHeader.go | 2 + src/scanner/queueDirectory.go | 4 + src/scanner/scanFile.go | 2 +- 16 files changed, 142 insertions(+), 57 deletions(-) create mode 100644 lib/sys/sys_windows.q create mode 100644 src/os/windows/pe/ImportDirectory.go rename src/os/windows/pe/{NTHeader.go => PEHeader.go} (83%) diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q new file mode 100644 index 0000000..dfab144 --- /dev/null +++ b/lib/sys/sys_windows.q @@ -0,0 +1,3 @@ +write(_ Int, _ Pointer, _ Int) -> Int { + return 0 +} \ No newline at end of file diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index c32b2fb..1457c40 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -9,6 +9,9 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "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" ) @@ -337,8 +340,26 @@ restart: } 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 { address := dataStart + pointer.Resolve() diff --git a/src/config/config.go b/src/config/config.go index db3b7c1..555e379 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -8,12 +8,6 @@ const ( // The base address is the virtual address for our ELF file. 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 ( @@ -45,3 +39,5 @@ func Reset() { TargetOS = "mac" } } + +// diff --git a/src/os/linux/elf/Constants.go b/src/os/linux/elf/Constants.go index bd4600f..6396c66 100644 --- a/src/os/linux/elf/Constants.go +++ b/src/os/linux/elf/Constants.go @@ -1,5 +1,13 @@ 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 ( LittleEndian = 1 TypeExecutable = 2 diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index fbe2370..084b903 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -22,8 +22,8 @@ type ELF struct { // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { - dataOffset := config.CodeOffset + int64(len(code)) - dataPadding := common.Padding(dataOffset, config.Align) + dataOffset := CodeOffset + int64(len(code)) + dataPadding := common.Padding(dataOffset, Align) dataOffset += dataPadding return &ELF{ @@ -37,7 +37,7 @@ func New(code []byte, data []byte) *ELF { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: config.BaseAddress + config.CodeOffset, + EntryPointInMemory: config.BaseAddress + CodeOffset, ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -51,12 +51,12 @@ func New(code []byte, data []byte) *ELF { CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: config.CodeOffset, - VirtualAddress: config.BaseAddress + config.CodeOffset, - PhysicalAddress: config.BaseAddress + config.CodeOffset, + Offset: CodeOffset, + VirtualAddress: config.BaseAddress + CodeOffset, + PhysicalAddress: config.BaseAddress + CodeOffset, SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), - Align: config.Align, + Align: Align, }, DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, @@ -66,7 +66,7 @@ func New(code []byte, data []byte) *ELF { PhysicalAddress: config.BaseAddress + dataOffset, SizeInFile: int64(len(data)), SizeInMemory: int64(len(data)), - Align: config.Align, + Align: Align, }, CodePadding: nil, Code: code, diff --git a/src/os/mac/macho/Constants.go b/src/os/mac/macho/Constants.go index eb9e320..83c62d9 100644 --- a/src/os/mac/macho/Constants.go +++ b/src/os/mac/macho/Constants.go @@ -1,5 +1,13 @@ 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 const ( diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 806c96d..1bc1325 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -25,7 +25,7 @@ func New(code []byte, data []byte) *MachO { MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, NumCommands: 4, - SizeCommands: 0x48*3 + 184, + SizeCommands: 72*3 + 184, Flags: FlagNoUndefs, Reserved: 0, }, @@ -40,7 +40,7 @@ func (m *MachO) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, Address: 0, SizeInMemory: config.BaseAddress, @@ -55,12 +55,12 @@ func (m *MachO) Write(writer io.Writer) { codeStart := uint64(32 + m.Header.SizeCommands) codeLength := uint64(len(m.Code)) codeEnd := codeStart + codeLength - dataPadding := common.Padding(codeEnd, config.Align) + dataPadding := common.Padding(codeEnd, Align) dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress + codeStart, SizeInMemory: codeLength, @@ -74,7 +74,7 @@ func (m *MachO) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, Address: config.BaseAddress + dataStart, SizeInMemory: uint64(len(m.Data)), diff --git a/src/os/windows/pe/Constants.go b/src/os/windows/pe/Constants.go index c77bea7..4d38ad5 100644 --- a/src/os/windows/pe/Constants.go +++ b/src/os/windows/pe/Constants.go @@ -1,5 +1,13 @@ 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 const ( IMAGE_FILE_MACHINE_AMD64 = 0x8664 diff --git a/src/os/windows/pe/DOSHeader.go b/src/os/windows/pe/DOSHeader.go index 809d8fc..d9a638b 100644 --- a/src/os/windows/pe/DOSHeader.go +++ b/src/os/windows/pe/DOSHeader.go @@ -1,9 +1,11 @@ package pe +const DOSHeaderSize = 64 + // 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 { Magic [4]byte _ [56]byte - NTHeaderOffset uint32 + PEHeaderOffset uint32 } diff --git a/src/os/windows/pe/EXE.go b/src/os/windows/pe/EXE.go index 13aa5e5..3be2970 100644 --- a/src/os/windows/pe/EXE.go +++ b/src/os/windows/pe/EXE.go @@ -1,6 +1,7 @@ package pe import ( + "bytes" "encoding/binary" "io" @@ -8,53 +9,58 @@ import ( "git.akyoto.dev/cli/q/src/os/common" ) +const NumSections = 2 + // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader - NTHeader + PEHeader OptionalHeader64 - CodeHeader SectionHeader - Code []byte - Data []byte + Sections [NumSections]SectionHeader + CodePadding []byte + Code []byte + DataPadding []byte + Data []byte } // New creates a new EXE file. func New(code []byte, data []byte) *EXE { - const codeStart = 0x170 - const optHeaderSize = 0xF0 + codeStart := uint32(DOSHeaderSize + PEHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) + codePadding := common.Padding(codeStart, Align) + codeStart += codePadding - codeSize := uint32(len(code)) - headerSize := uint32(codeStart) - sectionAlign := uint32(0x10) - fileAlign := uint32(0x10) - imageSize := uint32(codeStart + len(code)) - imageSize += common.Padding(imageSize, sectionAlign) + dataStart := codeStart + uint32(len(code)) + dataPadding := common.Padding(dataStart, Align) + dataStart += dataPadding + + imageSize := uint32(dataStart + uint32(len(data))) + imageSize += common.Padding(imageSize, Align) return &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, - NTHeaderOffset: 0x40, + PEHeaderOffset: 0x40, }, - NTHeader: NTHeader{ + PEHeader: PEHeader{ Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, - NumberOfSections: 1, - SizeOfOptionalHeader: optHeaderSize, + NumberOfSections: NumSections, + SizeOfOptionalHeader: OptionalHeader64Size, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, OptionalHeader64: OptionalHeader64{ Magic: 0x020B, // PE32+ executable MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, - SizeOfCode: codeSize, + SizeOfCode: uint32(len(code)), AddressOfEntryPoint: codeStart, ImageBase: config.BaseAddress, - SectionAlignment: sectionAlign, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: fileAlign, // power of 2 + SectionAlignment: Align, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: Align, // power of 2 MajorOperatingSystemVersion: 0x06, MajorSubsystemVersion: 0x06, SizeOfImage: imageSize, - SizeOfHeaders: headerSize, + SizeOfHeaders: codeStart, // section bodies begin here 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, @@ -81,25 +87,39 @@ func New(code []byte, data []byte) *EXE { {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, + Sections: [NumSections]SectionHeader{ + { + Name: [8]byte{'.', 'c', 'o', 'd', 'e'}, + 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, + }, + { + 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, - // Data: data, + CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), + Code: code, + DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), + 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.PEHeader) 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.Data) + binary.Write(writer, binary.LittleEndian, &pe.DataPadding) + binary.Write(writer, binary.LittleEndian, &pe.Data) } diff --git a/src/os/windows/pe/ImportDirectory.go b/src/os/windows/pe/ImportDirectory.go new file mode 100644 index 0000000..4857781 --- /dev/null +++ b/src/os/windows/pe/ImportDirectory.go @@ -0,0 +1,9 @@ +package pe + +type ImportDirectory struct { + OriginalFirstThunk uint32 + TimeDateStamp uint32 + ForwarderChain uint32 + Name uint32 + FirstThunk uint32 +} diff --git a/src/os/windows/pe/OptionalHeader64.go b/src/os/windows/pe/OptionalHeader64.go index 9a26a7e..a6ce912 100644 --- a/src/os/windows/pe/OptionalHeader64.go +++ b/src/os/windows/pe/OptionalHeader64.go @@ -1,5 +1,7 @@ package pe +const OptionalHeader64Size = 240 + type OptionalHeader64 struct { Magic uint16 MajorLinkerVersion uint8 diff --git a/src/os/windows/pe/NTHeader.go b/src/os/windows/pe/PEHeader.go similarity index 83% rename from src/os/windows/pe/NTHeader.go rename to src/os/windows/pe/PEHeader.go index f563c82..2de3439 100644 --- a/src/os/windows/pe/NTHeader.go +++ b/src/os/windows/pe/PEHeader.go @@ -1,6 +1,8 @@ package pe -type NTHeader struct { +const PEHeaderSize = 24 + +type PEHeader struct { Signature [4]byte Machine uint16 NumberOfSections uint16 diff --git a/src/os/windows/pe/SectionHeader.go b/src/os/windows/pe/SectionHeader.go index 8e95e85..2ab40e7 100644 --- a/src/os/windows/pe/SectionHeader.go +++ b/src/os/windows/pe/SectionHeader.go @@ -1,5 +1,7 @@ package pe +const SectionHeaderSize = 40 + type SectionHeader struct { Name [8]byte VirtualSize uint32 diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index f1abe54..cc49a20 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -29,6 +29,10 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } + if strings.HasSuffix(name, "_windows.q") && config.TargetOS != "windows" { + return + } + fullPath := filepath.Join(directory, name) s.queueFile(fullPath, pkg) }) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 1b9c795..4f7c6e1 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -249,7 +249,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { register := x64.InputRegisters[count] 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) }