[PATCH] debugedit: Support files with more than 65280 ELF sections

Mark Wielaard mark@klomp.org
Mon Mar 9 17:38:41 GMT 2026


debugedit was using e_shnum, e_phnum and e_shstrndx directly from the
Elf header. Unfortunately these are too small to contain large
values. Use elf_getshdrnum and elf_getshdrstrndx and store results in
DSO. Also use gelf_getsymshndx instead of gelf_getsym to get the
proper section index for relocation symbols.

This also works around a crash in libelf when trying to update the Elf
when section zero hasn't been loaded yet.

Include a wrapper with an alternative implemention for elf_scnshndx,
which was broken before elfutils 0.193.

Add testcase that generates approx 65548 sections plus debuginfo and
check debugedit can rewrite the file paths.

	* configure.ac: Add AC_PROG_CXX.
	* tools/debugedit.c (DSO): Add shnum and shstrndx.
	(scnshndx): New static function wrapper for elf_scnshndx.
	(setup_relbuf): Get xndxdata if shnum >= SHN_LORESERVE.
	Call gelf_getsymshndx.
	(edit_dwarf2): Use DSO shnum and shstrndx.
	(fdopen_dso): Set shnum and shstrndx.
	(handle_build_id): Use DSO phnum.
	(main): Use DSO shnum and shstrndx.
	* tests/data/SOURCES/large.c: New test source file.
	* tests/Makefile.am (EXTRA_DIST): Add large.c.
	* tests/atlocal.in: Set CXX and CXXFLAGS.
	* tests/debugedit.at (DEBUGEDIT_SETUP_LARGE): New m4 define.
	Add debugedit large file test.

https://sourceware.org/bugzilla/show_bug.cgi?id=33819

Signed-off-by: Mark Wielaard <mark@klomp.org>
---
 configure.ac               |   1 +
 tests/Makefile.am          |   3 +-
 tests/atlocal.in           |   2 +
 tests/data/SOURCES/large.c |  29 ++++++++++
 tests/debugedit.at         |  37 ++++++++++++
 tools/debugedit.c          | 116 ++++++++++++++++++++++++++++++-------
 6 files changed, 167 insertions(+), 21 deletions(-)
 create mode 100644 tests/data/SOURCES/large.c

diff --git a/configure.ac b/configure.ac
index a0cbb5445803..7ab3d3c9c558 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,6 +42,7 @@ AC_USE_SYSTEM_EXTENSIONS
 AC_PROG_AWK
 AC_PROG_SED
 m4_version_prereq([2.70], [AC_PROG_CC], [AC_PROG_CC_C99])
+AC_PROG_CXX
 AC_PROG_LN_S
 AC_CHECK_TOOL([LD], [ld])
 AC_CHECK_TOOL([READELF], [readelf])
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e17678dcc40d..331864ffae21 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -41,7 +41,8 @@ EXTRA_DIST += data/SOURCES/foo.c \
 	      data/SOURCES/bar.c \
 	      data/SOURCES/baz.c \
 	      data/SOURCES/dupe.c \
-	      data/SOURCES/foobar.h
+	      data/SOURCES/foobar.h \
+	      data/SOURCES/large.c
 
 TESTSUITE = $(srcdir)/testsuite
 
diff --git a/tests/atlocal.in b/tests/atlocal.in
index 5b1b367c1c23..8e8c3df74b05 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -6,7 +6,9 @@ PATH=@abs_builddir@:@abs_top_builddir@:$top_srcdir:$srcdir:$PATH
 # We do use the CC and LD found by configure, but explicitly keep
 # CFLAGS and LDFLAGS empty because the tests use specific flags.
 CC="@CC@"
+CXX="@CXX@"
 CFLAGS=""
+CXXFLAGS=""
 LD="@LD@"
 AR="@AR@"
 LDFLAGS=""
diff --git a/tests/data/SOURCES/large.c b/tests/data/SOURCES/large.c
new file mode 100644
index 000000000000..9314d452391c
--- /dev/null
+++ b/tests/data/SOURCES/large.c
@@ -0,0 +1,29 @@
+// Generates approx 65548 sections ( 32768 comdat groups + 32770 .text )
+// when build as an object file
+// https://sourceware.org/bugzilla/show_bug.cgi?id=33819
+// Testcase suggested by James Abbatiello <abbeyj@gmail.com>>
+
+template <int I> void foo() { }
+
+#define F0 foo<__COUNTER__>();
+#define F1 F0 F0
+#define F2 F1 F1
+#define F3 F2 F2
+#define F4 F3 F3
+#define F5 F4 F4
+#define F6 F5 F5
+#define F7 F6 F6
+#define F8 F7 F7
+#define F9 F8 F8
+#define F10 F9 F9
+#define F11 F10 F10
+#define F12 F11 F11
+#define F13 F12 F12
+#define F14 F13 F13
+#define F15 F14 F14
+
+int main()
+{
+  F15;
+  return 0;
+}
diff --git a/tests/debugedit.at b/tests/debugedit.at
index b1a52ef9cb5a..fd0f8bb3d892 100644
--- a/tests/debugedit.at
+++ b/tests/debugedit.at
@@ -63,6 +63,15 @@ $LD $LDFLAGS -r -o foobarbaz.part.o foo.o subdir_bar/bar.o baz.o
 $CC $CFLAGS -g3 $GZ_FLAG $1 -o foobarbaz.exe foo.o subdir_bar/bar.o baz.o
 ]])
 
+m4_define([DEBUGEDIT_SETUP_LARGE],[[
+# Create some a large test binaries (65280+ sections).
+
+export HOME=${PWD}
+cp "${abs_srcdir}"/data/SOURCES/large.c .
+
+$CXX $CXXFLAGS -g -c large.c
+]])
+
 # ===
 # Check debugedit --help doesn't crash and burn.
 # ===
@@ -938,3 +947,31 @@ AT_CHECK([[debugedit -p -b $(pwd) -d /foo/bar/baz ./main]])
 AT_CHECK([[stat -c "%x %y" main]], [0], [expout], [])
 
 AT_CLEANUP
+
+# debugedit should handle Elf files with > 65280 sections
+AT_SETUP([debugedit large file])
+AT_KEYWORDS([debugedit] [large])
+DEBUGEDIT_SETUP_LARGE
+
+# The debuginfo contains the current working directory
+AT_CHECK([[
+$READELF --debug-dump=info ./large.o | grep $(pwd)
+]],[0],[ignore],[ignore])
+
+AT_CHECK([[debugedit -b $(pwd) -d /foo ./large.o]])
+
+# Now it should not contain $(pwd)
+AT_CHECK([[
+$READELF --debug-dump=info ./large.o | grep $(pwd)
+]],[1],[ignore],[ignore])
+
+# Make sure it does contain /foo instead.
+AT_DATA([expout],
+[/foo
+])
+AT_CHECK([[
+$READELF --debug-dump=info ./large.o | grep -E 'DW_AT_(name|comp_dir)' \
+        | rev | cut -d: -f1 | rev | cut -c2- | grep ^/foo | sort -u
+]],[0],[expout],[ignore])
+
+AT_CLEANUP
\ No newline at end of file
diff --git a/tools/debugedit.c b/tools/debugedit.c
index b3f637230100..03788ff92a70 100644
--- a/tools/debugedit.c
+++ b/tools/debugedit.c
@@ -232,6 +232,8 @@ typedef struct
   const char *filename;
   int lastscn;
   size_t phnum;
+  size_t shnum;
+  size_t shstrndx;
   struct strings debug_str, debug_line_str;
   struct debug_lines lines;
   /* List of CUs that keeps track of version, ptr_size,
@@ -574,6 +576,48 @@ rel_cmp (const void *a, const void *b)
   return 0;
 }
 
+/* Wrapper for elf_scnshndx which is broken before elfutils 0.193.  */
+static int
+scnshndx (Elf *elf, Elf_Scn *scn)
+{
+#if !_ELFUTILS_PREREQ (0, 193)
+  size_t scnndx = elf_ndxscn (scn);
+  /* By convention the SHT_SYMTAB_SHNDX section is right after the
+     SHT_SYMTAB section, so start there.  */
+  Elf_Scn *nscn = scn;
+  while ((nscn = elf_nextscn (elf, nscn)) != NULL)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (nscn, &shdr_mem);
+      if (shdr == NULL)
+        return -1;
+
+      if (shdr->sh_type == SHT_SYMTAB_SHNDX && shdr->sh_link == scnndx)
+        return elf_ndxscn (nscn);
+    }
+
+  /* OK, not found, start from the top.  */
+  nscn = NULL;
+  while ((nscn = elf_nextscn (elf, nscn)) != NULL
+         && elf_ndxscn (nscn) != scnndx)
+    {
+      GElf_Shdr shdr_mem;
+      GElf_Shdr *shdr = gelf_getshdr (nscn, &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 elf_ndxscn (nscn);
+    }
+
+  /* No shndx found, but no errors.  */
+  return 0;
+#else
+  return elf_scnshndx (scn);
+#endif
+}
+
 /* Returns a malloced REL array, or NULL when there are no relocations
    for this section.  When there are relocations, will setup relend,
    as the last REL, and reltype, as SHT_REL or SHT_RELA.  */
@@ -585,7 +629,11 @@ setup_relbuf (DSO *dso, debug_section *sec)
   GElf_Rela rela;
   GElf_Sym sym;
   GElf_Addr base = dso->shdr[sec->sec].sh_addr;
+  Elf_Scn *symscn;
   Elf_Data *symdata = NULL;
+  Elf_Data *xndxdata = NULL;
+  int xndxscnidx;
+  Elf32_Word shndx;
   int rtype;
   REL *relbuf;
   REL *relend;
@@ -615,12 +663,23 @@ setup_relbuf (DSO *dso, debug_section *sec)
   if (relbuf == NULL)
     error (1, errno, "%s: Could not allocate memory", dso->filename);
 
-  symdata = elf_getdata (dso->scn[dso->shdr[i].sh_link], NULL);
+  symscn = dso->scn[dso->shdr[i].sh_link];
+  symdata = elf_getdata (symscn, NULL);
   assert (symdata != NULL && symdata->d_buf != NULL);
   assert (elf_getdata (dso->scn[dso->shdr[i].sh_link], symdata) == NULL);
   assert (symdata->d_off == 0);
   assert (symdata->d_size == dso->shdr[dso->shdr[i].sh_link].sh_size);
 
+  /* Get extended section index table if there are 64k+ sections.  */
+  xndxscnidx = dso->shnum >= SHN_LORESERVE ? scnshndx (dso->elf, symscn) : 0;
+  if (xndxscnidx > 0)
+    {
+      xndxdata = elf_getdata (elf_getscn (dso->elf, xndxscnidx), NULL);
+      if (xndxdata == NULL)
+	error (1, 0, "%s: Could not get extended section index table: %s",
+	       dso->filename, elf_errmsg (-1));
+    }
+
   for (ndx = 0, relend = relbuf; ndx < maxndx; ++ndx)
     {
       if (dso->shdr[i].sh_type == SHT_REL)
@@ -632,20 +691,23 @@ setup_relbuf (DSO *dso, debug_section *sec)
 	}
       else
 	gelf_getrela (data, ndx, &rela);
-      gelf_getsym (symdata, ELF64_R_SYM (rela.r_info), &sym);
+      gelf_getsymshndx (symdata, xndxdata, ELF64_R_SYM (rela.r_info),
+			&sym, &shndx);
+      if (sym.st_shndx != SHN_XINDEX)
+	shndx = sym.st_shndx;
       /* Relocations against section symbols are uninteresting in REL.  */
       if (dso->shdr[i].sh_type == SHT_REL && sym.st_value == 0)
 	continue;
       /* Only consider relocations against .debug_str,
 	 .debug_str_offsets, .debug_line, .debug_line_str,
 	 .debug_macro and .debug_abbrev.  */
-      if (sym.st_shndx == 0 ||
-	  (sym.st_shndx != debug_sections[DEBUG_STR].sec
-	   && sym.st_shndx != debug_sections[DEBUG_STR_OFFSETS].sec
-	   && sym.st_shndx != debug_sections[DEBUG_LINE].sec
-	   && sym.st_shndx != debug_sections[DEBUG_LINE_STR].sec
-	   && sym.st_shndx != debug_sections[DEBUG_MACRO].sec
-	   && sym.st_shndx != debug_sections[DEBUG_ABBREV].sec))
+      if (shndx == 0 ||
+	  (shndx != debug_sections[DEBUG_STR].sec
+	   && shndx != debug_sections[DEBUG_STR_OFFSETS].sec
+	   && shndx != debug_sections[DEBUG_LINE].sec
+	   && shndx != debug_sections[DEBUG_LINE_STR].sec
+	   && shndx != debug_sections[DEBUG_MACRO].sec
+	   && shndx != debug_sections[DEBUG_ABBREV].sec))
 	continue;
 
       rtype = GELF_R_TYPE (rela.r_info);
@@ -2963,11 +3025,11 @@ edit_dwarf2 (DSO *dso)
       debug_sections[i].relsec = 0;
     }
 
-  for (i = 1; i < dso->ehdr.e_shnum; ++i)
+  for (i = 1; i < dso->shnum; ++i)
     if (! (dso->shdr[i].sh_flags & (SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR))
 	&& dso->shdr[i].sh_size)
       {
-        const char *name = strptr (dso, dso->ehdr.e_shstrndx,
+        const char *name = strptr (dso, dso->shstrndx,
 				   dso->shdr[i].sh_name);
 
 	if (name != NULL
@@ -3513,7 +3575,7 @@ fdopen_dso (int fd, const char *name)
   GElf_Ehdr ehdr;
   int i;
   DSO *dso = NULL;
-  size_t phnum;
+  size_t phnum, shnum, shstrndx;
 
   if (dest_dir == NULL && (!do_build_id || no_recompute_build_id))
     elf = elf_begin (fd, ELF_C_READ, NULL);
@@ -3544,11 +3606,23 @@ fdopen_dso (int fd, const char *name)
       goto error_out;
     }
 
+  if (elf_getshdrnum (elf, &shnum) != 0)
+    {
+      error (0, 0, "Couldn't get number of shdrs: %s", elf_errmsg (-1));
+      goto error_out;
+    }
+
+  if (elf_getshdrstrndx (elf, &shstrndx) != 0)
+    {
+      error (0, 0, "Couldn't get section string index: %s", elf_errmsg (-1));
+      goto error_out;
+    }
+
   /* Allocate DSO structure. Leave place for additional 20 new section
      headers.  */
   dso = (DSO *)
-	malloc (sizeof(DSO) + (ehdr.e_shnum + 20) * sizeof(GElf_Shdr)
-	        + (ehdr.e_shnum + 20) * sizeof(Elf_Scn *));
+	malloc (sizeof(DSO) + (shnum + 20) * sizeof(GElf_Shdr)
+	        + (shnum + 20) * sizeof(Elf_Scn *));
   if (!dso)
     {
       error (0, ENOMEM, "Could not open DSO");
@@ -3569,10 +3643,12 @@ fdopen_dso (int fd, const char *name)
   memset (dso, 0, sizeof(DSO));
   dso->elf = elf;
   dso->phnum = phnum;
+  dso->shnum = shnum;
+  dso->shstrndx = shstrndx;
   dso->ehdr = ehdr;
-  dso->scn = (Elf_Scn **) &dso->shdr[ehdr.e_shnum + 20];
+  dso->scn = (Elf_Scn **) &dso->shdr[shnum + 20];
 
-  for (i = 0; i < ehdr.e_shnum; ++i)
+  for (i = 0; i < shnum; ++i)
     {
       dso->scn[i] = elf_getscn (elf, i);
       gelf_getshdr (dso->scn[i], dso->shdr + i);
@@ -3668,7 +3744,7 @@ handle_build_id (DSO *dso, Elf_Data *build_id,
 
     x.d_type = ELF_T_PHDR;
     x.d_size = sizeof u.phdr;
-    for (i = 0; i < dso->ehdr.e_phnum; ++i)
+    for (i = 0; i < dso->phnum; ++i)
       {
 	if (gelf_getphdr (dso->elf, i, &u.phdr) == NULL)
 	  goto bad;
@@ -3680,7 +3756,7 @@ handle_build_id (DSO *dso, Elf_Data *build_id,
 
     x.d_type = ELF_T_SHDR;
     x.d_size = sizeof u.shdr;
-    for (i = 0; i < dso->ehdr.e_shnum; ++i)
+    for (i = 0; i < dso->shnum; ++i)
       if (dso->scn[i] != NULL)
 	{
 	  u.shdr = dso->shdr[i];
@@ -3859,7 +3935,7 @@ main (int argc, char *argv[])
   if (dso == NULL)
     exit (1);
 
-  for (i = 1; i < dso->ehdr.e_shnum; i++)
+  for (i = 1; i < dso->shnum; i++)
     {
       const char *name;
 
@@ -3876,7 +3952,7 @@ main (int argc, char *argv[])
 	  }
 	  /*@fallthrough@*/
 	case SHT_PROGBITS:
-	  name = strptr (dso, dso->ehdr.e_shstrndx, dso->shdr[i].sh_name);
+	  name = strptr (dso, dso->shstrndx, dso->shdr[i].sh_name);
 	  /* TODO: Handle stabs */
 	  if (name != NULL && strcmp (name, ".stab") == 0)
 	    {
-- 
2.53.0



More information about the Debugedit mailing list