From fbe6aa80bbd6ba31bdcb828e2dc9ca4028724718 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 29 Oct 2023 12:24:40 +0100 Subject: [PATCH] Added memory address patching --- src/asm/Address.go | 3 +++ src/asm/Assembler.go | 55 ++++++++++++++++++++++++++++++++++++------ src/asm/Instruction.go | 15 +++--------- src/asm/Mnemonic.go | 4 +++ src/asm/Result.go | 17 +++++++++++-- src/build/Build.go | 15 +++++++++--- src/config/config.go | 8 ++++++ src/elf/ELF.go | 15 +++++------- src/elf/Header.go | 1 - 9 files changed, 98 insertions(+), 35 deletions(-) create mode 100644 src/config/config.go diff --git a/src/asm/Address.go b/src/asm/Address.go index 14b073b..9c1651d 100644 --- a/src/asm/Address.go +++ b/src/asm/Address.go @@ -2,3 +2,6 @@ package asm // Address represents a memory address. type Address = uint32 + +// Index references an instruction by its position. +type Index = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index a751e33..a92fc62 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -1,37 +1,78 @@ package asm import ( + "bytes" + "encoding/binary" + + "git.akyoto.dev/cli/q/src/asm/x64" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" ) // Assembler contains a list of instructions. type Assembler struct { instructions []Instruction - labels map[string]int + labels map[string]Index + verbose bool } // New creates a new assembler. func New() *Assembler { return &Assembler{ instructions: make([]Instruction, 0, 8), - labels: map[string]int{}, + labels: map[string]Index{}, + verbose: true, } } // Finalize generates the final machine code. func (a *Assembler) Finalize() *Result { - final := Result{} + result := &Result{} + pointers := map[Address]Address{} + code := bytes.NewBuffer(result.Code) - for _, instr := range a.instructions { - instr.Write(&final.Code) + for _, x := range a.instructions { + if a.verbose { + log.Info.Println(x.String()) + } + + switch x.Mnemonic { + case MOV: + x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) + + case MOVDATA: + position := result.AddData(x.Data) + x64.MoveRegNum32(code, uint8(x.Destination), 0) + pointers[Address(code.Len()-4)] = position + + case SYSCALL: + x64.Syscall(code) + } } - return &final + result.Code = code.Bytes() + dataStart := config.BaseAddress + config.CodeOffset + Address(len(result.Code)) + + for codePos, dataPos := range pointers { + binary.LittleEndian.PutUint32(result.Code[codePos:codePos+4], dataStart+dataPos) + } + + return result } // AddLabel creates a new label at the current position. func (a *Assembler) AddLabel(name string) { - a.labels[name] = len(a.instructions) + a.labels[name] = Index(len(a.instructions)) +} + +// MoveRegisterData moves a data section address into the given register. +func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { + a.instructions = append(a.instructions, Instruction{ + Mnemonic: MOVDATA, + Destination: reg, + Data: data, + }) } // MoveRegisterNumber moves a number into the given register. diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index 1883cee..0c91ee1 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -2,9 +2,7 @@ package asm import ( "fmt" - "io" - "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/register" ) @@ -14,16 +12,7 @@ type Instruction struct { Source register.ID Destination register.ID Number uint64 -} - -// Write writes the machine code of the instruction. -func (x *Instruction) Write(w io.ByteWriter) { - switch x.Mnemonic { - case MOV: - x64.MoveRegNum32(w, uint8(x.Destination), uint32(x.Number)) - case SYSCALL: - x64.Syscall(w) - } + Data []byte } // String returns the assembler representation of the instruction. @@ -31,6 +20,8 @@ func (x *Instruction) String() string { switch x.Mnemonic { case MOV: return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number) + case MOVDATA: + return fmt.Sprintf("%s %s, %v", x.Mnemonic, x.Destination, x.Data) case SYSCALL: return x.Mnemonic.String() default: diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go index dea828b..3d3a16c 100644 --- a/src/asm/Mnemonic.go +++ b/src/asm/Mnemonic.go @@ -5,6 +5,7 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota MOV + MOVDATA SYSCALL ) @@ -13,6 +14,9 @@ func (m Mnemonic) String() string { case MOV: return "mov" + case MOVDATA: + return "mov" + case SYSCALL: return "syscall" } diff --git a/src/asm/Result.go b/src/asm/Result.go index 62eedad..8261d82 100644 --- a/src/asm/Result.go +++ b/src/asm/Result.go @@ -4,6 +4,19 @@ import "bytes" // Result is the compilation result and contains the machine code as well as the data. type Result struct { - Code bytes.Buffer - Data bytes.Buffer + Code []byte + Data []byte +} + +// AddData adds the given bytes to the data block and returns the address relative to the start of the data section. +func (result *Result) AddData(block []byte) Address { + position := bytes.Index(result.Data, block) + + if position != -1 { + return Address(position) + } + + address := Address(len(result.Data)) + result.Data = append(result.Data, block...) + return address } diff --git a/src/build/Build.go b/src/build/Build.go index 88d08d5..197665e 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -43,8 +43,16 @@ func (build *Build) Run() error { a.MoveRegisterNumber(register.Syscall0, syscall.Write) a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterNumber(register.Syscall2, 0x4000a2) - a.MoveRegisterNumber(register.Syscall3, 6) + hello := []byte("Hello\n") + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + world := []byte("World\n") + a.MoveRegisterData(register.Syscall2, world) + a.MoveRegisterNumber(register.Syscall3, uint64(len(world))) a.Syscall() a.MoveRegisterNumber(register.Syscall0, syscall.Exit) @@ -52,13 +60,12 @@ func (build *Build) Run() error { a.Syscall() result := a.Finalize() - result.Data.WriteString("Hello\n") if !build.WriteExecutable { return nil } - return writeToDisk(build.Executable(), result.Code.Bytes(), result.Data.Bytes()) + return writeToDisk(build.Executable(), result.Code, result.Data) } // Compile compiles all the functions. diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 0000000..42bc97f --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,8 @@ +package config + +const ( + MinAddress = 0x10000 + BaseAddress = 0x40 * MinAddress + CodeOffset = 0x80 + Align = 0x10 +) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 30c1ce0..92d45c6 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -3,11 +3,8 @@ package elf import ( "encoding/binary" "io" -) -const ( - minAddress = 0x10000 - baseAddress = 0x40 * minAddress + "git.akyoto.dev/cli/q/src/config" ) // ELF represents an ELF file. @@ -31,7 +28,7 @@ func New(code []byte, data []byte) *ELF { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: baseAddress + 0x80, + EntryPointInMemory: config.BaseAddress + config.CodeOffset, ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -45,12 +42,12 @@ func New(code []byte, data []byte) *ELF { ProgramHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable, - Offset: 0x80, - VirtualAddress: baseAddress + 0x80, - PhysicalAddress: baseAddress + 0x80, + Offset: config.CodeOffset, + VirtualAddress: config.BaseAddress + config.CodeOffset, + PhysicalAddress: config.BaseAddress + config.CodeOffset, SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), - Align: Align, + Align: config.Align, }, Code: code, Data: data, diff --git a/src/elf/Header.go b/src/elf/Header.go index 089bf00..672f065 100644 --- a/src/elf/Header.go +++ b/src/elf/Header.go @@ -5,7 +5,6 @@ const ( TypeExecutable = 2 ArchitectureAMD64 = 0x3E HeaderSize = 64 - Align = 16 ) // Header contains general information.