work on elf output

This commit is contained in:
mappu 2023-12-11 14:24:25 +13:00
parent ca759ad23f
commit e03434bd50
3 changed files with 97 additions and 29 deletions

View File

@ -12,7 +12,7 @@ import (
type section struct { type section struct {
name string name string
name_shstrtabOffset int name_shstrtabOffset int
buff bytes.Buffer buff *bytes.Buffer
} }
type symtabEntry struct { type symtabEntry struct {
@ -37,12 +37,16 @@ func NewCompiler() *compiler {
symtab: map[string]symtabEntry{}, symtab: map[string]symtabEntry{},
} }
// Fake 0th entry
// First, there's an all-zero entry that is reserved for extended ELF headers
c.sections = append(c.sections, section{})
c.sections = append(c.sections, section{ c.sections = append(c.sections, section{
name: `.shstrtab`, // Mandatory: the table that names sections themselves name: `.shstrtab`, // Mandatory: the table that names sections themselves
name_shstrtabOffset: 1, name_shstrtabOffset: 1,
buff: bytes.Buffer{}, buff: &bytes.Buffer{},
}) })
c.shstrtab = &c.sections[0] c.shstrtab = &c.sections[1]
// The first byte in a string table is conventionally expected be \x00, so that you can reference // The first byte in a string table is conventionally expected be \x00, so that you can reference
// null strings with it // null strings with it
c.shstrtab.buff.WriteByte(0) c.shstrtab.buff.WriteByte(0)
@ -99,7 +103,7 @@ func (c *compiler) CreateSymbol(name string, class string, offset int64, length
esym.st_shndx = uint16(sectionIndex) esym.st_shndx = uint16(sectionIndex)
esym.st_size = uint64(length) esym.st_size = uint64(length)
err := binary.Write(&symtabSec.buff, binary.LittleEndian, &esym) err := binary.Write(symtabSec.buff, binary.LittleEndian, &esym)
return err return err
} }
@ -119,11 +123,7 @@ func (c *compiler) MustUint64(val uint64) {
c.Must(ret) c.Must(ret)
} }
func (c *compiler) FindOrCreateSection(sectionName string) *section { func (c *compiler) FindSectionIndex(sectionName string) (int, bool) {
if len(sectionName) == 0 || sectionName[0] != '.' {
panic("section name should start with leading period")
}
for i, sec := range c.sections { for i, sec := range c.sections {
if sec.name != sectionName { if sec.name != sectionName {
@ -131,6 +131,19 @@ func (c *compiler) FindOrCreateSection(sectionName string) *section {
} }
// found it // found it
return i, true
}
return 0, false
}
func (c *compiler) FindOrCreateSection(sectionName string) *section {
if len(sectionName) == 0 || sectionName[0] != '.' {
panic("section name should start with leading period")
}
if i, ok := c.FindSectionIndex(sectionName); ok {
return &c.sections[i] return &c.sections[i]
} }
@ -138,7 +151,7 @@ func (c *compiler) FindOrCreateSection(sectionName string) *section {
c.sections = append(c.sections, section{ c.sections = append(c.sections, section{
name: sectionName, name: sectionName,
name_shstrtabOffset: c.shstrtab.buff.Len(), name_shstrtabOffset: c.shstrtab.buff.Len(),
buff: bytes.Buffer{}, buff: &bytes.Buffer{},
}) })
c.shstrtab.buff.WriteString(sectionName) c.shstrtab.buff.WriteString(sectionName)
@ -163,7 +176,7 @@ func (c *compiler) Reloc(symbolName string, mode ElfRelocationType, addOffset in
rr.r_info = uint64(syminfo.symtabSectionIndex)<<32 | uint64(mode) // high bits: Index of search symbol in the symtab. low bits: mode type rr.r_info = uint64(syminfo.symtabSectionIndex)<<32 | uint64(mode) // high bits: Index of search symbol in the symtab. low bits: mode type
rr.r_addend = addOffset rr.r_addend = addOffset
err := binary.Write(&relaSec.buff, binary.LittleEndian, &rr) err := binary.Write(relaSec.buff, binary.LittleEndian, &rr)
if err != nil { if err != nil {
return err return err
} }
@ -373,6 +386,10 @@ func (c *compiler) Compile(t Token) error {
// linked (adding a program header and page alignment) // linked (adding a program header and page alignment)
func (c *compiler) Finalize(dest io.Writer) error { func (c *compiler) Finalize(dest io.Writer) error {
// Find some well-known section indexes
symtabSectionIndex, _ := c.FindSectionIndex(`.symtab`)
shstrtabSectionIndex, _ := c.FindSectionIndex(`.shstrtab`)
// Write ELF header // Write ELF header
ehdr := Elf64_Ehdr{} ehdr := Elf64_Ehdr{}
ehdr.e_ident[0] = 0x7f ehdr.e_ident[0] = 0x7f
@ -382,16 +399,18 @@ func (c *compiler) Finalize(dest io.Writer) error {
ehdr.e_ident[4] = 2 // 64-bit format ehdr.e_ident[4] = 2 // 64-bit format
ehdr.e_ident[5] = 1 // little endian ehdr.e_ident[5] = 1 // little endian
ehdr.e_ident[6] = 1 // ELFv1 is the only format ehdr.e_ident[6] = 1 // ELFv1 is the only format
ehdr.e_ident[7] = 3 // Linux-compatible ABI ehdr.e_ident[7] = 0 // Don't declare any ABI
ehdr.e_type = 0 // ET_NONE ehdr.e_type = ET_REL
ehdr.e_machine = 0x3E // x86_64 ehdr.e_machine = 0x3E // x86_64
ehdr.e_version = 1 // ELFv1 again ehdr.e_version = 1 // ELFv1 again
//ehdr.e_flags = 11 // ????
ehdr.e_ehsize = 64
ehdr.e_shoff = 64 // The Ehdr is 64 bytes long, sections start immediately following ehdr.e_shoff = 64 // The Ehdr is 64 bytes long, sections start immediately following
ehdr.e_shentsize = 64 // Each Shdr is also 64 bytes long ehdr.e_shentsize = 64 // Each Shdr is also 64 bytes long
ehdr.e_shnum = uint16(len(c.sections)) ehdr.e_shnum = uint16(len(c.sections))
ehdr.e_shstrndx = 0 // We always put the .shstrtab as the 0th section ehdr.e_shstrndx = uint16(shstrtabSectionIndex)
err := binary.Write(dest, binary.LittleEndian, &ehdr) err := binary.Write(dest, binary.LittleEndian, &ehdr)
if err != nil { if err != nil {
@ -400,30 +419,50 @@ func (c *compiler) Finalize(dest io.Writer) error {
// Don't declare a program header // Don't declare a program header
// Write section headers // Write fake 0th section header
dest.Write(make([]byte, 64))
// Write remaining section headers
pctr := 64 + (64 * len(c.sections)) pctr := 64 + (64 * len(c.sections))
for _, sec := range c.sections { for _, sec := range c.sections[1:] {
shdr := Elf64_Shdr{} shdr := Elf64_Shdr{}
shdr.sh_name = uint32(sec.name_shstrtabOffset) shdr.sh_name = uint32(sec.name_shstrtabOffset)
// Default (unknown section):
// Treat as extra read-only data (.rodata)
shdr.sh_type = 1 // SHT_PROGBITS, program data
shdr.sh_flags = 0x10 // MERGE
switch sec.name { switch sec.name {
case ".text": case ".text":
shdr.sh_type = 1 // SHT_PROGBITS, program data shdr.sh_type = SHT_PROGBITS
shdr.sh_flags = 0x2 | 0x4 | 0x10 // WRITE|ALLOC|MERGE shdr.sh_flags = 0x2 | 0x4 | 0x10 // WRITE|ALLOC|MERGE
case ".data": case ".data":
shdr.sh_type = 1 // SHT_PROGBITS, program data shdr.sh_type = SHT_PROGBITS
shdr.sh_flags = 0x2 | 0x10 // WRITE|MERGE shdr.sh_flags = 0x2 | 0x10 // WRITE|MERGE
case ".symtab": case ".symtab":
shdr.sh_type = 2 // SHT_SYMTAB shdr.sh_type = SHT_SYMTAB
shdr.sh_flags = 0x10 | 0x20 // MERGE|STRINGS shdr.sh_flags = 0x10 | 0x20 // MERGE|STRINGS
shdr.sh_info = uint32(len(c.symtab))
shdr.sh_entsize = uint64(sec.buff.Len()) // Number of fixed-sized symtab entries
shdr.sh_link = 1 // The index of the section containing the actual strings. We reuse shstrtab(!?!)
case ".shstrtab": case ".shstrtab":
shdr.sh_type = 3 // SHT_STRTAB shdr.sh_type = SHT_STRTAB
shdr.sh_flags = 0x10 | 0x20 // MERGE|STRINGS shdr.sh_flags = 0x10 | 0x20 // MERGE|STRINGS
case ".rodata": default:
fallthrough if strings.HasPrefix(sec.name, ".rela.") {
default: // Treat anything unknown as read-only data shdr.sh_type = SHT_RELA
shdr.sh_type = 1 // SHT_PROGBITS, program data shdr.sh_flags = 0 // ?
shdr.sh_flags = 0x10 // MERGE shdr.sh_link = uint32(symtabSectionIndex) // The index of the symtab section
// Find the index of the section for which this relocates. Match by name
srcSectionIdx, ok := c.FindSectionIndex(sec.name[5:])
if !ok {
return fmt.Errorf("Missing parent section for relocation section %q", sec.name)
}
shdr.sh_info = uint32(srcSectionIdx)
}
} }
shdr.sh_offset = uint64(pctr) shdr.sh_offset = uint64(pctr)
@ -438,7 +477,7 @@ func (c *compiler) Finalize(dest io.Writer) error {
} }
// Write binary content // Write binary content
for _, sec := range c.sections { for _, sec := range c.sections[1:] {
expectLen := sec.buff.Len() expectLen := sec.buff.Len()
n, err := sec.buff.WriteTo(dest) n, err := sec.buff.WriteTo(dest)
if err != nil { if err != nil {

33
elf.go
View File

@ -18,6 +18,15 @@ type Elf64_Ehdr struct {
e_shstrndx uint16 e_shstrndx uint16
} }
// File types
const (
ET_NONE = 0
ET_REL = 1
ET_EXEC = 2
ET_DYN = 3
ET_CORE = 4
)
// Elf64_Phdr is the Program Header // Elf64_Phdr is the Program Header
type Elf64_Phdr struct { type Elf64_Phdr struct {
p_type uint32 p_type uint32
@ -30,6 +39,26 @@ type Elf64_Phdr struct {
p_align uint64 p_align uint64
} }
const (
SHT_NULL = 0
SHT_PROGBITS = 1
SHT_SYMTAB = 2
SHT_STRTAB = 3
SHT_RELA = 4
SHT_HASH = 5
SHT_DYNAMIC = 6
SHT_NOTE = 7
SHT_NOBITS = 8
SHT_REL = 9
SHT_SHLIB = 10
SHT_DYNSYM = 11
SHT_INIT_ARRAY = 14
SHT_FINI_ARRAY = 15
SHT_PREINIT_ARRAY = 16
SHT_GROUP = 17
SHT_SYMTAB_SHNDX = 18
)
// Elf64_Shdr is the Section header // Elf64_Shdr is the Section header
type Elf64_Shdr struct { type Elf64_Shdr struct {
sh_name uint32 sh_name uint32
@ -66,8 +95,8 @@ const (
// Elf64_Sym is a symbol // Elf64_Sym is a symbol
type Elf64_Sym struct { type Elf64_Sym struct {
st_name uint32 st_name uint32
st_info byte st_info uint8
st_other byte st_other uint8
st_shndx uint16 st_shndx uint16
st_value uint64 st_value uint64
st_size uint64 st_size uint64

View File

@ -47,7 +47,7 @@ global _start: ;tell linker entry point
assemble(strings.NewReader(src), ioutil.Discard) assemble(strings.NewReader(src), ioutil.Discard)
*/ */
fh, err := os.OpenFile("output.o", os.O_CREATE|os.O_WRONLY, 0644) fh, err := os.OpenFile("output.o", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil { if err != nil {
panic(err) panic(err)
} }