Improved mac support

This commit is contained in:
Eduard Urbach 2024-08-12 12:16:01 +02:00
parent 58f010b81a
commit cf52919edc
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
25 changed files with 119 additions and 34 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ var (
func init() {
debug.SetGCPercent(-1)
Reset()
var err error
Executable, err = os.Executable()

View File

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

View File

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

View File

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

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
)

View File

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