package elf
import (
"bytes"
"compress/zlib"
"debug/dwarf"
"encoding/binary"
"errors"
"fmt"
"internal/saferio"
"internal/zstd"
"io"
"os"
"strings"
"unsafe"
)
type FileHeader struct {
Class Class
Data Data
Version Version
OSABI OSABI
ABIVersion uint8
ByteOrder binary .ByteOrder
Type Type
Machine Machine
Entry uint64
}
type File struct {
FileHeader
Sections []*Section
Progs []*Prog
closer io .Closer
gnuNeed []verneed
gnuVersym []byte
}
type SectionHeader struct {
Name string
Type SectionType
Flags SectionFlag
Addr uint64
Offset uint64
Size uint64
Link uint32
Info uint32
Addralign uint64
Entsize uint64
FileSize uint64
}
type Section struct {
SectionHeader
io .ReaderAt
sr *io .SectionReader
compressionType CompressionType
compressionOffset int64
}
func (s *Section ) Data () ([]byte , error ) {
return saferio .ReadData (s .Open (), s .Size )
}
func (f *File ) stringTable (link uint32 ) ([]byte , error ) {
if link <= 0 || link >= uint32 (len (f .Sections )) {
return nil , errors .New ("section has invalid string table link" )
}
return f .Sections [link ].Data ()
}
func (s *Section ) Open () io .ReadSeeker {
if s .Type == SHT_NOBITS {
return io .NewSectionReader (&nobitsSectionReader {}, 0 , int64 (s .Size ))
}
var zrd func (io .Reader ) (io .ReadCloser , error )
if s .Flags &SHF_COMPRESSED == 0 {
if !strings .HasPrefix (s .Name , ".zdebug" ) {
return io .NewSectionReader (s .sr , 0 , 1 <<63 -1 )
}
b := make ([]byte , 12 )
n , _ := s .sr .ReadAt (b , 0 )
if n != 12 || string (b [:4 ]) != "ZLIB" {
return io .NewSectionReader (s .sr , 0 , 1 <<63 -1 )
}
s .compressionOffset = 12
s .compressionType = COMPRESS_ZLIB
s .Size = binary .BigEndian .Uint64 (b [4 :12 ])
zrd = zlib .NewReader
} else if s .Flags &SHF_ALLOC != 0 {
return errorReader {&FormatError {int64 (s .Offset ),
"SHF_COMPRESSED applies only to non-allocable sections" , s .compressionType }}
}
switch s .compressionType {
case COMPRESS_ZLIB :
zrd = zlib .NewReader
case COMPRESS_ZSTD :
zrd = func (r io .Reader ) (io .ReadCloser , error ) {
return io .NopCloser (zstd .NewReader (r )), nil
}
}
if zrd == nil {
return errorReader {&FormatError {int64 (s .Offset ), "unknown compression type" , s .compressionType }}
}
return &readSeekerFromReader {
reset : func () (io .Reader , error ) {
fr := io .NewSectionReader (s .sr , s .compressionOffset , int64 (s .FileSize )-s .compressionOffset )
return zrd (fr )
},
size : int64 (s .Size ),
}
}
type ProgHeader struct {
Type ProgType
Flags ProgFlag
Off uint64
Vaddr uint64
Paddr uint64
Filesz uint64
Memsz uint64
Align uint64
}
type Prog struct {
ProgHeader
io .ReaderAt
sr *io .SectionReader
}
func (p *Prog ) Open () io .ReadSeeker { return io .NewSectionReader (p .sr , 0 , 1 <<63 -1 ) }
type Symbol struct {
Name string
Info, Other byte
Section SectionIndex
Value, Size uint64
Version string
Library string
}
type FormatError struct {
off int64
msg string
val any
}
func (e *FormatError ) Error () 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
}
func Open (name string ) (*File , error ) {
f , err := os .Open (name )
if err != nil {
return nil , err
}
ff , err := NewFile (f )
if err != nil {
f .Close ()
return nil , err
}
ff .closer = f
return ff , nil
}
func (f *File ) Close () error {
var err error
if f .closer != nil {
err = f .closer .Close ()
f .closer = nil
}
return err
}
func (f *File ) SectionByType (typ SectionType ) *Section {
for _ , s := range f .Sections {
if s .Type == typ {
return s
}
}
return nil
}
func NewFile (r io .ReaderAt ) (*File , error ) {
sr := io .NewSectionReader (r , 0 , 1 <<63 -1 )
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 :
default :
return nil , &FormatError {0 , "unknown ELF class" , f .Class }
}
f .Data = Data (ident [EI_DATA ])
var bo binary .ByteOrder
switch f .Data {
case ELFDATA2LSB :
bo = binary .LittleEndian
case ELFDATA2MSB :
bo = binary .BigEndian
default :
return nil , &FormatError {0 , "unknown ELF data encoding" , f .Data }
}
f .ByteOrder = bo
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 ]
var phoff int64
var phentsize , phnum int
var shoff int64
var shentsize , shnum , shstrndx int
switch f .Class {
case ELFCLASS32 :
var hdr Header32
data := make ([]byte , unsafe .Sizeof (hdr ))
if _ , err := sr .ReadAt (data , 0 ); err != nil {
return nil , err
}
f .Type = Type (bo .Uint16 (data [unsafe .Offsetof (hdr .Type ):]))
f .Machine = Machine (bo .Uint16 (data [unsafe .Offsetof (hdr .Machine ):]))
f .Entry = uint64 (bo .Uint32 (data [unsafe .Offsetof (hdr .Entry ):]))
if v := Version (bo .Uint32 (data [unsafe .Offsetof (hdr .Version ):])); v != f .Version {
return nil , &FormatError {0 , "mismatched ELF version" , v }
}
phoff = int64 (bo .Uint32 (data [unsafe .Offsetof (hdr .Phoff ):]))
phentsize = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Phentsize ):]))
phnum = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Phnum ):]))
shoff = int64 (bo .Uint32 (data [unsafe .Offsetof (hdr .Shoff ):]))
shentsize = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Shentsize ):]))
shnum = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Shnum ):]))
shstrndx = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Shstrndx ):]))
case ELFCLASS64 :
var hdr Header64
data := make ([]byte , unsafe .Sizeof (hdr ))
if _ , err := sr .ReadAt (data , 0 ); err != nil {
return nil , err
}
f .Type = Type (bo .Uint16 (data [unsafe .Offsetof (hdr .Type ):]))
f .Machine = Machine (bo .Uint16 (data [unsafe .Offsetof (hdr .Machine ):]))
f .Entry = bo .Uint64 (data [unsafe .Offsetof (hdr .Entry ):])
if v := Version (bo .Uint32 (data [unsafe .Offsetof (hdr .Version ):])); v != f .Version {
return nil , &FormatError {0 , "mismatched ELF version" , v }
}
phoff = int64 (bo .Uint64 (data [unsafe .Offsetof (hdr .Phoff ):]))
phentsize = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Phentsize ):]))
phnum = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Phnum ):]))
shoff = int64 (bo .Uint64 (data [unsafe .Offsetof (hdr .Shoff ):]))
shentsize = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Shentsize ):]))
shnum = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Shnum ):]))
shstrndx = int (bo .Uint16 (data [unsafe .Offsetof (hdr .Shstrndx ):]))
}
if shoff < 0 {
return nil , &FormatError {0 , "invalid shoff" , shoff }
}
if phoff < 0 {
return nil , &FormatError {0 , "invalid phoff" , phoff }
}
if shoff == 0 && shnum != 0 {
return nil , &FormatError {0 , "invalid ELF shnum for shoff=0" , shnum }
}
if shnum > 0 && shstrndx >= shnum {
return nil , &FormatError {0 , "invalid ELF shstrndx" , shstrndx }
}
var wantPhentsize , wantShentsize int
switch f .Class {
case ELFCLASS32 :
wantPhentsize = 8 * 4
wantShentsize = 10 * 4
case ELFCLASS64 :
wantPhentsize = 2 *4 + 6 *8
wantShentsize = 4 *4 + 6 *8
}
if phnum > 0 && phentsize < wantPhentsize {
return nil , &FormatError {0 , "invalid ELF phentsize" , phentsize }
}
f .Progs = make ([]*Prog , phnum )
phdata , err := saferio .ReadDataAt (sr , uint64 (phnum )*uint64 (phentsize ), phoff )
if err != nil {
return nil , err
}
for i := 0 ; i < phnum ; i ++ {
off := uintptr (i ) * uintptr (phentsize )
p := new (Prog )
switch f .Class {
case ELFCLASS32 :
var ph Prog32
p .ProgHeader = ProgHeader {
Type : ProgType (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Type ):])),
Flags : ProgFlag (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Flags ):])),
Off : uint64 (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Off ):])),
Vaddr : uint64 (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Vaddr ):])),
Paddr : uint64 (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Paddr ):])),
Filesz : uint64 (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Filesz ):])),
Memsz : uint64 (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Memsz ):])),
Align : uint64 (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Align ):])),
}
case ELFCLASS64 :
var ph Prog64
p .ProgHeader = ProgHeader {
Type : ProgType (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Type ):])),
Flags : ProgFlag (bo .Uint32 (phdata [off +unsafe .Offsetof (ph .Flags ):])),
Off : bo .Uint64 (phdata [off +unsafe .Offsetof (ph .Off ):]),
Vaddr : bo .Uint64 (phdata [off +unsafe .Offsetof (ph .Vaddr ):]),
Paddr : bo .Uint64 (phdata [off +unsafe .Offsetof (ph .Paddr ):]),
Filesz : bo .Uint64 (phdata [off +unsafe .Offsetof (ph .Filesz ):]),
Memsz : bo .Uint64 (phdata [off +unsafe .Offsetof (ph .Memsz ):]),
Align : bo .Uint64 (phdata [off +unsafe .Offsetof (ph .Align ):]),
}
}
if int64 (p .Off ) < 0 {
return nil , &FormatError {phoff + int64 (off ), "invalid program header offset" , p .Off }
}
if int64 (p .Filesz ) < 0 {
return nil , &FormatError {phoff + int64 (off ), "invalid program header file size" , p .Filesz }
}
p .sr = io .NewSectionReader (r , int64 (p .Off ), int64 (p .Filesz ))
p .ReaderAt = p .sr
f .Progs [i ] = p
}
if shoff > 0 && shnum == 0 {
var typ , link uint32
sr .Seek (shoff , io .SeekStart )
switch f .Class {
case ELFCLASS32 :
sh := new (Section32 )
if err := binary .Read (sr , bo , sh ); err != nil {
return nil , err
}
shnum = int (sh .Size )
typ = sh .Type
link = sh .Link
case ELFCLASS64 :
sh := new (Section64 )
if err := binary .Read (sr , bo , sh ); err != nil {
return nil , err
}
shnum = int (sh .Size )
typ = sh .Type
link = sh .Link
}
if SectionType (typ ) != SHT_NULL {
return nil , &FormatError {shoff , "invalid type of the initial section" , SectionType (typ )}
}
if shnum < int (SHN_LORESERVE ) {
return nil , &FormatError {shoff , "invalid ELF shnum contained in sh_size" , shnum }
}
if shstrndx == int (SHN_XINDEX ) {
shstrndx = int (link )
if shstrndx < int (SHN_LORESERVE ) {
return nil , &FormatError {shoff , "invalid ELF shstrndx contained in sh_link" , shstrndx }
}
}
}
if shnum > 0 && shentsize < wantShentsize {
return nil , &FormatError {0 , "invalid ELF shentsize" , shentsize }
}
c := saferio .SliceCap [Section ](uint64 (shnum ))
if c < 0 {
return nil , &FormatError {0 , "too many sections" , shnum }
}
f .Sections = make ([]*Section , 0 , c )
names := make ([]uint32 , 0 , c )
shdata , err := saferio .ReadDataAt (sr , uint64 (shnum )*uint64 (shentsize ), shoff )
if err != nil {
return nil , err
}
for i := 0 ; i < shnum ; i ++ {
off := uintptr (i ) * uintptr (shentsize )
s := new (Section )
switch f .Class {
case ELFCLASS32 :
var sh Section32
names = append (names , bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Name ):]))
s .SectionHeader = SectionHeader {
Type : SectionType (bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Type ):])),
Flags : SectionFlag (bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Flags ):])),
Addr : uint64 (bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Addr ):])),
Offset : uint64 (bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Off ):])),
FileSize : uint64 (bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Size ):])),
Link : bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Link ):]),
Info : bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Info ):]),
Addralign : uint64 (bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Addralign ):])),
Entsize : uint64 (bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Entsize ):])),
}
case ELFCLASS64 :
var sh Section64
names = append (names , bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Name ):]))
s .SectionHeader = SectionHeader {
Type : SectionType (bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Type ):])),
Flags : SectionFlag (bo .Uint64 (shdata [off +unsafe .Offsetof (sh .Flags ):])),
Offset : bo .Uint64 (shdata [off +unsafe .Offsetof (sh .Off ):]),
FileSize : bo .Uint64 (shdata [off +unsafe .Offsetof (sh .Size ):]),
Addr : bo .Uint64 (shdata [off +unsafe .Offsetof (sh .Addr ):]),
Link : bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Link ):]),
Info : bo .Uint32 (shdata [off +unsafe .Offsetof (sh .Info ):]),
Addralign : bo .Uint64 (shdata [off +unsafe .Offsetof (sh .Addralign ):]),
Entsize : bo .Uint64 (shdata [off +unsafe .Offsetof (sh .Entsize ):]),
}
}
if int64 (s .Offset ) < 0 {
return nil , &FormatError {shoff + int64 (off ), "invalid section offset" , int64 (s .Offset )}
}
if int64 (s .FileSize ) < 0 {
return nil , &FormatError {shoff + int64 (off ), "invalid section size" , int64 (s .FileSize )}
}
s .sr = io .NewSectionReader (r , int64 (s .Offset ), int64 (s .FileSize ))
if s .Flags &SHF_COMPRESSED == 0 {
s .ReaderAt = s .sr
s .Size = s .FileSize
} else {
switch f .Class {
case ELFCLASS32 :
var ch Chdr32
chdata := make ([]byte , unsafe .Sizeof (ch ))
if _ , err := s .sr .ReadAt (chdata , 0 ); err != nil {
return nil , err
}
s .compressionType = CompressionType (bo .Uint32 (chdata [unsafe .Offsetof (ch .Type ):]))
s .Size = uint64 (bo .Uint32 (chdata [unsafe .Offsetof (ch .Size ):]))
s .Addralign = uint64 (bo .Uint32 (chdata [unsafe .Offsetof (ch .Addralign ):]))
s .compressionOffset = int64 (unsafe .Sizeof (ch ))
case ELFCLASS64 :
var ch Chdr64
chdata := make ([]byte , unsafe .Sizeof (ch ))
if _ , err := s .sr .ReadAt (chdata , 0 ); err != nil {
return nil , err
}
s .compressionType = CompressionType (bo .Uint32 (chdata [unsafe .Offsetof (ch .Type ):]))
s .Size = bo .Uint64 (chdata [unsafe .Offsetof (ch .Size ):])
s .Addralign = bo .Uint64 (chdata [unsafe .Offsetof (ch .Addralign ):])
s .compressionOffset = int64 (unsafe .Sizeof (ch ))
}
}
f .Sections = append (f .Sections , s )
}
if len (f .Sections ) == 0 {
return f , nil
}
if shstrndx == 0 {
return f , nil
}
shstr := f .Sections [shstrndx ]
if shstr .Type != SHT_STRTAB {
return nil , &FormatError {shoff + int64 (shstrndx *shentsize ), "invalid ELF section name string table type" , shstr .Type }
}
shstrtab , err := shstr .Data ()
if 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 (typ SectionType ) ([]Symbol , []byte , error ) {
switch f .Class {
case ELFCLASS64 :
return f .getSymbols64 (typ )
case ELFCLASS32 :
return f .getSymbols32 (typ )
}
return nil , nil , errors .New ("not implemented" )
}
var ErrNoSymbols = errors .New ("no symbol section" )
func (f *File ) getSymbols32 (typ SectionType ) ([]Symbol , []byte , error ) {
symtabSection := f .SectionByType (typ )
if symtabSection == nil {
return nil , nil , ErrNoSymbols
}
data , err := symtabSection .Data ()
if err != nil {
return nil , nil , fmt .Errorf ("cannot load symbol section: %w" , err )
}
if len (data ) == 0 {
return nil , nil , errors .New ("symbol section is empty" )
}
if len (data )%Sym32Size != 0 {
return nil , nil , errors .New ("length of symbol section is not a multiple of SymSize" )
}
strdata , err := f .stringTable (symtabSection .Link )
if err != nil {
return nil , nil , fmt .Errorf ("cannot load string table section: %w" , err )
}
data = data [Sym32Size :]
symbols := make ([]Symbol , len (data )/Sym32Size )
i := 0
var sym Sym32
for len (data ) > 0 {
sym .Name = f .ByteOrder .Uint32 (data [0 :4 ])
sym .Value = f .ByteOrder .Uint32 (data [4 :8 ])
sym .Size = f .ByteOrder .Uint32 (data [8 :12 ])
sym .Info = data [12 ]
sym .Other = data [13 ]
sym .Shndx = f .ByteOrder .Uint16 (data [14 :16 ])
str , _ := getString (strdata , int (sym .Name ))
symbols [i ].Name = str
symbols [i ].Info = sym .Info
symbols [i ].Other = sym .Other
symbols [i ].Section = SectionIndex (sym .Shndx )
symbols [i ].Value = uint64 (sym .Value )
symbols [i ].Size = uint64 (sym .Size )
i ++
data = data [Sym32Size :]
}
return symbols , strdata , nil
}
func (f *File ) getSymbols64 (typ SectionType ) ([]Symbol , []byte , error ) {
symtabSection := f .SectionByType (typ )
if symtabSection == nil {
return nil , nil , ErrNoSymbols
}
data , err := symtabSection .Data ()
if err != nil {
return nil , nil , fmt .Errorf ("cannot load symbol section: %w" , err )
}
if len (data )%Sym64Size != 0 {
return nil , nil , errors .New ("length of symbol section is not a multiple of Sym64Size" )
}
strdata , err := f .stringTable (symtabSection .Link )
if err != nil {
return nil , nil , fmt .Errorf ("cannot load string table section: %w" , err )
}
data = data [Sym64Size :]
symbols := make ([]Symbol , len (data )/Sym64Size )
i := 0
var sym Sym64
for len (data ) > 0 {
sym .Name = f .ByteOrder .Uint32 (data [0 :4 ])
sym .Info = data [4 ]
sym .Other = data [5 ]
sym .Shndx = f .ByteOrder .Uint16 (data [6 :8 ])
sym .Value = f .ByteOrder .Uint64 (data [8 :16 ])
sym .Size = f .ByteOrder .Uint64 (data [16 :24 ])
str , _ := getString (strdata , int (sym .Name ))
symbols [i ].Name = str
symbols [i ].Info = sym .Info
symbols [i ].Other = sym .Other
symbols [i ].Section = SectionIndex (sym .Shndx )
symbols [i ].Value = sym .Value
symbols [i ].Size = sym .Size
i ++
data = data [Sym64Size :]
}
return symbols , strdata , nil
}
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
}
func (f *File ) Section (name string ) *Section {
for _ , s := range f .Sections {
if s .Name == name {
return s
}
}
return nil
}
func (f *File ) applyRelocations (dst []byte , rels []byte ) error {
switch {
case f .Class == ELFCLASS64 && f .Machine == EM_X86_64 :
return f .applyRelocationsAMD64 (dst , rels )
case f .Class == ELFCLASS32 && f .Machine == EM_386 :
return f .applyRelocations386 (dst , rels )
case f .Class == ELFCLASS32 && f .Machine == EM_ARM :
return f .applyRelocationsARM (dst , rels )
case f .Class == ELFCLASS64 && f .Machine == EM_AARCH64 :
return f .applyRelocationsARM64 (dst , rels )
case f .Class == ELFCLASS32 && f .Machine == EM_PPC :
return f .applyRelocationsPPC (dst , rels )
case f .Class == ELFCLASS64 && f .Machine == EM_PPC64 :
return f .applyRelocationsPPC64 (dst , rels )
case f .Class == ELFCLASS32 && f .Machine == EM_MIPS :
return f .applyRelocationsMIPS (dst , rels )
case f .Class == ELFCLASS64 && f .Machine == EM_MIPS :
return f .applyRelocationsMIPS64 (dst , rels )
case f .Class == ELFCLASS64 && f .Machine == EM_LOONGARCH :
return f .applyRelocationsLOONG64 (dst , rels )
case f .Class == ELFCLASS64 && f .Machine == EM_RISCV :
return f .applyRelocationsRISCV64 (dst , rels )
case f .Class == ELFCLASS64 && f .Machine == EM_S390 :
return f .applyRelocationss390x (dst , rels )
case f .Class == ELFCLASS64 && f .Machine == EM_SPARCV9 :
return f .applyRelocationsSPARC64 (dst , rels )
default :
return errors .New ("applyRelocations: not implemented" )
}
}
func canApplyRelocation(sym *Symbol ) bool {
return sym .Section != SHN_UNDEF && sym .Section < SHN_LORESERVE
}
func (f *File ) applyRelocationsAMD64 (dst []byte , rels []byte ) error {
if len (rels )%24 != 0 {
return errors .New ("length of relocation section is not a multiple of 24" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (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 == 0 || symNo > uint64 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_X86_64_64 :
if rela .Off +8 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val64 := sym .Value + uint64 (rela .Addend )
f .ByteOrder .PutUint64 (dst [rela .Off :rela .Off +8 ], val64 )
case R_X86_64_32 :
if rela .Off +4 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) applyRelocations386 (dst []byte , rels []byte ) error {
if len (rels )%8 != 0 {
return errors .New ("length of relocation section is not a multiple of 8" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rel Rel32
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rel )
symNo := rel .Info >> 8
t := R_386 (rel .Info & 0xff )
if symNo == 0 || symNo > uint32 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if t == R_386_32 {
if rel .Off +4 >= uint32 (len (dst )) {
continue
}
val := f .ByteOrder .Uint32 (dst [rel .Off : rel .Off +4 ])
val += uint32 (sym .Value )
f .ByteOrder .PutUint32 (dst [rel .Off :rel .Off +4 ], val )
}
}
return nil
}
func (f *File ) applyRelocationsARM (dst []byte , rels []byte ) error {
if len (rels )%8 != 0 {
return errors .New ("length of relocation section is not a multiple of 8" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rel Rel32
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rel )
symNo := rel .Info >> 8
t := R_ARM (rel .Info & 0xff )
if symNo == 0 || symNo > uint32 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
switch t {
case R_ARM_ABS32 :
if rel .Off +4 >= uint32 (len (dst )) {
continue
}
val := f .ByteOrder .Uint32 (dst [rel .Off : rel .Off +4 ])
val += uint32 (sym .Value )
f .ByteOrder .PutUint32 (dst [rel .Off :rel .Off +4 ], val )
}
}
return nil
}
func (f *File ) applyRelocationsARM64 (dst []byte , rels []byte ) error {
if len (rels )%24 != 0 {
return errors .New ("length of relocation section is not a multiple of 24" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rela Rela64
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rela )
symNo := rela .Info >> 32
t := R_AARCH64 (rela .Info & 0xffff )
if symNo == 0 || symNo > uint64 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_AARCH64_ABS64 :
if rela .Off +8 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val64 := sym .Value + uint64 (rela .Addend )
f .ByteOrder .PutUint64 (dst [rela .Off :rela .Off +8 ], val64 )
case R_AARCH64_ABS32 :
if rela .Off +4 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) applyRelocationsPPC (dst []byte , rels []byte ) error {
if len (rels )%12 != 0 {
return errors .New ("length of relocation section is not a multiple of 12" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rela Rela32
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rela )
symNo := rela .Info >> 8
t := R_PPC (rela .Info & 0xff )
if symNo == 0 || symNo > uint32 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_PPC_ADDR32 :
if rela .Off +4 >= uint32 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) applyRelocationsPPC64 (dst []byte , rels []byte ) error {
if len (rels )%24 != 0 {
return errors .New ("length of relocation section is not a multiple of 24" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rela Rela64
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rela )
symNo := rela .Info >> 32
t := R_PPC64 (rela .Info & 0xffff )
if symNo == 0 || symNo > uint64 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_PPC64_ADDR64 :
if rela .Off +8 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val64 := sym .Value + uint64 (rela .Addend )
f .ByteOrder .PutUint64 (dst [rela .Off :rela .Off +8 ], val64 )
case R_PPC64_ADDR32 :
if rela .Off +4 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) applyRelocationsMIPS (dst []byte , rels []byte ) error {
if len (rels )%8 != 0 {
return errors .New ("length of relocation section is not a multiple of 8" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rel Rel32
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rel )
symNo := rel .Info >> 8
t := R_MIPS (rel .Info & 0xff )
if symNo == 0 || symNo > uint32 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
switch t {
case R_MIPS_32 :
if rel .Off +4 >= uint32 (len (dst )) {
continue
}
val := f .ByteOrder .Uint32 (dst [rel .Off : rel .Off +4 ])
val += uint32 (sym .Value )
f .ByteOrder .PutUint32 (dst [rel .Off :rel .Off +4 ], val )
}
}
return nil
}
func (f *File ) applyRelocationsMIPS64 (dst []byte , rels []byte ) error {
if len (rels )%24 != 0 {
return errors .New ("length of relocation section is not a multiple of 24" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rela Rela64
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rela )
var symNo uint64
var t R_MIPS
if f .ByteOrder == binary .BigEndian {
symNo = rela .Info >> 32
t = R_MIPS (rela .Info & 0xff )
} else {
symNo = rela .Info & 0xffffffff
t = R_MIPS (rela .Info >> 56 )
}
if symNo == 0 || symNo > uint64 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_MIPS_64 :
if rela .Off +8 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val64 := sym .Value + uint64 (rela .Addend )
f .ByteOrder .PutUint64 (dst [rela .Off :rela .Off +8 ], val64 )
case R_MIPS_32 :
if rela .Off +4 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) applyRelocationsLOONG64 (dst []byte , rels []byte ) error {
if len (rels )%24 != 0 {
return errors .New ("length of relocation section is not a multiple of 24" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rela Rela64
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rela )
var symNo uint64
var t R_LARCH
symNo = rela .Info >> 32
t = R_LARCH (rela .Info & 0xffff )
if symNo == 0 || symNo > uint64 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_LARCH_64 :
if rela .Off +8 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val64 := sym .Value + uint64 (rela .Addend )
f .ByteOrder .PutUint64 (dst [rela .Off :rela .Off +8 ], val64 )
case R_LARCH_32 :
if rela .Off +4 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) applyRelocationsRISCV64 (dst []byte , rels []byte ) error {
if len (rels )%24 != 0 {
return errors .New ("length of relocation section is not a multiple of 24" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rela Rela64
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rela )
symNo := rela .Info >> 32
t := R_RISCV (rela .Info & 0xffff )
if symNo == 0 || symNo > uint64 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_RISCV_64 :
if rela .Off +8 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val64 := sym .Value + uint64 (rela .Addend )
f .ByteOrder .PutUint64 (dst [rela .Off :rela .Off +8 ], val64 )
case R_RISCV_32 :
if rela .Off +4 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) applyRelocationss390x (dst []byte , rels []byte ) error {
if len (rels )%24 != 0 {
return errors .New ("length of relocation section is not a multiple of 24" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rela Rela64
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rela )
symNo := rela .Info >> 32
t := R_390 (rela .Info & 0xffff )
if symNo == 0 || symNo > uint64 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_390_64 :
if rela .Off +8 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val64 := sym .Value + uint64 (rela .Addend )
f .ByteOrder .PutUint64 (dst [rela .Off :rela .Off +8 ], val64 )
case R_390_32 :
if rela .Off +4 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) applyRelocationsSPARC64 (dst []byte , rels []byte ) error {
if len (rels )%24 != 0 {
return errors .New ("length of relocation section is not a multiple of 24" )
}
symbols , _ , err := f .getSymbols (SHT_SYMTAB )
if err != nil {
return err
}
b := bytes .NewReader (rels )
var rela Rela64
for b .Len () > 0 {
binary .Read (b , f .ByteOrder , &rela )
symNo := rela .Info >> 32
t := R_SPARC (rela .Info & 0xff )
if symNo == 0 || symNo > uint64 (len (symbols )) {
continue
}
sym := &symbols [symNo -1 ]
if !canApplyRelocation (sym ) {
continue
}
switch t {
case R_SPARC_64 , R_SPARC_UA64 :
if rela .Off +8 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val64 := sym .Value + uint64 (rela .Addend )
f .ByteOrder .PutUint64 (dst [rela .Off :rela .Off +8 ], val64 )
case R_SPARC_32 , R_SPARC_UA32 :
if rela .Off +4 >= uint64 (len (dst )) || rela .Addend < 0 {
continue
}
val32 := uint32 (sym .Value ) + uint32 (rela .Addend )
f .ByteOrder .PutUint32 (dst [rela .Off :rela .Off +4 ], val32 )
}
}
return nil
}
func (f *File ) DWARF () (*dwarf .Data , error ) {
dwarfSuffix := func (s *Section ) string {
switch {
case strings .HasPrefix (s .Name , ".debug_" ):
return s .Name [7 :]
case strings .HasPrefix (s .Name , ".zdebug_" ):
return s .Name [8 :]
default :
return ""
}
}
sectionData := func (i int , s *Section ) ([]byte , error ) {
b , err := s .Data ()
if err != nil && uint64 (len (b )) < s .Size {
return nil , err
}
if f .Type == ET_EXEC {
return b , nil
}
for _ , r := range f .Sections {
if r .Type != SHT_RELA && r .Type != SHT_REL {
continue
}
if int (r .Info ) != i {
continue
}
rd , err := r .Data ()
if err != nil {
return nil , err
}
err = f .applyRelocations (b , rd )
if err != nil {
return nil , err
}
}
return b , nil
}
var dat = map [string ][]byte {"abbrev" : nil , "info" : nil , "str" : nil , "line" : nil , "ranges" : nil }
for i , s := range f .Sections {
suffix := dwarfSuffix (s )
if suffix == "" {
continue
}
if _ , ok := dat [suffix ]; !ok {
continue
}
b , err := sectionData (i , s )
if err != nil {
return nil , err
}
dat [suffix ] = b
}
d , err := dwarf .New (dat ["abbrev" ], nil , nil , dat ["info" ], dat ["line" ], nil , dat ["ranges" ], dat ["str" ])
if err != nil {
return nil , err
}
for i , s := range f .Sections {
suffix := dwarfSuffix (s )
if suffix == "" {
continue
}
if _ , ok := dat [suffix ]; ok {
continue
}
b , err := sectionData (i , s )
if err != nil {
return nil , err
}
if suffix == "types" {
if err := d .AddTypes (fmt .Sprintf ("types-%d" , i ), b ); err != nil {
return nil , err
}
} else {
if err := d .AddSection (".debug_" +suffix , b ); err != nil {
return nil , err
}
}
}
return d , nil
}
func (f *File ) Symbols () ([]Symbol , error ) {
sym , _ , err := f .getSymbols (SHT_SYMTAB )
return sym , err
}
func (f *File ) DynamicSymbols () ([]Symbol , error ) {
sym , str , err := f .getSymbols (SHT_DYNSYM )
if err != nil {
return nil , err
}
if f .gnuVersionInit (str ) {
for i := range sym {
sym [i ].Library , sym [i ].Version = f .gnuVersion (i )
}
}
return sym , nil
}
type ImportedSymbol struct {
Name string
Version string
Library string
}
func (f *File ) ImportedSymbols () ([]ImportedSymbol , error ) {
sym , str , err := f .getSymbols (SHT_DYNSYM )
if err != nil {
return nil , err
}
f .gnuVersionInit (str )
var all []ImportedSymbol
for i , s := range sym {
if ST_BIND (s .Info ) == STB_GLOBAL && s .Section == SHN_UNDEF {
all = append (all , ImportedSymbol {Name : s .Name })
sym := &all [len (all )-1 ]
sym .Library , sym .Version = f .gnuVersion (i )
}
}
return all , nil
}
type verneed struct {
File string
Name string
}
func (f *File ) gnuVersionInit (str []byte ) bool {
if f .gnuNeed != nil {
return true
}
vn := f .SectionByType (SHT_GNU_VERNEED )
if vn == nil {
return false
}
d , _ := vn .Data ()
var need []verneed
i := 0
for {
if i +16 > len (d ) {
break
}
vers := f .ByteOrder .Uint16 (d [i : i +2 ])
if vers != 1 {
break
}
cnt := f .ByteOrder .Uint16 (d [i +2 : i +4 ])
fileoff := f .ByteOrder .Uint32 (d [i +4 : i +8 ])
aux := f .ByteOrder .Uint32 (d [i +8 : i +12 ])
next := f .ByteOrder .Uint32 (d [i +12 : i +16 ])
file , _ := getString (str , int (fileoff ))
var name string
j := i + int (aux )
for c := 0 ; c < int (cnt ); c ++ {
if j +16 > len (d ) {
break
}
other := f .ByteOrder .Uint16 (d [j +6 : j +8 ])
nameoff := f .ByteOrder .Uint32 (d [j +8 : j +12 ])
next := f .ByteOrder .Uint32 (d [j +12 : j +16 ])
name , _ = getString (str , int (nameoff ))
ndx := int (other )
if ndx >= len (need ) {
a := make ([]verneed , 2 *(ndx +1 ))
copy (a , need )
need = a
}
need [ndx ] = verneed {file , name }
if next == 0 {
break
}
j += int (next )
}
if next == 0 {
break
}
i += int (next )
}
vs := f .SectionByType (SHT_GNU_VERSYM )
if vs == nil {
return false
}
d , _ = vs .Data ()
f .gnuNeed = need
f .gnuVersym = d
return true
}
func (f *File ) gnuVersion (i int ) (library string , version string ) {
i = (i + 1 ) * 2
if i >= len (f .gnuVersym ) {
return
}
s := f .gnuVersym [i :]
if len (s ) < 2 {
return
}
j := int (f .ByteOrder .Uint16 (s ))
if j < 2 || j >= len (f .gnuNeed ) {
return
}
n := &f .gnuNeed [j ]
return n .File , n .Name
}
func (f *File ) ImportedLibraries () ([]string , error ) {
return f .DynString (DT_NEEDED )
}
func (f *File ) DynString (tag DynTag ) ([]string , error ) {
switch tag {
case DT_NEEDED , DT_SONAME , DT_RPATH , DT_RUNPATH :
default :
return nil , fmt .Errorf ("non-string-valued tag %v" , tag )
}
ds := f .SectionByType (SHT_DYNAMIC )
if ds == nil {
return nil , nil
}
d , err := ds .Data ()
if err != nil {
return nil , err
}
dynSize := 8
if f .Class == ELFCLASS64 {
dynSize = 16
}
if len (d )%dynSize != 0 {
return nil , errors .New ("length of dynamic section is not a multiple of dynamic entry size" )
}
str , err := f .stringTable (ds .Link )
if err != nil {
return nil , err
}
var all []string
for len (d ) > 0 {
var t DynTag
var v uint64
switch f .Class {
case ELFCLASS32 :
t = DynTag (f .ByteOrder .Uint32 (d [0 :4 ]))
v = uint64 (f .ByteOrder .Uint32 (d [4 :8 ]))
d = d [8 :]
case ELFCLASS64 :
t = DynTag (f .ByteOrder .Uint64 (d [0 :8 ]))
v = f .ByteOrder .Uint64 (d [8 :16 ])
d = d [16 :]
}
if t == tag {
s , ok := getString (str , int (v ))
if ok {
all = append (all , s )
}
}
}
return all , nil
}
func (f *File ) DynValue (tag DynTag ) ([]uint64 , error ) {
ds := f .SectionByType (SHT_DYNAMIC )
if ds == nil {
return nil , nil
}
d , err := ds .Data ()
if err != nil {
return nil , err
}
dynSize := 8
if f .Class == ELFCLASS64 {
dynSize = 16
}
if len (d )%dynSize != 0 {
return nil , errors .New ("length of dynamic section is not a multiple of dynamic entry size" )
}
var vals []uint64
for len (d ) > 0 {
var t DynTag
var v uint64
switch f .Class {
case ELFCLASS32 :
t = DynTag (f .ByteOrder .Uint32 (d [0 :4 ]))
v = uint64 (f .ByteOrder .Uint32 (d [4 :8 ]))
d = d [8 :]
case ELFCLASS64 :
t = DynTag (f .ByteOrder .Uint64 (d [0 :8 ]))
v = f .ByteOrder .Uint64 (d [8 :16 ])
d = d [16 :]
}
if t == tag {
vals = append (vals , v )
}
}
return vals , nil
}
type nobitsSectionReader struct {}
func (*nobitsSectionReader ) ReadAt (p []byte , off int64 ) (n int , err error ) {
return 0 , errors .New ("unexpected read from SHT_NOBITS section" )
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .