Separated code and data

This commit is contained in:
Eduard Urbach 2023-10-20 13:22:06 +02:00
parent aab33fe86d
commit 0fe419da86
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
8 changed files with 76 additions and 67 deletions

View File

@ -2,6 +2,7 @@ package build
import ( import (
"bufio" "bufio"
"bytes"
"os" "os"
"path/filepath" "path/filepath"
@ -11,61 +12,62 @@ import (
// Build describes a compiler build. // Build describes a compiler build.
type Build struct { type Build struct {
ExecutableName string Name string
ExecutablePath string Directory string
Code bytes.Buffer
Data bytes.Buffer
WriteExecutable bool WriteExecutable bool
} }
// New creates a new build. // New creates a new build.
func New(directory string) (*Build, error) { func New(directory string) *Build {
directory, err := filepath.Abs(directory) return &Build{
Name: filepath.Base(directory),
if err != nil { Directory: directory,
return nil, err WriteExecutable: true,
}
} }
file, err := os.Open(directory) // Run parses the input files and generates an executable file.
func (build *Build) Run() error {
err := build.Compile()
if err != nil { if err != nil {
return nil, err return err
}
if build.WriteExecutable {
return writeToDisk(build.Executable(), build.Code.Bytes(), build.Data.Bytes())
}
return nil
}
// Executable returns the path to the executable.
func (build *Build) Executable() string {
return filepath.Join(build.Directory, build.Name)
}
// Compile compiles all the functions.
func (build *Build) Compile() error {
file, err := os.Open(build.Directory)
if err != nil {
return err
} }
defer file.Close() defer file.Close()
files, err := file.Readdirnames(0) files, err := file.Readdirnames(0)
if err != nil { if err != nil {
return nil, err return err
} }
for _, name := range files { for _, name := range files {
log.Info.Println(name) log.Info.Println(name)
} }
executableName := filepath.Base(directory) build.Code.Write([]byte{
build := &Build{
ExecutableName: executableName,
ExecutablePath: filepath.Join(directory, executableName),
WriteExecutable: true,
}
return build, nil
}
// Run parses the input files and generates an executable file.
func (build *Build) Run() error {
code := build.Compile()
if build.WriteExecutable {
return writeToDisk(build.ExecutablePath, code)
}
return nil
}
// Compile compiles all the functions.
func (build *Build) Compile() []byte {
return []byte{
0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1
0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1
0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2 0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2
@ -75,13 +77,14 @@ func (build *Build) Compile() []byte {
0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60
0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0
0x0f, 0x05, // syscall 0x0f, 0x05, // syscall
})
'H', 'e', 'l', 'l', 'o', '\n', build.Data.Write([]byte{'H', 'e', 'l', 'l', 'o', '\n'})
} return nil
} }
// writeToDisk writes the executable file to disk. // writeToDisk writes the executable file to disk.
func writeToDisk(filePath string, code []byte) error { func writeToDisk(filePath string, code []byte, data []byte) error {
file, err := os.Create(filePath) file, err := os.Create(filePath)
if err != nil { if err != nil {
@ -89,7 +92,7 @@ func writeToDisk(filePath string, code []byte) error {
} }
buffer := bufio.NewWriter(file) buffer := bufio.NewWriter(file)
executable := elf.New(code) executable := elf.New(code, data)
executable.Write(buffer) executable.Write(buffer)
buffer.Flush() buffer.Flush()

View File

@ -10,16 +10,17 @@ const (
baseAddress = 0x40 * minAddress baseAddress = 0x40 * minAddress
) )
// ELF64 represents an ELF 64-bit file. // ELF represents an ELF file.
type ELF64 struct { type ELF struct {
Header Header
ProgramHeader ProgramHeader
Code []byte Code []byte
Data []byte
} }
// New creates a new 64-bit ELF binary. // New creates a new ELF binary.
func New(code []byte) *ELF64 { func New(code []byte, data []byte) *ELF {
elf := &ELF64{ elf := &ELF{
Header: Header{ Header: Header{
Magic: [4]byte{0x7F, 'E', 'L', 'F'}, Magic: [4]byte{0x7F, 'E', 'L', 'F'},
Class: 2, Class: 2,
@ -43,7 +44,7 @@ func New(code []byte) *ELF64 {
}, },
ProgramHeader: ProgramHeader{ ProgramHeader: ProgramHeader{
Type: ProgramTypeLOAD, Type: ProgramTypeLOAD,
Flags: ProgramFlagsExecutable | ProgramFlagsReadable, Flags: ProgramFlagsExecutable,
Offset: 0x80, Offset: 0x80,
VirtualAddress: baseAddress + 0x80, VirtualAddress: baseAddress + 0x80,
PhysicalAddress: baseAddress + 0x80, PhysicalAddress: baseAddress + 0x80,
@ -52,15 +53,17 @@ func New(code []byte) *ELF64 {
Align: Align, Align: Align,
}, },
Code: code, Code: code,
Data: data,
} }
return elf return elf
} }
// Write writes the ELF64 format to the given writer. // Write writes the ELF64 format to the given writer.
func (elf *ELF64) Write(writer io.Writer) { func (elf *ELF) Write(writer io.Writer) {
binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.Header)
binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader)
writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0})
writer.Write(elf.Code) writer.Write(elf.Code)
writer.Write(elf.Data)
} }

View File

@ -5,6 +5,7 @@ import (
"git.akyoto.dev/cli/q/cli/log" "git.akyoto.dev/cli/q/cli/log"
) )
// Build builds an executable.
func Build(args []string) int { func Build(args []string) int {
directory := "." directory := "."
@ -12,12 +13,7 @@ func Build(args []string) int {
directory = args[0] directory = args[0]
} }
b, err := build.New(directory) b := build.New(directory)
if err != nil {
log.Error.Println(err)
return 1
}
for i := 1; i < len(args); i++ { for i := 1; i < len(args); i++ {
switch args[i] { switch args[i] {
@ -30,7 +26,7 @@ func Build(args []string) int {
} }
} }
err = b.Run() err := b.Run()
if err != nil { if err != nil {
log.Error.Println(err) log.Error.Println(err)

View File

@ -4,6 +4,7 @@ import (
"git.akyoto.dev/cli/q/cli/log" "git.akyoto.dev/cli/q/cli/log"
) )
// Help shows the command line argument usage.
func Help(args []string) int { func Help(args []string) int {
log.Error.Println("Usage: q [command] [options]") log.Error.Println("Usage: q [command] [options]")
log.Error.Println("") log.Error.Println("")

View File

@ -1,5 +1,8 @@
package cli package cli
// Main is the entry point for the CLI frontend.
// It returns the exit code of the compiler.
// We never call os.Exit directly here because it's bad for testing.
func Main(args []string) int { func Main(args []string) int {
if len(args) == 0 { if len(args) == 0 {
return Help(nil) return Help(nil)

View File

@ -7,6 +7,7 @@ import (
"git.akyoto.dev/cli/q/cli/log" "git.akyoto.dev/cli/q/cli/log"
) )
// System shows system information.
func System(args []string) int { func System(args []string) int {
line := "%-19s%s\n" line := "%-19s%s\n"

View File

@ -1,12 +1,21 @@
package main_test package cli_test
import ( import (
"io"
"os"
"testing" "testing"
"git.akyoto.dev/cli/q/cli" "git.akyoto.dev/cli/q/cli"
"git.akyoto.dev/cli/q/cli/log"
"git.akyoto.dev/go/assert" "git.akyoto.dev/go/assert"
) )
func TestMain(m *testing.M) {
log.Info.SetOutput(io.Discard)
log.Error.SetOutput(io.Discard)
os.Exit(m.Run())
}
func TestCLI(t *testing.T) { func TestCLI(t *testing.T) {
type cliTest struct { type cliTest struct {
arguments []string arguments []string
@ -28,3 +37,11 @@ func TestCLI(t *testing.T) {
assert.Equal(t, exitCode, test.expectedExitCode) assert.Equal(t, exitCode, test.expectedExitCode)
} }
} }
func BenchmarkBuild(b *testing.B) {
args := []string{"build", "examples/hello", "--dry"}
for i := 0; i < b.N; i++ {
cli.Main(args)
}
}

View File

@ -1,15 +0,0 @@
package main_test
import (
"io"
"os"
"testing"
"git.akyoto.dev/cli/q/cli/log"
)
func TestMain(m *testing.M) {
log.Info.SetOutput(io.Discard)
log.Error.SetOutput(io.Discard)
os.Exit(m.Run())
}