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/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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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]}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
|
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"
|
"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) {
|
@ -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
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"
|
"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)))
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user