Added basic support for arm64
This commit is contained in:
10
src/arm/Call.go
Normal file
10
src/arm/Call.go
Normal file
@ -0,0 +1,10 @@
|
||||
package arm
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// Call branches to a PC-relative offset, setting the register X30 to PC+4.
|
||||
// The offset starts from the address of this instruction and is encoded as "imm26" times 4.
|
||||
// This instruction is also known as BL (branch with link).
|
||||
func Call(code []byte, offset uint32) []byte {
|
||||
return binary.LittleEndian.AppendUint32(code, uint32(0b100101<<26)|offset)
|
||||
}
|
29
src/arm/Move.go
Normal file
29
src/arm/Move.go
Normal file
@ -0,0 +1,29 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
)
|
||||
|
||||
// MoveRegisterNumber moves an integer into the given register.
|
||||
func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
return MoveZero(code, destination, 0, uint16(number))
|
||||
}
|
||||
|
||||
// MoveKeep moves a 16-bit integer into the given register and keeps all other bits.
|
||||
func MoveKeep(code []byte, destination cpu.Register, halfword int, number uint16) []byte {
|
||||
x := mov(0b11, halfword, number, destination)
|
||||
return binary.LittleEndian.AppendUint32(code, x)
|
||||
}
|
||||
|
||||
// MoveZero moves a 16-bit integer into the given register and clears all other bits to zero.
|
||||
func MoveZero(code []byte, destination cpu.Register, halfword int, number uint16) []byte {
|
||||
x := mov(0b10, halfword, number, destination)
|
||||
return binary.LittleEndian.AppendUint32(code, x)
|
||||
}
|
||||
|
||||
// mov encodes a generic move instruction.
|
||||
func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 {
|
||||
return (1 << 31) | (opCode << 29) | (0b100101 << 23) | uint32(halfword<<21) | uint32(number<<5) | uint32(destination)
|
||||
}
|
43
src/arm/Move_test.go
Normal file
43
src/arm/Move_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestMoveKeep(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number uint16
|
||||
Code []byte
|
||||
}{
|
||||
{arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xF2}},
|
||||
{arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xF2}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("movk %s, %x", pattern.Register, pattern.Number)
|
||||
code := arm.MoveKeep(nil, pattern.Register, 0, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveZero(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number uint16
|
||||
Code []byte
|
||||
}{
|
||||
{arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}},
|
||||
{arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xD2}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("movz %s, %x", pattern.Register, pattern.Number)
|
||||
code := arm.MoveZero(nil, pattern.Register, 0, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
6
src/arm/Nop.go
Normal file
6
src/arm/Nop.go
Normal file
@ -0,0 +1,6 @@
|
||||
package arm
|
||||
|
||||
// Nop does nothing. This can be used for alignment purposes.
|
||||
func Nop(code []byte) []byte {
|
||||
return append(code, 0x1F, 0x20, 0x03, 0xD5)
|
||||
}
|
@ -38,7 +38,20 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28}
|
||||
InputRegisters = SyscallInputRegisters
|
||||
OutputRegisters = SyscallInputRegisters
|
||||
SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5}
|
||||
SyscallOutputRegisters = []cpu.Register{X0, X1}
|
||||
WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7}
|
||||
WindowsOutputRegisters = []cpu.Register{X0, X1}
|
||||
|
||||
CPU = cpu.CPU{
|
||||
General: GeneralRegisters,
|
||||
Input: InputRegisters,
|
||||
Output: OutputRegisters,
|
||||
SyscallInput: SyscallInputRegisters,
|
||||
SyscallOutput: SyscallOutputRegisters,
|
||||
NumRegisters: 32,
|
||||
}
|
||||
)
|
||||
|
6
src/arm/Return.go
Normal file
6
src/arm/Return.go
Normal file
@ -0,0 +1,6 @@
|
||||
package arm
|
||||
|
||||
// Return transfers program control to the caller.
|
||||
func Return(code []byte) []byte {
|
||||
return append(code, 0xC0, 0x03, 0x5F, 0xD6)
|
||||
}
|
6
src/arm/Syscall.go
Normal file
6
src/arm/Syscall.go
Normal file
@ -0,0 +1,6 @@
|
||||
package arm
|
||||
|
||||
// Syscall is the primary way to communicate with the OS kernel.
|
||||
func Syscall(code []byte) []byte {
|
||||
return append(code, 0x01, 0x00, 0x00, 0xD4)
|
||||
}
|
16
src/arm/arm_test.go
Normal file
16
src/arm/arm_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package arm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestARM(t *testing.T) {
|
||||
assert.DeepEqual(t, arm.Call(nil, 0), []byte{0x00, 0x00, 0x00, 0x94})
|
||||
assert.DeepEqual(t, arm.MoveRegisterNumber(nil, arm.X0, 42), arm.MoveZero(nil, arm.X0, 0, 42))
|
||||
assert.DeepEqual(t, arm.Nop(nil), []byte{0x1F, 0x20, 0x03, 0xD5})
|
||||
assert.DeepEqual(t, arm.Return(nil), []byte{0xC0, 0x03, 0x5F, 0xD6})
|
||||
assert.DeepEqual(t, arm.Syscall(nil), []byte{0x01, 0x00, 0x00, 0xD4})
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package asmc
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/asm"
|
||||
"git.urbach.dev/cli/q/src/config"
|
||||
"git.urbach.dev/cli/q/src/dll"
|
||||
@ -24,8 +25,20 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) {
|
||||
dlls: dlls,
|
||||
}
|
||||
|
||||
for _, x := range a.Instructions {
|
||||
c.compile(x)
|
||||
switch config.TargetArch {
|
||||
case config.ARM:
|
||||
for _, x := range a.Instructions {
|
||||
c.compileARM(x)
|
||||
}
|
||||
|
||||
c.code = arm.MoveRegisterNumber(c.code, arm.X0, 0)
|
||||
c.code = arm.MoveRegisterNumber(c.code, arm.X8, 0x5D)
|
||||
c.code = arm.Syscall(c.code)
|
||||
|
||||
case config.X86:
|
||||
for _, x := range a.Instructions {
|
||||
c.compileX86(x)
|
||||
}
|
||||
}
|
||||
|
||||
c.resolvePointers()
|
||||
|
25
src/asmc/compileARM.go
Normal file
25
src/asmc/compileARM.go
Normal file
@ -0,0 +1,25 @@
|
||||
package asmc
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/asm"
|
||||
)
|
||||
|
||||
func (c *compiler) compileARM(x asm.Instruction) {
|
||||
switch x.Mnemonic {
|
||||
// case asm.MOVE:
|
||||
// switch operands := x.Data.(type) {
|
||||
// case *asm.RegisterNumber:
|
||||
// c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number)
|
||||
// }
|
||||
|
||||
// case asm.RETURN:
|
||||
// c.code = arm.Return(c.code)
|
||||
|
||||
// case asm.SYSCALL:
|
||||
// c.code = arm.Syscall(c.code)
|
||||
|
||||
default:
|
||||
c.code = arm.Nop(c.code)
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import (
|
||||
"git.urbach.dev/cli/q/src/x86"
|
||||
)
|
||||
|
||||
func (c *compiler) compile(x asm.Instruction) {
|
||||
func (c *compiler) compileX86(x asm.Instruction) {
|
||||
switch x.Mnemonic {
|
||||
case asm.ADD:
|
||||
switch operands := x.Data.(type) {
|
@ -48,7 +48,14 @@ func buildExecutable(args []string) (*build.Build, error) {
|
||||
return b, &ExpectedParameterError{Parameter: "arch"}
|
||||
}
|
||||
|
||||
config.TargetArch = args[i]
|
||||
switch args[i] {
|
||||
case "arm":
|
||||
config.TargetArch = config.ARM
|
||||
case "x86":
|
||||
config.TargetArch = config.X86
|
||||
default:
|
||||
return b, &InvalidValueError{Value: args[i], Parameter: "arch"}
|
||||
}
|
||||
|
||||
case "--os":
|
||||
i++
|
||||
@ -77,7 +84,7 @@ func buildExecutable(args []string) (*build.Build, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if config.TargetOS == config.Unknown {
|
||||
if config.TargetOS == config.UnknownOS {
|
||||
return b, &InvalidValueError{Value: runtime.GOOS, Parameter: "os"}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ func Help(w io.Writer, code int) int {
|
||||
Commands:
|
||||
|
||||
build [directory | file] build an executable from a file or directory
|
||||
--arch [arch] cross-compile for another CPU architecture [x86|arm|riscv]
|
||||
--arch [arch] cross-compile for another CPU architecture [x86|arm]
|
||||
--assembly, -a show assembly instructions
|
||||
--dry, -d skip writing the executable to disk
|
||||
--os [os] cross-compile for another OS [linux|mac|windows]
|
||||
|
9
src/config/arch.go
Normal file
9
src/config/arch.go
Normal file
@ -0,0 +1,9 @@
|
||||
package config
|
||||
|
||||
type Arch uint8
|
||||
|
||||
const (
|
||||
UnknownArch Arch = iota
|
||||
ARM
|
||||
X86
|
||||
)
|
@ -3,12 +3,12 @@ package config
|
||||
import "runtime"
|
||||
|
||||
var (
|
||||
ConstantFold bool // Calculates the result of operations on constants at compile time.
|
||||
Dry bool // Skips writing the executable to disk.
|
||||
ShowAssembly bool // Shows assembly instructions at the end.
|
||||
ShowStatistics bool // Shows statistics at the end.
|
||||
TargetArch string // Target architecture.
|
||||
TargetOS OS // Target platform.
|
||||
ConstantFold bool // Calculates the result of operations on constants at compile time.
|
||||
Dry bool // Skips writing the executable to disk.
|
||||
ShowAssembly bool // Shows assembly instructions at the end.
|
||||
ShowStatistics bool // Shows statistics at the end.
|
||||
TargetArch Arch // Target architecture.
|
||||
TargetOS OS // Target platform.
|
||||
)
|
||||
|
||||
// Reset resets the configuration to its default values.
|
||||
@ -16,7 +16,15 @@ func Reset() {
|
||||
ShowAssembly = false
|
||||
ShowStatistics = false
|
||||
Dry = false
|
||||
TargetArch = runtime.GOARCH
|
||||
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
TargetArch = X86
|
||||
case "arm":
|
||||
TargetArch = ARM
|
||||
default:
|
||||
TargetArch = UnknownArch
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
@ -26,7 +34,7 @@ func Reset() {
|
||||
case "windows":
|
||||
TargetOS = Windows
|
||||
default:
|
||||
TargetOS = Unknown
|
||||
TargetOS = UnknownOS
|
||||
}
|
||||
|
||||
Optimize(true)
|
||||
|
@ -3,7 +3,7 @@ package config
|
||||
type OS uint8
|
||||
|
||||
const (
|
||||
Unknown OS = iota
|
||||
UnknownOS OS = iota
|
||||
Linux
|
||||
Mac
|
||||
Windows
|
||||
|
@ -1,7 +1,9 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/arm"
|
||||
"git.urbach.dev/cli/q/src/asm"
|
||||
"git.urbach.dev/cli/q/src/config"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/cli/q/src/fs"
|
||||
"git.urbach.dev/cli/q/src/register"
|
||||
@ -11,6 +13,15 @@ import (
|
||||
|
||||
// NewFunction creates a new function.
|
||||
func NewFunction(pkg string, name string, file *fs.File) *Function {
|
||||
var cpu *cpu.CPU
|
||||
|
||||
switch config.TargetArch {
|
||||
case config.ARM:
|
||||
cpu = &arm.CPU
|
||||
case config.X86:
|
||||
cpu = &x86.CPU
|
||||
}
|
||||
|
||||
return &Function{
|
||||
Package: pkg,
|
||||
Name: name,
|
||||
@ -23,14 +34,7 @@ func NewFunction(pkg string, name string, file *fs.File) *Function {
|
||||
Stack: scope.Stack{
|
||||
Scopes: []*scope.Scope{{}},
|
||||
},
|
||||
CPU: cpu.CPU{
|
||||
General: x86.GeneralRegisters,
|
||||
Input: x86.InputRegisters,
|
||||
Output: x86.OutputRegisters,
|
||||
SyscallInput: x86.SyscallInputRegisters,
|
||||
SyscallOutput: x86.SyscallOutputRegisters,
|
||||
NumRegisters: 16,
|
||||
},
|
||||
CPU: cpu,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ const (
|
||||
LittleEndian = 1
|
||||
TypeExecutable = 2
|
||||
ArchitectureAMD64 = 0x3E
|
||||
ArchitectureARM64 = 0xB7
|
||||
)
|
||||
|
||||
type ProgramType int32
|
||||
|
@ -23,8 +23,16 @@ func Write(writer io.Writer, code []byte, data []byte) {
|
||||
var (
|
||||
codeStart, codePadding = fs.Align(HeaderEnd, config.Align)
|
||||
dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align)
|
||||
arch int16
|
||||
)
|
||||
|
||||
switch config.TargetArch {
|
||||
case config.ARM:
|
||||
arch = ArchitectureARM64
|
||||
case config.X86:
|
||||
arch = ArchitectureAMD64
|
||||
}
|
||||
|
||||
elf := &ELF{
|
||||
Header: Header{
|
||||
Magic: [4]byte{0x7F, 'E', 'L', 'F'},
|
||||
@ -34,7 +42,7 @@ func Write(writer io.Writer, code []byte, data []byte) {
|
||||
OSABI: 0,
|
||||
ABIVersion: 0,
|
||||
Type: TypeExecutable,
|
||||
Architecture: ArchitectureAMD64,
|
||||
Architecture: arch,
|
||||
FileVersion: 1,
|
||||
EntryPointInMemory: int64(config.BaseAddress + codeStart),
|
||||
ProgramHeaderOffset: HeaderSize,
|
||||
|
@ -9,6 +9,11 @@ const (
|
||||
CPU_ARM_64 CPU = CPU_ARM | 0x01000000
|
||||
)
|
||||
|
||||
const (
|
||||
CPU_SUBTYPE_ARM64_ALL = 0
|
||||
CPU_SUBTYPE_X86_64_ALL = 3
|
||||
)
|
||||
|
||||
type Prot uint32
|
||||
|
||||
const (
|
||||
|
@ -28,13 +28,24 @@ func Write(writer io.Writer, code []byte, data []byte) {
|
||||
var (
|
||||
codeStart, codePadding = fs.Align(HeaderEnd, config.Align)
|
||||
dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align)
|
||||
arch CPU
|
||||
microArch uint32
|
||||
)
|
||||
|
||||
switch config.TargetArch {
|
||||
case config.ARM:
|
||||
arch = CPU_ARM_64
|
||||
microArch = CPU_SUBTYPE_ARM64_ALL | 0x80000000
|
||||
case config.X86:
|
||||
arch = CPU_X86_64
|
||||
microArch = CPU_SUBTYPE_X86_64_ALL | 0x80000000
|
||||
}
|
||||
|
||||
m := &MachO{
|
||||
Header: Header{
|
||||
Magic: 0xFEEDFACF,
|
||||
Architecture: CPU_X86_64,
|
||||
MicroArchitecture: 3 | 0x80000000,
|
||||
Architecture: arch,
|
||||
MicroArchitecture: microArch,
|
||||
Type: TypeExecute,
|
||||
NumCommands: 4,
|
||||
SizeCommands: SizeCommands,
|
||||
|
@ -35,12 +35,20 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) {
|
||||
importDirectorySize = DLLImportSize * len(dllImports)
|
||||
importSectionSize = len(imports)*8 + len(dllData) + importDirectorySize
|
||||
imageSize, _ = fs.Align(importsStart+importSectionSize, config.Align)
|
||||
arch uint16
|
||||
)
|
||||
|
||||
if dlls.Contains("user32") {
|
||||
subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI
|
||||
}
|
||||
|
||||
switch config.TargetArch {
|
||||
case config.ARM:
|
||||
arch = IMAGE_FILE_MACHINE_ARM64
|
||||
case config.X86:
|
||||
arch = IMAGE_FILE_MACHINE_AMD64
|
||||
}
|
||||
|
||||
pe := &EXE{
|
||||
DOSHeader: DOSHeader{
|
||||
Magic: [4]byte{'M', 'Z', 0, 0},
|
||||
@ -48,7 +56,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) {
|
||||
},
|
||||
NTHeader: NTHeader{
|
||||
Signature: [4]byte{'P', 'E', 0, 0},
|
||||
Machine: IMAGE_FILE_MACHINE_AMD64,
|
||||
Machine: arch,
|
||||
NumberOfSections: uint16(NumSections),
|
||||
TimeDateStamp: 0,
|
||||
PointerToSymbolTable: 0,
|
||||
|
@ -10,6 +10,6 @@ import (
|
||||
type Machine struct {
|
||||
scope.Stack
|
||||
Assembler asm.Assembler
|
||||
CPU cpu.CPU
|
||||
CPU *cpu.CPU
|
||||
RegisterHistory []uint64
|
||||
}
|
||||
|
@ -5,14 +5,14 @@ import "git.urbach.dev/cli/q/src/cpu"
|
||||
// Call places the return address on the top of the stack and continues
|
||||
// program flow at the new address.
|
||||
// The address is relative to the next instruction.
|
||||
func Call(code []byte, address uint32) []byte {
|
||||
func Call(code []byte, offset uint32) []byte {
|
||||
return append(
|
||||
code,
|
||||
0xE8,
|
||||
byte(address),
|
||||
byte(address>>8),
|
||||
byte(address>>16),
|
||||
byte(address>>24),
|
||||
byte(offset),
|
||||
byte(offset>>8),
|
||||
byte(offset>>16),
|
||||
byte(offset>>24),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -30,4 +30,13 @@ var (
|
||||
WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9}
|
||||
WindowsOutputRegisters = []cpu.Register{RAX}
|
||||
WindowsVolatileRegisters = []cpu.Register{RCX, RDX, R8, R9, R10, R11}
|
||||
|
||||
CPU = cpu.CPU{
|
||||
General: GeneralRegisters,
|
||||
Input: InputRegisters,
|
||||
Output: OutputRegisters,
|
||||
SyscallInput: SyscallInputRegisters,
|
||||
SyscallOutput: SyscallOutputRegisters,
|
||||
NumRegisters: 16,
|
||||
}
|
||||
)
|
||||
|
Reference in New Issue
Block a user