elf_scnshndx is a elfutils extension to libelf that given a SHT_SYMTAB section returns the index to the corresponding SHT_SYMTAB_SHNDX section, if it exists. This is needed when there are more than 64K sections and there are symbols that have to refer to a section with an index larger than 64K, because the Elf Sym st_shndx field is only 16 bits.
This was implemented by adding an shndx_index field to the Elf_Scn struct which is updated when reading the section headers. This takes up space in every section and is hard to proof correct. In the case of using ELF_C_READ_MMAP the shndx_index field was only updated when the shdrs needed to be converted from file to memory order. And the two places were this function was used in readelf.c and elf-print-reloc-syms.c the wrong section was used to lookup the extended index table. There were also no tests for this functionality. Replace the elf_scnshndx implementation with a simpler lookup over all sections. This sounds inefficient, but in practice the SHT_SYMTAB_SHNDX section is the next section after the SHT_SYMTAB section. elf_scnshndx only needs to be called when there are more than SHN_LORESERVE (0xff00) sections. And normally a user would just lookup the SHT_SYMTAB and SHT_SYMTAB_SHNDX sections at the same time (which is what readelf does when showing the symbol table, as does nm, objcopy and libdwfl). Add a testfile manyfuncs.c that when compiled contains 64K symbols and sections. Make sure to use -fasynchronous-unwind-tables so there is at least one relocatable section that uses all function symbols (e.g. on arm32 where there is no .eh_frame by default). This can then be used to verify the readelf --relocs support. Add another test, test-manyfuncs that explicitly goes through the symbol table and associated extended index table and verify each function symbol matches the section name. There are For riscv there are local, notype, symbols at the start of each executable section which relocations refer to instead of the section symbol. Since all these local symbols are called ".L0" this isn't very useful, so print the section name instead. For powerpc ELFv1 all function symbols go through the .opd section. Allow this in the new test-manyfuncs test. * libelf/elf32_getshdr.c (load_shdr_wrlock): Remove handling of shndx_index. * libelf/elf_begin.c (file_read_elf): Likewise. * libelf/elf_scnshndx.c (elf_scnshndx): Rewritten. * libelf/libelf.h (elf_scnshndx): Added full documentation. * libelf/libelfP.h (struct Elf_Scn): Remove shndx_index field. (__elf_scnshndx_internal): Removed. * src/readelf.c (handle_relocs_rel): Use symscn in call to elf_scnshndx. Print section name for local start section label. (handle_relocs_rela): Likewise. * tests/Makefile.am (check_PROGRAMS): Add test-manyfuncs. (manyfuncs.o): New target. (check-local): New target, depends on manyfuncs.o. (TESTS): Add run-readelf-r-manyfuncs.sh and run-test-manyfuncs.sh. (EXTRA_DIST): Add run-readelf-r-manyfuncs.sh, run-test-manyfuncs.sh and manyfuncs.c. (test_manyfuncs_LDADD): New variable. (EXTRA_test_manyfuncs_DEPENDENCIES): New variable. (CLEANFILES): Add manyfuncs.o. * tests/elf-print-reloc-syms.c (print_reloc_symnames): Use symscn in call to elf_scnshndx. * tests/manyfuncs.c: New test file to generate 64K symbols and sections. * tests/run-readelf-r-manyfuncs.sh: New test wrapper. * tests/run-test-manyfuncs.sh: Likewise. * tests/test-manyfuncs.c: New test. Signed-off-by: Mark Wielaard <m...@klomp.org> --- libelf/elf32_getshdr.c | 14 -- libelf/elf_begin.c | 26 --- libelf/elf_scnshndx.c | 52 ++++- libelf/libelf.h | 9 +- libelf/libelfP.h | 4 - src/readelf.c | 32 ++- tests/Makefile.am | 26 ++- tests/elf-print-reloc-syms.c | 4 +- tests/manyfuncs.c | 53 +++++ tests/run-readelf-r-manyfuncs.sh | 40 ++++ tests/run-test-manyfuncs.sh | 23 +++ tests/test-manyfuncs.c | 343 +++++++++++++++++++++++++++++++ 12 files changed, 560 insertions(+), 66 deletions(-) create mode 100644 tests/manyfuncs.c create mode 100755 tests/run-readelf-r-manyfuncs.sh create mode 100755 tests/run-test-manyfuncs.sh create mode 100644 tests/test-manyfuncs.c diff --git a/libelf/elf32_getshdr.c b/libelf/elf32_getshdr.c index 19b690a8e87b..e4bebe18f225 100644 --- a/libelf/elf32_getshdr.c +++ b/libelf/elf32_getshdr.c @@ -146,20 +146,6 @@ load_shdr_wrlock (Elf_Scn *scn) CONVERT_TO (shdr[cnt].sh_addralign, notcvt[cnt].sh_addralign); CONVERT_TO (shdr[cnt].sh_entsize, notcvt[cnt].sh_entsize); - - /* If this is a section with an extended index add a - reference in the section which uses the extended - index. */ - if (shdr[cnt].sh_type == SHT_SYMTAB_SHNDX - && shdr[cnt].sh_link < shnum) - elf->state.ELFW(elf,LIBELFBITS).scns.data[shdr[cnt].sh_link].shndx_index - = cnt; - - /* Set the own shndx_index field in case it has not yet - been set. */ - if (elf->state.ELFW(elf,LIBELFBITS).scns.data[cnt].shndx_index == 0) - elf->state.ELFW(elf,LIBELFBITS).scns.data[cnt].shndx_index - = -1; } if (copy) diff --git a/libelf/elf_begin.c b/libelf/elf_begin.c index 2b3b465f63ec..3ed1f8d78cc2 100644 --- a/libelf/elf_begin.c +++ b/libelf/elf_begin.c @@ -412,19 +412,6 @@ file_read_elf (int fildes, void *map_address, unsigned char *e_ident, ((char *) map_address + offset + elf->state.elf32.shdr[cnt].sh_offset); elf->state.elf32.scns.data[cnt].list = &elf->state.elf32.scns; - - /* If this is a section with an extended index add a - reference in the section which uses the extended - index. */ - if (elf->state.elf32.shdr[cnt].sh_type == SHT_SYMTAB_SHNDX - && elf->state.elf32.shdr[cnt].sh_link < scncnt) - elf->state.elf32.scns.data[elf->state.elf32.shdr[cnt].sh_link].shndx_index - = cnt; - - /* Set the own shndx_index field in case it has not yet - been set. */ - if (elf->state.elf32.scns.data[cnt].shndx_index == 0) - elf->state.elf32.scns.data[cnt].shndx_index = -1; } } else @@ -510,19 +497,6 @@ file_read_elf (int fildes, void *map_address, unsigned char *e_ident, ((char *) map_address + offset + elf->state.elf64.shdr[cnt].sh_offset); elf->state.elf64.scns.data[cnt].list = &elf->state.elf64.scns; - - /* If this is a section with an extended index add a - reference in the section which uses the extended - index. */ - if (elf->state.elf64.shdr[cnt].sh_type == SHT_SYMTAB_SHNDX - && elf->state.elf64.shdr[cnt].sh_link < scncnt) - elf->state.elf64.scns.data[elf->state.elf64.shdr[cnt].sh_link].shndx_index - = cnt; - - /* Set the own shndx_index field in case it has not yet - been set. */ - if (elf->state.elf64.scns.data[cnt].shndx_index == 0) - elf->state.elf64.scns.data[cnt].shndx_index = -1; } } else diff --git a/libelf/elf_scnshndx.c b/libelf/elf_scnshndx.c index 5b783faa3c97..61662b9a9628 100644 --- a/libelf/elf_scnshndx.c +++ b/libelf/elf_scnshndx.c @@ -1,5 +1,6 @@ /* Get the section index of the extended section index table. Copyright (C) 2007 Red Hat, Inc. + Copyright (C) 2025 Mark J. Wielaard <m...@klomp.org> This file is part of elfutils. Contributed by Ulrich Drepper <drep...@redhat.com>, 2007. @@ -37,14 +38,53 @@ int elf_scnshndx (Elf_Scn *scn) { - if (unlikely (scn->shndx_index == 0)) + size_t scnndx; + GElf_Shdr shdr_mem; + GElf_Shdr *shdr; + Elf *elf; + Elf_Scn *nscn; + + if (scn == NULL) + return -1; + + scnndx = scn->index; + elf = scn->elf; + + shdr = gelf_getshdr (scn, &shdr_mem); + if (shdr == NULL) + return -1; + + /* Only SYMTAB sections can have a SHNDX section. */ + if (shdr->sh_type != SHT_SYMTAB) + return 0; + + /* By convention the SHT_SYMTAB_SHNDX section is right after the the + SHT_SYMTAB section, so start there. */ + nscn = scn; + while ((nscn = elf_nextscn (elf, nscn)) != NULL) { - /* We do not have the value yet. We get it as a side effect of - getting a section header. */ - GElf_Shdr shdr_mem; - (void) INTUSE(gelf_getshdr) (scn, &shdr_mem); + shdr = gelf_getshdr (nscn, &shdr_mem); + if (shdr == NULL) + return -1; + + if (shdr->sh_type == SHT_SYMTAB_SHNDX && shdr->sh_link == scnndx) + return nscn->index; + } + + /* OK, not found, start from the top. */ + nscn = NULL; + while ((nscn = elf_nextscn (elf, nscn)) != NULL + && nscn->index != scnndx) + { + shdr = gelf_getshdr (nscn, &shdr_mem); + if (shdr == NULL) + return -1; + + if (shdr->sh_type == SHT_SYMTAB_SHNDX && shdr->sh_link == scnndx) + return nscn->index; } - return scn->shndx_index; + /* No shndx found, but no errors. */ + return 0; } INTDEF(elf_scnshndx) diff --git a/libelf/libelf.h b/libelf/libelf.h index d3f057b49b89..7bf88bb250f4 100644 --- a/libelf/libelf.h +++ b/libelf/libelf.h @@ -315,7 +315,14 @@ extern Elf_Scn *elf_nextscn (Elf *__elf, Elf_Scn *__scn); extern Elf_Scn *elf_newscn (Elf *__elf); /* Get the section index of the extended section index table for the - given symbol table. */ + given symbol table. Returns -1 when the given Elf_Scn is NULL or + if an error occurred during lookup, elf_errno will be set. Returns + 0 if the given Elf_Scn isn't a symbol table (sh_type is not + SHT_SYMTAB) or no extended section index table could be + found. Otherwise the section index of the extended section index + table for the given Elf_Scn is returned. An extended index table + has a sh_type of SHT_SYMTAB_SHNDX and a sh_link equal to the given + symbol table section index. */ extern int elf_scnshndx (Elf_Scn *__scn); /* Get the number of sections in the ELF file. If the file uses more diff --git a/libelf/libelfP.h b/libelf/libelfP.h index a9213ab3ce3a..66e7e4dd846c 100644 --- a/libelf/libelfP.h +++ b/libelf/libelfP.h @@ -218,9 +218,6 @@ struct Elf_Scn int data_read; /* Nonzero if the section was created by the user or if the data from the file/memory is read. */ - int shndx_index; /* Index of the extended section index - table for this symbol table (if this - section is a symbol table). */ size_t index; /* Index of this section. */ struct Elf *elf; /* The underlying ELF file. */ @@ -524,7 +521,6 @@ extern Elf_Scn *__elf_getscn_internal (Elf *__elf, size_t __index) attribute_hidden; extern Elf_Scn *__elf_nextscn_internal (Elf *__elf, Elf_Scn *__scn) attribute_hidden; -extern int __elf_scnshndx_internal (Elf_Scn *__scn) attribute_hidden; extern Elf_Data *__elf_getdata_internal (Elf_Scn *__scn, Elf_Data *__data) attribute_hidden; extern Elf_Data *__elf_getdata_rdlock (Elf_Scn *__scn, Elf_Data *__data) diff --git a/src/readelf.c b/src/readelf.c index da37921602f1..2f85a2a03eb1 100644 --- a/src/readelf.c +++ b/src/readelf.c @@ -1,6 +1,6 @@ /* Print information from ELF file in human-readable form. Copyright (C) 1999-2018 Red Hat, Inc. - Copyright (C) 2023 Mark J. Wielaard <m...@klomp.org> + Copyright (C) 2023, 2025 Mark J. Wielaard <m...@klomp.org> This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -2111,9 +2111,10 @@ handle_relocs_rel (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn, GElf_Shdr *shdr) return; } - /* Search for the optional extended section index table. */ + /* Search for the optional extended section index table if there are + more than 64k sections. */ Elf_Data *xndxdata = NULL; - int xndxscnidx = elf_scnshndx (scn); + int xndxscnidx = shnum >= SHN_LORESERVE ? elf_scnshndx (symscn) : 0; if (unlikely (xndxscnidx > 0)) xndxdata = elf_getdata (elf_getscn (ebl->elf, xndxscnidx), NULL); @@ -2218,7 +2219,11 @@ handle_relocs_rel (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn, GElf_Shdr *shdr) _("INVALID SYMBOL"), (long int) GELF_R_SYM (rel->r_info)); } - else if (GELF_ST_TYPE (sym->st_info) != STT_SECTION) + else if (GELF_ST_TYPE (sym->st_info) != STT_SECTION + && !(GELF_ST_TYPE (sym->st_info) == STT_NOTYPE + && GELF_ST_BIND (sym->st_info) == STB_LOCAL + && sym->st_shndx != SHN_UNDEF + && sym->st_value == 0)) // local start section label printf (" %#0*" PRIx64 " %-20s %#0*" PRIx64 " %s\n", class == ELFCLASS32 ? 10 : 18, rel->r_offset, likely (ebl_reloc_type_check (ebl, @@ -2232,7 +2237,9 @@ handle_relocs_rel (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn, GElf_Shdr *shdr) elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name)); else { - /* This is a relocation against a STT_SECTION symbol. */ + /* This is a relocation against a STT_SECTION symbol + or a local start section label for which we print + section name. */ GElf_Shdr secshdr_mem; GElf_Shdr *secshdr; secshdr = gelf_getshdr (elf_getscn (ebl->elf, @@ -2300,9 +2307,10 @@ handle_relocs_rela (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn, GElf_Shdr *shdr) return; } - /* Search for the optional extended section index table. */ + /* Search for the optional extended section index table if there are + more than 64k sections. */ Elf_Data *xndxdata = NULL; - int xndxscnidx = elf_scnshndx (scn); + int xndxscnidx = shnum >= SHN_LORESERVE ? elf_scnshndx (symscn) : 0; if (unlikely (xndxscnidx > 0)) xndxdata = elf_getdata (elf_getscn (ebl->elf, xndxscnidx), NULL); @@ -2409,7 +2417,11 @@ handle_relocs_rela (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn, GElf_Shdr *shdr) _("INVALID SYMBOL"), (long int) GELF_R_SYM (rel->r_info)); } - else if (GELF_ST_TYPE (sym->st_info) != STT_SECTION) + else if (GELF_ST_TYPE (sym->st_info) != STT_SECTION + && !(GELF_ST_TYPE (sym->st_info) == STT_NOTYPE + && GELF_ST_BIND (sym->st_info) == STB_LOCAL + && sym->st_shndx != SHN_UNDEF + && sym->st_value == 0)) // local start section label printf ("\ %#0*" PRIx64 " %-15s %#0*" PRIx64 " %+6" PRId64 " %s\n", class == ELFCLASS32 ? 10 : 18, rel->r_offset, @@ -2425,7 +2437,9 @@ handle_relocs_rela (Ebl *ebl, GElf_Ehdr *ehdr, Elf_Scn *scn, GElf_Shdr *shdr) elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name)); else { - /* This is a relocation against a STT_SECTION symbol. */ + /* This is a relocation against a STT_SECTION symbol + or a local start section label for which we print + section name. */ GElf_Shdr secshdr_mem; GElf_Shdr *secshdr; secshdr = gelf_getshdr (elf_getscn (ebl->elf, diff --git a/tests/Makefile.am b/tests/Makefile.am index ec6cc9011a63..335f192583da 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -64,7 +64,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ getphdrnum leb128 read_unaligned \ msg_tst system-elf-libelf-test system-elf-gelf-test \ nvidia_extended_linemap_libdw elf-print-reloc-syms \ - cu-dwp-section-info declfiles \ + cu-dwp-section-info declfiles test-manyfuncs \ $(asm_TESTS) asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \ @@ -82,6 +82,16 @@ backtrace-child-biarch$(EXEEXT): backtrace-child.c $(AM_LDFLAGS) $(LDFLAGS) $(backtrace_child_LDFLAGS) \ -o $@ $< +# A test object with 64K sections and function symbols +# Don't try to optimize or add debuginfo +# Do explicitly add unwind tables, so there are definitely relocations +# to all function symbols/sections. +manyfuncs.o: manyfuncs.c + $(AM_V_CC)$(CC) -fasynchronous-unwind-tables -g0 -O0 -o $@ -c $< + +# We also want the manyfuncs.o (test) file generated +check-local: manyfuncs.o + if GCOV GCOV_FLAGS=-fprofile-arcs -ftest-coverage else @@ -142,6 +152,7 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \ run-readelf-aranges.sh run-readelf-line.sh run-readelf-z.sh \ run-readelf-frames.sh \ run-readelf-n.sh \ + run-readelf-r-manyfuncs.sh \ run-retain.sh \ run-native-test.sh run-bug1-test.sh \ run-debuglink.sh run-debugaltlink.sh run-buildid.sh \ @@ -217,7 +228,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \ run-readelf-dw-form-indirect.sh run-strip-largealign.sh \ run-readelf-Dd.sh run-dwfl-core-noncontig.sh run-cu-dwp-section-info.sh \ run-declfiles.sh \ - run-sysroot.sh + run-sysroot.sh \ + run-test-manyfuncs.sh if !BIARCH export ELFUTILS_DISABLE_BIARCH = 1 @@ -414,6 +426,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ testfile_gnu_props.32be.o.bz2 \ testfile_gnu_props.64be.o.bz2 \ testfile-gnu-property-note-aarch64.bz2 \ + run-readelf-r-manyfuncs.sh \ run-retain.sh testfile-retain.o.bz2 \ run-allfcts-multi.sh \ test-offset-loop.bz2 test-offset-loop.alt.bz2 \ @@ -688,7 +701,9 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ testfile-dwp-4-cu-index-overflow.dwp.bz2 \ testfile-dwp-cu-index-overflow.source \ testfile-define-file.bz2 \ - testfile-sysroot.tar.bz2 run-sysroot.sh run-debuginfod-seekable.sh + testfile-sysroot.tar.bz2 run-sysroot.sh \ + run-test-manyfuncs.sh manyfuncs.c \ + run-debuginfod-seekable.sh if USE_VALGRIND @@ -869,6 +884,9 @@ nvidia_extended_linemap_libdw_LDADD = $(libelf) $(libdw) elf_print_reloc_syms_LDADD = $(libelf) cu_dwp_section_info_LDADD = $(libdw) declfiles_LDADD = $(libdw) +test_manyfuncs_LDADD = $(libelf) +# Make sure the manyfunc.o test object is generated before test-manyfuncs +EXTRA_test_manyfuncs_DEPENDENCIES = manyfuncs.o # We want to test the libelf headers against the system elf.h header. # Don't include any -I CPPFLAGS. Except when we install our own elf.h. @@ -888,7 +906,7 @@ system_elf_gelf_test_LDADD = $(libelf) # A lock file used to make sure only one test dumps core at a time MOSTLYCLEANFILES = core-dump-backtrace.lock -CLEANFILES = $(BUILT_SOURCES) +CLEANFILES = $(BUILT_SOURCES) manyfuncs.o if GCOV check: check-am coverage diff --git a/tests/elf-print-reloc-syms.c b/tests/elf-print-reloc-syms.c index b9453f6b7353..c3a8b0732699 100644 --- a/tests/elf-print-reloc-syms.c +++ b/tests/elf-print-reloc-syms.c @@ -43,8 +43,8 @@ print_reloc_symnames (Elf *elf, Elf_Scn *scn, GElf_Shdr *shdr, size_t sh_entsize /* Search for the optional extended section index table. */ Elf_Data *xndxdata = NULL; - int xndxscnidx = elf_scnshndx (scn); - if (xndxscnidx) + int xndxscnidx = elf_scnshndx (symscn); + if (xndxscnidx > 0) xndxdata = elf_getdata (elf_getscn (elf, xndxscnidx), NULL); /* Get the section header string table index. */ diff --git a/tests/manyfuncs.c b/tests/manyfuncs.c new file mode 100644 index 000000000000..f76d2690c7b2 --- /dev/null +++ b/tests/manyfuncs.c @@ -0,0 +1,53 @@ +/* Test file for a binary with 64K+ sections and symbols. + Copyright (C) 2025 Mark J. Wielaard <m...@klomp.org> + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + + +/* We need a quoted string starting with a dot (the section name). */ +#define DQ(x) #x +#define DOTQUOTE(x) DQ(.x) + +/* We want 2 * (8 ^ 5) = 2 * ((2 ^ 3) ^ 5) = 2 ^ 16 = 64K functions. */ +#define f(x) __attribute__ ((section (DOTQUOTE(x)))) void x(void) {}; +#define A(x) f(x##0) f(x##1) f(x##2) f(x##3) f(x##4) f(x##5) f(x##6) f(x##7) +#define B(x) A(x##0) A(x##1) A(x##2) A(x##3) A(x##4) A(x##5) A(x##6) A(x##7) +#define C(x) B(x##0) B(x##1) B(x##2) B(x##3) B(x##4) B(x##5) B(x##6) B(x##7) +#define D(x) C(x##0) C(x##1) C(x##2) C(x##3) C(x##4) C(x##5) C(x##6) C(x##7) +#define E(x) D(x##0) D(x##1) D(x##2) D(x##3) D(x##4) D(x##5) D(x##6) D(x##7) +E(y) +E(z) + +#undef f +#undef A +#undef B +#undef C +#undef D +#undef E + +/* Call all functions from main. */ +#define f(x) x(); +#define A(x) f(x##0) f(x##1) f(x##2) f(x##3) f(x##4) f(x##5) f(x##6) f(x##7) +#define B(x) A(x##0) A(x##1) A(x##2) A(x##3) A(x##4) A(x##5) A(x##6) A(x##7) +#define C(x) B(x##0) B(x##1) B(x##2) B(x##3) B(x##4) B(x##5) B(x##6) B(x##7) +#define D(x) C(x##0) C(x##1) C(x##2) C(x##3) C(x##4) C(x##5) C(x##6) C(x##7) +#define E(x) D(x##0) D(x##1) D(x##2) D(x##3) D(x##4) D(x##5) D(x##6) D(x##7) + +int +main () +{ +E(y) +E(z) +} diff --git a/tests/run-readelf-r-manyfuncs.sh b/tests/run-readelf-r-manyfuncs.sh new file mode 100755 index 000000000000..a7bb0478bb7f --- /dev/null +++ b/tests/run-readelf-r-manyfuncs.sh @@ -0,0 +1,40 @@ +#! /bin/sh +# Test readelf -r on file containing 64K+ functions and sections +# Copyright (C) 2025 Mark J. Wielaard <m...@klomp.org> +# This file is part of elfutils. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# elfutils is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +. $srcdir/test-subr.sh + +# Tests readelf -r on file containing 64K+ functions and sections +# This forces the use of elf_scnshndx to lookup section indexes +# The test makes sure that for relocations against sectio symbols +# the section names are resolved even for section indexes > 64K. +# +# Note the sections are named after the symbols (with a '.' in front). + +testrun ${abs_top_builddir}/src/readelf -r \ + ${abs_top_builddir}/tests/manyfuncs.o | grep -F ".y00000" || exit 1 + +testrun ${abs_top_builddir}/src/readelf -r \ + ${abs_top_builddir}/tests/manyfuncs.o | grep -F ".z00000" || exit 1 + +testrun ${abs_top_builddir}/src/readelf -r \ + ${abs_top_builddir}/tests/manyfuncs.o | grep -F ".y77777" || exit 1 + +testrun ${abs_top_builddir}/src/readelf -r \ + ${abs_top_builddir}/tests/manyfuncs.o | grep -F ".z77777" || exit 1 + +exit 0 diff --git a/tests/run-test-manyfuncs.sh b/tests/run-test-manyfuncs.sh new file mode 100755 index 000000000000..110c599c4116 --- /dev/null +++ b/tests/run-test-manyfuncs.sh @@ -0,0 +1,23 @@ +#! /bin/sh +# Copyright (C) 2025 Mark J. Wielaard <m...@klomp.org> +# This file is part of elfutils. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# elfutils is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +. $srcdir/test-subr.sh + +testrun ${abs_top_builddir}/tests/test-manyfuncs \ + ${abs_top_builddir}/tests/manyfuncs.o + +exit 0 diff --git a/tests/test-manyfuncs.c b/tests/test-manyfuncs.c new file mode 100644 index 000000000000..725690203ff1 --- /dev/null +++ b/tests/test-manyfuncs.c @@ -0,0 +1,343 @@ +/* Test program for reading file with 64k+ sections and symbols. + Copyright (C) 2025 Mark J. Wielaard <m...@klomp.org> + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "system.h" + +#include ELFUTILS_HEADER(elf) +#include <gelf.h> + +/* Various fields (Elf_Half) can only contain up to 64k values. So + they need to be accessed through libelf functions which know where + the "true" value can be found. Also to get the section associated + with a symbol will need an extended symbol table entry. Use + elf_scnshndx to get at it and check we can get both symbol and + associated section names. */ + +static void +check_elf (const char *fname, bool use_mmap) +{ + printf ("\nfname (use_mmap: %d): %s\n", use_mmap, fname); + + int fd = open (fname, O_RDONLY); + if (fd == -1) + { + printf ("cannot open `%s': %s\n", fname, strerror (errno)); + exit (1); + } + + Elf *elf = elf_begin (fd, use_mmap ? ELF_C_READ_MMAP : ELF_C_READ, NULL); + if (elf == NULL) + { + printf ("cannot create ELF descriptor: %s\n", elf_errmsg (-1)); + exit (1); + } + + /* How many sections are there? */ + size_t shnum; + if (elf_getshdrnum (elf, &shnum) < 0) + { + printf ("elf_getshdrstrndx: %s\n", elf_errmsg (-1)); + exit (1); + } + + if (shnum < SHN_LORESERVE) + { + printf ("Not enough section for test, %zd < %d\n", + shnum, SHN_LORESERVE); + exit (1); + } + + printf ("shnum: %zd\n", shnum); + + /* Get the section that contains the section header names. Check it + can be found and it contains the substring 'str' in its own + name. */ + size_t shstrndx; + if (elf_getshdrstrndx (elf, &shstrndx) < 0) + { + printf ("elf_getshdrstrndx: %s\n", elf_errmsg (-1)); + exit (1); + } + + Elf_Scn *scn = elf_getscn (elf, shstrndx); + if (scn == NULL) + { + printf ("cannot get section at index %zd: %s\n", shstrndx, + elf_errmsg (-1)); + exit (1); + } + + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + if (shdr == NULL) + { + printf ("cannot get header for shstrndx section: %s\n", + elf_errmsg (-1)); + exit (1); + } + + /* Assume the section name contains at least the substring "str". */ + const char *sname = elf_strptr (elf, shstrndx, shdr->sh_name); + if (sname == NULL || strstr (sname, "str") == NULL) + { + printf ("Bad section name: %s\n", sname); + exit (1); + } + + printf ("shstrndx section name: %s\n", sname); + + /* The shstrndx section isn't a SYMTAB section, so elf_scnshndx will + return zero. */ + int shndx = elf_scnshndx (scn); + if (shndx < 0) + { + printf ("elf_scnshndx error for shstrndx section: %s\n", + elf_errmsg (-1)); + exit (1); + } + if (shndx > 0) + { + printf ("elf_scnshndx not zero for shstrndx section: %d\n", shndx); + exit (1); + } + + /* Find the symtab and symtab_shndx by hand. */ + size_t symtabndx = 0; + size_t symtabstrndx = 0; + size_t symtabsndx = 0; + size_t scns = 1; /* scn zero is skipped. */ + Elf_Scn *symtabscn = NULL; + Elf_Scn *xndxscn = NULL; + scn = NULL; + while ((scn = elf_nextscn (elf, scn)) != NULL) + { + if (scns != elf_ndxscn (scn)) + { + printf ("Unexpected elf_ndxscn %zd != %zd\n", + scns, elf_ndxscn (scn)); + exit (1); + } + + shdr = gelf_getshdr (scn, &shdr_mem); + if (shdr == NULL) + { + printf ("couldn't get shdr\n"); + exit (1); + } + + if (shdr->sh_type == SHT_SYMTAB) + { + symtabndx = elf_ndxscn (scn); + symtabstrndx = shdr->sh_link; + symtabscn = scn; + } + + if (shdr->sh_type == SHT_SYMTAB_SHNDX) + { + symtabsndx = elf_ndxscn (scn); + xndxscn = scn; + } + + /* We could break if we have both symtabndx and symtabsndx, but + we want to run through all sections to see if we get them + all and sanity check shnum. */ + scns++; + } + + printf ("scns: %zd\n", scns); + if (scns != shnum) + { + printf ("scns (%zd) != shnum (%zd)\n", scns, shnum); + exit (1); + } + + printf ("symtabndx: %zd\n", symtabndx); + if (symtabndx == 0 || symtabscn == NULL) + { + printf ("No SYMTAB\n"); + exit (1); + } + + printf ("symtabsndx: %zd\n", symtabsndx); + if (symtabsndx == 0) + { + printf ("No SYMTAB_SNDX\n"); + exit (1); + } + + int scnshndx = elf_scnshndx (symtabscn); + printf ("scnshndx: %d\n", scnshndx); + if (scnshndx < 0) + { + printf ("elf_scnshndx failed: %s\n", elf_errmsg (-1)); + exit (1); + } + + if (scnshndx == 0) + { + printf ("elf_scnshndx couldn't find scnshndx, returned zero\n"); + exit (1); + } + + if ((size_t) scnshndx != symtabsndx) + { + printf ("elf_scnshndx found wrong scnshndx (%d)," + " should have been (%zd)\n", scnshndx, symtabsndx); + exit (1); + } + + Elf_Data *symdata = elf_getdata (symtabscn, NULL); + if (symdata == NULL) + { + printf ("Couldn't elf_getdata for symtabscn: %s\n", elf_errmsg (-1)); + exit (1); + } + + size_t nsyms = symdata->d_size / (gelf_getclass (elf) == ELFCLASS32 + ? sizeof (Elf32_Sym) + : sizeof (Elf64_Sym)); + + Elf_Data *xndxdata = elf_getdata (xndxscn, NULL); + if (xndxdata == NULL) + { + printf ("Couldn't elf_getdata for xndxscn: %s\n", elf_errmsg (-1)); + exit (1); + } + + /* Now for every [yz]ddddd symbol , check that it matches the + section name (minus the starting dot). */ + size_t yzsymcnt = 0; + for (size_t cnt = 0; cnt < nsyms; ++cnt) + { + const char *sym_name; + const char *section_name; + GElf_Word xndx; + GElf_Sym sym_mem; + GElf_Sym *sym = gelf_getsymshndx (symdata, xndxdata, cnt, + &sym_mem, &xndx); + + if (sym == NULL) + { + printf ("gelf_getsymshndx failed: %s\n", elf_errmsg (-1)); + exit (1); + } + + if (sym->st_shndx != SHN_XINDEX) + xndx = sym->st_shndx; + + sym_name = elf_strptr (elf, symtabstrndx, sym->st_name); + if (sym_name == NULL) + { + printf ("elf_strptr returned NULL for sym %zd: %s\n", + cnt, elf_errmsg (-1)); + exit (1); + } + + scn = elf_getscn (elf, xndx); + if (scn == NULL) + { + printf ("cannot get section at index %d: %s\n", xndx, + elf_errmsg (-1)); + exit (1); + } + + shdr = gelf_getshdr (scn, &shdr_mem); + if (shdr == NULL) + { + printf ("cannot get header for sym xndx section: %s\n", + elf_errmsg (-1)); + exit (1); + } + + section_name = elf_strptr (elf, shstrndx, shdr->sh_name); + if (section_name == NULL) + { + printf ("elf_strptr returned NULL for section: %s\n", + elf_errmsg (-1)); + exit (1); + } + + if (GELF_ST_TYPE (sym->st_info) == STT_FUNC + && (sym_name[0] == 'y' || sym_name[0] == 'z') + && strlen (sym_name) == 6) + { + yzsymcnt++; + /* Every [yz]ddddd symbol comes from a section named after + the symbol prefixed with '.'. Except for powerpc ELFv1, + where all symbols point into the .opd. */ + if ((section_name[0] != '.' + || strcmp (sym_name, §ion_name[1]) != 0) + && strcmp (section_name, ".opd") != 0) + { + printf ("BAD SYM/SECTION %zd sym: %s, section: %s\n", + cnt, sym_name, section_name); + exit (1); + } + } + } + +#define YZSYMCNT (size_t) (2 * 8 * 8 * 8 * 8 * 8) + printf ("yzsymcnt: %zd\n", yzsymcnt); + if (yzsymcnt != YZSYMCNT) + { + printf ("Wrong number of yzsymcnts: %zd, should be %zd\n", + yzsymcnt, YZSYMCNT); + exit (1); + } + + if (elf_end (elf) != 0) + { + printf ("failure in elf_end: %s\n", elf_errmsg (-1)); + exit (1); + } + + close (fd); +} + +int +main (int argc, char *argv[] __attribute__ ((unused))) +{ + elf_version (EV_CURRENT); + + if (argc < 2) + { + printf ("Need at least one (file name) argument\n"); + exit (1); + } + + /* Test all file using ELF_C_READ and ELF_C_READ_MMAP. */ + for (int i = 1; i < argc; i++) + { + check_elf (argv[i], false); + check_elf (argv[i], true); + } + + return 0; +} -- 2.48.1