From 58f010b81a5bf2ef179ce7b79cccd8499db40fb8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 11 Aug 2024 21:15:47 +0200 Subject: [PATCH] Added Mach-O file format --- src/compiler/Result.go | 29 +++++++++-- src/macho/CPU.go | 10 ++++ src/macho/Header.go | 13 +++++ src/macho/HeaderFlags.go | 32 ++++++++++++ src/macho/HeaderType.go | 12 +++++ src/macho/LoadCommand.go | 34 +++++++++++++ src/macho/MachO.go | 107 +++++++++++++++++++++++++++++++++++++++ src/macho/Prot.go | 9 ++++ src/macho/Segment64.go | 16 ++++++ src/macho/Thread.go | 8 +++ 10 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 src/macho/CPU.go create mode 100644 src/macho/Header.go create mode 100644 src/macho/HeaderFlags.go create mode 100644 src/macho/HeaderType.go create mode 100644 src/macho/LoadCommand.go create mode 100644 src/macho/MachO.go create mode 100644 src/macho/Prot.go create mode 100644 src/macho/Segment64.go create mode 100644 src/macho/Thread.go diff --git a/src/compiler/Result.go b/src/compiler/Result.go index ca51ba1..8551388 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -4,11 +4,13 @@ import ( "bufio" "io" "os" + "runtime" "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/os/linux" ) @@ -31,13 +33,22 @@ func (r *Result) finalize() ([]byte, []byte) { Data: make(map[string][]byte, r.DataCount), } + sysExit := 0 + + switch runtime.GOOS { + case "linux": + sysExit = linux.Exit + case "darwin": + sysExit = 1 | 0x2000000 + } + final.Call("main.main") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() @@ -117,7 +128,17 @@ func (r *Result) WriteFile(path string) error { // write writes an executable file to the given writer. func write(writer io.Writer, code []byte, data []byte) error { buffer := bufio.NewWriter(writer) - executable := elf.New(code, data) - executable.Write(buffer) + + switch runtime.GOOS { + case "darwin": + exe := macho.New(code, data) + exe.Write(buffer) + case "linux": + exe := elf.New(code, data) + exe.Write(buffer) + default: + panic("unsupported platform") + } + return buffer.Flush() } diff --git a/src/macho/CPU.go b/src/macho/CPU.go new file mode 100644 index 0000000..3888aaa --- /dev/null +++ b/src/macho/CPU.go @@ -0,0 +1,10 @@ +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 +) diff --git a/src/macho/Header.go b/src/macho/Header.go new file mode 100644 index 0000000..f7798be --- /dev/null +++ b/src/macho/Header.go @@ -0,0 +1,13 @@ +package macho + +// Header contains general information. +type Header struct { + Magic uint32 + Architecture CPU + MicroArchitecture uint32 + Type HeaderType + NumCommands uint32 + SizeCommands uint32 + Flags HeaderFlags + Reserved uint32 +} diff --git a/src/macho/HeaderFlags.go b/src/macho/HeaderFlags.go new file mode 100644 index 0000000..4b17f50 --- /dev/null +++ b/src/macho/HeaderFlags.go @@ -0,0 +1,32 @@ +package macho + +type HeaderFlags uint32 + +const ( + FlagNoUndefs HeaderFlags = 0x1 + FlagIncrLink HeaderFlags = 0x2 + FlagDyldLink HeaderFlags = 0x4 + FlagBindAtLoad HeaderFlags = 0x8 + FlagPrebound HeaderFlags = 0x10 + FlagSplitSegs HeaderFlags = 0x20 + FlagLazyInit HeaderFlags = 0x40 + FlagTwoLevel HeaderFlags = 0x80 + FlagForceFlat HeaderFlags = 0x100 + FlagNoMultiDefs HeaderFlags = 0x200 + FlagNoFixPrebinding HeaderFlags = 0x400 + FlagPrebindable HeaderFlags = 0x800 + FlagAllModsBound HeaderFlags = 0x1000 + FlagSubsectionsViaSymbols HeaderFlags = 0x2000 + FlagCanonical HeaderFlags = 0x4000 + FlagWeakDefines HeaderFlags = 0x8000 + FlagBindsToWeak HeaderFlags = 0x10000 + FlagAllowStackExecution HeaderFlags = 0x20000 + FlagRootSafe HeaderFlags = 0x40000 + FlagSetuidSafe HeaderFlags = 0x80000 + FlagNoReexportedDylibs HeaderFlags = 0x100000 + FlagPIE HeaderFlags = 0x200000 + FlagDeadStrippableDylib HeaderFlags = 0x400000 + FlagHasTLVDescriptors HeaderFlags = 0x800000 + FlagNoHeapExecution HeaderFlags = 0x1000000 + FlagAppExtensionSafe HeaderFlags = 0x2000000 +) diff --git a/src/macho/HeaderType.go b/src/macho/HeaderType.go new file mode 100644 index 0000000..f106b97 --- /dev/null +++ b/src/macho/HeaderType.go @@ -0,0 +1,12 @@ +package macho + +type HeaderType uint32 + +const ( + TypeObject HeaderType = 0x1 + TypeExecute HeaderType = 0x2 + TypeCore HeaderType = 0x4 + TypeDylib HeaderType = 0x6 + TypeBundle HeaderType = 0x8 + TypeDsym HeaderType = 0xA +) diff --git a/src/macho/LoadCommand.go b/src/macho/LoadCommand.go new file mode 100644 index 0000000..0d4b04f --- /dev/null +++ b/src/macho/LoadCommand.go @@ -0,0 +1,34 @@ +package macho + +type LoadCommand uint32 + +const ( + LcSegment LoadCommand = 0x1 + LcSymtab LoadCommand = 0x2 + LcThread LoadCommand = 0x4 + LcUnixthread LoadCommand = 0x5 + LcDysymtab LoadCommand = 0xB + LcDylib LoadCommand = 0xC + LcIdDylib LoadCommand = 0xD + LcLoadDylinker LoadCommand = 0xE + LcIdDylinker LoadCommand = 0xF + LcSegment64 LoadCommand = 0x19 + LcUuid LoadCommand = 0x1B + LcCodeSignature LoadCommand = 0x1D + LcSegmentSplitInfo LoadCommand = 0x1E + LcRpath LoadCommand = 0x8000001C + LcEncryptionInfo LoadCommand = 0x21 + LcDyldInfo LoadCommand = 0x22 + LcDyldInfoOnly LoadCommand = 0x80000022 + LcVersionMinMacosx LoadCommand = 0x24 + LcVersionMinIphoneos LoadCommand = 0x25 + LcFunctionStarts LoadCommand = 0x26 + LcDyldEnvironment LoadCommand = 0x27 + LcMain LoadCommand = 0x80000028 + LcDataInCode LoadCommand = 0x29 + LcSourceVersion LoadCommand = 0x2A + LcDylibCodeSignDrs LoadCommand = 0x2B + LcEncryptionInfo64 LoadCommand = 0x2C + LcVersionMinTvos LoadCommand = 0x2F + LcVersionMinWatchos LoadCommand = 0x30 +) diff --git a/src/macho/MachO.go b/src/macho/MachO.go new file mode 100644 index 0000000..c2fd883 --- /dev/null +++ b/src/macho/MachO.go @@ -0,0 +1,107 @@ +package macho + +import ( + "bytes" + "encoding/binary" + "io" + + "git.akyoto.dev/cli/q/src/config" +) + +// MachO is the executable format used on MacOS. +type MachO struct { + Header + Code []byte + Data []byte +} + +// New creates a new Mach-O binary. +func New(code []byte, data []byte) *MachO { + return &MachO{ + Header: Header{ + Magic: 0xFEEDFACF, + Architecture: CPU_X86_64, + MicroArchitecture: 0x80000003, + Type: TypeExecute, + NumCommands: 3, + SizeCommands: 0x48*2 + 184, + Flags: FlagNoUndefs, + Reserved: 0, + }, + Code: code, + Data: data, + } +} + +// Write writes the Mach-O format to the given writer. +func (m *MachO) Write(writer io.Writer) { + binary.Write(writer, binary.LittleEndian, &m.Header) + + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 0x48, + Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, + Address: 0, + SizeInMemory: config.BaseAddress, + Offset: 0, + SizeInFile: 0, + NumSections: 0, + Flag: 0, + MaxProt: 0, + InitProt: 0, + }) + + codeStart := 32 + m.Header.SizeCommands + totalSize := uint64(codeStart) + uint64(len(m.Code)) + + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 0x48, + Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, + Address: config.BaseAddress, + SizeInMemory: totalSize, + Offset: 0, + SizeInFile: totalSize, + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable | ProtWritable | ProtExecutable, + InitProt: ProtReadable | ProtExecutable, + }) + + binary.Write(writer, binary.LittleEndian, &Thread{ + LoadCommand: LcUnixthread, + Len: 184, + Type: 0x4, + }) + + binary.Write(writer, binary.LittleEndian, []uint32{ + 42, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + config.BaseAddress + uint32(codeStart), 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }) + + writer.Write(m.Code) + + if totalSize < 4096 { + writer.Write(bytes.Repeat([]byte{0}, 4096-int(totalSize))) + } +} diff --git a/src/macho/Prot.go b/src/macho/Prot.go new file mode 100644 index 0000000..562b624 --- /dev/null +++ b/src/macho/Prot.go @@ -0,0 +1,9 @@ +package macho + +type Prot uint32 + +const ( + ProtReadable Prot = 0x1 + ProtWritable Prot = 0x2 + ProtExecutable Prot = 0x4 +) diff --git a/src/macho/Segment64.go b/src/macho/Segment64.go new file mode 100644 index 0000000..382e18f --- /dev/null +++ b/src/macho/Segment64.go @@ -0,0 +1,16 @@ +package macho + +// Segment64 is a segment load command. +type Segment64 struct { + LoadCommand + Length uint32 + Name [16]byte + Address uint64 + SizeInMemory uint64 + Offset uint64 + SizeInFile uint64 + MaxProt Prot + InitProt Prot + NumSections uint32 + Flag uint32 +} diff --git a/src/macho/Thread.go b/src/macho/Thread.go new file mode 100644 index 0000000..69f9053 --- /dev/null +++ b/src/macho/Thread.go @@ -0,0 +1,8 @@ +package macho + +// Thread is a thread state load command. +type Thread struct { + LoadCommand + Len uint32 + Type uint32 +}