[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