Added a linker
All checks were successful
/ test (push) Successful in 15s

This commit is contained in:
Eduard Urbach 2025-06-23 16:19:16 +02:00
parent 3ae47f93eb
commit cc2e98ca49
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
26 changed files with 541 additions and 11 deletions

37
src/elf/AddSections.go Normal file
View file

@ -0,0 +1,37 @@
package elf
import "bytes"
// AddSections adds section headers to the ELF file.
func (elf *ELF) AddSections() {
elf.StringTable = []byte("\000.text\000.shstrtab\000")
stringTableStart := elf.DataHeader.Offset + elf.DataHeader.SizeInFile
sectionHeaderStart := stringTableStart + int64(len(elf.StringTable))
elf.SectionHeaders = []SectionHeader{
{
Type: SectionTypeNULL,
},
{
NameIndex: int32(bytes.Index(elf.StringTable, []byte(".text\000"))),
Type: SectionTypePROGBITS,
Flags: SectionFlagsAllocate | SectionFlagsExecutable,
VirtualAddress: elf.CodeHeader.VirtualAddress,
Offset: elf.CodeHeader.Offset,
SizeInFile: elf.CodeHeader.SizeInFile,
Align: elf.CodeHeader.Align,
},
{
NameIndex: int32(bytes.Index(elf.StringTable, []byte(".shstrtab\000"))),
Type: SectionTypeSTRTAB,
Offset: int64(stringTableStart),
SizeInFile: int64(len(elf.StringTable)),
Align: 1,
},
}
elf.SectionHeaderEntrySize = SectionHeaderSize
elf.SectionHeaderEntryCount = int16(len(elf.SectionHeaders))
elf.SectionHeaderOffset = int64(sectionHeaderStart)
elf.SectionNameStringTableIndex = 2
}

15
src/elf/Arch.go Normal file
View file

@ -0,0 +1,15 @@
package elf
import "git.urbach.dev/cli/q/src/build"
// Arch converts the architecture variable to an ELF-specific constant.
func Arch(arch build.Arch) int16 {
switch arch {
case build.ARM:
return ArchitectureARM64
case build.X86:
return ArchitectureAMD64
default:
return 0
}
}

11
src/elf/Constants.go Normal file
View file

@ -0,0 +1,11 @@
package elf
const (
LittleEndian = 1
TypeExecutable = 2
TypeDynamic = 3
ArchitectureAMD64 = 0x3E
ArchitectureARM64 = 0xB7
ArchitectureRISCV = 0xF3
HeaderEnd = HeaderSize + ProgramHeaderSize*2
)

79
src/elf/ELF.go Normal file
View file

@ -0,0 +1,79 @@
package elf
import (
"encoding/binary"
"io"
"git.urbach.dev/cli/q/src/build"
"git.urbach.dev/cli/q/src/exe"
)
// ELF represents an ELF file.
type ELF struct {
Header
CodeHeader ProgramHeader
DataHeader ProgramHeader
SectionHeaders []SectionHeader
StringTable []byte
}
// Write writes the ELF64 format to the given writer.
func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte) {
x := exe.New(HeaderEnd, b.FileAlign, b.MemoryAlign)
x.InitSections(codeBytes, dataBytes)
code := x.Sections[0]
data := x.Sections[1]
elf := &ELF{
Header: Header{
Magic: [4]byte{0x7F, 'E', 'L', 'F'},
Class: 2,
Endianness: LittleEndian,
Version: 1,
OSABI: 0,
ABIVersion: 0,
Type: TypeDynamic,
Architecture: Arch(b.Arch),
FileVersion: 1,
EntryPointInMemory: int64(code.MemoryOffset),
ProgramHeaderOffset: HeaderSize,
SectionHeaderOffset: 0,
Flags: 0,
Size: HeaderSize,
ProgramHeaderEntrySize: ProgramHeaderSize,
ProgramHeaderEntryCount: 2,
SectionHeaderEntrySize: 0,
SectionHeaderEntryCount: 0,
SectionNameStringTableIndex: 0,
},
CodeHeader: ProgramHeader{
Type: ProgramTypeLOAD,
Flags: ProgramFlagsExecutable | ProgramFlagsReadable,
Offset: int64(code.FileOffset),
VirtualAddress: int64(code.MemoryOffset),
SizeInFile: int64(len(code.Bytes)),
SizeInMemory: int64(len(code.Bytes)),
Align: int64(b.MemoryAlign),
},
DataHeader: ProgramHeader{
Type: ProgramTypeLOAD,
Flags: ProgramFlagsReadable,
Offset: int64(data.FileOffset),
VirtualAddress: int64(data.MemoryOffset),
SizeInFile: int64(len(data.Bytes)),
SizeInMemory: int64(len(data.Bytes)),
Align: int64(b.MemoryAlign),
},
}
elf.AddSections()
binary.Write(writer, binary.LittleEndian, &elf.Header)
binary.Write(writer, binary.LittleEndian, &elf.CodeHeader)
binary.Write(writer, binary.LittleEndian, &elf.DataHeader)
writer.Seek(int64(code.Padding), io.SeekCurrent)
writer.Write(code.Bytes)
writer.Seek(int64(data.Padding), io.SeekCurrent)
writer.Write(data.Bytes)
writer.Write(elf.StringTable)
binary.Write(writer, binary.LittleEndian, &elf.SectionHeaders)
}

15
src/elf/ELF_test.go Normal file
View file

@ -0,0 +1,15 @@
package elf_test
import (
"testing"
"git.urbach.dev/cli/q/src/build"
"git.urbach.dev/cli/q/src/elf"
"git.urbach.dev/cli/q/src/exe"
)
func TestWrite(t *testing.T) {
elf.Write(&exe.Discard{}, &build.Build{Arch: build.ARM, FileAlign: 0x4000, MemoryAlign: 0x4000}, nil, nil)
elf.Write(&exe.Discard{}, &build.Build{Arch: build.X86, FileAlign: 0x1000, MemoryAlign: 0x1000}, nil, nil)
elf.Write(&exe.Discard{}, &build.Build{Arch: build.UnknownArch, FileAlign: 0x1000, MemoryAlign: 0x1000}, nil, nil)
}

28
src/elf/Header.go Normal file
View file

@ -0,0 +1,28 @@
package elf
// HeaderSize is equal to the size of a header in bytes.
const 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
}

10
src/elf/ProgramFlags.go Normal file
View file

@ -0,0 +1,10 @@
package elf
// ProgramFlags specifies the permissions for a segment.
type ProgramFlags int32
const (
ProgramFlagsExecutable ProgramFlags = 0x1
ProgramFlagsWritable ProgramFlags = 0x2
ProgramFlagsReadable ProgramFlags = 0x4
)

16
src/elf/ProgramHeader.go Normal file
View file

@ -0,0 +1,16 @@
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
}

15
src/elf/ProgramType.go Normal file
View file

@ -0,0 +1,15 @@
package elf
// ProgramType indicates the program type.
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
)

12
src/elf/SectionFlags.go Normal file
View file

@ -0,0 +1,12 @@
package elf
// SectionFlags defines flags for sections.
type SectionFlags int64
const (
SectionFlagsWritable SectionFlags = 1 << 0
SectionFlagsAllocate SectionFlags = 1 << 1
SectionFlagsExecutable SectionFlags = 1 << 2
SectionFlagsStrings SectionFlags = 1 << 5
SectionFlagsTLS SectionFlags = 1 << 10
)

18
src/elf/SectionHeader.go Normal file
View file

@ -0,0 +1,18 @@
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
}

19
src/elf/SectionType.go Normal file
View file

@ -0,0 +1,19 @@
package elf
// SectionType defines the type of the section.
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
)