//nolint:revive package elf // https://refspecs.linuxbase.org/elf/gabi4+/contents.html // https://man7.org/linux/man-pages/man5/elf.5.html // https://github.com/torvalds/linux/blob/master/include/uapi/linux/elf.h // TODO: dwarf import ( "strings" "github.com/wader/fq/format" "github.com/wader/fq/pkg/decode" "github.com/wader/fq/pkg/interp" "github.com/wader/fq/pkg/scalar" ) func init() { interp.RegisterFormat(decode.Format{ Name: format.ELF, Description: "Executable and Linkable Format", Groups: []string{format.PROBE}, DecodeFn: elfDecode, }) } const ( LITTLE_ENDIAN = 1 BIG_ENDIAN = 2 ) var endianNames = scalar.UToSymStr{ LITTLE_ENDIAN: "little_endian", BIG_ENDIAN: "big_endian", } var classBits = scalar.UToSymU{ 1: 32, 2: 64, } const ( CLASS_32 = 1 CLASS_64 = 2 ) var osABINames = scalar.UToSymStr{ 0: "sysv", 1: "hpux", 2: "netbsd", 3: "linux", 4: "hurd", 5: "86open", 6: "solaris", 7: "monterey", 8: "irix", 9: "freebsd", 10: "tru64", 11: "modesto", 12: "openbsd", 97: "arm", 255: "standalone", } var typeNames = scalar.URangeToScalar{ {Range: [2]uint64{0x00, 0x00}, S: scalar.S{Sym: "none"}}, {Range: [2]uint64{0x01, 0x01}, S: scalar.S{Sym: "rel"}}, {Range: [2]uint64{0x02, 0x02}, S: scalar.S{Sym: "exec"}}, {Range: [2]uint64{0x03, 0x03}, S: scalar.S{Sym: "dyn"}}, {Range: [2]uint64{0x04, 0x04}, S: scalar.S{Sym: "core"}}, {Range: [2]uint64{0xfe00, 0xfeff}, S: scalar.S{Sym: "os"}}, {Range: [2]uint64{0xff00, 0xffff}, S: scalar.S{Sym: "proc"}}, } const ( EM_X86_64 = 0x3e EM_ARM64 = 0xb7 ) var machineNames = scalar.UToScalar{ 0x00: {Description: "No specific instruction set"}, 0x01: {Sym: "we_32100", Description: "AT&T WE 32100"}, 0x02: {Sym: "sparc", Description: "SPARC"}, 0x03: {Sym: "x86", Description: "x86"}, 0x04: {Sym: "m68k", Description: "Motorola 68000 (M68k)"}, 0x05: {Sym: "m88k", Description: "Motorola 88000 (M88k)"}, 0x06: {Sym: "intel_mcu", Description: "Intel MCU"}, 0x07: {Sym: "intel_80860", Description: "Intel 80860"}, 0x08: {Sym: "mips", Description: "MIPS"}, 0x09: {Sym: "s370", Description: "IBM_System/370"}, 0x0a: {Sym: "mips_rs3000le", Description: "MIPS RS3000 Little-endian"}, 0x0e: {Sym: "pa_risc", Description: "Hewlett-Packard PA-RISC"}, 0x0f: {Description: "Reserved for future use"}, 0x13: {Sym: "80960", Description: "Intel 80960"}, 0x14: {Sym: "powerpc", Description: "PowerPC"}, 0x15: {Sym: "powerpc64", Description: "PowerPC (64-bit)"}, 0x16: {Sym: "s390", Description: "S390, including S390x"}, 0x17: {Sym: "ibm_spu_spc", Description: "IBM SPU/SPC"}, 0x24: {Sym: "nec_v800", Description: "NEC V800"}, 0x25: {Sym: "fr20", Description: "Fujitsu FR20"}, 0x26: {Sym: "trw_rh_32", Description: "TRW RH-32"}, 0x27: {Sym: "motorola_rce", Description: "Motorola RCE"}, 0x28: {Sym: "arm", Description: "ARM (up to ARMv7/Aarch32)"}, 0x29: {Sym: "alpha", Description: "Digital Alpha"}, 0x2a: {Sym: "superh", Description: "SuperH"}, 0x2b: {Sym: "sparc_v9", Description: "SPARC Version 9"}, 0x2c: {Sym: "siemens_tricore", Description: "Siemens TriCore embedded processor"}, 0x2d: {Sym: "argonaut_risc", Description: "Argonaut RISC Core"}, 0x2e: {Sym: "h8_300", Description: "Hitachi H8/300"}, 0x2f: {Sym: "h8_300h", Description: "Hitachi H8/300H"}, 0x30: {Sym: "h8s", Description: "Hitachi H8S"}, 0x31: {Sym: "h8/500", Description: "Hitachi H8/500"}, 0x32: {Sym: "ia_64", Description: "IA-64"}, 0x33: {Sym: "mips_x", Description: "Stanford MIPS-X"}, 0x34: {Sym: "coldfire", Description: "Motorola ColdFire"}, 0x35: {Sym: "m68hc12", Description: "Motorola M68HC12"}, 0x36: {Sym: "fujitsu_mma", Description: "Fujitsu MMA Multimedia Accelerator"}, 0x37: {Sym: "siemens_pcp", Description: "Siemens PCP"}, 0x38: {Sym: "sony_ncpu_risc", Description: "Sony nCPU embedded RISC processor"}, 0x39: {Sym: "denso_ndr1", Description: "Denso NDR1 microprocessor"}, 0x3a: {Sym: "motorola_star", Description: "Motorola Star*Core processor"}, 0x3b: {Sym: "toyota_me16", Description: "Toyota ME16 processor"}, 0x3c: {Sym: "st100", Description: "STMicroelectronics ST100 processor"}, 0x3d: {Sym: "tinyj", Description: "Advanced Logic Corp. TinyJ embedded processor family"}, EM_X86_64: {Sym: "x86_64", Description: "AMD x86-64"}, 0x8c: {Sym: "tms320C6000", Description: "TMS320C6000 Family"}, EM_ARM64: {Sym: "arm64", Description: "ARM 64-bits (ARMv8/Aarch64)"}, 0xf3: {Sym: "risc_v", Description: "RISC-V"}, 0xf7: {Sym: "bpf", Description: "Berkeley Packet Filter"}, 0x101: {Sym: "wdc_65C816", Description: "WDC 65C816"}, } var phTypeNames = scalar.URangeToScalar{ {Range: [2]uint64{0x00000000, 0x00000000}, S: scalar.S{Sym: "null", Description: "Unused element"}}, {Range: [2]uint64{0x00000001, 0x00000001}, S: scalar.S{Sym: "load", Description: "Loadable segment"}}, {Range: [2]uint64{0x00000002, 0x00000002}, S: scalar.S{Sym: "dynamic", Description: "Dynamic linking information"}}, {Range: [2]uint64{0x00000003, 0x00000003}, S: scalar.S{Sym: "interp", Description: "Interpreter to invoke"}}, {Range: [2]uint64{0x00000004, 0x00000004}, S: scalar.S{Sym: "note", Description: "Auxiliary information"}}, {Range: [2]uint64{0x00000005, 0x00000005}, S: scalar.S{Sym: "shlib", Description: "Reserved but has unspecified"}}, {Range: [2]uint64{0x00000006, 0x00000006}, S: scalar.S{Sym: "phdr", Description: "Program header location and size"}}, {Range: [2]uint64{0x00000007, 0x00000007}, S: scalar.S{Sym: "tls", Description: "Thread-Local Storage template"}}, {Range: [2]uint64{0x6474e550, 0x6474e550}, S: scalar.S{Sym: "gnu_eh_frame", Description: "GNU frame unwind information"}}, {Range: [2]uint64{0x6474e551, 0x6474e551}, S: scalar.S{Sym: "gnu_stack", Description: "GNU stack permission"}}, {Range: [2]uint64{0x6474e552, 0x6474e552}, S: scalar.S{Sym: "gnu_relro", Description: "GNU read-only after relocation"}}, {Range: [2]uint64{0x60000000, 0x6fffffff}, S: scalar.S{Sym: "os", Description: "Operating system-specific"}}, {Range: [2]uint64{0x70000000, 0x7fffffff}, S: scalar.S{Sym: "proc", Description: "Processor-specific"}}, } const ( SHT_NULL = 0x0 SHT_PROGBITS = 0x1 SHT_SYMTAB = 0x2 SHT_STRTAB = 0x3 SHT_RELA = 0x4 SHT_HASH = 0x5 SHT_DYNAMIC = 0x6 SHT_NOTE = 0x7 SHT_NOBITS = 0x8 SHT_REL = 0x9 SHT_SHLIB = 0x0a SHT_DYNSYM = 0x0b SHT_INIT_ARRAY = 0x0e SHT_FINI_ARRAY = 0x0f SHT_PREINIT_ARRAY = 0x10 SHT_GROUP = 0x11 SHT_SYMTAB_SHNDX = 0x12 SHT_GNU_HASH = 0x6ffffff6 ) var sectionHeaderTypeMap = scalar.UToScalar{ SHT_NULL: {Sym: "null", Description: "Header inactive"}, SHT_PROGBITS: {Sym: "progbits", Description: "Information defined by the program"}, SHT_SYMTAB: {Sym: "symtab", Description: "Symbol table"}, SHT_STRTAB: {Sym: "strtab", Description: "String table"}, SHT_RELA: {Sym: "rela", Description: "Relocation entries with explicit addends"}, SHT_HASH: {Sym: "hash", Description: "Symbol hash table"}, SHT_DYNAMIC: {Sym: "dynamic", Description: "Information for dynamic linking"}, SHT_NOTE: {Sym: "note", Description: "Information that marks the file in some way"}, SHT_NOBITS: {Sym: "nobits", Description: "No space in the file"}, SHT_REL: {Sym: "rel", Description: "Relocation entries without explicit addends"}, SHT_SHLIB: {Sym: "shlib", Description: "Reserved but has unspecified semantics"}, SHT_DYNSYM: {Sym: "dynsym", Description: "Dynamic linking symbol table"}, SHT_INIT_ARRAY: {Sym: "init_array", Description: "Initialization functions"}, SHT_FINI_ARRAY: {Sym: "fini_array", Description: "Termination functions"}, SHT_PREINIT_ARRAY: {Sym: "preinit_array", Description: "Pre initialization functions"}, SHT_GROUP: {Sym: "group", Description: "Section group"}, SHT_SYMTAB_SHNDX: {Sym: "symtab_shndx", Description: ""}, SHT_GNU_HASH: {Sym: "gnu_hash", Description: "GNU symbol hash table"}, } const ( STRTAB_DYNSTR = ".dynstr" STRTAB_SHSTRTAB = ".shstrtab" STRTAB_STRTAB = ".strtab" ) const ( DT_NULL = 0 DT_NEEDED = 1 DT_PLTRELSZ = 2 DT_PLTGOT = 3 DT_HASH = 4 DT_STRTAB = 5 DT_SYMTAB = 6 DT_RELA = 7 DT_RELASZ = 8 DT_RELAENT = 9 DT_STRSZ = 10 DT_SYMENT = 11 DT_INIT = 12 DT_FINI = 13 DT_SONAME = 14 DT_RPATH = 15 DT_SYMBOLIC = 16 DT_REL = 17 DT_RELSZ = 18 DT_RELENT = 19 DT_PLTREL = 20 DT_DEBUG = 21 DT_TEXTREL = 22 DT_JMPREL = 23 DT_BIND_NOW = 24 DT_INIT_ARRAY = 25 DT_FINI_ARRAY = 26 DT_INIT_ARRAYSZ = 27 DT_FINI_ARRAYSZ = 28 DT_RUNPATH = 29 DT_FLAGS = 30 // TODO: flag map DT_ENCODING = 32 // or DT_PREINIT_ARRAY ? DT_PREINIT_ARRAYSZ = 33 DT_LOOS = 0x6000000D DT_HIOS = 0x6ffff000 DT_LOPROC = 0x70000000 DT_HIPROC = 0x7fffffff ) const ( dUnIgnored = iota dUnVal dUnPtr dUnUnspecified ) type dtEntry struct { r [2]uint64 dUn int s scalar.S } type dynamicTableEntries []dtEntry func (d dynamicTableEntries) lookup(u uint64) (dtEntry, bool) { for _, de := range d { if de.r[0] >= u && de.r[1] <= u { return de, true } } return dtEntry{}, false } func (d dynamicTableEntries) MapScalar(s scalar.S) (scalar.S, error) { u := s.ActualU() if de, ok := d.lookup(u); ok { s = de.s s.Actual = u } return s, nil } var dynamicTableMap = dynamicTableEntries{ {r: [2]uint64{DT_NULL, DT_NULL}, dUn: dUnIgnored, s: scalar.S{Sym: "null", Description: "Marks end of dynamic section"}}, {r: [2]uint64{DT_NEEDED, DT_NEEDED}, dUn: dUnVal, s: scalar.S{Sym: "needed", Description: "String table offset to name of a needed library"}}, {r: [2]uint64{DT_PLTRELSZ, DT_PLTRELSZ}, dUn: dUnVal, s: scalar.S{Sym: "pltrelsz", Description: "Size in bytes of PLT relocation entries"}}, {r: [2]uint64{DT_PLTGOT, DT_PLTGOT}, dUn: dUnPtr, s: scalar.S{Sym: "pltgot", Description: "Address of PLT and/or GOT"}}, {r: [2]uint64{DT_HASH, DT_HASH}, dUn: dUnPtr, s: scalar.S{Sym: "hash", Description: "Address of symbol hash table"}}, {r: [2]uint64{DT_STRTAB, DT_STRTAB}, dUn: dUnPtr, s: scalar.S{Sym: "strtab", Description: "Address of string table"}}, {r: [2]uint64{DT_SYMTAB, DT_SYMTAB}, dUn: dUnPtr, s: scalar.S{Sym: "symtab", Description: "Address of symbol table"}}, {r: [2]uint64{DT_RELA, DT_RELA}, dUn: dUnPtr, s: scalar.S{Sym: "rela", Description: "Address of Rela relocation table"}}, {r: [2]uint64{DT_RELASZ, DT_RELASZ}, dUn: dUnVal, s: scalar.S{Sym: "relasz", Description: "Size in bytes of the Rela relocation table"}}, {r: [2]uint64{DT_RELAENT, DT_RELAENT}, dUn: dUnVal, s: scalar.S{Sym: "relaent", Description: "Size in bytes of a Rela relocation table entry"}}, {r: [2]uint64{DT_STRSZ, DT_STRSZ}, dUn: dUnVal, s: scalar.S{Sym: "strsz", Description: "Size in bytes of string table"}}, {r: [2]uint64{DT_SYMENT, DT_SYMENT}, dUn: dUnVal, s: scalar.S{Sym: "syment", Description: "Size in bytes of a symbol table entry"}}, {r: [2]uint64{DT_INIT, DT_INIT}, dUn: dUnPtr, s: scalar.S{Sym: "init", Description: "Address of the initialization function"}}, {r: [2]uint64{DT_FINI, DT_FINI}, dUn: dUnPtr, s: scalar.S{Sym: "fini", Description: "Address of the termination function"}}, {r: [2]uint64{DT_SONAME, DT_SONAME}, dUn: dUnVal, s: scalar.S{Sym: "soname", Description: "String table offset to name of shared object"}}, {r: [2]uint64{DT_RPATH, DT_RPATH}, dUn: dUnVal, s: scalar.S{Sym: "rpath", Description: "String table offset to library search path (deprecated)"}}, {r: [2]uint64{DT_SYMBOLIC, DT_SYMBOLIC}, dUn: dUnIgnored, s: scalar.S{Sym: "symbolic", Description: "Alert linker to search this shared object before the executable for symbols DT_REL Address of Rel relocation table"}}, {r: [2]uint64{DT_REL, DT_REL}, dUn: dUnPtr, s: scalar.S{Sym: "rel", Description: ""}}, {r: [2]uint64{DT_RELSZ, DT_RELSZ}, dUn: dUnVal, s: scalar.S{Sym: "relsz", Description: "Size in bytes of Rel relocation table"}}, {r: [2]uint64{DT_RELENT, DT_RELENT}, dUn: dUnVal, s: scalar.S{Sym: "relent", Description: "Size in bytes of a Rel table entry"}}, {r: [2]uint64{DT_PLTREL, DT_PLTREL}, dUn: dUnVal, s: scalar.S{Sym: "pltrel", Description: "Type of relocation entry to which the PLT refers (Rela or Rel)"}}, {r: [2]uint64{DT_DEBUG, DT_DEBUG}, dUn: dUnPtr, s: scalar.S{Sym: "debug", Description: "Undefined use for debugging"}}, {r: [2]uint64{DT_TEXTREL, DT_TEXTREL}, dUn: dUnIgnored, s: scalar.S{Sym: "textrel", Description: "Absence of this entry indicates that no relocation entries should apply to a nonwritable segment"}}, {r: [2]uint64{DT_JMPREL, DT_JMPREL}, dUn: dUnPtr, s: scalar.S{Sym: "jmprel", Description: "Address of relocation entries associated solely with the PLT"}}, {r: [2]uint64{DT_BIND_NOW, DT_BIND_NOW}, dUn: dUnIgnored, s: scalar.S{Sym: "bind_now", Description: "Instruct dynamic linker to process all relocations before transferring control to the executable"}}, {r: [2]uint64{DT_INIT_ARRAY, DT_INIT_ARRAY}, dUn: dUnPtr, s: scalar.S{Sym: "init_array", Description: "Address of the array of pointers to initialization functions"}}, {r: [2]uint64{DT_FINI_ARRAY, DT_FINI_ARRAY}, dUn: dUnPtr, s: scalar.S{Sym: "fini_array", Description: "Address of the array of pointers to termination functions"}}, {r: [2]uint64{DT_INIT_ARRAYSZ, DT_INIT_ARRAYSZ}, dUn: dUnVal, s: scalar.S{Sym: "init_arraysz", Description: "Size in bytes of the array of initialization functions"}}, {r: [2]uint64{DT_FINI_ARRAYSZ, DT_FINI_ARRAYSZ}, dUn: dUnVal, s: scalar.S{Sym: "fini_arraysz", Description: "Size in bytes of the array of termination functions "}}, {r: [2]uint64{DT_RUNPATH, DT_RUNPATH}, dUn: dUnVal, s: scalar.S{Sym: "runpath", Description: "String table offset to library search path"}}, {r: [2]uint64{DT_FLAGS, DT_FLAGS}, dUn: dUnVal, s: scalar.S{Sym: "flags", Description: "Flag values specific to the object being loaded"}}, // TODO: flag ma}}, {r: [2]uint64{DT_ENCODING, DT_ENCODING}, dUn: dUnUnspecified, s: scalar.S{Sym: "encoding", Description: ""}}, // or DT_PREINIT_ARRAY }}, {r: [2]uint64{DT_PREINIT_ARRAYSZ, DT_PREINIT_ARRAYSZ}, dUn: dUnVal, s: scalar.S{Sym: "preinit_arraysz", Description: "Address of the array of pointers to pre-initialization functions"}}, {r: [2]uint64{DT_LOOS, DT_HIOS}, dUn: dUnUnspecified, s: scalar.S{Sym: "lo", Description: "Operating system-specific semantics"}}, {r: [2]uint64{DT_LOPROC, DT_HIPROC}, dUn: dUnUnspecified, s: scalar.S{Sym: "proc", Description: "Processor-specific semantics"}}, } var symbolTableBindingMap = scalar.UToSymStr{ 0: "local", 1: "global", 2: "weak", 10: "loos", 12: "hios", 13: "proc", 14: "proc", 15: "proc", } var symbolTableTypeMap = scalar.UToSymStr{ 0: "notype", 1: "object", 2: "func", 3: "section", 4: "file", 5: "common", 6: "tls", 10: "loos", 12: "hios", 13: "proc", 14: "proc", 15: "proc", } var symbolTableVisibilityMap = scalar.UToSymStr{ 0: "default", 1: "internal", 2: "hidden", 3: "protected", } func strIndexNull(idx int, s string) string { if idx > len(s) { return "" } i := strings.IndexByte(s[idx:], 0) if i == -1 { return s } return s[idx : idx+i] } type strTable string func (m strTable) MapScalar(s scalar.S) (scalar.S, error) { s.Sym = strIndexNull(int(s.ActualU()), string(m)) return s, nil } func elfDecodeSymbolHashTable(d *decode.D) { nBucket := d.FieldU32("nbucket") nChain := d.FieldU32("nchain") repeatFn := func(r int, fn func(d *decode.D)) func(d *decode.D) { return func(d *decode.D) { for i := 0; i < r; i++ { fn(d) } } } d.FieldArray("buckets", repeatFn(int(nBucket), func(d *decode.D) { d.FieldU32("bucket") })) d.FieldArray("chains", repeatFn(int(nChain), func(d *decode.D) { d.FieldU32("chain") })) } func elfDecodeSymbolTable(d *decode.D, ec elfContext, nEntries int, strTab string) { for i := 0; i < nEntries; i++ { d.FieldStruct("symbol", func(d *decode.D) { switch ec.archBits { case 32: d.FieldU32("name", strTable(strTab)) d.FieldU32("value") d.FieldU32("size") d.FieldU4("bind", symbolTableBindingMap) d.FieldU4("type", symbolTableTypeMap) d.FieldU6("other_unused") d.FieldU2("visibility", symbolTableVisibilityMap) d.FieldU16("shndx") case 64: d.FieldU32("name", strTable(strTab)) d.FieldU4("bind", symbolTableBindingMap) d.FieldU4("type", symbolTableTypeMap) d.FieldU6("other_unused") d.FieldU2("visibility", symbolTableVisibilityMap) d.FieldU16("shndx") d.FieldU64("value") d.FieldU64("size") } }) } } func elfDecodeGNUHash(d *decode.D, ec elfContext, size int64, strTab string) { d.FramedFn(size, func(d *decode.D) { nBuckets := d.FieldU32("nbuckets") d.FieldU32("symndx") maskwords := d.FieldU32("maskwords") d.FieldU32("shift2") repeatFn := func(r int, fn func(d *decode.D)) func(d *decode.D) { return func(d *decode.D) { for i := 0; i < r; i++ { fn(d) } } } // TODO: possible to map to symbols? _ = strTab d.FieldArray("bloom_filter", repeatFn(int(maskwords), func(d *decode.D) { d.FieldU("maskword", ec.archBits) })) d.FieldArray("buckets", repeatFn(int(nBuckets), func(d *decode.D) { d.FieldU32("bucket") })) d.FieldArray("values", func(d *decode.D) { for !d.End() { d.FieldU32("value") } }) }) } type dynamicContext struct { entries int strTabPtr int64 strSzVal int64 strTab string symEnt int64 } func elfReadDynamicTags(d *decode.D, ec *elfContext) dynamicContext { var strTabPtr int64 var strSzVal int64 var symEnt int64 var entries int seenNull := false for !seenNull { entries++ tag := d.U(ec.archBits) valPtr := d.U(ec.archBits) switch tag { case DT_STRTAB: strTabPtr = int64(valPtr) * 8 case DT_STRSZ: strSzVal = int64(valPtr) * 8 case DT_SYMENT: symEnt = int64(valPtr) * 8 case DT_NULL: seenNull = true } } return dynamicContext{ entries: entries, strTabPtr: strTabPtr, strSzVal: strSzVal, symEnt: symEnt, } } type symbol struct { name uint64 value uint64 } func elfReadSymbolTable(d *decode.D, ec *elfContext, sh sectionHeader) []symbol { var ss []symbol for i := 0; i < int(sh.size/sh.entSize); i++ { var name uint64 var value uint64 switch ec.archBits { case 32: name = d.U32() // name value = d.U32() // value d.U32() // size d.U4() // bind d.U4() // type d.U6() // other_unused d.U2() // visibility d.U16() // shndx case 64: name = d.U32() // name d.U4() // bind d.U4() // type d.U6() // other_unused d.U2() // visibility d.U16() // shndx value = d.U64() // value d.U64() // size } ss = append(ss, symbol{name: name, value: value}) } return ss } type sectionHeader struct { addr int64 offset int64 size int64 entSize int64 name int typ int dc dynamicContext // if SHT_DYNAMIC symbols []symbol } const maxStrTabSize = 100_000_000 func readStrTab(d *decode.D, firstBit int64, nBytes int64) string { if nBytes > maxStrTabSize { d.Errorf("string table too large %d > %d", nBytes, maxStrTabSize) } return string(d.BytesRange(firstBit, int(nBytes))) } func elfReadSectionHeaders(d *decode.D, ec *elfContext) { for i := 0; i < ec.shNum; i++ { d.SeekAbs(ec.shOff + int64(i)*ec.shEntSize) var sh sectionHeader switch ec.archBits { case 32: sh.name = int(d.U32()) sh.typ = int(d.U32()) d.U32() // flags sh.addr = int64(d.U32() * 8) // addr sh.offset = int64(d.U32()) * 8 sh.size = int64(d.U32()) * 8 d.U32() // link d.U32() // info d.U32() // addralign sh.entSize = int64(d.U32()) * 8 case 64: sh.name = int(d.U32()) sh.typ = int(d.U32()) d.U64() // addr sh.addr = int64(d.U64() * 8) // flags sh.offset = int64(d.U64()) * 8 sh.size = int64(d.U64()) * 8 d.U32() // link d.U32() // info d.U64() // addralign sh.entSize = int64(d.U64()) * 8 default: panic("unreachable") } switch sh.typ { case SHT_DYNAMIC: d.SeekAbs(sh.offset) sh.dc = elfReadDynamicTags(d, ec) case SHT_SYMTAB: d.SeekAbs(sh.offset) sh.symbols = elfReadSymbolTable(d, ec, sh) } ec.sections = append(ec.sections, sh) } // for dynamic linking sections find offset to string table by looking up // section by address using string stable address for i := range ec.sections { sh := &ec.sections[i] if sh.typ != SHT_DYNAMIC { continue } if i, ok := ec.sectionIndexByAddr(sh.dc.strTabPtr); ok { strTabSh := ec.sections[i] sh.dc.strTab = readStrTab(d, strTabSh.offset, sh.dc.strSzVal/8) } } ec.strTabMap = map[string]string{} var shStrTab string if ec.shStrNdx >= len(ec.sections) { d.Fatalf("can't find shStrNdx %d", ec.shStrNdx) } sh := ec.sections[ec.shStrNdx] shStrTab = readStrTab(d, sh.offset, sh.size/8) for _, sh := range ec.sections { if sh.typ != SHT_STRTAB { continue } ec.strTabMap[strIndexNull(sh.name, shStrTab)] = readStrTab(d, sh.offset, sh.size/8) } } type elfContext struct { archBits int machine int endian decode.Endian phOff int64 phNum int phSize int64 shOff int64 shNum int shEntSize int64 shStrNdx int sections []sectionHeader strTabMap map[string]string } func (ec *elfContext) sectionIndexByAddr(addr int64) (int, bool) { for i, s := range ec.sections { if s.addr == addr { return i, true } } return 0, false } func elfDecodeHeader(d *decode.D, ec *elfContext) { var class uint64 var archBits int var endian uint64 d.FieldStruct("ident", func(d *decode.D) { d.FieldRawLen("magic", 4*8, d.AssertBitBuf([]byte("\x7fELF"))) class = d.FieldU8("class", classBits) endian = d.FieldU8("data", endianNames) d.FieldU8("version") d.FieldU8("os_abi", osABINames) d.FieldU8("abi_version") d.FieldRawLen("pad", 7*8, d.BitBufIsZero()) }) switch class { case CLASS_32: archBits = 32 case CLASS_64: archBits = 64 default: d.Fatalf("unknown class %d", class) } switch endian { case LITTLE_ENDIAN: d.Endian = decode.LittleEndian case BIG_ENDIAN: d.Endian = decode.BigEndian default: d.Fatalf("unknown endian %d", endian) } d.FieldU16("type", typeNames, scalar.ActualHex) machine := d.FieldU16("machine", machineNames, scalar.ActualHex) d.FieldU32("version") d.FieldU("entry", archBits) phOff := d.FieldU("phoff", archBits) shOff := d.FieldU("shoff", archBits) d.FieldU32("flags") d.FieldU16("ehsize") phSize := d.FieldU16("phentsize") phNum := d.FieldU16("phnum") shEntSize := d.FieldU16("shentsize") shNum := d.FieldU16("shnum") shStrNdx := d.FieldU16("shstrndx") ec.archBits = archBits ec.endian = d.Endian ec.machine = int(machine) ec.phOff = int64(phOff) * 8 ec.phNum = int(phNum) ec.phSize = int64(phSize) * 8 ec.shOff = int64(shOff) * 8 ec.shNum = int(shNum) ec.shEntSize = int64(shEntSize) * 8 ec.shStrNdx = int(shStrNdx) } func elfDecodeProgramHeader(d *decode.D, ec elfContext) { pFlags := func(d *decode.D) { d.FieldStruct("flags", func(d *decode.D) { if d.Endian == decode.LittleEndian { d.FieldU5("unused0") d.FieldBool("r") d.FieldBool("w") d.FieldBool("x") d.FieldU24("unused1") } else { d.FieldU29("unused0") d.FieldBool("r") d.FieldBool("w") d.FieldBool("x") } }) } var offset uint64 var size uint64 switch ec.archBits { case 32: d.FieldU32("type", phTypeNames) offset = d.FieldU("offset", ec.archBits, scalar.ActualHex) d.FieldU("vaddr", ec.archBits, scalar.ActualHex) d.FieldU("paddr", ec.archBits, scalar.ActualHex) size = d.FieldU32("filesz") d.FieldU32("memsz") pFlags(d) d.FieldU32("align") case 64: d.FieldU32("type", phTypeNames) pFlags(d) offset = d.FieldU("offset", ec.archBits, scalar.ActualHex) d.FieldU("vaddr", ec.archBits, scalar.ActualHex) d.FieldU("paddr", ec.archBits, scalar.ActualHex) size = d.FieldU64("filesz") d.FieldU64("memsz") d.FieldU64("align") } d.RangeFn(int64(offset*8), int64(size*8), func(d *decode.D) { d.FieldRawLen("data", d.BitsLeft()) }) } func elfDecodeProgramHeaders(d *decode.D, ec elfContext) { for i := 0; i < ec.phNum; i++ { d.FieldStruct("program_header", func(d *decode.D) { d.SeekAbs(ec.phOff + int64(i)*ec.phSize) elfDecodeProgramHeader(d, ec) }) } } func elfDecodeDynamicTag(d *decode.D, ec elfContext, dc dynamicContext) { dtTag := d.FieldU("tag", ec.archBits, dynamicTableMap) name := "unspecified" dfMapper := scalar.ActualHex if de, ok := dynamicTableMap.lookup(dtTag); ok { switch de.dUn { case dUnIgnored: name = "ignored" case dUnVal: name = "val" dfMapper = scalar.ActualDec case dUnPtr: name = "ptr" } } switch dtTag { case DT_NEEDED: d.FieldU(name, ec.archBits, dfMapper, strTable(dc.strTab)) case DT_HASH: v := d.FieldU(name, ec.archBits, dfMapper) if i, ok := ec.sectionIndexByAddr(int64(v) * 8); ok { d.FieldValueU("section_index", uint64(i)) } case DT_SYMTAB, DT_STRTAB, DT_PLTGOT, DT_JMPREL, DT_INIT, DT_FINI: v := d.FieldU(name, ec.archBits, dfMapper) if i, ok := ec.sectionIndexByAddr(int64(v) * 8); ok { d.FieldValueU("section_index", uint64(i)) } default: d.FieldU(name, ec.archBits, dfMapper) } } func elfDecodeDynamicTags(d *decode.D, ec elfContext, dc dynamicContext) { for i := 0; i < dc.entries; i++ { d.FieldStruct("dynamic_tags", func(d *decode.D) { elfDecodeDynamicTag(d, ec, dc) }) } } func elfDecodeSectionHeader(d *decode.D, ec elfContext, sh sectionHeader) { shFlags := func(d *decode.D, archBits int) { d.FieldStruct("flags", func(d *decode.D) { if d.Endian == decode.LittleEndian { d.FieldBool("link_order") d.FieldBool("info_link") d.FieldBool("strings") d.FieldBool("merge") d.FieldU1("unused0") d.FieldBool("execinstr") d.FieldBool("alloc") d.FieldBool("write") d.FieldBool("tls") d.FieldBool("group") d.FieldBool("os_nonconforming") d.FieldU9("unused1") d.FieldU8("os_specific") d.FieldU4("processor_specific") if archBits == 64 { d.FieldU32("unused2") } } else { // TODO: add.FieldUnused that is per decoder? if archBits == 64 { d.FieldU32("unused0") } d.FieldU4("processor_specific") d.FieldU8("os_specific") d.FieldU9("unused1") d.FieldBool("tls") d.FieldBool("group") d.FieldBool("os_nonconforming") d.FieldBool("link_order") d.FieldBool("info_link") d.FieldBool("strings") d.FieldBool("merge") d.FieldU1("unused2") d.FieldBool("execinstr") d.FieldBool("alloc") d.FieldBool("write") } }) } var offset int64 var size int64 var entSize int64 var typ uint64 switch ec.archBits { case 32: d.FieldU32("name", strTable(ec.strTabMap[STRTAB_SHSTRTAB])) typ = d.FieldU32("type", sectionHeaderTypeMap, scalar.ActualHex) shFlags(d, ec.archBits) d.FieldU("addr", ec.archBits, scalar.ActualHex) offset = int64(d.FieldU("offset", ec.archBits)) * 8 size = int64(d.FieldU32("size", scalar.ActualHex) * 8) d.FieldU32("link") d.FieldU32("info") d.FieldU32("addralign") entSize = int64(d.FieldU32("entsize") * 8) case 64: d.FieldU32("name", strTable(ec.strTabMap[STRTAB_SHSTRTAB])) typ = d.FieldU32("type", sectionHeaderTypeMap, scalar.ActualHex) shFlags(d, ec.archBits) d.FieldU("addr", ec.archBits, scalar.ActualHex) offset = int64(d.FieldU("offset", ec.archBits, scalar.ActualHex) * 8) size = int64(d.FieldU64("size") * 8) d.FieldU32("link") d.FieldU32("info") d.FieldU64("addralign") entSize = int64(d.FieldU64("entsize") * 8) } if typ == SHT_NOBITS { // section occupies no space in file return } d.SeekAbs(offset) switch typ { case SHT_STRTAB: d.FieldUTF8("string", int(size/8)) case SHT_DYNAMIC: d.FieldArray("dynamic_tags", func(d *decode.D) { elfDecodeDynamicTags(d, ec, sh.dc) }) case SHT_HASH: d.FieldStruct("symbol_hash_table", elfDecodeSymbolHashTable) case SHT_SYMTAB: d.FieldArray("symbol_table", func(d *decode.D) { elfDecodeSymbolTable(d, ec, int(size/entSize), ec.strTabMap[STRTAB_STRTAB]) }) case SHT_DYNSYM: d.FieldArray("symbol_table", func(d *decode.D) { elfDecodeSymbolTable(d, ec, int(size/entSize), ec.strTabMap[STRTAB_DYNSTR]) }) case SHT_PROGBITS: // TODO: name progbits? // TODO: decode opcodes d.FieldRawLen("data", size) case SHT_GNU_HASH: d.FieldStruct("gnu_hash", func(d *decode.D) { elfDecodeGNUHash(d, ec, size, ec.strTabMap[STRTAB_DYNSTR]) }) default: d.FieldRawLen("data", size) } } func elfDecodeSectionHeaders(d *decode.D, ec elfContext) { for i := 0; i < ec.shNum; i++ { d.SeekAbs(ec.shOff + int64(i)*ec.shEntSize) d.FieldStruct("section_header", func(d *decode.D) { elfDecodeSectionHeader(d, ec, ec.sections[i]) }) } } func elfDecode(d *decode.D, _ any) any { var ec elfContext d.FieldStruct("header", func(d *decode.D) { elfDecodeHeader(d, &ec) }) d.Endian = ec.endian // a first pass to find all sections and string table information etc elfReadSectionHeaders(d, &ec) d.FieldArray("program_headers", func(d *decode.D) { elfDecodeProgramHeaders(d, ec) }) d.FieldArray("section_headers", func(d *decode.D) { elfDecodeSectionHeaders(d, ec) }) return nil }