Added macho package
All checks were successful
/ test (push) Successful in 15s

This commit is contained in:
Eduard Urbach 2025-06-30 20:31:07 +02:00
parent 436691ae40
commit dbc865ee67
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
14 changed files with 317 additions and 0 deletions

12
docs/macho.md Normal file
View file

@ -0,0 +1,12 @@
# Mach-O
## Notes
MacOS requires including the headers in the __TEXT segment.
## Links
- https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h
- https://en.wikipedia.org/wiki/Mach-O
- https://github.com/aidansteele/osx-abi-macho-file-format-reference
- https://stackoverflow.com/questions/39863112/what-is-required-for-a-mach-o-executable-to-load

View file

@ -8,6 +8,7 @@ import (
"git.urbach.dev/cli/q/src/core" "git.urbach.dev/cli/q/src/core"
"git.urbach.dev/cli/q/src/data" "git.urbach.dev/cli/q/src/data"
"git.urbach.dev/cli/q/src/elf" "git.urbach.dev/cli/q/src/elf"
"git.urbach.dev/cli/q/src/macho"
) )
// WriteFile writes an executable file to disk. // WriteFile writes an executable file to disk.
@ -37,6 +38,8 @@ func WriteFile(executable string, b *build.Build, env *core.Environment) error {
switch b.OS { switch b.OS {
case build.Linux: case build.Linux:
elf.Write(file, b, code, data) elf.Write(file, b, code, data)
case build.Mac:
macho.Write(file, b, code, data)
} }
err = file.Close() err = file.Close()

15
src/macho/Arch.go Normal file
View file

@ -0,0 +1,15 @@
package macho
import "git.urbach.dev/cli/q/src/build"
// Arch returns the CPU architecture used in the Mach-O header.
func Arch(arch build.Arch) (CPU, uint32) {
switch arch {
case build.ARM:
return CPU_ARM_64, CPU_SUBTYPE_ARM64_ALL | 0x80000000
case build.X86:
return CPU_X86_64, CPU_SUBTYPE_X86_64_ALL | 0x80000000
default:
return 0, 0
}
}

15
src/macho/CPU.go Normal file
View file

@ -0,0 +1,15 @@
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
)
const (
CPU_SUBTYPE_ARM64_ALL = 0
CPU_SUBTYPE_X86_64_ALL = 3
)

7
src/macho/Constants.go Normal file
View file

@ -0,0 +1,7 @@
package macho
const (
BaseAddress = 0x1000000
SizeCommands = Segment64Size*3 + ThreadSize
HeaderEnd = HeaderSize + SizeCommands
)

15
src/macho/Header.go Normal file
View file

@ -0,0 +1,15 @@
package macho
const HeaderSize = 32
// Header contains general information.
type Header struct {
Magic uint32
Architecture CPU
MicroArchitecture uint32
Type HeaderType
NumCommands uint32
SizeCommands uint32
Flags HeaderFlags
Reserved uint32
}

32
src/macho/HeaderFlags.go Normal file
View file

@ -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
)

12
src/macho/HeaderType.go Normal file
View file

@ -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
)

34
src/macho/LoadCommand.go Normal file
View file

@ -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
)

119
src/macho/MachO.go Normal file
View file

@ -0,0 +1,119 @@
package macho
import (
"encoding/binary"
"io"
"git.urbach.dev/cli/q/src/build"
"git.urbach.dev/cli/q/src/exe"
)
// MachO is the executable format used on MacOS.
type MachO struct {
Header
PageZero Segment64
CodeHeader Segment64
DataHeader Segment64
UnixThread Thread
}
// Write writes the Mach-O format to the given writer.
func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte) {
x := exe.New(HeaderEnd, b.FileAlign, b.MemoryAlign)
x.InitSections(codeBytes, dataBytes)
code := x.Sections[0]
data := x.Sections[1]
arch, microArch := Arch(b.Arch)
entryPoint := BaseAddress + code.MemoryOffset
m := &MachO{
Header: Header{
Magic: 0xFEEDFACF,
Architecture: arch,
MicroArchitecture: microArch,
Type: TypeExecute,
NumCommands: 4,
SizeCommands: SizeCommands,
Flags: FlagNoUndefs | FlagPIE | FlagNoHeapExecution,
Reserved: 0,
},
PageZero: Segment64{
LoadCommand: LcSegment64,
Length: 72,
Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'},
Address: 0,
SizeInMemory: uint64(BaseAddress),
Offset: 0,
SizeInFile: 0,
NumSections: 0,
Flag: 0,
MaxProt: 0,
InitProt: 0,
},
CodeHeader: Segment64{
LoadCommand: LcSegment64,
Length: Segment64Size,
Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'},
Address: uint64(BaseAddress),
SizeInMemory: uint64(code.MemoryOffset + len(code.Bytes)),
Offset: 0,
SizeInFile: uint64(code.FileOffset + len(code.Bytes)),
NumSections: 0,
Flag: 0,
MaxProt: ProtReadable | ProtExecutable,
InitProt: ProtReadable | ProtExecutable,
},
DataHeader: Segment64{
LoadCommand: LcSegment64,
Length: Segment64Size,
Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'},
Address: uint64(BaseAddress + data.MemoryOffset),
SizeInMemory: uint64(len(data.Bytes)),
Offset: uint64(data.FileOffset),
SizeInFile: uint64(len(data.Bytes)),
NumSections: 0,
Flag: 0,
MaxProt: ProtReadable,
InitProt: ProtReadable,
},
UnixThread: Thread{
LoadCommand: LcUnixthread,
Len: ThreadSize,
Type: 0x4,
Data: [43]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,
uint32(entryPoint), 0,
0, 0,
0, 0,
0, 0,
0, 0,
},
},
}
binary.Write(writer, binary.LittleEndian, &m.Header)
binary.Write(writer, binary.LittleEndian, &m.PageZero)
binary.Write(writer, binary.LittleEndian, &m.CodeHeader)
binary.Write(writer, binary.LittleEndian, &m.DataHeader)
binary.Write(writer, binary.LittleEndian, &m.UnixThread)
writer.Seek(int64(code.Padding), io.SeekCurrent)
writer.Write(code.Bytes)
writer.Seek(int64(data.Padding), io.SeekCurrent)
writer.Write(data.Bytes)
}

15
src/macho/MachO_test.go Normal file
View file

@ -0,0 +1,15 @@
package macho_test
import (
"testing"
"git.urbach.dev/cli/q/src/build"
"git.urbach.dev/cli/q/src/exe"
"git.urbach.dev/cli/q/src/macho"
)
func TestWrite(t *testing.T) {
macho.Write(&exe.Discard{}, &build.Build{Arch: build.ARM, FileAlign: 0x4000, MemoryAlign: 0x4000}, nil, nil)
macho.Write(&exe.Discard{}, &build.Build{Arch: build.X86, FileAlign: 0x1000, MemoryAlign: 0x1000}, nil, nil)
macho.Write(&exe.Discard{}, &build.Build{Arch: build.UnknownArch, FileAlign: 0x1000, MemoryAlign: 0x1000}, nil, nil)
}

9
src/macho/Prot.go Normal file
View file

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

18
src/macho/Segment64.go Normal file
View file

@ -0,0 +1,18 @@
package macho
const Segment64Size = 72
// 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
}

11
src/macho/Thread.go Normal file
View file

@ -0,0 +1,11 @@
package macho
const ThreadSize = 184
// Thread is a thread state load command.
type Thread struct {
LoadCommand
Len uint32
Type uint32
Data [43]uint32
}