Go Home Page
The Go Programming Language

Source file src/pkg/debug/elf/file.go

// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package elf implements access to ELF object files.
package elf

import (
    "bytes"
    "debug/dwarf"
    "encoding/binary"
    "fmt"
    "io"
    "os"
)

// TODO: error reporting detail

/*
 * Internal ELF representation
 */

// A FileHeader represents an ELF file header.
type FileHeader struct {
    Class      Class
    Data       Data
    Version    Version
    OSABI      OSABI
    ABIVersion uint8
    ByteOrder  binary.ByteOrder
    Type       Type
    Machine    Machine
}

// A File represents an open ELF file.
type File struct {
    FileHeader
    Sections []*Section
    Progs    []*Prog
    closer   io.Closer
}

// A SectionHeader represents a single ELF section header.
type SectionHeader struct {
    Name      string
    Type      SectionType
    Flags     SectionFlag
    Addr      uint64
    Offset    uint64
    Size      uint64
    Link      uint32
    Info      uint32
    Addralign uint64
    Entsize   uint64
}

// A Section represents a single section in an ELF file.
type Section struct {
    SectionHeader

    // Embed ReaderAt for ReadAt method.
    // Do not embed SectionReader directly
    // to avoid having Read and Seek.
    // If a client wants Read and Seek it must use
    // Open() to avoid fighting over the seek offset
    // with other clients.
    io.ReaderAt
    sr *io.SectionReader
}

// Data reads and returns the contents of the ELF section.
func (s *Section) Data() ([]byte, os.Error) {
    dat := make([]byte, s.sr.Size())
    n, err := s.sr.ReadAt(dat, 0)
    return dat[0:n], err
}

// Open returns a new ReadSeeker reading the ELF section.
func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }

// A ProgHeader represents a single ELF program header.
type ProgHeader struct {
    Type   ProgType
    Flags  ProgFlag
    Vaddr  uint64
    Paddr  uint64
    Filesz uint64
    Memsz  uint64
    Align  uint64
}

// A Prog represents a single ELF program header in an ELF binary.
type Prog struct {
    ProgHeader

    // Embed ReaderAt for ReadAt method.
    // Do not embed SectionReader directly
    // to avoid having Read and Seek.
    // If a client wants Read and Seek it must use
    // Open() to avoid fighting over the seek offset
    // with other clients.
    io.ReaderAt
    sr *io.SectionReader
}

// Open returns a new ReadSeeker reading the ELF program body.
func (p *Prog) Open() io.ReadSeeker { return io.NewSectionReader(p.sr, 0, 1<<63-1) }

// A Symbol represents an entry in an ELF symbol table section.
type Symbol struct {
    Name        uint32
    Info, Other byte
    Section     uint32
    Value, Size uint64
}

/*
 * ELF reader
 */

type FormatError struct {
    off int64
    msg string
    val interface{}
}

func (e *FormatError) String() string {
    msg := e.msg
    if e.val != nil {
        msg += fmt.Sprintf(" '%v' ", e.val)
    }
    msg += fmt.Sprintf("in record at byte %#x", e.off)
    return msg
}

// Open opens the named file using os.Open and prepares it for use as an ELF binary.
func Open(name string) (*File, os.Error) {
    f, err := os.Open(name, os.O_RDONLY, 0)
    if err != nil {
        return nil, err
    }
    ff, err := NewFile(f)
    if err != nil {
        f.Close()
        return nil, err
    }
    ff.closer = f
    return ff, nil
}

// Close closes the File.
// If the File was created using NewFile directly instead of Open,
// Close has no effect.
func (f *File) Close() os.Error {
    var err os.Error
    if f.closer != nil {
        err = f.closer.Close()
        f.closer = nil
    }
    return err
}

// NewFile creates a new File for accessing an ELF binary in an underlying reader.
// The ELF binary is expected to start at position 0 in the ReaderAt.
func NewFile(r io.ReaderAt) (*File, os.Error) {
    sr := io.NewSectionReader(r, 0, 1<<63-1)
    // Read and decode ELF identifier
    var ident [16]uint8
    if _, err := r.ReadAt(ident[0:], 0); err != nil {
        return nil, err
    }
    if ident[0] != '\x7f' || ident[1] != 'E' || ident[2] != 'L' || ident[3] != 'F' {
        return nil, &FormatError{0, "bad magic number", ident[0:4]}
    }

    f := new(File)
    f.Class = Class(ident[EI_CLASS])
    switch f.Class {
    case ELFCLASS32:
    case ELFCLASS64:
        // ok
    default:
        return nil, &FormatError{0, "unknown ELF class", f.Class}
    }

    f.Data = Data(ident[EI_DATA])
    switch f.Data {
    case ELFDATA2LSB:
        f.ByteOrder = binary.LittleEndian
    case ELFDATA2MSB:
        f.ByteOrder = binary.BigEndian
    default:
        return nil, &FormatError{0, "unknown ELF data encoding", f.Data}
    }

    f.Version = Version(ident[EI_VERSION])
    if f.Version != EV_CURRENT {
        return nil, &FormatError{0, "unknown ELF version", f.Version}
    }

    f.OSABI = OSABI(ident[EI_OSABI])
    f.ABIVersion = ident[EI_ABIVERSION]

    // Read ELF file header
    var shoff int64
    var shentsize, shnum, shstrndx int
    shstrndx = -1
    switch f.Class {
    case ELFCLASS32:
        hdr := new(Header32)
        sr.Seek(0, 0)
        if err := binary.Read(sr, f.ByteOrder, hdr); err != nil {
            return nil, err
        }
        f.Type = Type(hdr.Type)
        f.Machine = Machine(hdr.Machine)
        if v := Version(hdr.Version); v != f.Version {
            return nil, &FormatError{0, "mismatched ELF version", v}
        }
        shoff = int64(hdr.Shoff)
        shentsize = int(hdr.Shentsize)
        shnum = int(hdr.Shnum)
        shstrndx = int(hdr.Shstrndx)
    case ELFCLASS64:
        hdr := new(Header64)
        sr.Seek(0, 0)
        if err := binary.Read(sr, f.ByteOrder, hdr); err != nil {
            return nil, err
        }
        f.Type = Type(hdr.Type)
        f.Machine = Machine(hdr.Machine)
        if v := Version(hdr.Version); v != f.Version {
            return nil, &FormatError{0, "mismatched ELF version", v}
        }
        shoff = int64(hdr.Shoff)
        shentsize = int(hdr.Shentsize)
        shnum = int(hdr.Shnum)
        shstrndx = int(hdr.Shstrndx)
    }
    if shstrndx < 0 || shstrndx >= shnum {
        return nil, &FormatError{0, "invalid ELF shstrndx", shstrndx}
    }

    // Read program headers
    // TODO

    // Read section headers
    f.Sections = make([]*Section, shnum)
    names := make([]uint32, shnum)
    for i := 0; i < shnum; i++ {
        off := shoff + int64(i)*int64(shentsize)
        sr.Seek(off, 0)
        s := new(Section)
        switch f.Class {
        case ELFCLASS32:
            sh := new(Section32)
            if err := binary.Read(sr, f.ByteOrder, sh); err != nil {
                return nil, err
            }
            names[i] = sh.Name
            s.SectionHeader = SectionHeader{
                Type:      SectionType(sh.Type),
                Flags:     SectionFlag(sh.Flags),
                Addr:      uint64(sh.Addr),
                Offset:    uint64(sh.Off),
                Size:      uint64(sh.Size),
                Link:      uint32(sh.Link),
                Info:      uint32(sh.Info),
                Addralign: uint64(sh.Addralign),
                Entsize:   uint64(sh.Entsize),
            }
        case ELFCLASS64:
            sh := new(Section64)
            if err := binary.Read(sr, f.ByteOrder, sh); err != nil {
                return nil, err
            }
            names[i] = sh.Name
            s.SectionHeader = SectionHeader{
                Type:      SectionType(sh.Type),
                Flags:     SectionFlag(sh.Flags),
                Offset:    uint64(sh.Off),
                Size:      uint64(sh.Size),
                Addr:      uint64(sh.Addr),
                Link:      uint32(sh.Link),
                Info:      uint32(sh.Info),
                Addralign: uint64(sh.Addralign),
                Entsize:   uint64(sh.Entsize),
            }
        }
        s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Size))
        s.ReaderAt = s.sr
        f.Sections[i] = s
    }

    // Load section header string table.
    s := f.Sections[shstrndx]
    shstrtab := make([]byte, s.Size)
    if _, err := r.ReadAt(shstrtab, int64(s.Offset)); err != nil {
        return nil, err
    }
    for i, s := range f.Sections {
        var ok bool
        s.Name, ok = getString(shstrtab, int(names[i]))
        if !ok {
            return nil, &FormatError{shoff + int64(i*shentsize), "bad section name index", names[i]}
        }
    }

    return f, nil
}

func (f *File) getSymbols() ([]Symbol, os.Error) {
    switch f.Class {
    case ELFCLASS64:
        return f.getSymbols64()
    }

    return nil, os.ErrorString("not implemented")
}

// GetSymbols returns a slice of Symbols from parsing the symbol table.
func (f *File) getSymbols64() ([]Symbol, os.Error) {
    var symtabSection *Section
    for _, section := range f.Sections {
        if section.Type == SHT_SYMTAB {
            symtabSection = section
            break
        }
    }

    if symtabSection == nil {
        return nil, os.ErrorString("no symbol section")
    }

    data, err := symtabSection.Data()
    if err != nil {
        return nil, os.ErrorString("cannot load symbol section")
    }
    symtab := bytes.NewBuffer(data)
    if symtab.Len()%Sym64Size != 0 {
        return nil, os.ErrorString("length of symbol section is not a multiple of Sym64Size")
    }

    // The first entry is all zeros.
    var skip [Sym64Size]byte
    symtab.Read(skip[0:])

    symbols := make([]Symbol, symtab.Len()/Sym64Size)

    i := 0
    var sym Sym64
    for symtab.Len() > 0 {
        binary.Read(symtab, f.ByteOrder, &sym)
        symbols[i].Name = sym.Name
        symbols[i].Info = sym.Info
        symbols[i].Other = sym.Other
        symbols[i].Section = uint32(sym.Shndx)
        symbols[i].Value = sym.Value
        symbols[i].Size = sym.Size
        i++
    }

    return symbols, nil
}

// getString extracts a string from an ELF string table.
func getString(section []byte, start int) (string, bool) {
    if start < 0 || start >= len(section) {
        return "", false
    }

    for end := start; end < len(section); end++ {
        if section[end] == 0 {
            return string(section[start:end]), true
        }
    }
    return "", false
}

// Section returns a section with the given name, or nil if no such
// section exists.
func (f *File) Section(name string) *Section {
    for _, s := range f.Sections {
        if s.Name == name {
            return s
        }
    }
    return nil
}

// applyRelocations applies relocations to dst. rels is a relocations section
// in RELA format.
func (f *File) applyRelocations(dst []byte, rels []byte) os.Error {
    if f.Class == ELFCLASS64 && f.Machine == EM_X86_64 {
        return f.applyRelocationsAMD64(dst, rels)
    }

    return os.ErrorString("not implemented")
}

func (f *File) applyRelocationsAMD64(dst []byte, rels []byte) os.Error {
    if len(rels)%Sym64Size != 0 {
        return os.ErrorString("length of relocation section is not a multiple of Sym64Size")
    }

    symbols, err := f.getSymbols()
    if err != nil {
        return err
    }

    b := bytes.NewBuffer(rels)
    var rela Rela64

    for b.Len() > 0 {
        binary.Read(b, f.ByteOrder, &rela)
        symNo := rela.Info >> 32
        t := R_X86_64(rela.Info & 0xffff)

        if symNo >= uint64(len(symbols)) {
            continue
        }
        sym := &symbols[symNo]
        if SymType(sym.Info&0xf) != STT_SECTION {
            // We don't handle non-section relocations for now.
            continue
        }

        switch t {
        case R_X86_64_64:
            if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
                continue
            }
            f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], uint64(rela.Addend))
        case R_X86_64_32:
            if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
                continue
            }
            f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], uint32(rela.Addend))
        }
    }

    return nil
}

func (f *File) DWARF() (*dwarf.Data, os.Error) {
    // There are many other DWARF sections, but these
    // are the required ones, and the debug/dwarf package
    // does not use the others, so don't bother loading them.
    var names = [...]string{"abbrev", "info", "str"}
    var dat [len(names)][]byte
    for i, name := range names {
        name = ".debug_" + name
        s := f.Section(name)
        if s == nil {
            continue
        }
        b, err := s.Data()
        if err != nil && uint64(len(b)) < s.Size {
            return nil, err
        }
        dat[i] = b
    }

    // If there's a relocation table for .debug_info, we have to process it
    // now otherwise the data in .debug_info is invalid for x86-64 objects.
    rela := f.Section(".rela.debug_info")
    if rela != nil && rela.Type == SHT_RELA && f.Machine == EM_X86_64 {
        data, err := rela.Data()
        if err != nil {
            return nil, err
        }
        err = f.applyRelocations(dat[1], data)
        if err != nil {
            return nil, err
        }
    }

    abbrev, info, str := dat[0], dat[1], dat[2]
    return dwarf.New(abbrev, nil, nil, info, nil, nil, nil, str)
}