[binutils-gdb] gprof support for split debuginfo

"Frank Ch. Eigler via Binutils-cvs" <[email protected]>
Newsgroups gmane.comp.gnu.binutils.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=634e1cb34a986229f861ea9085bf5fe6fe1a6088

commit 634e1cb34a986229f861ea9085bf5fe6fe1a6088
Author: Frank Ch. Eigler <[email protected]>
Date:   Fri Feb 6 14:07:47 2026 -0500

    gprof support for split debuginfo
    
    Use debug BFD for symbol and line information when a separated
    debuginfo file is found, enabling gprof to work correctly with
    binaries stripped with "strip -g".  Baby steps toward possible
    future debuginfod/dwz support.
    
    gprof/ChangeLog:
    
    2026-04-29  Frank Ch. Eigler  <[email protected]>
    
            * corefile.c: Add support for separated debuginfo files.
            (core_debug_bfd): New static variable to store separated debug BFD.
            (open_separated_debug_file): New function to locate and open external
            debug files via .gnu_debuglink, .gnu_debugaltlink, or build-id.
            (core_init): Call open_separated_debug_file() after opening the main
            binary to locate external debug info.
            * testsuite/Makefile.am, testsuite/tst-gmon-gprof-l2.sh: Run -l
            test again, but against stripped version of test binary.
            * testsuite/Makefile.in: Regenerated.
    
    Signed-off-by: Frank Ch. Eigler <[email protected]>

Diff:
---
 gprof/corefile.c                     | 72 +++++++++++++++++++++++++++++++++--
 gprof/testsuite/Makefile.am          | 20 ++++++++--
 gprof/testsuite/Makefile.in          | 33 +++++++++++++---
 gprof/testsuite/tst-gmon-gprof-l2.sh | 74 ++++++++++++++++++++++++++++++++++++
 4 files changed, 186 insertions(+), 13 deletions(-)

diff --git a/gprof/corefile.c b/gprof/corefile.c
index f5dea466dd6..a6bbb6d45ea 100644
--- a/gprof/corefile.c
+++ b/gprof/corefile.c
@@ -34,6 +34,7 @@
 bfd *core_bfd;
 static int core_num_syms;
 static asymbol **core_syms;
+static bfd *core_debug_bfd;	/* Separated debuginfo file, if any.  */
 asection *core_text_sect;
 void * core_text_space;
 
@@ -173,6 +174,59 @@ read_function_mappings (const char *filename)
   fclose (file);
 }
 
+/* Attempt to open separated debuginfo files (e.g., created with "strip -g").
+   This function tries to follow GNU debug links to locate external debug info
+   and opens them to make symbol and line information available.  */
+
+static void
+open_separated_debug_file (bfd *abfd)
+{
+  char *debug_filename = NULL;
+  bfd *debug_bfd;
+
+  /* Try to follow .gnu_debuglink first (standard separated debug file).  */
+  debug_filename = bfd_follow_gnu_debuglink (abfd, NULL);
+
+  if (!debug_filename)
+    {
+      /* Try alternate debug info (.gnu_debugaltlink).  */
+      debug_filename = bfd_follow_gnu_debugaltlink (abfd, NULL);
+    }
+
+  if (!debug_filename)
+    {
+      /* Try build-id based debug file location.  */
+      debug_filename = bfd_follow_build_id_debuglink (abfd, NULL);
+    }
+
+  if (debug_filename)
+    {
+      debug_bfd = bfd_openr (debug_filename, 0);
+
+      if (debug_bfd)
+	{
+	  if (bfd_check_format (debug_bfd, bfd_object))
+	    {
+	      /* Successfully opened the debug file.
+	         Enable decompression on the debug file.  */
+	      if ((debug_bfd->flags & BFD_DECOMPRESS) == 0)
+		debug_bfd->flags |= BFD_DECOMPRESS;
+
+	      /* Store the debug BFD for use during symbol reading.
+	         We'll use this for bfd_canonicalize_symtab and other symbol ops.  */
+	      core_debug_bfd = debug_bfd;
+	    }
+	  else
+	    {
+	      /* Debug file format check failed.  */
+	      bfd_close (debug_bfd);
+	    }
+	}
+
+      free (debug_filename);
+    }
+}
+
 void
 core_init (const char * aout_name)
 {
@@ -196,6 +250,11 @@ core_init (const char * aout_name)
       done (1);
     }
 
+  /* Attempt to open separated debuginfo files if available.
+     This handles binaries stripped with "strip -g" by locating and opening
+     the external debug information via .gnu_debuglink or build-id.  */
+  open_separated_debug_file (core_bfd);
+
   /* Get core's text section.  */
   core_text_sect = bfd_get_section_by_name (core_bfd, ".text");
   if (!core_text_sect)
@@ -212,7 +271,10 @@ core_init (const char * aout_name)
   /* Read core's symbol table.  */
 
   /* This will probably give us more than we need, but that's ok.  */
-  core_sym_bytes = bfd_get_symtab_upper_bound (core_bfd);
+  /* Use debug BFD for symbol info if we found a separated debuginfo file.  */
+  bfd *sym_bfd = core_debug_bfd ? core_debug_bfd : core_bfd;
+
+  core_sym_bytes = bfd_get_symtab_upper_bound (sym_bfd);
   if (core_sym_bytes < 0)
     {
       fprintf (stderr, "%s: %s: %s\n", whoami, aout_name,
@@ -221,7 +283,7 @@ core_init (const char * aout_name)
     }
 
   core_syms = (asymbol **) xmalloc (core_sym_bytes);
-  core_num_syms = bfd_canonicalize_symtab (core_bfd, core_syms);
+  core_num_syms = bfd_canonicalize_symtab (sym_bfd, core_syms);
 
   if (core_num_syms < 0)
     {
@@ -230,7 +292,7 @@ core_init (const char * aout_name)
       done (1);
     }
 
-  synth_count = bfd_get_synthetic_symtab (core_bfd, core_num_syms, core_syms,
+  synth_count = bfd_get_synthetic_symtab (sym_bfd, core_num_syms, core_syms,
 					  0, NULL, &synthsyms);
   if (synth_count > 0)
     {
@@ -468,8 +530,10 @@ get_src_info (bfd_vma addr, const char **filename, const char **name,
 {
   const char *fname = 0, *func_name = 0;
   int l = 0;
+  /* Use debug BFD for line info if we have a separated debuginfo file.  */
+  bfd *info_bfd = core_debug_bfd ? core_debug_bfd : core_bfd;
 
-  if (bfd_find_nearest_line (core_bfd, core_text_sect, core_syms,
+  if (bfd_find_nearest_line (info_bfd, core_text_sect, core_syms,
 			     addr - core_text_sect->vma,
 			     &fname, &func_name, (unsigned int *) &l)
       && fname && func_name && l)
diff --git a/gprof/testsuite/Makefile.am b/gprof/testsuite/Makefile.am
index 7ab1c2ea6b7..2f8a383a8dd 100644
--- a/gprof/testsuite/Makefile.am
+++ b/gprof/testsuite/Makefile.am
@@ -37,9 +37,23 @@ tst-gmon-gprof.out: tst-gmon$(EXEEXT) $(GPROF)
 
 check_SCRIPTS += tst-gmon-gprof-l.sh
 check_DATA += tst-gmon-gprof-l.out
-# Run tst-gmon-gprof-l.sh after tst-gmon-gprof.sh to avoid the race
-# condition since they both generate gmon.out.
-tst-gmon-gprof-l.out: tst-gmon$(EXEEXT) $(GPROF) tst-gmon-gprof.out
+tst-gmon-gprof-l.out: tst-gmon$(EXEEXT) $(GPROF)
 	$(srcdir)/tst-gmon-gprof-l.sh $(GPROF) tst-gmon$(EXEEXT)
 
+# Create a separated-debuginfo version of the initial binary 
+check_SCRIPTS += tst-gmon-gprof-l2.sh
+tst-gmon2$(EXEEXT) tst-gmon2.debug: tst-gmon$(EXEEXT)
+	cp -p tst-gmon$(EXEEXT) tst-gmon2$(EXEEXT)
+	objcopy --only-keep-debug tst-gmon2$(EXEEXT) tst-gmon2.debug
+	strip --strip-debug tst-gmon2$(EXEEXT)
+	objcopy --add-gnu-debuglink=tst-gmon2.debug tst-gmon2$(EXEEXT)
+
+tst-gmon-gprof-l2.out: tst-gmon2$(EXEEXT) tst-gmon2.debug $(GPROF)
+	$(srcdir)/tst-gmon-gprof-l2.sh $(GPROF) tst-gmon2$(EXEEXT)
+check_DATA += tst-gmon-gprof-l2.out
+MOSTLYCLEANFILES += tst-gmon2$(EXEEXT) tst-gmon2.debug
+
+# Run all tests in series, so they don't fight over the gmon.out file
+.NOTPARALLEL:
+
 endif NATIVE
diff --git a/gprof/testsuite/Makefile.in b/gprof/testsuite/Makefile.in
index 77cecd153b1..0160e44a8d2 100644
--- a/gprof/testsuite/Makefile.in
+++ b/gprof/testsuite/Makefile.in
@@ -90,9 +90,14 @@ POST_UNINSTALL = :
 build_triplet = @build@
 host_triplet = @host@
 target_triplet = @target@
-@NATIVE_TRUE@am__append_1 = tst-gmon-gprof.sh tst-gmon-gprof-l.sh
-@NATIVE_TRUE@am__append_2 = tst-gmon-gprof.out tst-gmon-gprof-l.out
-@NATIVE_TRUE@am__append_3 = tst-gmon.$(OBJEXT) tst-gmon$(EXEEXT) gmon.out
+
+# Create a separated-debuginfo version of the initial binary 
+@NATIVE_TRUE@am__append_1 = tst-gmon-gprof.sh tst-gmon-gprof-l.sh \
+@NATIVE_TRUE@	tst-gmon-gprof-l2.sh
+@NATIVE_TRUE@am__append_2 = tst-gmon-gprof.out tst-gmon-gprof-l.out \
+@NATIVE_TRUE@	tst-gmon-gprof-l2.out
+@NATIVE_TRUE@am__append_3 = tst-gmon.$(OBJEXT) tst-gmon$(EXEEXT) \
+@NATIVE_TRUE@	gmon.out tst-gmon2$(EXEEXT) tst-gmon2.debug
 subdir = testsuite
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/../bfd/warning.m4 \
@@ -713,6 +718,13 @@ tst-gmon-gprof-l.sh.log: tst-gmon-gprof-l.sh
 	--log-file $$b.log --trs-file $$b.trs \
 	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
 	"$$tst" $(AM_TESTS_FD_REDIRECT)
+tst-gmon-gprof-l2.sh.log: tst-gmon-gprof-l2.sh
+	@p='tst-gmon-gprof-l2.sh'; \
+	b='tst-gmon-gprof-l2.sh'; \
+	$(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+	--log-file $$b.log --trs-file $$b.trs \
+	$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+	"$$tst" $(AM_TESTS_FD_REDIRECT)
 .test.log:
 	@p='$<'; \
 	$(am__set_b); \
@@ -887,10 +899,19 @@ uninstall-am:
 @NATIVE_TRUE@	$(LINK) tst-gmon.$(OBJEXT)
 @[email protected]: tst-gmon$(EXEEXT) $(GPROF)
 @NATIVE_TRUE@	$(srcdir)/tst-gmon-gprof.sh $(GPROF) tst-gmon$(EXEEXT)
-# Run tst-gmon-gprof-l.sh after tst-gmon-gprof.sh to avoid the race
-# condition since they both generate gmon.out.
-@[email protected]: tst-gmon$(EXEEXT) $(GPROF) tst-gmon-gprof.out
+@[email protected]: tst-gmon$(EXEEXT) $(GPROF)
 @NATIVE_TRUE@	$(srcdir)/tst-gmon-gprof-l.sh $(GPROF) tst-gmon$(EXEEXT)
+@NATIVE_TRUE@tst-gmon2$(EXEEXT) tst-gmon2.debug: tst-gmon$(EXEEXT)
+@NATIVE_TRUE@	cp -p tst-gmon$(EXEEXT) tst-gmon2$(EXEEXT)
+@NATIVE_TRUE@	objcopy --only-keep-debug tst-gmon2$(EXEEXT) tst-gmon2.debug
+@NATIVE_TRUE@	strip --strip-debug tst-gmon2$(EXEEXT)
+@NATIVE_TRUE@	objcopy --add-gnu-debuglink=tst-gmon2.debug tst-gmon2$(EXEEXT)
+
+@[email protected]: tst-gmon2$(EXEEXT) tst-gmon2.debug $(GPROF)
+@NATIVE_TRUE@	$(srcdir)/tst-gmon-gprof-l2.sh $(GPROF) tst-gmon2$(EXEEXT)
+
+# Run all tests in series, so they don't fight over the gmon.out file
+@[email protected]:
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/gprof/testsuite/tst-gmon-gprof-l2.sh b/gprof/testsuite/tst-gmon-gprof-l2.sh
new file mode 100755
index 00000000000..530d022e472
--- /dev/null
+++ b/gprof/testsuite/tst-gmon-gprof-l2.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+# Check the output of gprof against a carfully crafted binary.
+# Copyright (C) 2017-2025 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# The GNU C Library 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
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+GPROF="$1"
+if test -z "$GPROF"; then
+  # Exit 0 for automake test script run.
+  exit 0
+fi
+
+program="$2"
+# Generate gmon.out
+data=gmon.out
+rm -f $data
+./$program
+if test ! -s $data; then
+    echo "FAIL"
+    exit 1
+fi
+
+LC_ALL=C
+export LC_ALL
+set -e
+exec 2>&1
+
+actual=${program}.actual-l
+expected=${program}.expected-l
+expected_dot=${program}.expected_dot-l
+cleanup () {
+    rm -f "$actual"
+    rm -f "$expected"
+    rm -f "$expected_dot"
+}
+trap cleanup 0
+
+cat > "$expected" <<EOF
+25 f1 2000
+31 f2 1000
+40 f3 1
+EOF
+
+# Special version for powerpc with function descriptors.
+cat > "$expected_dot" <<EOF
+25 .f1 2000
+31 .f2 1000
+40 .f3 1
+EOF
+
+"$GPROF" -l -C "$program" "$data" \
+    | awk -F  '[(): ]' '/executions/{print $2, $5, $8}' \
+    | sort > "$actual"
+
+if cmp -s "$actual" "$expected_dot" \
+   || diff -u --label expected "$expected" --label actual "$actual" ; then
+    echo "PASS"
+else
+    echo "FAIL"
+    exit 1
+fi
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.