From cf52919edc385647a9e185ca00d59aab91f20780 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 12:16:01 +0200 Subject: [PATCH] Improved mac support --- src/asm/Finalize.go | 2 +- src/cli/Build.go | 23 +++++++++++++++- src/cli/Main_test.go | 2 ++ src/cli/Run.go | 2 +- src/compiler/Result.go | 34 +++++++++++++----------- src/config/config.go | 27 ++++++++++++++++--- src/config/init.go | 1 + src/errors/ExpectedCLIParameter.go | 13 +++++++++ src/{ => os/linux}/elf/ELF.go | 0 src/{ => os/linux}/elf/ELF_test.go | 2 +- src/{ => os/linux}/elf/Header.go | 0 src/{ => os/linux}/elf/Padding.go | 2 +- src/{ => os/linux}/elf/ProgramHeader.go | 0 src/{ => os/linux}/elf/SectionHeader.go | 0 src/{ => os/linux}/elf/elf.md | 0 src/os/mac/Syscall.go | 10 +++++++ src/{ => os/mac}/macho/CPU.go | 0 src/{ => os/mac}/macho/Header.go | 0 src/{ => os/mac}/macho/HeaderFlags.go | 0 src/{ => os/mac}/macho/HeaderType.go | 0 src/{ => os/mac}/macho/LoadCommand.go | 0 src/{ => os/mac}/macho/MachO.go | 35 ++++++++++++++++++------- src/{ => os/mac}/macho/Prot.go | 0 src/{ => os/mac}/macho/Segment64.go | 0 src/{ => os/mac}/macho/Thread.go | 0 25 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 src/errors/ExpectedCLIParameter.go rename src/{ => os/linux}/elf/ELF.go (100%) rename src/{ => os/linux}/elf/ELF_test.go (75%) rename src/{ => os/linux}/elf/Header.go (100%) rename src/{ => os/linux}/elf/Padding.go (62%) rename src/{ => os/linux}/elf/ProgramHeader.go (100%) rename src/{ => os/linux}/elf/SectionHeader.go (100%) rename src/{ => os/linux}/elf/elf.md (100%) create mode 100644 src/os/mac/Syscall.go rename src/{ => os/mac}/macho/CPU.go (100%) rename src/{ => os/mac}/macho/Header.go (100%) rename src/{ => os/mac}/macho/HeaderFlags.go (100%) rename src/{ => os/mac}/macho/HeaderType.go (100%) rename src/{ => os/mac}/macho/LoadCommand.go (100%) rename src/{ => os/mac}/macho/MachO.go (69%) rename src/{ => os/mac}/macho/Prot.go (100%) rename src/{ => os/mac}/macho/Segment64.go (100%) rename src/{ => os/mac}/macho/Thread.go (100%) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 446048a..c59ed54 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/os/linux/elf" "git.akyoto.dev/cli/q/src/sizeof" ) diff --git a/src/cli/Build.go b/src/cli/Build.go index ea5926e..00fc49a 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -18,7 +18,7 @@ func Build(args []string) int { fmt.Fprintln(os.Stderr, err) switch err.(type) { - case *errors.UnknownCLIParameter: + case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: return 2 default: @@ -37,10 +37,31 @@ func buildWithArgs(args []string) (*build.Build, error) { switch args[i] { case "-a", "--assembler": config.Assembler = true + case "-d", "--dry": config.Dry = true + case "-v", "--verbose": config.Assembler = true + + case "--arch": + i++ + + if i >= len(args) { + return b, &errors.ExpectedCLIParameter{Parameter: "arch"} + } + + config.TargetArch = args[i] + + case "--os": + i++ + + if i >= len(args) { + return b, &errors.ExpectedCLIParameter{Parameter: "os"} + } + + config.TargetOS = args[i] + default: if strings.HasPrefix(args[i], "-") { return b, &errors.UnknownCLIParameter{Parameter: args[i]} diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 4d40adb..93138a8 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -4,6 +4,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/cli" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) @@ -31,6 +32,7 @@ func TestCLI(t *testing.T) { for _, test := range tests { t.Log(test.arguments) + config.Reset() exitCode := cli.Main(test.arguments) assert.Equal(t, exitCode, test.expectedExitCode) } diff --git a/src/cli/Run.go b/src/cli/Run.go index 20b49f6..11a5969 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -16,7 +16,7 @@ func Run(args []string) int { fmt.Fprintln(os.Stderr, err) switch err.(type) { - case *errors.UnknownCLIParameter: + case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: return 2 default: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 8551388..18880ae 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -2,16 +2,18 @@ package compiler import ( "bufio" + "fmt" "io" "os" - "runtime" "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" "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" + "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/mac" + "git.akyoto.dev/cli/q/src/os/mac/macho" ) // Result contains all the compiled functions in a build. @@ -35,11 +37,11 @@ func (r *Result) finalize() ([]byte, []byte) { sysExit := 0 - switch runtime.GOOS { + switch config.TargetOS { case "linux": sysExit = linux.Exit - case "darwin": - sysExit = 1 | 0x2000000 + case "mac": + sysExit = mac.Exit } final.Call("main.main") @@ -47,17 +49,17 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() - final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) - final.Syscall() - // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { final.Merge(f.Assembler) }) + final.Label(asm.LABEL, "_crash") + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.Syscall() + code, data := final.Finalize() return code, data } @@ -129,15 +131,15 @@ func (r *Result) WriteFile(path string) error { func write(writer io.Writer, code []byte, data []byte) error { buffer := bufio.NewWriter(writer) - switch runtime.GOOS { - case "darwin": - exe := macho.New(code, data) - exe.Write(buffer) + switch config.TargetOS { case "linux": exe := elf.New(code, data) exe.Write(buffer) + case "mac": + exe := macho.New(code, data) + exe.Write(buffer) default: - panic("unsupported platform") + return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } return buffer.Flush() diff --git a/src/config/config.go b/src/config/config.go index 9254e64..db3b7c1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -1,5 +1,7 @@ package config +import "runtime" + const ( // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. MinAddress = 0x10000 @@ -16,11 +18,30 @@ const ( var ( // Shows the assembly instructions at the end of the compilation. - Assembler = false + Assembler bool // Calculates the result of operations on constants at compile time. - ConstantFold = true + ConstantFold bool // Skips writing the executable to disk. - Dry = false + Dry bool + + // Target architecture. + TargetArch string + + // Target platform. + TargetOS string ) + +// Reset resets the configuration to its default values. +func Reset() { + Assembler = false + ConstantFold = true + Dry = false + TargetArch = runtime.GOARCH + TargetOS = runtime.GOOS + + if TargetOS == "darwin" { + TargetOS = "mac" + } +} diff --git a/src/config/init.go b/src/config/init.go index 5f0f972..8ef2be7 100644 --- a/src/config/init.go +++ b/src/config/init.go @@ -16,6 +16,7 @@ var ( func init() { debug.SetGCPercent(-1) + Reset() var err error Executable, err = os.Executable() diff --git a/src/errors/ExpectedCLIParameter.go b/src/errors/ExpectedCLIParameter.go new file mode 100644 index 0000000..0c25c6b --- /dev/null +++ b/src/errors/ExpectedCLIParameter.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// ExpectedCLIParameter error is created when a command line parameter is missing. +type ExpectedCLIParameter struct { + Parameter string +} + +// Error generates the string representation. +func (err *ExpectedCLIParameter) Error() string { + return fmt.Sprintf("Expected parameter '%s'", err.Parameter) +} diff --git a/src/elf/ELF.go b/src/os/linux/elf/ELF.go similarity index 100% rename from src/elf/ELF.go rename to src/os/linux/elf/ELF.go diff --git a/src/elf/ELF_test.go b/src/os/linux/elf/ELF_test.go similarity index 75% rename from src/elf/ELF_test.go rename to src/os/linux/elf/ELF_test.go index 45a9aa2..a2051d8 100644 --- a/src/elf/ELF_test.go +++ b/src/os/linux/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/os/linux/elf" ) func TestELF(t *testing.T) { diff --git a/src/elf/Header.go b/src/os/linux/elf/Header.go similarity index 100% rename from src/elf/Header.go rename to src/os/linux/elf/Header.go diff --git a/src/elf/Padding.go b/src/os/linux/elf/Padding.go similarity index 62% rename from src/elf/Padding.go rename to src/os/linux/elf/Padding.go index 5cca58e..e56666a 100644 --- a/src/elf/Padding.go +++ b/src/os/linux/elf/Padding.go @@ -1,6 +1,6 @@ package elf // Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding(n int64, align int64) int64 { +func Padding[T int | int32 | int64 | uint | uint32 | uint64](n T, align T) T { return align - (n % align) } diff --git a/src/elf/ProgramHeader.go b/src/os/linux/elf/ProgramHeader.go similarity index 100% rename from src/elf/ProgramHeader.go rename to src/os/linux/elf/ProgramHeader.go diff --git a/src/elf/SectionHeader.go b/src/os/linux/elf/SectionHeader.go similarity index 100% rename from src/elf/SectionHeader.go rename to src/os/linux/elf/SectionHeader.go diff --git a/src/elf/elf.md b/src/os/linux/elf/elf.md similarity index 100% rename from src/elf/elf.md rename to src/os/linux/elf/elf.md diff --git a/src/os/mac/Syscall.go b/src/os/mac/Syscall.go new file mode 100644 index 0000000..2111c25 --- /dev/null +++ b/src/os/mac/Syscall.go @@ -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 +) diff --git a/src/macho/CPU.go b/src/os/mac/macho/CPU.go similarity index 100% rename from src/macho/CPU.go rename to src/os/mac/macho/CPU.go diff --git a/src/macho/Header.go b/src/os/mac/macho/Header.go similarity index 100% rename from src/macho/Header.go rename to src/os/mac/macho/Header.go diff --git a/src/macho/HeaderFlags.go b/src/os/mac/macho/HeaderFlags.go similarity index 100% rename from src/macho/HeaderFlags.go rename to src/os/mac/macho/HeaderFlags.go diff --git a/src/macho/HeaderType.go b/src/os/mac/macho/HeaderType.go similarity index 100% rename from src/macho/HeaderType.go rename to src/os/mac/macho/HeaderType.go diff --git a/src/macho/LoadCommand.go b/src/os/mac/macho/LoadCommand.go similarity index 100% rename from src/macho/LoadCommand.go rename to src/os/mac/macho/LoadCommand.go diff --git a/src/macho/MachO.go b/src/os/mac/macho/MachO.go similarity index 69% rename from src/macho/MachO.go rename to src/os/mac/macho/MachO.go index c2fd883..d4fc05b 100644 --- a/src/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -6,6 +6,7 @@ import ( "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. @@ -21,10 +22,10 @@ func New(code []byte, data []byte) *MachO { Header: Header{ Magic: 0xFEEDFACF, Architecture: CPU_X86_64, - MicroArchitecture: 0x80000003, + MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, - NumCommands: 3, - SizeCommands: 0x48*2 + 184, + NumCommands: 4, + SizeCommands: 0x48*3 + 184, Flags: FlagNoUndefs, Reserved: 0, }, @@ -52,22 +53,38 @@ func (m *MachO) Write(writer io.Writer) { }) codeStart := 32 + m.Header.SizeCommands - totalSize := uint64(codeStart) + uint64(len(m.Code)) + 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: totalSize, + SizeInMemory: codeEnd, Offset: 0, - SizeInFile: totalSize, + 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, @@ -100,8 +117,6 @@ func (m *MachO) Write(writer io.Writer) { }) writer.Write(m.Code) - - if totalSize < 4096 { - writer.Write(bytes.Repeat([]byte{0}, 4096-int(totalSize))) - } + writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(m.Data) } diff --git a/src/macho/Prot.go b/src/os/mac/macho/Prot.go similarity index 100% rename from src/macho/Prot.go rename to src/os/mac/macho/Prot.go diff --git a/src/macho/Segment64.go b/src/os/mac/macho/Segment64.go similarity index 100% rename from src/macho/Segment64.go rename to src/os/mac/macho/Segment64.go diff --git a/src/macho/Thread.go b/src/os/mac/macho/Thread.go similarity index 100% rename from src/macho/Thread.go rename to src/os/mac/macho/Thread.go