Improved mac support
This commit is contained in:
parent
58f010b81a
commit
cf52919edc
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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]}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ var (
|
||||
|
||||
func init() {
|
||||
debug.SetGCPercent(-1)
|
||||
Reset()
|
||||
|
||||
var err error
|
||||
Executable, err = os.Executable()
|
||||
|
13
src/errors/ExpectedCLIParameter.go
Normal file
13
src/errors/ExpectedCLIParameter.go
Normal 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)
|
||||
}
|
@ -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) {
|
@ -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
10
src/os/mac/Syscall.go
Normal 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
|
||||
)
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user