[PATCH] Add debugedit-classify-ar and use it before running do_ar_file
Mark Wielaard
mark@klomp.org
Fri Aug 29 15:15:51 GMT 2025
do_ar_file in find-debuginfo can be somewhat slow extracting archive
members and adding them to a new ar file after debugedit processing.
debugedit-classify-ar is a new helper that checks if an ar file has to
be processed, so no unnecessary work is done. It checks that the given
file is a regular archive containing ELF members that have .debug
sections and that no errors occur while reading the archive and
members. It can also check that there is a maximum number of members.
Use this in debuginfo with a limit of 768 members.
* tools/debugedit-classify-ar.c: New file.
* scripts/find-debuginfo.in (do_file): Use debugedit-classify-ar
before invoking do_ar_file.
* Makefile.am (bin_PROGRAMS): Add debugedit-classify-ar.
(debugedit_classify_ar_{SOURCES,CFLAGS,LDADD}): New variables.
(dist_man_MANS): Add debugedit-classify-ar.1.
(debugedit-classify-ar.1): New target.
* .gitignore: Add debugedit-classify-ar[.1].
https://sourceware.org/bugzilla/show_bug.cgi?id=33305
Signed-off-by: Mark Wielaard <mark@klomp.org>
Signed-off-by: Mark Wielaard <mark@klomp.org>
---
.gitignore | 2 +
Makefile.am | 19 +-
scripts/find-debuginfo.in | 6 +-
tools/debugedit-classify-ar.c | 314 ++++++++++++++++++++++++++++++++++
4 files changed, 337 insertions(+), 4 deletions(-)
create mode 100644 tools/debugedit-classify-ar.c
diff --git a/.gitignore b/.gitignore
index c96a0af905a6..589fdabbe01e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,8 @@ debugedit
debugedit.1
sepdebugcrcfix
sepdebugcrcfix.1
+debugedit-classify-ar
+debugedit-classify-ar.1
find-debuginfo
find-debuginfo.1
diff --git a/Makefile.am b/Makefile.am
index 99555bdad153..d3068d5eb13e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
## Process this file with automake to produce Makefile.in.
-## Copyright (C) 2021 Mark J. Wielaard <mark@klomp.org>
+## Copyright (C) 2021, 2025 Mark J. Wielaard <mark@klomp.org>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
@@ -22,7 +22,7 @@ SUBDIRS = tests
AM_CFLAGS = -Wall
# All our programs
-bin_PROGRAMS = debugedit sepdebugcrcfix
+bin_PROGRAMS = debugedit sepdebugcrcfix debugedit-classify-ar
# Install find-debuginfo in $(bindir)
bin_SCRIPTS = find-debuginfo
@@ -52,8 +52,13 @@ sepdebugcrcfix_SOURCES = tools/sepdebugcrcfix.c
sepdebugcrcfix_CFLAGS = @LIBELF_CFLAGS@ $(AM_CFLAGS)
sepdebugcrcfix_LDADD = @LIBELF_LIBS@
+debugedit_classify_ar_SOURCES = tools/debugedit-classify-ar.c
+debugedit_classify_ar_CFLAGS = @LIBELF_CFLAGS@ $(AM_CFLAGS)
+debugedit_classify_ar_LDADD = @LIBELF_LIBS@
+
# Manual pages are generated for dist
-dist_man_MANS = debugedit.1 sepdebugcrcfix.1 find-debuginfo.1
+dist_man_MANS = debugedit.1 sepdebugcrcfix.1 find-debuginfo.1 \
+ debugedit-classify-ar.1
# The 'case' ensures the man pages are only generated if the corresponding
# source script (the first prerequisite) or configure.ac (for the version)
@@ -77,6 +82,14 @@ sepdebugcrcfix.1: tools/sepdebugcrcfix.c configure.ac sepdebugcrcfix$(EXEEXT)
* ) : ;; \
esac
+debugedit-classify-ar.1: tools/debugedit-classify-ar.c configure.ac debugedit-classify-ar$(EXEEXT)
+ @case '$?' in \
+ *$<* | *configure.ac* ) $(HELP2MAN) -N --output=$@ \
+ --name='Checks whether ELF archive members should be processed' \
+ ./debugedit-classify-ar$(EXEEXT) ;;\
+ * ) : ;; \
+ esac
+
find-debuginfo.1: $(top_srcdir)/scripts/find-debuginfo.in configure.ac find-debuginfo
@case '$?' in \
*$<* | *configure.ac* ) $(HELP2MAN) -N --output=$@ \
diff --git a/scripts/find-debuginfo.in b/scripts/find-debuginfo.in
index b211a3c729c8..4c3e424158b9 100755
--- a/scripts/find-debuginfo.in
+++ b/scripts/find-debuginfo.in
@@ -600,7 +600,11 @@ do_file()
local ar_re="^.*\.a$"
if [[ $f =~ $ar_re ]]; then # treat as static archive
- do_ar_file "$1" "$2" "$3"
+ # Sanity check the ar file, skip if it doesn't have debuginfo
+ # or too many members.
+ if debugedit-classify-ar -m 768 "$f"; then
+ do_ar_file "$1" "$2" "$3"
+ fi
return
fi
diff --git a/tools/debugedit-classify-ar.c b/tools/debugedit-classify-ar.c
new file mode 100644
index 000000000000..c601fda25905
--- /dev/null
+++ b/tools/debugedit-classify-ar.c
@@ -0,0 +1,314 @@
+/* Quick and dirty ELF archive member debug checker.
+ Copyright (C) 2025 Mark J. Wielaard <mark@klomp.org>
+ This file is part of debugedit.
+
+ 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 <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <error.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <libelf.h>
+#include <gelf.h>
+
+// Negative is infinite, zero is failure, positive is max number to accept.
+static int max_members = -1;
+
+/* Returns -1 on error, 0 if member isn't an Elf objects or if it is
+ an Elf object but doesn't contain any .[z]debug sections, 1 if it
+ is an Elf object with .debug sections. */
+static int
+classify_ar_member (Elf *member, const char *name, const char *file)
+{
+ /* Not an elf member? OK, no debug. */
+ if (elf_kind (member) != ELF_K_ELF)
+ return 0;
+
+ /* No sections? OK, no debug. */
+ size_t nshdrs;
+ if (elf_getshdrnum (member, &nshdrs) != 0)
+ {
+ error (0, 0, "couldn't get section header number: %s: '%s[%s]'",
+ elf_errmsg (-1), file, name);
+ return -1;
+ }
+ if (nshdrs == 0)
+ return 0;
+
+ size_t shstrndx;
+ if (elf_getshdrstrndx (member, &shstrndx) < 0)
+ {
+ error (0, 0, "couldn't get section header string table: %s: '%s[%s]'",
+ elf_errmsg (-1), file, name);
+ return -1;
+ }
+
+ bool found_debug = false;
+ Elf_Scn *scn = NULL;
+ while ((scn = elf_nextscn (member, scn)) != NULL)
+ {
+ GElf_Shdr shdr_mem;
+ GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
+ if (shdr == NULL)
+ {
+ error (0, 0, "couldn't get section header: %s: '%s[%s]'",
+ elf_errmsg (-1), file, name);
+ return -1;
+ }
+
+ const char *sname = elf_strptr (member, shstrndx, shdr->sh_name);
+ if (sname == NULL)
+ {
+ error (0, 0, "couldn't get section name: %s: '%s[%s]'",
+ elf_errmsg (-1), file, name);
+ return -1;
+ }
+
+ /* Just check the name. */
+ if (strncmp (sname, ".debug_", strlen (".debug_")) == 0
+ || strncmp (sname, ".zdebug_", strlen (".zdebug_")) == 0)
+ found_debug = true;
+ }
+
+ return found_debug ? 1 : 0;
+}
+
+static int
+classify_ar_elf (int fd, Elf *ar, const char *file)
+{
+ int members = 0;
+ bool err = false;
+ bool found_debug = false;
+ int cmd = ELF_C_READ;
+ Elf *elf;
+ while ((elf = elf_begin (fd, cmd, ar)) != NULL)
+ {
+ Elf_Arhdr *arhdr = elf_getarhdr (elf);
+ if (arhdr == NULL)
+ {
+ error (0, 0, "couldn't get ar header: %s: '%s'",
+ elf_errmsg (-1), file);
+ err = true;
+ break;
+ }
+
+ char *name = arhdr->ar_name ?: "<no-name>";
+ int res = classify_ar_member (elf, name, file);
+ if (res < 0)
+ err = true;
+ else if (res > 0)
+ found_debug = true;
+
+ cmd = elf_next (elf);
+
+ if (elf_end (elf) != 0)
+ {
+ error(0, 0, "closing ar member: %s: '%s[%s]",
+ elf_errmsg (-1), file, name);
+ err = true;
+ }
+
+ if (err)
+ break;
+
+ members++;
+ }
+
+ if (err)
+ return -1;
+
+ if (!found_debug)
+ {
+ error (0, 0, "no member with debug sections: %s", file);
+ return -1;
+ }
+
+ if (max_members > 0 && members > max_members)
+ {
+ error (0, 0, "too many members (%d): %s", members, file);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Returns zero if it is an ELF archive with max members of which at
+ least one is an ELF object with .[z]debug sections. Returns -1 if
+ the file isn't a regular file, not an ELF archive, an error occurs
+ while processing it, no member is an ELF object with debug sections
+ or the archive contains more than max members. */
+static int
+classify_ar_file (const char *file)
+{
+ /* Don't open symlinks. */
+ int fd = open (file, O_RDONLY | O_NOFOLLOW);
+ if (fd < 0)
+ {
+ if (errno == ELOOP)
+ error (0, 0, "cannot open symbolic link '%s'", file);
+ else
+ error (0, errno, "cannot open '%s'", file);
+ return -1;
+ }
+
+ struct stat st;
+ if (fstat (fd, &st) != 0)
+ {
+ error (0, errno, "cannot fstat '%s'", file);
+ close (fd);
+ return -1;
+ }
+
+ if (S_ISDIR (st.st_mode))
+ {
+ error (0, 0, "cannot open directory '%s'", file);
+ close (fd);
+ return -1;
+ }
+
+ if (!S_ISREG (st.st_mode))
+ {
+ error (0, 0, "not a regular file '%s'", file);
+ close (fd);
+ return -1;
+ }
+
+ Elf *elf = elf_begin (fd, ELF_C_READ, NULL);
+ if (elf == NULL)
+ {
+ error (0, 0, "cannot open Elf file: %s: '%s'", elf_errmsg (-1), file);
+ close (fd);
+ return -1;
+ }
+
+ if (elf_kind (elf) != ELF_K_AR)
+ {
+ error (0, 0, "not an ELF archive: %s\n", file);
+ elf_end (elf);
+ close (fd);
+ return -1;
+ }
+
+ int res = classify_ar_elf (fd, elf, file);
+
+ elf_end (elf);
+ close (fd);
+
+ return res;
+}
+
+static struct option optionsTable[] =
+ {
+ { "max-members", required_argument, 0, 'm' },
+ { "version", no_argument, 0, 'V' },
+ { "help", no_argument, 0, '?' },
+ { "usage", no_argument, 0, 'u' },
+ { NULL, 0, 0, 0 }
+ };
+
+static const char *optionsChars = "m:V?u";
+
+static const char *helpText =
+ "Usage: %s [OPTION...] FILE\n"
+ " -m, --max-members=NUM Maximum number of archive members to accept\n"
+ "\n"
+ "Help options:\n"
+ " -?, --help Show this help message\n"
+ " -u, --usage Display brief usage message\n"
+ " -V, --version Show program version\n";
+
+static const char *usageText =
+ "Usage: %s [-m|--max-members NUM]\n"
+ " [-?|--help] [-u|--usage]\n"
+ " [-V|--version] FILE\n";
+
+static void
+help (const char *progname, bool error)
+{
+ FILE *f = error ? stderr : stdout;
+ fprintf (f, helpText, progname);
+ exit (error ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static void
+usage (const char *progname, bool error)
+{
+ FILE *f = error ? stderr : stdout;
+ fprintf (f, usageText, progname);
+ exit (error ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int
+main (int argc, char **argv)
+{
+ bool show_version = false;
+
+ /* Process arguments. */
+ while (1)
+ {
+ int opt_ndx = -1;
+ int c = getopt_long (argc, argv, optionsChars, optionsTable, &opt_ndx);
+
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ default:
+ case '?':
+ help (argv[0], opt_ndx == -1);
+ break;
+
+ case 'u':
+ usage (argv[0], false);
+ break;
+
+ case 'V':
+ show_version = true;
+ break;
+
+ case 'm':
+ max_members = atoi (optarg);
+ if (max_members == 0)
+ help (argv[0], true);
+ break;
+ }
+ }
+
+ if (show_version)
+ {
+ printf("%s %s\n", argv[0], VERSION);
+ exit(EXIT_SUCCESS);
+ }
+
+ if (optind != argc - 1)
+ {
+ error (0, 0, "Need one FILE as input");
+ usage (argv[0], true);
+ }
+
+ elf_version(EV_CURRENT);
+
+ return classify_ar_file (argv[optind]) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
--
2.50.1
More information about the Debugedit
mailing list