Improved mac support

This commit is contained in:
Eduard Urbach 2024-08-12 12:16:01 +02:00
parent af259b364c
commit f70a2e848d
Signed by: eduard
GPG key ID: 49226B848C78F6C8
25 changed files with 119 additions and 34 deletions

89
src/os/linux/elf/ELF.go Normal file
View file

@ -0,0 +1,89 @@
package elf
import (
"bytes"
"encoding/binary"
"io"
"git.akyoto.dev/cli/q/src/config"
)
// ELF represents an ELF file.
type ELF struct {
Header
CodeHeader ProgramHeader
DataHeader ProgramHeader
CodePadding []byte
Code []byte
DataPadding []byte
Data []byte
}
// New creates a new ELF binary.
func New(code []byte, data []byte) *ELF {
dataOffset := config.CodeOffset + int64(len(code))
dataPadding := Padding(dataOffset, config.Align)
dataOffset += dataPadding
return &ELF{
Header: Header{
Magic: [4]byte{0x7F, 'E', 'L', 'F'},
Class: 2,
Endianness: LittleEndian,
Version: 1,
OSABI: 0,
ABIVersion: 0,
Type: TypeExecutable,
Architecture: ArchitectureAMD64,
FileVersion: 1,
EntryPointInMemory: config.BaseAddress + config.CodeOffset,
ProgramHeaderOffset: HeaderSize,
SectionHeaderOffset: 0,
Flags: 0,
Size: HeaderSize,
ProgramHeaderEntrySize: ProgramHeaderSize,
ProgramHeaderEntryCount: 2,
SectionHeaderEntrySize: SectionHeaderSize,
SectionHeaderEntryCount: 0,
SectionNameStringTableIndex: 0,
},
CodeHeader: ProgramHeader{
Type: ProgramTypeLOAD,
Flags: ProgramFlagsExecutable | ProgramFlagsReadable,
Offset: config.CodeOffset,
VirtualAddress: config.BaseAddress + config.CodeOffset,
PhysicalAddress: config.BaseAddress + config.CodeOffset,
SizeInFile: int64(len(code)),
SizeInMemory: int64(len(code)),
Align: config.Align,
},
DataHeader: ProgramHeader{
Type: ProgramTypeLOAD,
Flags: ProgramFlagsReadable,
Offset: dataOffset,
VirtualAddress: config.BaseAddress + dataOffset,
PhysicalAddress: config.BaseAddress + dataOffset,
SizeInFile: int64(len(data)),
SizeInMemory: int64(len(data)),
Align: config.Align,
},
CodePadding: nil,
Code: code,
DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)),
Data: data,
}
}
// Write writes the ELF64 format to the given writer.
func (elf *ELF) Write(writer io.Writer) {
binary.Write(writer, binary.LittleEndian, &elf.Header)
binary.Write(writer, binary.LittleEndian, &elf.CodeHeader)
binary.Write(writer, binary.LittleEndian, &elf.DataHeader)
writer.Write(elf.CodePadding)
writer.Write(elf.Code)
if len(elf.Data) > 0 {
writer.Write(elf.DataPadding)
writer.Write(elf.Data)
}
}

View file

@ -0,0 +1,13 @@
package elf_test
import (
"io"
"testing"
"git.akyoto.dev/cli/q/src/os/linux/elf"
)
func TestELF(t *testing.T) {
exe := elf.New(nil, nil)
exe.Write(io.Discard)
}

View file

@ -0,0 +1,32 @@
package elf
const (
LittleEndian = 1
TypeExecutable = 2
ArchitectureAMD64 = 0x3E
HeaderSize = 64
)
// Header contains general information.
type Header struct {
Magic [4]byte
Class byte
Endianness byte
Version byte
OSABI byte
ABIVersion byte
_ [7]byte
Type int16
Architecture int16
FileVersion int32
EntryPointInMemory int64
ProgramHeaderOffset int64
SectionHeaderOffset int64
Flags int32
Size int16
ProgramHeaderEntrySize int16
ProgramHeaderEntryCount int16
SectionHeaderEntrySize int16
SectionHeaderEntryCount int16
SectionNameStringTableIndex int16
}

View file

@ -0,0 +1,6 @@
package elf
// 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 {
return align - (n % align)
}

View file

@ -0,0 +1,37 @@
package elf
// ProgramHeaderSize is equal to the size of a program header in bytes.
const ProgramHeaderSize = 56
// ProgramHeader points to the executable part of our program.
type ProgramHeader struct {
Type ProgramType
Flags ProgramFlags
Offset int64
VirtualAddress int64
PhysicalAddress int64
SizeInFile int64
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

@ -0,0 +1,45 @@
package elf
// SectionHeaderSize is equal to the size of a section header in bytes.
const SectionHeaderSize = 64
// SectionHeader points to the data sections of our program.
type SectionHeader struct {
NameIndex int32
Type SectionType
Flags SectionFlags
VirtualAddress int64
Offset int64
SizeInFile int64
Link int32
Info int32
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
)

30
src/os/linux/elf/elf.md Normal file
View file

@ -0,0 +1,30 @@
# ELF
## Basic structure
1. ELF header (0x00 - 0x40)
2. Program header (0x40 - 0x78)
3. Padding (0x78 - 0x80)
4. Machine code (0x80)
## Entry point
The entry point is defined in the first 64 bytes (ELF header).
## Base address
The minimum base address is controlled by the `mmap` settings:
```shell
sysctl vm.mmap_min_addr
```
Usually, this value is 65536 (0x1000).
## Initialization in Linux
ELF loader:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/binfmt_elf.c
ELF register definitions:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/elf.h

10
src/os/mac/Syscall.go Normal file
View file

@ -0,0 +1,10 @@
package mac
// Syscall numbers are divided into classes, here we need the BSD inherited syscalls.
const SyscallClassUnix = 0x2000000
// https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/syscalls.master
const (
Exit = 1 | SyscallClassUnix
Write = 4 | SyscallClassUnix
)

10
src/os/mac/macho/CPU.go Normal file
View file

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

View file

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

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
)

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
)

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
)

122
src/os/mac/macho/MachO.go Normal file
View file

@ -0,0 +1,122 @@
package macho
import (
"bytes"
"encoding/binary"
"io"
"git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/os/linux/elf"
)
// 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: 3 | 0x80000000,
Type: TypeExecute,
NumCommands: 4,
SizeCommands: 0x48*3 + 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
codeEnd := uint64(codeStart) + uint64(len(m.Code))
dataPadding := elf.Padding(codeEnd, 4096)
dataStart := codeEnd + dataPadding
binary.Write(writer, binary.LittleEndian, &Segment64{
LoadCommand: LcSegment64,
Length: 0x48,
Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'},
Address: config.BaseAddress,
SizeInMemory: codeEnd,
Offset: 0,
SizeInFile: codeEnd,
NumSections: 0,
Flag: 0,
MaxProt: ProtReadable | ProtWritable | ProtExecutable,
InitProt: ProtReadable | ProtExecutable,
})
binary.Write(writer, binary.LittleEndian, &Segment64{
LoadCommand: LcSegment64,
Length: 0x48,
Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'},
Address: config.BaseAddress + dataStart,
SizeInMemory: uint64(len(m.Data)),
Offset: dataStart,
SizeInFile: uint64(len(m.Data)),
NumSections: 0,
Flag: 0,
MaxProt: ProtReadable,
InitProt: ProtReadable,
})
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)
writer.Write(bytes.Repeat([]byte{0}, int(dataPadding)))
writer.Write(m.Data)
}

9
src/os/mac/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
)

View file

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

View file

@ -0,0 +1,8 @@
package macho
// Thread is a thread state load command.
type Thread struct {
LoadCommand
Len uint32
Type uint32
}