From 4554fb82cbd43482dacb70eef34d040892aa3499 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 20:29:36 +0200 Subject: [PATCH] Implemented ELF header --- README.md | 2 +- build/Build.go | 40 +++++++++++++++++++++++++++++- build/elf/Header.go | 32 ++++++++++++++++++++++++ build/elf/ProgramHeader.go | 37 +++++++++++++++++++++++++++ build/elf/SectionHeader.go | 45 +++++++++++++++++++++++++++++++++ build/elf/elf.go | 51 ++++++++++++++++++++++++++++++++++++++ build/elf/elf.md | 12 +++++++++ 7 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 build/elf/Header.go create mode 100644 build/elf/ProgramHeader.go create mode 100644 build/elf/SectionHeader.go create mode 100644 build/elf/elf.go create mode 100644 build/elf/elf.md diff --git a/README.md b/README.md index 99d512d..37b03bc 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ go build ## Usage ```shell -q build +./q build examples/hello ``` ## License diff --git a/build/Build.go b/build/Build.go index 9a838b9..338c5be 100644 --- a/build/Build.go +++ b/build/Build.go @@ -1,6 +1,12 @@ package build -import "path/filepath" +import ( + "bufio" + "os" + "path/filepath" + + "git.akyoto.dev/cli/q/build/elf" +) // Build describes a compiler build. type Build struct { @@ -30,5 +36,37 @@ func New(directory string) (*Build, error) { // Run parses the input files and generates an executable file. func (build *Build) Run() error { + if build.WriteExecutable { + sampleCode := []byte{ + 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov rax, 60 + 0xbf, 0x00, 0x00, 0x00, 0x00, // mov rdi, 0 + 0x0f, 0x05, // syscall + } + + return writeToDisk(build.ExecutablePath, sampleCode, nil) + } + return nil } + +// writeToDisk writes the executable file to disk. +func writeToDisk(filePath string, code []byte, data []byte) error { + file, err := os.Create(filePath) + + if err != nil { + return err + } + + buffer := bufio.NewWriter(file) + executable := elf.New() + executable.Write(buffer) + buffer.Flush() + + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(filePath, 0755) +} diff --git a/build/elf/Header.go b/build/elf/Header.go new file mode 100644 index 0000000..672f065 --- /dev/null +++ b/build/elf/Header.go @@ -0,0 +1,32 @@ +package elf + +const ( + LittleEndian = 1 + TypeExecutable = 2 + ArchitectureAMD64 = 0x3E + HeaderSize = 64 +) + +// Header contains general information. +type Header struct { + Magic [4]byte + Class byte + Endianness byte + Version byte + OSABI byte + ABIVersion byte + _ [7]byte + Type int16 + Architecture int16 + FileVersion int32 + EntryPointInMemory int64 + ProgramHeaderOffset int64 + SectionHeaderOffset int64 + Flags int32 + Size int16 + ProgramHeaderEntrySize int16 + ProgramHeaderEntryCount int16 + SectionHeaderEntrySize int16 + SectionHeaderEntryCount int16 + SectionNameStringTableIndex int16 +} diff --git a/build/elf/ProgramHeader.go b/build/elf/ProgramHeader.go new file mode 100644 index 0000000..cef76c7 --- /dev/null +++ b/build/elf/ProgramHeader.go @@ -0,0 +1,37 @@ +package elf + +// ProgramHeaderSize is equal to the size of a program header in bytes. +const ProgramHeaderSize = 56 + +// ProgramHeader points to the executable part of our program. +type ProgramHeader struct { + Type ProgramType + Flags ProgramFlags + Offset int64 + VirtualAddress int64 + PhysicalAddress int64 + SizeInFile int64 + SizeInMemory int64 + Align int64 +} + +type ProgramType int32 + +const ( + ProgramTypeNULL ProgramType = 0 + ProgramTypeLOAD ProgramType = 1 + ProgramTypeDYNAMIC ProgramType = 2 + ProgramTypeINTERP ProgramType = 3 + ProgramTypeNOTE ProgramType = 4 + ProgramTypeSHLIB ProgramType = 5 + ProgramTypePHDR ProgramType = 6 + ProgramTypeTLS ProgramType = 7 +) + +type ProgramFlags int32 + +const ( + ProgramFlagsExecutable ProgramFlags = 0x1 + ProgramFlagsWritable ProgramFlags = 0x2 + ProgramFlagsReadable ProgramFlags = 0x4 +) diff --git a/build/elf/SectionHeader.go b/build/elf/SectionHeader.go new file mode 100644 index 0000000..4745796 --- /dev/null +++ b/build/elf/SectionHeader.go @@ -0,0 +1,45 @@ +package elf + +// SectionHeaderSize is equal to the size of a section header in bytes. +const SectionHeaderSize = 64 + +// SectionHeader points to the data sections of our program. +type SectionHeader struct { + NameIndex int32 + Type SectionType + Flags SectionFlags + VirtualAddress int64 + Offset int64 + SizeInFile int64 + Link int32 + Info int32 + Align int64 + EntrySize int64 +} + +type SectionType int32 + +const ( + SectionTypeNULL SectionType = 0 + SectionTypePROGBITS SectionType = 1 + SectionTypeSYMTAB SectionType = 2 + SectionTypeSTRTAB SectionType = 3 + SectionTypeRELA SectionType = 4 + SectionTypeHASH SectionType = 5 + SectionTypeDYNAMIC SectionType = 6 + SectionTypeNOTE SectionType = 7 + SectionTypeNOBITS SectionType = 8 + SectionTypeREL SectionType = 9 + SectionTypeSHLIB SectionType = 10 + SectionTypeDYNSYM SectionType = 11 +) + +type SectionFlags int64 + +const ( + SectionFlagsWritable SectionFlags = 1 << 0 + SectionFlagsAllocate SectionFlags = 1 << 1 + SectionFlagsExecutable SectionFlags = 1 << 2 + SectionFlagsStrings SectionFlags = 1 << 5 + SectionFlagsTLS SectionFlags = 1 << 10 +) diff --git a/build/elf/elf.go b/build/elf/elf.go new file mode 100644 index 0000000..68f7bab --- /dev/null +++ b/build/elf/elf.go @@ -0,0 +1,51 @@ +package elf + +import ( + "encoding/binary" + "io" +) + +const ( + baseAddress = 0x400000 + programAlign = 16 + sectionAlign = 16 +) + +// ELF64 represents an ELF 64-bit file. +type ELF64 struct { + Header +} + +// New creates a new 64-bit ELF binary. +func New() *ELF64 { + elf := &ELF64{ + Header: Header{ + Magic: [4]byte{0x7F, 'E', 'L', 'F'}, + Class: 2, + Endianness: LittleEndian, + Version: 1, + OSABI: 0, + ABIVersion: 0, + Type: TypeExecutable, + Architecture: ArchitectureAMD64, + FileVersion: 1, + EntryPointInMemory: 0, + ProgramHeaderOffset: HeaderSize, + SectionHeaderOffset: 0, // TODO + Flags: 0, + Size: HeaderSize, + ProgramHeaderEntrySize: ProgramHeaderSize, + ProgramHeaderEntryCount: 0, + SectionHeaderEntrySize: SectionHeaderSize, + SectionHeaderEntryCount: 0, + SectionNameStringTableIndex: 0, + }, + } + + return elf +} + +// Write writes the ELF64 format to the given writer. +func (elf *ELF64) Write(writer io.Writer) { + binary.Write(writer, binary.LittleEndian, &elf.Header) +} diff --git a/build/elf/elf.md b/build/elf/elf.md new file mode 100644 index 0000000..dd0de12 --- /dev/null +++ b/build/elf/elf.md @@ -0,0 +1,12 @@ +# ELF + +## File contents + +- ELF header +- Program headers +- Sections +- Section headers + +## Entry point + +The entry point is defined in the first 64 bytes (ELF header).