aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2019-12-27 12:51:09 -0800
committerRob Landley <rob@landley.net>2020-01-08 17:51:57 -0600
commita3da7efae55c6e2d3ff66fdc476b35f5bbec09e5 (patch)
tree8804f3e15453da20617837421d0ee374361f2616
parent8f1a7f921aec469ea85247c12f204d0891d7853b (diff)
downloadtoybox-a3da7efae55c6e2d3ff66fdc476b35f5bbec09e5.tar.gz
Implement readelf(1).
Basic readelf(1) implementation, with output close enough to the binutils version to be usable with scripts that expect the binutils version. This started as an implementation of nm(1) until I realized that I almost always want readelf instead, and that you actually have to do much of the work needed for readelf just to implement nm. Arguably nm (being part of POSIX) belongs in toybox while readelf doesn't. An argument could also be made that neither really belongs in toybox, belonging in a separate set of development tools (such as binutils or the LLVM binutils). Doesn't support most of the architecture-specific stuff, most notably relocations, but is aware of things like ARM exidx sections and the common register state notes in core dumps for the "big four" architectures: arm, arm64, x86, and x86-64. Doesn't support symbol versions (but probably should). Doesn't support section groups or the -t "section details" (which is a long form of -S "section headers" that I've never seen used in practice and which isn't part of -a). Doesn't support dumping unwind info or the hash table bucket histograms. Reuses the table of ELF architectures from file(1). Not fuzzed, but successfully parses all the ELF files in my Ubuntu 18.04 system's lib directories. Attempts to exit with an error when presented with an invalid ELF file rather than struggle on as binutils seems to.
-rw-r--r--lib/lib.c26
-rw-r--r--lib/lib.h1
-rwxr-xr-xtests/readelf.test202
-rw-r--r--toys/pending/readelf.c589
-rw-r--r--toys/posix/file.c22
5 files changed, 820 insertions, 20 deletions
diff --git a/lib/lib.c b/lib/lib.c
index b14e2e9f..4eef1355 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -1409,3 +1409,29 @@ int is_tar_header(void *pkt)
return i && tar_cksum(pkt) == i;
}
+
+char *elf_arch_name(int type)
+{
+ int i;
+ // Values from include/linux/elf-em.h (plus arch/*/include/asm/elf.h)
+ // Names are linux/arch/ directory (sometimes before 32/64 bit merges)
+ struct {int val; char *name;} types[] = {{0x9026, "alpha"}, {93, "arc"},
+ {195, "arcv2"}, {40, "arm"}, {183, "arm64"}, {0x18ad, "avr32"},
+ {247, "bpf"}, {106, "blackfin"}, {140, "c6x"}, {23, "cell"}, {76, "cris"},
+ {252, "csky"}, {0x5441, "frv"}, {46, "h8300"}, {164, "hexagon"},
+ {50, "ia64"}, {88, "m32r"}, {0x9041, "m32r"}, {4, "m68k"}, {174, "metag"},
+ {189, "microblaze"}, {0xbaab, "microblaze-old"}, {8, "mips"},
+ {10, "mips-old"}, {89, "mn10300"}, {0xbeef, "mn10300-old"}, {113, "nios2"},
+ {92, "openrisc"}, {0x8472, "openrisc-old"}, {15, "parisc"}, {20, "ppc"},
+ {21, "ppc64"}, {243, "riscv"}, {22, "s390"}, {0xa390, "s390-old"},
+ {135, "score"}, {42, "sh"}, {2, "sparc"}, {18, "sparc8+"}, {43, "sparc9"},
+ {188, "tile"}, {191, "tilegx"}, {3, "386"}, {6, "486"}, {62, "x86-64"},
+ {94, "xtensa"}, {0xabc7, "xtensa-old"}
+ };
+
+ for (i = 0; i<ARRAY_LEN(types); i++) {
+ if (type==types[i].val) return types[i].name;
+ }
+ sprintf(libbuf, "unknown arch %d", type);
+ return libbuf;
+} \ No newline at end of file
diff --git a/lib/lib.h b/lib/lib.h
index 9e933401..e2914a09 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -271,6 +271,7 @@ void reset_env(struct passwd *p, int clear);
void loggit(int priority, char *format, ...);
unsigned tar_cksum(void *data);
int is_tar_header(void *pkt);
+char *elf_arch_name(int type);
#define HR_SPACE 1 // Space between number and units
#define HR_B 2 // Use "B" for single byte units
diff --git a/tests/readelf.test b/tests/readelf.test
new file mode 100755
index 00000000..9c0e9567
--- /dev/null
+++ b/tests/readelf.test
@@ -0,0 +1,202 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+elf=$FILES/elf/ndk-elf-note
+
+# toybox uses Linux kernel architecture names, so rewrite binutils with sed.
+NOSPACE=1 testing "-h" "readelf -hW $elf-full | sed s/AArch64/arm64/g" \
+"ELF Header:
+ Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
+ Class: ELF64
+ Data: 2's complement, little endian
+ Version: 1 (current)
+ OS/ABI: UNIX - System V
+ ABI Version: 0
+ Type: DYN (Shared object file)
+ Machine: arm64
+ Version: 0x1
+ Entry point address: 0x660
+ Start of program headers: 64 (bytes into file)
+ Start of section headers: 7776 (bytes into file)
+ Flags: 0x0
+ Size of this header: 64 (bytes)
+ Size of program headers: 56 (bytes)
+ Number of program headers: 9
+ Size of section headers: 64 (bytes)
+ Number of section headers: 32
+ Section header string table index: 29
+" "" ""
+
+# We format the key to flags differently and don't include obsolete ones.
+NOSPACE=1 testing "-S" "readelf -SW $elf-full | head -36" \
+"There are 32 section headers, starting at offset 0x1e60:
+
+Section Headers:
+ [Nr] Name Type Address Off Size ES Flg Lk Inf Al
+ [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
+ [ 1] .interp PROGBITS 0000000000000238 000238 000015 00 A 0 0 1
+ [ 2] .note.android.ident NOTE 0000000000000250 000250 000098 00 A 0 0 4
+ [ 3] .note.gnu.build-id NOTE 00000000000002e8 0002e8 000024 00 A 0 0 4
+ [ 4] .hash HASH 0000000000000310 000310 000048 04 A 5 0 8
+ [ 5] .dynsym DYNSYM 0000000000000358 000358 000138 18 A 6 3 8
+ [ 6] .dynstr STRTAB 0000000000000490 000490 000097 00 A 0 0 1
+ [ 7] .gnu.version VERSYM 0000000000000528 000528 00001a 02 A 5 0 2
+ [ 8] .gnu.version_r VERNEED 0000000000000548 000548 000020 00 A 6 1 8
+ [ 9] .rela.dyn RELA 0000000000000568 000568 000060 18 A 5 0 8
+ [10] .rela.plt RELA 00000000000005c8 0005c8 000048 18 AI 5 19 8
+ [11] .plt PROGBITS 0000000000000610 000610 000050 10 AX 0 0 16
+ [12] .text PROGBITS 0000000000000660 000660 00008c 00 AX 0 0 4
+ [13] .eh_frame_hdr PROGBITS 00000000000006ec 0006ec 000014 00 A 0 0 4
+ [14] .eh_frame PROGBITS 0000000000000700 000700 000030 00 A 0 0 8
+ [15] .preinit_array PREINIT_ARRAY 0000000000010d68 000d68 000010 08 WA 0 0 8
+ [16] .init_array INIT_ARRAY 0000000000010d78 000d78 000010 08 WA 0 0 8
+ [17] .fini_array FINI_ARRAY 0000000000010d88 000d88 000010 08 WA 0 0 8
+ [18] .dynamic DYNAMIC 0000000000010d98 000d98 000210 10 WA 6 0 8
+ [19] .got PROGBITS 0000000000010fa8 000fa8 000058 08 WA 0 0 8
+ [20] .bss NOBITS 0000000000011000 001000 000008 00 WA 0 0 8
+ [21] .comment PROGBITS 0000000000000000 001000 000107 01 MS 0 0 1
+ [22] .debug_pubnames PROGBITS 0000000000000000 001107 00001b 00 0 0 1
+ [23] .debug_info PROGBITS 0000000000000000 001122 00004b 00 0 0 1
+ [24] .debug_abbrev PROGBITS 0000000000000000 00116d 000037 00 0 0 1
+ [25] .debug_line PROGBITS 0000000000000000 0011a4 00003f 00 0 0 1
+ [26] .debug_str PROGBITS 0000000000000000 0011e3 000138 01 MS 0 0 1
+ [27] .debug_macinfo PROGBITS 0000000000000000 00131b 000001 00 0 0 1
+ [28] .debug_pubtypes PROGBITS 0000000000000000 00131c 00001a 00 0 0 1
+ [29] .shstrtab STRTAB 0000000000000000 001d0c 000151 00 0 0 1
+ [30] .symtab SYMTAB 0000000000000000 001338 0007e0 18 31 68 8
+ [31] .strtab STRTAB 0000000000000000 001b18 0001f4 00 0 0 1
+" "" ""
+
+NOSPACE=1 testing "-l" "readelf -lW $elf-short" "
+Elf file type is DYN (Shared object file)
+Entry point 0x1001
+There are 10 program headers, starting at offset 52
+
+Program Headers:
+ Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
+ PHDR 0x000034 0x00000034 0x00000034 0x00140 0x00140 R 0x4
+ INTERP 0x000174 0x00000174 0x00000174 0x00013 0x00013 R 0x1
+ [Requesting program interpreter: /system/bin/linker]
+ LOAD 0x000000 0x00000000 0x00000000 0x00404 0x00404 R 0x1000
+ LOAD 0x001000 0x00001000 0x00001000 0x00140 0x00140 R E 0x1000
+ LOAD 0x002000 0x00002000 0x00002000 0x00144 0x00144 RW 0x1000
+ DYNAMIC 0x002018 0x00002018 0x00002018 0x00100 0x00100 RW 0x4
+ GNU_RELRO 0x002000 0x00002000 0x00002000 0x00144 0x01000 R 0x1
+ GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0
+ NOTE 0x000188 0x00000188 0x00000188 0x00038 0x00038 R 0x4
+ EXIDX 0x0001c0 0x000001c0 0x000001c0 0x00030 0x00030 R 0x4
+
+ Section to Segment mapping:
+ Segment Sections...
+ 00
+ 01 .interp
+ 02 .interp .note.android.ident .note.gnu.build-id .ARM.exidx .dynsym .gnu.version .gnu.version_r .gnu.hash .dynstr .rel.dyn .relr.dyn .rel.plt
+ 03 .text .plt
+ 04 .preinit_array .init_array .fini_array .dynamic .got .got.plt
+ 05 .dynamic
+ 06 .preinit_array .init_array .fini_array .dynamic .got .got.plt
+ 07
+ 08 .note.android.ident .note.gnu.build-id
+ 09 .ARM.exidx
+" "" ""
+
+# binutils doesn't line up the column headers for 64-bit ELF files.
+NOSPACE=1 testing "-d" "readelf -dW $elf-full" "
+Dynamic section at offset 0xd98 contains 33 entries:
+ Tag Type Name/Value
+ 0x0000000000000001 (NEEDED) Shared library: [libc.so]
+ 0x0000000000000001 (NEEDED) Shared library: [libm.so]
+ 0x0000000000000001 (NEEDED) Shared library: [libdl.so]
+ 0x0000000000000020 (PREINIT_ARRAY) 0x10d68
+ 0x0000000000000021 (PREINIT_ARRAYSZ) 0x10
+ 0x0000000000000019 (INIT_ARRAY) 0x10d78
+ 0x000000000000001b (INIT_ARRAYSZ) 16 (bytes)
+ 0x000000000000001a (FINI_ARRAY) 0x10d88
+ 0x000000000000001c (FINI_ARRAYSZ) 16 (bytes)
+ 0x0000000000000004 (HASH) 0x310
+ 0x0000000000000005 (STRTAB) 0x490
+ 0x0000000000000006 (SYMTAB) 0x358
+ 0x000000000000000a (STRSZ) 151 (bytes)
+ 0x000000000000000b (SYMENT) 24 (bytes)
+ 0x0000000000000015 (DEBUG) 0x0
+ 0x0000000000000003 (PLTGOT) 0x10fa8
+ 0x0000000000000002 (PLTRELSZ) 72 (bytes)
+ 0x0000000000000014 (PLTREL) RELA
+ 0x0000000000000017 (JMPREL) 0x5c8
+ 0x0000000000000007 (RELA) 0x568
+ 0x0000000000000008 (RELASZ) 96 (bytes)
+ 0x0000000000000009 (RELAENT) 24 (bytes)
+ 0x000000000000001e (FLAGS) BIND_NOW
+ 0x000000006ffffffb (FLAGS_1) Flags: NOW
+ 0x000000006ffffffe (VERNEED) 0x548
+ 0x000000006fffffff (VERNEEDNUM) 1
+ 0x000000006ffffff0 (VERSYM) 0x528
+ 0x000000006ffffff9 (RELACOUNT) 4
+ 0x0000000000000000 (NULL) 0x0
+ 0x0000000000000000 (NULL) 0x0
+ 0x0000000000000000 (NULL) 0x0
+ 0x0000000000000000 (NULL) 0x0
+ 0x0000000000000000 (NULL) 0x0
+" "" ""
+
+# toybox does a better job of decoding Android's ELF notes than binutils.
+SKIP_HOST=1 testing "-n" "readelf -nW $elf-short" "
+Displaying notes found in: .note.android.ident
+ Owner Data size Description
+ Android 0x00000004 NT_VERSION API level 28
+
+Displaying notes found in: .note.gnu.build-id
+ Owner Data size Description
+ GNU 0x00000010 NT_GNU_BUILD_ID da6a5f4ca8da163b9339326e626d8a3c
+" "" ""
+
+testing "-p" "readelf -p22 $elf-short" "
+String dump of section '.shstrtab':
+ [ 1] .preinit_array
+ [ 10] .init_array
+ [ 1c] .fini_array
+ [ 28] .ARM.exidx
+ [ 33] .text
+ [ 39] .got
+ [ 3e] .note.android.ident
+ [ 52] .got.plt
+ [ 5b] .rel.plt
+ [ 64] .ARM.attributes
+ [ 74] .dynstr
+ [ 7c] .gnu.version_r
+ [ 8b] .interp
+ [ 93] .relr.dyn
+ [ 9d] .rel.dyn
+ [ a6] .gnu.version
+ [ b3] .dynsym
+ [ bb] .gnu.hash
+ [ c5] .note.gnu.build-id
+ [ d8] .dynamic
+ [ e1] .shstrtab
+ [ eb] .gnu_debugdata
+
+" "" ""
+
+testing "-x" "readelf -x22 $elf-short" "
+Hex dump of section '.shstrtab':
+ 0x00000000 002e7072 65696e69 745f6172 72617900 ..preinit_array.
+ 0x00000010 2e696e69 745f6172 72617900 2e66696e .init_array..fin
+ 0x00000020 695f6172 72617900 2e41524d 2e657869 i_array..ARM.exi
+ 0x00000030 6478002e 74657874 002e676f 74002e6e dx..text..got..n
+ 0x00000040 6f74652e 616e6472 6f69642e 6964656e ote.android.iden
+ 0x00000050 74002e67 6f742e70 6c74002e 72656c2e t..got.plt..rel.
+ 0x00000060 706c7400 2e41524d 2e617474 72696275 plt..ARM.attribu
+ 0x00000070 74657300 2e64796e 73747200 2e676e75 tes..dynstr..gnu
+ 0x00000080 2e766572 73696f6e 5f72002e 696e7465 .version_r..inte
+ 0x00000090 7270002e 72656c72 2e64796e 002e7265 rp..relr.dyn..re
+ 0x000000a0 6c2e6479 6e002e67 6e752e76 65727369 l.dyn..gnu.versi
+ 0x000000b0 6f6e002e 64796e73 796d002e 676e752e on..dynsym..gnu.
+ 0x000000c0 68617368 002e6e6f 74652e67 6e752e62 hash..note.gnu.b
+ 0x000000d0 75696c64 2d696400 2e64796e 616d6963 uild-id..dynamic
+ 0x000000e0 002e7368 73747274 6162002e 676e755f ..shstrtab..gnu_
+ 0x000000f0 64656275 67646174 61000000 000000 debugdata......
+
+" "" ""
diff --git a/toys/pending/readelf.c b/toys/pending/readelf.c
new file mode 100644
index 00000000..838e37d7
--- /dev/null
+++ b/toys/pending/readelf.c
@@ -0,0 +1,589 @@
+/* readelf.c - display information about ELF files.
+ *
+ * Copyright 2019 The Android Open Source Project
+ *
+ * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/nm.html
+
+USE_READELF(NEWTOY(readelf, "<1(dyn-syms)adhlnp:SsWx:", TOYFLAG_USR|TOYFLAG_BIN))
+
+config READELF
+ bool "readelf"
+ default y
+ help
+ usage: readelf [-adhlnSsW] [-p SECTION] [-x SECTION] [file...]
+
+ Displays information about ELF files.
+
+ -a Equivalent to -dhlnSs
+ -d Show dynamic section
+ -h Show ELF header
+ -l Show program headers
+ -n Show notes
+ -p S Dump strings found in named/numbered section
+ -S Show section headers
+ -s Show symbol tables (.dynsym and .symtab)
+ -W Don't truncate fields (default in toybox)
+ -x S Hex dump of named/numbered section
+
+ --dyn-syms Show just .dynsym symbol table
+*/
+
+#define FOR_readelf
+#include "toys.h"
+
+GLOBALS(
+ char *x, *p;
+
+ char *elf, *shstrtab, *f;
+ off_t shoff, phoff, size;
+ int bits, shnum, shentsize, phentsize;
+ int64_t (*elf_int)(void *ptr, unsigned size);
+)
+
+// Section header.
+struct sh {
+ int type, link, info;
+ long flags, addr, offset, size, addralign, entsize;
+ char *name;
+};
+
+// Program header.
+struct ph {
+ int type, flags;
+ long offset, vaddr, paddr, filesz, memsz, align;
+};
+
+static void get_sh(int i, struct sh *s)
+{
+ char *shdr = TT.elf+TT.shoff+i*TT.shentsize;
+
+ if (i >= TT.shnum || shdr > TT.elf+TT.size-TT.shentsize) {
+ error_exit("%s: bad shdr %d",TT.f,i);
+ }
+
+ s->type = TT.elf_int(shdr+4, 4);
+ s->flags = TT.elf_int(shdr+8, 4*(TT.bits+1));
+ s->addr = TT.elf_int(shdr+8+4*(TT.bits+1), 4*(TT.bits+1));
+ s->offset = TT.elf_int(shdr+8+8*(TT.bits+1), 4*(TT.bits+1));
+ s->size = TT.elf_int(shdr+8+12*(TT.bits+1), 4*(TT.bits+1));
+ s->link = TT.elf_int(shdr+8+16*(TT.bits+1), 4);
+ s->info = TT.elf_int(shdr+12+16*(TT.bits+1), 4);
+ s->addralign = TT.elf_int(shdr+16+16*(TT.bits+1), 4*(TT.bits+1));
+ s->entsize = TT.elf_int(shdr+16+20*(TT.bits+1), 4*(TT.bits+1));
+
+ if (!TT.shstrtab) s->name = "?";
+ else {
+ s->name = TT.shstrtab + TT.elf_int(shdr, 4);
+ if (s->name >= TT.elf+TT.size) error_exit("%s: bad shdr name %d",TT.f,i);
+ if (s->offset >= TT.size-s->size && s->type != 8 /*SHT_NOBITS*/)
+ error_exit("%s: bad section %d",TT.f,i);
+ }
+}
+
+static int find_section(char *spec, struct sh *s)
+{
+ char *end;
+ int i;
+
+ // Valid section number?
+ errno = 0;
+ i = strtoul(spec, &end, 0);
+ if (!errno && !*end && i < TT.shnum) {
+ get_sh(i, s);
+ return 1;
+ }
+
+ // Search the section names.
+ for (i=0; i<TT.shnum; i++) {
+ get_sh(i, s);
+ if (!strcmp(s->name, spec)) return 1;
+ }
+
+ error_msg("%s: no section '%s", TT.f, spec);
+ return 0;
+}
+
+static void get_ph(int i, struct ph *ph)
+{
+ char *phdr = TT.elf+TT.phoff+i*TT.phentsize;
+
+ if (phdr > TT.elf+TT.size-TT.phentsize) error_exit("%s: bad phdr %d",TT.f,i);
+
+ // Elf64_Phdr reordered fields.
+ ph->type = TT.elf_int(phdr, 4);
+ if (TT.bits) {
+ ph->flags = TT.elf_int(phdr+=4, 4);
+ ph->offset = TT.elf_int(phdr+=4, 8);
+ ph->vaddr = TT.elf_int(phdr+=8, 8);
+ ph->paddr = TT.elf_int(phdr+=8, 8);
+ ph->filesz = TT.elf_int(phdr+=8, 8);
+ ph->memsz = TT.elf_int(phdr+=8, 8);
+ ph->align = TT.elf_int(phdr+=8, 8);
+ } else {
+ ph->offset = TT.elf_int(phdr+=4, 4);
+ ph->vaddr = TT.elf_int(phdr+=4, 4);
+ ph->paddr = TT.elf_int(phdr+=4, 4);
+ ph->filesz = TT.elf_int(phdr+=4, 4);
+ ph->memsz = TT.elf_int(phdr+=4, 4);
+ ph->flags = TT.elf_int(phdr+=4, 4);
+ ph->align = TT.elf_int(phdr+=4, 4);
+ }
+}
+
+#define MAP(...) __VA_ARGS__
+#define DECODER(name, values) \
+ static char *name(int type) { \
+ static char unknown[20]; \
+ struct {int v; char *s;} a[] = values; \
+ int i; \
+ \
+ for (i=0; i<ARRAY_LEN(a); i++) if (type==a[i].v) return a[i].s; \
+ sprintf(unknown, "0x%x", type); \
+ return unknown; \
+ }
+
+DECODER(dt_type, MAP({{0,"x(NULL)"},{1,"N(NEEDED)"},{2,"b(PLTRELSZ)"},
+ {3,"x(PLTGOT)"},{4,"x(HASH)"},{5,"x(STRTAB)"},{6,"x(SYMTAB)"},{7,"x(RELA)"},
+ {8,"b(RELASZ)"},{9,"b(RELAENT)"},{10,"b(STRSZ)"},{11,"b(SYMENT)"},
+ {12,"x(INIT)"},{13,"x(FINI)"},{14,"S(SONAME)"},{15,"R(RPATH)"},
+ {16,"x(SYMBOLIC)"},{17,"x(REL)"},{18,"b(RELSZ)"},{19,"b(RELENT)"},
+ {20,"P(PLTREL)"},{21,"x(DEBUG)"},{22,"x(TEXTREL)"},{23,"x(JMPREL)"},
+ {24,"d(BIND_NOW)"},{25,"x(INIT_ARRAY)"},{26,"x(FINI_ARRAY)"},
+ {27,"b(INIT_ARRAYSZ)"},{28,"b(FINI_ARRAYSZ)"},{29,"R(RUNPATH)"},
+ {30,"f(FLAGS)"},{32,"x(PREINIT_ARRAY)"},{33,"x(PREINIT_ARRAYSZ)"},
+ {0x6000000f,"x(ANDROID_REL)"},{0x60000010,"b(ANDROID_RELSZ)"},
+ {0x6ffffef5,"x(GNU_HASH)"},{0x6ffffef6,"x(TLSDESC_PLT)"},
+ {0x6ffffef7,"x(TLSDESC_GOT)"},
+ {0x6ffffff0,"x(VERSYM)"},{0x6ffffff9,"d(RELACOUNT)"},
+ {0x6ffffffa,"d(RELCOUNT)"},{0x6ffffffb,"F(FLAGS_1)"},
+ {0x6ffffffc," (VERDEF)"},{0x6ffffffd,"d(VERDEFNUM)"},
+ {0x6ffffffe,"x(VERNEED)"},{0x6fffffff,"d(VERNEEDNUM)"}}))
+
+DECODER(et_type, MAP({{0,"NONE (None)"},{1,"REL (Relocatable file)"},
+ {2,"EXEC (Executable file)"},{3,"DYN (Shared object file)"},
+ {4,"CORE (Core file)"}}))
+
+DECODER(nt_type_core, MAP({{1,"NT_PRSTATUS"},{2,"NT_FPREGSET"},
+ {3,"NT_PRPSINFO"},{5,"NT_PLATFORM"},{6,"NT_AUXV"},
+ {0x46494c45,"NT_FILE"},{0x53494749,"NT_SIGINFO"}}))
+
+DECODER(nt_type_linux, MAP({{0x200,"NT_386_TLS"},{0x202, "NT_X86_XSTATE"},
+ {0x400,"NT_ARM_VFP"},{0x401,"NT_ARM_TLS"},{0x405,"NT_ARM_SVE"}}))
+
+DECODER(os_abi, MAP({{0,"UNIX - System V"}}))
+
+DECODER(ph_type, MAP({{0,"NULL"},{1,"LOAD"},{2,"DYNAMIC"},{3,"INTERP"},
+ {4,"NOTE"},{5,"SHLIB"},{6,"PHDR"},{7,"TLS"},{0x6474e550,"GNU_EH_FRAME"},
+ {0x6474e551,"GNU_STACK"},{0x6474e552,"GNU_RELRO"},{0x70000001,"EXIDX"}}))
+
+DECODER(sh_type, MAP({{0,"NULL"},{1,"PROGBITS"},{2,"SYMTAB"},{3,"STRTAB"},
+ {4,"RELA"},{5,"HASH"},{6,"DYNAMIC"},{7,"NOTE"},{8,"NOBITS"},{9,"REL"},
+ {10,"SHLIB"},{11,"DYNSYM"},{14,"INIT_ARRAY"},{15,"FINI_ARRAY"},
+ {16,"PREINIT_ARRAY"},{17,"GROUP"},{18,"SYMTAB_SHNDX"},
+ {0x6ffffff6,"GNU_HASH"},{0x6ffffffd,"VERDEF"},{0x6ffffffe,"VERNEED"},
+ {0x6fffffff,"VERSYM"},{0x70000001,"ARM_EXIDX"},
+ {0x70000003,"ARM_ATTRIBUTES"}}))
+
+DECODER(stb_type, MAP({{0,"LOCAL"},{1,"GLOBAL"},{2,"WEAK"}}))
+
+DECODER(stt_type, MAP({{0,"NOTYPE"},{1,"OBJECT"},{2,"FUNC"},{3,"SECTION"},
+ {4,"FILE"},{5,"COMMON"},{6,"TLS"},{10,"GNU_IFUNC"}}))
+
+DECODER(stv_type, MAP({{0,"DEFAULT"},{1,"INTERNAL"},{2,"HIDDEN"},
+ {3,"PROTECTED"}}))
+
+static void show_symbols(struct sh *table, struct sh *strtab)
+{
+ char *symtab = TT.elf+table->offset, *ndx;
+ int sym_size = (TT.bits ? 24 : 16), numsym = table->size/sym_size, i;
+
+ if (numsym == 0) return;
+
+ xputc('\n');
+ printf("Symbol table '%s' contains %d entries:\n"
+ " Num: %*s Size Type Bind Vis Ndx Name\n",
+ table->name, numsym, 5+8*TT.bits, "Value");
+ for (i=0; i<numsym; i++) {
+ int st_name = TT.elf_int(symtab, 4), st_value, st_shndx;
+ unsigned char st_info, st_other;
+ long st_size;
+ char *name;
+
+ // The various fields were moved around for 64-bit.
+ if (TT.bits) {
+ st_info = symtab[4];
+ st_other = symtab[5];
+ st_shndx = TT.elf_int(symtab+6, 2);
+ st_value = TT.elf_int(symtab+8, 8);
+ st_size = TT.elf_int(symtab+16, 8);
+ } else {
+ st_value = TT.elf_int(symtab+4, 4);
+ st_size = TT.elf_int(symtab+8, 4);
+ st_info = symtab[12];
+ st_other = symtab[13];
+ st_shndx = TT.elf_int(symtab+14, 2);
+ }
+
+ name = TT.elf + strtab->offset + st_name;
+ if (name >= TT.elf+TT.size) error_exit("%s: bad symbol name", TT.f);
+
+ if (!st_shndx) ndx = "UND";
+ else if (st_shndx==0xfff1) ndx = "ABS";
+ else sprintf(ndx = toybuf, "%d", st_shndx);
+
+ // TODO: look up and show any symbol versions with @ or @@.
+
+ printf("%6d: %0*x %5ld %-7s %-6s %-9s%3s %s\n", i, 8*(TT.bits+1),
+ st_value, st_size, stt_type(st_info & 0xf), stb_type(st_info >> 4),
+ stv_type(st_other & 3), ndx, name);
+ symtab += sym_size;
+ }
+}
+
+static void show_notes(long offset, long size)
+{
+ char *note = TT.elf + offset;
+
+ printf(" %-20s %10s\tDescription\n", "Owner", "Data size");
+ while (note < TT.elf+offset+size) {
+ int namesz = TT.elf_int(note, 4), descsz = TT.elf_int(note+4, 4),
+ type = TT.elf_int(note+8, 4), j = 0;
+ char *name = note+12;
+
+ printf(" %-20.*s 0x%08x\t", namesz, name, descsz);
+ if (!memcmp(name, "GNU", 4)) {
+ if (type == 1) {
+ printf("NT_GNU_ABI_TAG\tOS: %s, ABI: %ld.%ld.%ld",
+ !TT.elf_int(note+16, 4)?"Linux":"?",
+ TT.elf_int(note+20, 4), TT.elf_int(note+24, 4),
+ TT.elf_int(note+28, 4)), j=1;
+ } else if (type == 3) {
+ printf("NT_GNU_BUILD_ID\t");
+ for (;j<descsz;j++) printf("%02x",note[16+j]);
+ } else if (type == 4) {
+ printf("NT_GNU_GOLD_VERSION\t%.*s", descsz, note+16), j=1;
+ }
+ } else if (!memcmp(name, "Android", 8)) {
+ if (type == 1) {
+ printf("NT_VERSION\tAPI level %ld", TT.elf_int(note+20, 4)), j=1;
+ if (descsz>=132) printf(", NDK %.64s (%.64s)",note+24,note+24+64);
+ }
+ } else if (!memcmp(name, "CORE", 5) || !memcmp(name, "LINUX", 6)) {
+ char *desc = *name=='C' ? nt_type_core(type) : nt_type_linux(type);
+
+ if (*desc != '0') printf("%s", desc), j=1;
+ }
+
+ // If we didn't do custom output above, show a hex dump.
+ if (!j) {
+ printf("0x%x\t", type);
+ for (;j<descsz;j++) printf("%c%02x",!j?'\t':' ',note[16+j]);
+ }
+ xputc('\n');
+
+ note += 3*4 + ((namesz+3)&~3) + ((descsz+3)&~3);
+ }
+}
+
+static void scan_elf()
+{
+ struct sh dynamic = {}, dynstr = {}, dynsym = {}, shstr = {}, strtab = {},
+ symtab = {}, s;
+ struct ph ph;
+ int endian, version, elf_type, flags, entry, ehsize, phnum, shstrndx, i,j,w;
+
+ if (TT.size < 45 || memcmp(TT.elf, "\177ELF", 4)) {
+ error_msg("%s: not ELF", TT.f);
+ return;
+ }
+
+ TT.bits = TT.elf[4] - 1;
+ endian = TT.elf[5];
+ version = TT.elf[6];
+ TT.elf_int = (endian==2) ? peek_be : peek_le;
+ if (TT.bits < 0 || TT.bits > 1 || endian < 1 || endian > 2 || version != 1) {
+ error_msg("%s: bad ELF", TT.f);
+ return;
+ }
+
+ elf_type = TT.elf_int(TT.elf+16, 2);
+ entry = TT.elf_int(TT.elf+24, 4+4*TT.bits);
+ TT.phoff = TT.elf_int(TT.elf+28+4*TT.bits, 4+4*TT.bits);
+ TT.shoff = TT.elf_int(TT.elf+32+8*TT.bits, 4+4*TT.bits);
+ flags = TT.elf_int(TT.elf+36+12*TT.bits, 4);
+ ehsize = TT.elf_int(TT.elf+40+12*TT.bits, 2);
+ TT.phentsize = TT.elf_int(TT.elf+42+12*TT.bits, 2);
+ phnum = TT.elf_int(TT.elf+44+12*TT.bits, 2);
+ TT.shentsize = TT.elf_int(TT.elf+46+12*TT.bits, 2);
+ TT.shnum = TT.elf_int(TT.elf+48+12*TT.bits, 2);
+ shstrndx = TT.elf_int(TT.elf+50+12*TT.bits, 2);
+
+ // Set up the section header string table so we can use section header names.
+ // Core files have shstrndx == 0.
+ TT.shstrtab = 0;
+ if (shstrndx != 0) {
+ get_sh(shstrndx, &shstr);
+ if (shstr.type != 3 /*SHT_STRTAB*/) {
+ error_msg("%s: bad shstrndx", TT.f);
+ return;
+ }
+ TT.shstrtab = TT.elf+shstr.offset;
+ }
+
+ if (toys.optc > 1) printf("\nFile: %s\n", TT.f);
+
+ if (FLAG(h)) {
+ printf("ELF Header:\n");
+ printf(" Magic: ");
+ for (i=0; i<16; i++) printf("%02x%c", TT.elf[i], i==15?'\n':' ');
+ printf(" Class: ELF%d\n", TT.bits?64:32);
+ printf(" Data: 2's complement, %s endian\n",
+ (endian==2)?"big":"little");
+ printf(" Version: 1 (current)\n");
+ printf(" OS/ABI: %s\n", os_abi(TT.elf[7]));
+ printf(" ABI Version: %d\n", TT.elf[8]);
+ printf(" Type: %s\n", et_type(elf_type));
+ printf(" Machine: %s\n",
+ elf_arch_name(TT.elf_int(TT.elf+18, 2)));
+ printf(" Version: 0x%x\n",
+ (int) TT.elf_int(TT.elf+20, 4));
+ printf(" Entry point address: 0x%x\n", entry);
+ printf(" Start of program headers: %ld (bytes into file)\n",
+ TT.phoff);
+ printf(" Start of section headers: %ld (bytes into file)\n",
+ TT.shoff);
+ printf(" Flags: 0x%x\n", flags);
+ printf(" Size of this header: %d (bytes)\n", ehsize);
+ printf(" Size of program headers: %d (bytes)\n", TT.phentsize);
+ printf(" Number of program headers: %d\n", phnum);
+ printf(" Size of section headers: %d (bytes)\n", TT.shentsize);
+ printf(" Number of section headers: %d\n", TT.shnum);
+ printf(" Section header string table index: %d\n", shstrndx);
+ }
+
+ w = 8*(TT.bits+1);
+ if (FLAG(S)) {
+ if (!TT.shnum) printf("\nThere are no sections in this file.\n");
+ else {
+ if (!FLAG(h)) {
+ printf("There are %d section headers, starting at offset %#lx:\n",
+ TT.shnum, TT.shoff);
+ }
+ printf("\n"
+ "Section Headers:\n"
+ " [Nr] %-20s %-14s %-*s %-6s %-6s ES Flg Lk Inf Al\n",
+ "Name", "Type", w, "Address", "Off", "Size");
+ }
+ }
+ // We need to iterate through the section headers even if we're not
+ // dumping them, to find specific sections.
+ for (i=0; i<TT.shnum; i++) {
+ get_sh(i, &s);
+ if (s.type == 2 /*SHT_SYMTAB*/) symtab = s;
+ else if (s.type == 6 /*SHT_DYNAMIC*/) dynamic = s;
+ else if (s.type == 11 /*SHT_DYNSYM*/) dynsym = s;
+ else if (s.type == 3 /*SHT_STRTAB*/) {
+ if (!strcmp(s.name, ".strtab")) strtab = s;
+ else if (!strcmp(s.name, ".dynstr")) dynstr = s;
+ }
+
+ if (FLAG(S)) {
+ char sh_flags[12] = {}, *p = sh_flags;
+
+ for (j=0; j<12; j++) if (s.flags&(1<<j)) *p++="WAXxMSILOTC"[j];
+ printf(" [%2d] %-20s %-14s %0*lx %06lx %06lx %02lx %3s %2d %2d %2ld\n",
+ i, s.name, sh_type(s.type), w, s.addr, s.offset, s.size,
+ s.entsize, sh_flags, s.link, s.info, s.addralign);
+ }
+ }
+ if (FLAG(S) && TT.shnum) {
+ printf("Key:\n"
+ " (W)rite, (A)lloc, e(X)ecute, (M)erge, (S)trings, (I)nfo\n"
+ " (L)ink order, (O)S, (G)roup, (T)LS, (C)ompressed, x=unknown\n");
+ }
+
+ if (FLAG(l)) {
+ xputc('\n');
+ if (!phnum) printf("There are no program headers in this file.\n");
+ else {
+ if (!FLAG(h)) {
+ printf("Elf file type is %s\n"
+ "Entry point %#x\n"
+ "There are %d program headers, starting at offset %ld\n"
+ "\n",
+ et_type(elf_type), entry, phnum, TT.phoff);
+ }
+ printf("Program Headers:\n"
+ " %-14s %-8s %-*s %-*s %-7s %-7s Flg Align\n", "Type",
+ "Offset", w, "VirtAddr", w, "PhysAddr", "FileSiz", "MemSiz");
+ for (i=0; i<phnum; i++) {
+ get_ph(i, &ph);
+ printf(" %-14s 0x%06lx 0x%0*lx 0x%0*lx 0x%05lx 0x%05lx %c%c%c %#lx\n",
+ ph_type(ph.type), ph.offset, w, ph.vaddr, w, ph.paddr,
+ ph.filesz, ph.memsz, ph.flags&4?'R':' ', ph.flags&2?'W':' ',
+ ph.flags&1?'E':' ', ph.align);
+ if (ph.type == 3 /*PH_INTERP*/) {
+ printf(" [Requesting program interpreter: %*s]\n",
+ (int) ph.filesz-1, TT.elf+ph.offset);
+ }
+ }
+
+ printf("\n"
+ " Section to Segment mapping:\n"
+ " Segment Sections...\n");
+ for (i=0; i<phnum; i++) {
+ get_ph(i, &ph);
+ printf(" %02d ", i);
+ for (j=0; j<TT.shnum; j++) {
+ get_sh(j, &s);
+ if (s.offset >= ph.offset && s.offset+s.size <= ph.offset+ph.filesz)
+ printf(" %s", s.name);
+ }
+ xputc('\n');
+ }
+ }
+ }
+
+ // binutils ld emits a bunch of extra DT_NULL entries, so binutils readelf
+ // uses two passes here! We just tell the truth, which matches -h.
+ if (FLAG(d)) {
+ char *dyn = TT.elf+dynamic.offset, *end = dyn+dynamic.size;
+
+ xputc('\n');
+ if (!dynamic.size) printf("There is no dynamic section in this file.\n");
+ else printf("Dynamic section at offset 0x%lx contains %ld entries:\n"
+ " %-*s %-20s %s\n",
+ dynamic.offset, dynamic.size/dynamic.entsize,
+ w+2, "Tag", "Type", "Name/Value");
+ for (; dyn < end; dyn += dynamic.entsize) {
+ int es = 4*(TT.bits+1);
+ long tag = TT.elf_int(dyn, es), val = TT.elf_int(dyn+es, es);
+ char *type = dt_type(tag);
+
+ printf(" 0x%0*lx %-20s ", w, tag, *type=='0' ? type : type+1);
+ if (*type == 'd') printf("%ld\n", val);
+ else if (*type == 'b') printf("%ld (bytes)\n", val);
+ else if (*type == 's') printf("%s\n", TT.elf+dynstr.offset+val);
+ else if (*type == 'f' || *type == 'F') {
+ struct bitname { int bit; char *s; }
+ df_names[] = {{0, "ORIGIN"},{1,"SYMBOLIC"},{2,"TEXTREL"},
+ {3,"BIND_NOW"},{4,"STATIC_TLS"},{}},
+ df_1_names[]={{0,"NOW"},{1,"GLOBAL"},{2,"GROUP"},{3,"NODELETE"},
+ {5,"INITFIRST"},{27,"PIE"},{}},
+ *names = *type == 'f' ? df_names : df_1_names;
+ int mask;
+
+ if (*type == 'F') printf("Flags: ");
+ for (j=0; names[j].s; j++) {
+ if (val & (mask=(1<<names[j].bit))) {
+ printf("%s%s", names[j].s, (val &= ~mask) ? " " : "");
+ }
+ }
+ if (val) printf("0x%lx", val);
+ xputc('\n');
+ } else if (*type == 'N' || *type == 'R' || *type == 'S') {
+ printf("%s: [%s]\n", *type=='N' ? "Shared library" :
+ (*type=='R' ? "Library runpath" : "Library soname"),
+ TT.elf+dynstr.offset+val);
+ } else if (*type == 'P') {
+ type = dt_type(val);
+ j = strlen(type);
+ if (*type != '0') type += 2, j -= 3;
+ printf("%*.*s\n", j, j, type);
+ } else printf("0x%lx\n", val);
+ }
+ }
+
+ if (FLAG(dyn_syms)) show_symbols(&dynsym, &dynstr);
+ if (FLAG(s)) show_symbols(&symtab, &strtab);
+
+ if (FLAG(n)) {
+ int found = 0;
+
+ for (i=0; i<TT.shnum; i++) {
+ get_sh(i, &s);
+ if (s.type == 7 /*SHT_NOTE*/) {
+ printf("\nDisplaying notes found in: %s\n", s.name);
+ show_notes(s.offset, s.size);
+ found = 1;
+ }
+ }
+ for (i=0; !found && i<phnum; i++) {
+ get_ph(i, &ph);
+ if (ph.type == 4 /*PT_NOTE*/) {
+ printf("\n"
+ "Displaying notes found at file offset 0x%lx with length 0x%lx:\n",
+ ph.offset, ph.filesz);
+ show_notes(ph.offset, ph.filesz);
+ }
+ }
+ }
+
+ if (FLAG(x)) {
+ if (find_section(TT.x, &s)) {
+ char *p = TT.elf+s.offset;
+ long offset = 0;
+
+ printf("\nHex dump of section '%s':\n", s.name);
+ while (offset < s.size) {
+ int space = 2*16 + 16/4;
+
+ printf(" 0x%08lx ", offset);
+ for (i=0; i<16 && offset < s.size; offset++) {
+ space -= printf("%02x%s", *p++, ++i%4 ? "" : " ");
+ }
+ printf("%*s", space, "");
+ for (p-=i; i; i--, p++) putchar(*p>=' ' && *p<='~' ? *p : '.');
+ xputc('\n');
+ }
+ printf("\n");
+ }
+ }
+
+ if (FLAG(p)) {
+ if (find_section(TT.p, &s)) {
+ char *begin = TT.elf+s.offset, *end = begin + s.size, *p = begin;
+ int any = 0;
+
+ printf("\nString dump of section '%s':\n", s.name);
+ for (; p < end; p++) {
+ if (isprint(*p)) {
+ printf(" [%6tx] ", p-begin);
+ while (p < end && isprint(*p)) putchar(*p++);
+ xputc('\n');
+ any=1;
+ }
+ }
+ if (!any) printf(" No strings found in this section.\n");
+ printf("\n");
+ }
+ }
+}
+
+void readelf_main(void)
+{
+ char **arg;
+ int all = FLAG_d|FLAG_h|FLAG_l|FLAG_n|FLAG_S|FLAG_s|FLAG_dyn_syms;
+
+ if (FLAG(a)) toys.optflags |= all;
+ if (FLAG(s)) toys.optflags |= FLAG_dyn_syms;
+ if (!(toys.optflags & (all|FLAG_p|FLAG_x))) help_exit("needs a flag");
+
+ for (arg = toys.optargs; *arg; arg++) {
+ int fd = open(TT.f = *arg, O_RDONLY);
+ struct stat sb;
+
+ if (fd == -1) perror_msg("%s", TT.f);
+ else {
+ if (fstat(fd, &sb)) perror_msg("%s", TT.f);
+ else if (!sb.st_size) error_msg("%s: empty", TT.f);
+ else if (!S_ISREG(sb.st_mode)) error_msg("%s: not a regular file",TT.f);
+ else {
+ TT.elf = xmmap(NULL, TT.size=sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ scan_elf();
+ munmap(TT.elf, TT.size);
+ }
+ close(fd);
+ }
+ }
+}
diff --git a/toys/posix/file.c b/toys/posix/file.c
index 5d6eef12..f2cd3af8 100644
--- a/toys/posix/file.c
+++ b/toys/posix/file.c
@@ -36,21 +36,6 @@ static void do_elf_file(int fd)
int endian = toybuf[5], bits = toybuf[4], i, j, dynamic = 0, stripped = 1,
phentsize, phnum, shsize, shnum;
int64_t (*elf_int)(void *ptr, unsigned size);
- // Values from include/linux/elf-em.h (plus arch/*/include/asm/elf.h)
- // Names are linux/arch/ directory (sometimes before 32/64 bit merges)
- struct {int val; char *name;} type[] = {{0x9026, "alpha"}, {93, "arc"},
- {195, "arcv2"}, {40, "arm"}, {183, "arm64"}, {0x18ad, "avr32"},
- {247, "bpf"}, {106, "blackfin"}, {140, "c6x"}, {23, "cell"}, {76, "cris"},
- {252, "csky"}, {0x5441, "frv"}, {46, "h8300"}, {164, "hexagon"},
- {50, "ia64"}, {88, "m32r"}, {0x9041, "m32r"}, {4, "m68k"}, {174, "metag"},
- {189, "microblaze"}, {0xbaab, "microblaze-old"}, {8, "mips"},
- {10, "mips-old"}, {89, "mn10300"}, {0xbeef, "mn10300-old"}, {113, "nios2"},
- {92, "openrisc"}, {0x8472, "openrisc-old"}, {15, "parisc"}, {20, "ppc"},
- {21, "ppc64"}, {243, "riscv"}, {22, "s390"}, {0xa390, "s390-old"},
- {135, "score"}, {42, "sh"}, {2, "sparc"}, {18, "sparc8+"}, {43, "sparc9"},
- {188, "tile"}, {191, "tilegx"}, {3, "386"}, {6, "486"}, {62, "x86-64"},
- {94, "xtensa"}, {0xabc7, "xtensa-old"}
- };
char *map = 0;
off_t phoff, shoff;
@@ -83,11 +68,8 @@ static void do_elf_file(int fd)
endian = 0;
}
- // e_machine, ala "x86", from big table above
- j = elf_int(toybuf+18, 2);
- for (i = 0; i<ARRAY_LEN(type); i++) if (j==type[i].val) break;
- if (i<ARRAY_LEN(type)) printf("%s", type[i].name);
- else printf("(unknown arch %d)", j);
+ // "x86".
+ printf("%s", elf_arch_name(elf_int(toybuf+18, 2)));
bits--;
// If what we've seen so far doesn't seem consistent, bail.