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/arch/x64"
"git.akyoto.dev/cli/q/src/config" "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" "git.akyoto.dev/cli/q/src/sizeof"
) )

View File

@ -18,7 +18,7 @@ func Build(args []string) int {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
switch err.(type) { switch err.(type) {
case *errors.UnknownCLIParameter: case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter:
return 2 return 2
default: default:
@ -37,10 +37,31 @@ func buildWithArgs(args []string) (*build.Build, error) {
switch args[i] { switch args[i] {
case "-a", "--assembler": case "-a", "--assembler":
config.Assembler = true config.Assembler = true
case "-d", "--dry": case "-d", "--dry":
config.Dry = true config.Dry = true
case "-v", "--verbose": case "-v", "--verbose":
config.Assembler = true 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: default:
if strings.HasPrefix(args[i], "-") { if strings.HasPrefix(args[i], "-") {
return b, &errors.UnknownCLIParameter{Parameter: args[i]} return b, &errors.UnknownCLIParameter{Parameter: args[i]}

View File

@ -4,6 +4,7 @@ import (
"testing" "testing"
"git.akyoto.dev/cli/q/src/cli" "git.akyoto.dev/cli/q/src/cli"
"git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/go/assert" "git.akyoto.dev/go/assert"
) )
@ -31,6 +32,7 @@ func TestCLI(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Log(test.arguments) t.Log(test.arguments)
config.Reset()
exitCode := cli.Main(test.arguments) exitCode := cli.Main(test.arguments)
assert.Equal(t, exitCode, test.expectedExitCode) assert.Equal(t, exitCode, test.expectedExitCode)
} }

View File

@ -16,7 +16,7 @@ func Run(args []string) int {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
switch err.(type) { switch err.(type) {
case *errors.UnknownCLIParameter: case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter:
return 2 return 2
default: default:

View File

@ -2,16 +2,18 @@ package compiler
import ( import (
"bufio" "bufio"
"fmt"
"io" "io"
"os" "os"
"runtime"
"git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/arch/x64"
"git.akyoto.dev/cli/q/src/asm" "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/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"
"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. // Result contains all the compiled functions in a build.
@ -35,11 +37,11 @@ func (r *Result) finalize() ([]byte, []byte) {
sysExit := 0 sysExit := 0
switch runtime.GOOS { switch config.TargetOS {
case "linux": case "linux":
sysExit = linux.Exit sysExit = linux.Exit
case "darwin": case "mac":
sysExit = 1 | 0x2000000 sysExit = mac.Exit
} }
final.Call("main.main") final.Call("main.main")
@ -47,17 +49,17 @@ func (r *Result) finalize() ([]byte, []byte) {
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0)
final.Syscall() 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 // This will place the main function immediately after the entry point
// and also add everything the main function calls recursively. // and also add everything the main function calls recursively.
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {
final.Merge(f.Assembler) 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() code, data := final.Finalize()
return code, data return code, data
} }
@ -129,15 +131,15 @@ func (r *Result) WriteFile(path string) error {
func write(writer io.Writer, code []byte, data []byte) error { func write(writer io.Writer, code []byte, data []byte) error {
buffer := bufio.NewWriter(writer) buffer := bufio.NewWriter(writer)
switch runtime.GOOS { switch config.TargetOS {
case "darwin":
exe := macho.New(code, data)
exe.Write(buffer)
case "linux": case "linux":
exe := elf.New(code, data) exe := elf.New(code, data)
exe.Write(buffer) exe.Write(buffer)
case "mac":
exe := macho.New(code, data)
exe.Write(buffer)
default: default:
panic("unsupported platform") return fmt.Errorf("unsupported platform '%s'", config.TargetOS)
} }
return buffer.Flush() return buffer.Flush()

View File

@ -1,5 +1,7 @@
package config package config
import "runtime"
const ( const (
// This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`.
MinAddress = 0x10000 MinAddress = 0x10000
@ -16,11 +18,30 @@ const (
var ( var (
// Shows the assembly instructions at the end of the compilation. // Shows the assembly instructions at the end of the compilation.
Assembler = false Assembler bool
// Calculates the result of operations on constants at compile time. // Calculates the result of operations on constants at compile time.
ConstantFold = true ConstantFold bool
// Skips writing the executable to disk. // 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() { func init() {
debug.SetGCPercent(-1) debug.SetGCPercent(-1)
Reset()
var err error var err error
Executable, err = os.Executable() 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" "io"
"testing" "testing"
"git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/os/linux/elf"
) )
func TestELF(t *testing.T) { func TestELF(t *testing.T) {

View File

@ -1,6 +1,6 @@
package elf package elf
// Padding calculates the padding needed to align `n` bytes with the specified alignment. // 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) 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" "io"
"git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/os/linux/elf"
) )
// MachO is the executable format used on MacOS. // MachO is the executable format used on MacOS.
@ -21,10 +22,10 @@ func New(code []byte, data []byte) *MachO {
Header: Header{ Header: Header{
Magic: 0xFEEDFACF, Magic: 0xFEEDFACF,
Architecture: CPU_X86_64, Architecture: CPU_X86_64,
MicroArchitecture: 0x80000003, MicroArchitecture: 3 | 0x80000000,
Type: TypeExecute, Type: TypeExecute,
NumCommands: 3, NumCommands: 4,
SizeCommands: 0x48*2 + 184, SizeCommands: 0x48*3 + 184,
Flags: FlagNoUndefs, Flags: FlagNoUndefs,
Reserved: 0, Reserved: 0,
}, },
@ -52,22 +53,38 @@ func (m *MachO) Write(writer io.Writer) {
}) })
codeStart := 32 + m.Header.SizeCommands 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{ binary.Write(writer, binary.LittleEndian, &Segment64{
LoadCommand: LcSegment64, LoadCommand: LcSegment64,
Length: 0x48, Length: 0x48,
Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'},
Address: config.BaseAddress, Address: config.BaseAddress,
SizeInMemory: totalSize, SizeInMemory: codeEnd,
Offset: 0, Offset: 0,
SizeInFile: totalSize, SizeInFile: codeEnd,
NumSections: 0, NumSections: 0,
Flag: 0, Flag: 0,
MaxProt: ProtReadable | ProtWritable | ProtExecutable, MaxProt: ProtReadable | ProtWritable | ProtExecutable,
InitProt: ProtReadable | 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{ binary.Write(writer, binary.LittleEndian, &Thread{
LoadCommand: LcUnixthread, LoadCommand: LcUnixthread,
Len: 184, Len: 184,
@ -100,8 +117,6 @@ func (m *MachO) Write(writer io.Writer) {
}) })
writer.Write(m.Code) writer.Write(m.Code)
writer.Write(bytes.Repeat([]byte{0}, int(dataPadding)))
if totalSize < 4096 { writer.Write(m.Data)
writer.Write(bytes.Repeat([]byte{0}, 4096-int(totalSize)))
}
} }