Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH 0/5] Improved support for core files with missing NT_FILE note
@ 2026-03-14 14:32 Andrew Burgess
  2026-03-14 14:32 ` [PATCH 1/5] gdb/testsuite: improve corefile-no-threads.py script Andrew Burgess
                   ` (5 more replies)
  0 siblings, 6 replies; 9+ messages in thread
From: Andrew Burgess @ 2026-03-14 14:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This series improves GDB's support for core files without an NT_FILE
note.  When this note is missing GDB is currently unable to associate
a build-id with any of the shared libraries that loaded as the core
file is opened.  As a result GDB might load the wrong shared library
if a local library happens to have the same name (e.g. is a different
version of the same library), and GDB will not perform a build-id
based lookup either locally, or through debuginfod, for the missing
shared library.

Patches #1 and #2 rework some existing tests.  This gets these tests
into a state where they can be reused for testing the fixes that come
later in the series.

Patches #3 and #4 are minor refactoring patches removing some code
that will make the later patch in this series harder.

Patch #5 is the actual fix.  After this, on GNU/Linux, GDB will be
able to associate a build-id with a shared library, even if the
NT_FILE note is missing from a core file.

Thanks,
Andrew

---

Andrew Burgess (5):
  gdb/testsuite: improve corefile-no-threads.py script
  gdb/testsuite: restructure gdb.base/corefile-buildid.exp
  gdb: remove 'num' argument from gdbarch_read_core_file_mappings
    callback
  gdb: remove pre-loop callback from gdbarch_read_core_file_mappings
  gdb/linux: handle missing NT_FILE note when opening core files

 gdb/arch-utils.c                              |   1 -
 gdb/arch-utils.h                              |   1 -
 gdb/corelow.c                                 |  68 ++--
 gdb/doc/python.texi                           |  10 +-
 gdb/gdbarch-gen.c                             |   4 +-
 gdb/gdbarch-gen.h                             |   4 +-
 gdb/gdbarch.h                                 |   8 +-
 gdb/gdbarch_components.py                     |   1 -
 gdb/gdbcore.h                                 |  28 +-
 gdb/linux-tdep.c                              | 342 +++++++++++-------
 gdb/testsuite/gdb.base/corefile-buildid.exp   | 290 +++++++++++----
 .../gdb.base/corefile-no-threads.exp          |  10 +-
 gdb/testsuite/gdb.base/corefile-no-threads.py |  47 ++-
 gdb/testsuite/gdb.python/py-corefile.py       |   4 +
 14 files changed, 555 insertions(+), 263 deletions(-)


base-commit: e4fe38115fea0a9f357527959893f18ca8d51a03
-- 
2.25.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/5] gdb/testsuite: improve corefile-no-threads.py script
  2026-03-14 14:32 [PATCH 0/5] Improved support for core files with missing NT_FILE note Andrew Burgess
@ 2026-03-14 14:32 ` Andrew Burgess
  2026-03-14 14:32 ` [PATCH 2/5] gdb/testsuite: restructure gdb.base/corefile-buildid.exp Andrew Burgess
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Andrew Burgess @ 2026-03-14 14:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Extend the Python script gdb.base/corefile-no-threads.py so that core
file note types can be specified by name, e.g. NT_PRSTATUS, rather
than having to use their hex value.

I've only added a few names for now.  The existing test only needs
NT_PRSTATUS, but I plan to reuse this script for a new test, in which
case I'll also need NT_FILE.  Additional names can be added in the
future as needed.

I then updated the gdb.base/corefile-no-threads.exp script to make use
of this functionality, and I improved the test pattern so that it
actually checks that a note was updated.

There should be no change in what is tested after this commit.
---
 .../gdb.base/corefile-no-threads.exp          | 10 ++--
 gdb/testsuite/gdb.base/corefile-no-threads.py | 47 ++++++++++++++-----
 2 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/gdb/testsuite/gdb.base/corefile-no-threads.exp b/gdb/testsuite/gdb.base/corefile-no-threads.exp
index dfb2f806e9e..d21d7cf4030 100644
--- a/gdb/testsuite/gdb.base/corefile-no-threads.exp
+++ b/gdb/testsuite/gdb.base/corefile-no-threads.exp
@@ -15,8 +15,8 @@
 
 # Check how GDB handles a core file which appears to have no threads
 # within it.  We do this by asking the kernel to create a "normal"
-# core file, then use a Python script modify the core file, hiding the
-# NT_PRSTATUS notes.  The NT_PRSTATUS notes contain the general
+# core file, then use a Python script to modify the core file, hiding
+# the NT_PRSTATUS notes.  The NT_PRSTATUS notes contain the general
 # purpose registers for each thread, so with these gone GDB will
 # believe the core file has no threads.
 #
@@ -52,7 +52,11 @@ clean_restart
 # Load the Python script, and run the command which modifies the core
 # file.
 gdb_test_no_output "source $::pyfile" "import python scripts"
-gdb_test "modify-core-file \"$corefile\" 0x1" ".*" \
+gdb_test "modify-core-file \"$corefile\" NT_PRSTATUS" \
+    [multi_line \
+	 "Located PT_NOTE segment: \[^\r\n\]+" \
+	 "\\s+Found note with type $hex at file offset $hex\\..*" \
+	 "Successfully updated $decimal note\\(s\\) in \[^\r\n\]+"] \
     "update core file"
 
 set saw_no_regs_warning false
diff --git a/gdb/testsuite/gdb.base/corefile-no-threads.py b/gdb/testsuite/gdb.base/corefile-no-threads.py
index 5ba82ae9486..fb2a91e57d0 100644
--- a/gdb/testsuite/gdb.base/corefile-no-threads.py
+++ b/gdb/testsuite/gdb.base/corefile-no-threads.py
@@ -18,10 +18,10 @@
 #
 # modify-core-file <corefile> <note type> [ <name regex> ]
 #
-# Find all notes in the core file <corefile> that have the type value
-# <note type>.  Change the type of those notes to 0xffffffff, which
-# should prevent GDB from processing the note.  The <corefile> is
-# modified in place.
+# Find all notes in the core file <corefile> that have the type
+# <note type>, either a name (e.g. NT_PRSTATUS) or an integer.
+# Change the type of those notes to 0xffffffff, which should prevent
+# GDB from processing the note.  The <corefile> is modified in place.
 #
 # The optional argument <name regex> is a regular expression to match
 # against the name of the note in addition to the type value check.
@@ -130,7 +130,7 @@ def invalidate_corefile_notes(core_filepath, type_val, name_re):
     ei_data = read_field(core_data, 5, "=b")
     ei_version = read_field(core_data, 6, "=b")
 
-    # Based on the endinanness of the core file, select a character to
+    # Based on the endianness of the core file, select a character to
     # use with the 'struct' module for unpacking multi-byte fields.
     if ei_data == ELF_DATA_2_LSB:
         endian_char = "<"
@@ -194,7 +194,7 @@ def invalidate_corefile_notes(core_filepath, type_val, name_re):
     # Read the e_type field and check this is a core file.
     e_type = read_field(core_data, ELF_HDR_TYPE_OFFSET, ELF_HALF_FMT)
     if e_type != ELF_TYPE_CORE:
-        raise gdb.GdbError("Unsuported ELF e_type %d" % (e_type))
+        raise gdb.GdbError("Unsupported ELF e_type %d" % (e_type))
 
     # Read the offset to the program header table, and the number of
     # entries in the program header table.
@@ -308,6 +308,31 @@ def invalidate_corefile_notes(core_filepath, type_val, name_re):
             )
 
 
+# A limited set of note names.  Extend this as needed.
+NOTE_TYPES = {
+    "NT_PRSTATUS": 1,
+    "NT_FPREGSET": 2,
+    "NT_PRPSINFO": 3,
+    "NT_TASKSTRUCT": 4,
+    "NT_AUXV": 6,
+    "NT_SIGINFO": 0x53494749,
+    "NT_FILE": 0x46494C45,
+    "NT_PRXFPREG": 0x46E62B7F,
+}
+
+
+def parse_type(type_str):
+    """Resolves string name to integer or parses raw hex/int."""
+    if type_str in NOTE_TYPES:
+        return NOTE_TYPES[type_str]
+    try:
+        return int(type_str, 0)
+    except ValueError as e:
+        raise gdb.GdbError(
+            "Error: Unable to parse '%s' as number: %s" % (type_str, str(e))
+        )
+
+
 class modify_core_file(gdb.Command):
     """Update notes within a core file.
 
@@ -315,7 +340,8 @@ class modify_core_file(gdb.Command):
     modify-core-file COREFILE NOTE_TYPE [ NAME_REGEX ]
 
     Within COREFILE, find any notes matching NOTE_TYPE, which should
-    be an integer.  Change the type of these notes to 0xffffffff.
+    be either a note type name (e.g. NT_PRSTATUS) or an integer.
+    Change the type of these notes to 0xffffffff.
 
     If NAME_REGEX is supplied, and is not the empty string, then only
     notes whose type matches NOTE_TYPE, and whose name matches
@@ -352,12 +378,7 @@ class modify_core_file(gdb.Command):
                 "Error: Cannot write to '%s'. Check file permissions." % (filename)
             )
 
-        try:
-            type_val = int(type_str, 0)
-        except ValueError as e:
-            raise gdb.GdbError(
-                "Error: Unable to parse '%s' as number: %s" % (type_str, str(e))
-            )
+        type_val = parse_type(type_str)
 
         if name_re_str != "":
             name_re = re.compile(name_re_str)
-- 
2.25.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 2/5] gdb/testsuite: restructure gdb.base/corefile-buildid.exp
  2026-03-14 14:32 [PATCH 0/5] Improved support for core files with missing NT_FILE note Andrew Burgess
  2026-03-14 14:32 ` [PATCH 1/5] gdb/testsuite: improve corefile-no-threads.py script Andrew Burgess
@ 2026-03-14 14:32 ` Andrew Burgess
  2026-03-14 14:32 ` [PATCH 3/5] gdb: remove 'num' argument from gdbarch_read_core_file_mappings callback Andrew Burgess
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Andrew Burgess @ 2026-03-14 14:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Restructure the gdb.base/corefile-buildid.exp test.  Previously this
test focused on testing that GDB could find the executable for a core
file based on the executable's build-id.  The test did include
building an executable that made use of shared libraries, but the test
never tried to confirm that GDB could find these shared libraries
based on their build-id, only the executable was being tested.

This rewrite extends the test so that, for the shared library using
executable, both of the shared libraries are moved into the debug
directory, we then check that they are found when the core file is
opened.  As the debug directory is indexed by build-id, this indicates
that GDB can find the shared libraries for a core file based on the
build-id of the shared libraries.  The existing executable lookup
tests are unchanged, this is just adding additional testing.
---
 gdb/testsuite/gdb.base/corefile-buildid.exp | 263 +++++++++++++++-----
 1 file changed, 196 insertions(+), 67 deletions(-)

diff --git a/gdb/testsuite/gdb.base/corefile-buildid.exp b/gdb/testsuite/gdb.base/corefile-buildid.exp
index 45c613b528f..1dec651e03c 100644
--- a/gdb/testsuite/gdb.base/corefile-buildid.exp
+++ b/gdb/testsuite/gdb.base/corefile-buildid.exp
@@ -52,22 +52,33 @@ proc create_core_file { progname } {
 }
 
 
-# Build a non-shared executable.
+# Build a non-shared executable.  MODE is a suffix used to generate
+# the executable name (which should be based on global TESTFILE).
+# Returns an empty list if anything goes wrong, otherwise, returns a
+# list containing the absolute filename of the executable.
 
-proc build_corefile_buildid_exec { progname } {
-    return [expr {[build_executable "build non-shared exec" $progname $::srcfile] != -1}]
+proc build_corefile_buildid_exec { mode } {
+    set progname $::testfile-$mode
+    if {[build_executable "build non-shared exec" $progname $::srcfile] == -1} {
+	return {}
+    }
+    return [list [standard_output_file $progname]]
 }
 
-# Build a shared executable.
+# Build a shared executable.  MODE is a suffix used to generate the
+# executable name (which should be based on global TESTFILE).  Returns
+# an empty list if anything goes wrong, otherwise, returns a list
+# containing the absolute filename of the executable (first) followed
+# by all the additional shared libraries.
 
-proc build_corefile_buildid_shared { progname } {
+proc build_corefile_buildid_shared { mode } {
     # Compile DSO.
+    set progname $::testfile-$mode
     set objdso [standard_output_file $::testfile-shlib-shr.so]
     if {[build_executable "build dso" $objdso $::srcfile2 {debug shlib}] == -1} {
-	return false
+	return {}
     }
 
-
     # Compile shared library.
     set srclib $::srcfile3
     set libname lib$::testfile.so
@@ -76,16 +87,16 @@ proc build_corefile_buildid_shared { progname } {
     set opts [list debug shlib_load shlib \
 		  additional_flags=-DSHLIB_NAME=\"$dlopen_lib\"]
     if {[build_executable "build solib" $objlib $::srcfile3 $opts] == -1} {
-	return false
+	return {}
     }
 
     # Compile main program.
     set opts [list debug shlib=$objlib additional_flags=-DTEST_SHARED]
     if {[build_executable "build shared exec" $progname $::srcfile $opts] == -1} {
-	return false
+	return {}
     }
 
-    return true
+    return [list [standard_output_file $progname] $objdso $objlib]
 }
 
 # Append DEBUGDIR to the debug-file-directory path.
@@ -145,18 +156,84 @@ proc check_exec_file {file} {
     }
 }
 
-# Test whether gdb can find an exec file from a core file's build-id.
-# The executable (and separate debuginfo if SEPDEBUG is true) is
-# copied to the .build-id directory.
-#
-# SUFFIX is appended to the .builid-id parent directory name to
-# keep all tests separate.
-# SYMLINK specifies whether build-id files should be copied or symlinked.
-# SHARED is a boolean indicating whether we are testing the shared
-# library core dump test case.
+# A convenience procedure to check if "info sharedlibrary" mentions the
+# files in the list EXPECTED_FILES.  Each filename in EXPECTED_FILES is an
+# absolute filename for a shared library GDB should have found when loading
+# the core file.
 
-proc locate_exec_from_core_build_id {corefile buildid \
-					 dirname progname \
+proc check_shlib_files { expected_files } {
+    set shlibs {}
+    set missing_shlib false
+    gdb_test_multiple "info sharedlibrary" "" {
+	-re "^info sharedlibrary\r\n" {
+	    exp_continue
+	}
+
+	-re "^From\\s+To\\s\[^\r\n\]+\r\n" {
+	    exp_continue
+	}
+
+	-re "^$::hex\\s+$::hex\\s+(?:Yes|No)\\s+(?:\\(\\*\\)\\s+)?(\[^\r\n\]+)\r\n" {
+	    set filename $expect_out(1,string)
+	    lappend shlibs $filename
+	    exp_continue
+	}
+
+	-re "^\\s+(?:Yes|No)\\s+(?:\\(\\*\\)\\s+)?(\[^\r\n\]+)\r\n" {
+	    # This shared library wasn't loaded correctly; GDB
+	    # probably failed to find the library file.  Don't add
+	    # this to the shlibs list as we want this library to
+	    # appear as missing.
+	    set missing_shlib true
+	    exp_continue
+	}
+
+	-re "^\\(\\*\\): Shared library is missing debugging information\\.\r\n" {
+	    exp_continue
+	}
+
+	-re "^$::gdb_prompt $" {
+	    # This proc is only called for the shared library test
+	    # case, in which case there should be at least two shared
+	    # libraries loaded.
+	    gdb_assert { !$missing_shlib && [llength $shlibs] >= 2 } \
+		$gdb_test_name
+	}
+    }
+
+    set count 0
+    foreach filename $expected_files {
+	incr count
+	gdb_assert { [lsearch -exact $shlibs $filename] != -1 } \
+	    "found shlib $count"
+    }
+}
+
+# Test whether gdb can find an exec file from a COREFILE's
+# build-id(s).  The basenames of the executable, and any shared
+# libraries built by this test script, that are used by the
+# executable, are in the list FILENAMES.  These files can all be found
+# in the directory DIRNAME.
+#
+# This function creates a debug directory and either copies, or
+# symlinks FILENAMES into the debug directory using the file's
+# build-id as the new filename within the newly created debug
+# directory.
+#
+# SEPDEBUG is a boolean, when true every file in FILENAMES has a
+# separate debug file, the same filename with ".debug" appended.  When
+# false, the debug information is contained within the file.
+#
+# SYMLINK is a boolean and indicates whether build-id files should be
+# copied or symlinked from DIRNAME.
+#
+# SHARED is a boolean indicating whether we are testing the shared
+# library core dump test case.  We carry out more checks when running
+# the shared library case, checking that GDB managed to load all the
+# shared libraries correctly too.
+
+proc locate_exec_from_core_build_id {corefile \
+					 dirname filenames \
 					 sepdebug symlink shared} {
     clean_restart
 
@@ -177,78 +254,130 @@ proc locate_exec_from_core_build_id {corefile buildid \
     } else {
 	set d "${d}_not-stripped"
     }
-
     set debugdir [standard_output_file $d]
-    remote_exec build \
-	"mkdir -p [file join $debugdir [file dirname $buildid]]"
 
-    set files_list {}
-    lappend files_list [file join $dirname [file tail $progname]] \
-	$buildid
-    if {$sepdebug} {
-	lappend files_list [file join $dirname [file tail $progname]].debug \
-	    "$buildid.debug"
-    }
+    # The following loop does two jobs.  The primary task is to either
+    # copy or symlink the files within DIRNAME into DEBUGDIR.  Within
+    # DEBUGDIR files are placed into a tree based on their build-id.
+    #
+    # As this loop is calculating build-ids anyway, the build-ids are
+    # recorded in the ALL_BUILDIDS list, retaining the order that
+    # items are found in FILENAMES.
+    set all_buildids {}
+    foreach filename $filenames {
+	# Get the build-id for FILENAME without ".debug" on the end.
+	# This will have the format: '.build-id/xx/xxxxx'
+	set buildid [build_id_debug_filename_get \
+			 [file join $dirname $filename] ""]
+	if {$buildid == ""} {
+	    untested "no build-id for $filename"
+	    return
+	}
+	verbose -log "build-id for $filename is $buildid"
+	lappend all_buildids $buildid
 
-    foreach {target name} $files_list {
-	set t [file join $dirname [file tail $target]]
-	if {$symlink} {
-	    remote_exec build "ln -s $t [file join $debugdir $name]"
-	} else {
-	    remote_exec build "cp $t [file join $debugdir $name]"
+	# Create the sub-directory of DEBUGDIR based on BUILDID.
+	remote_exec build \
+	    "mkdir -p [file join $debugdir [file dirname $buildid]]"
+
+	# Build a list of source target filename pairs.
+	set files_list {}
+	lappend files_list $filename $buildid
+	if {$sepdebug} {
+	    lappend files_list ${filename}.debug ${buildid}.debug
+	}
+
+	# Copy the source file from DIRNAME into DEBUGDIR.
+	foreach {target name} $files_list {
+	    set t [file join $dirname $target]
+	    set n [file join $debugdir $name]
+	    if {$symlink} {
+		remote_exec build "ln -s $t $n"
+	    } else {
+		remote_exec build "cp $t $n"
+	    }
 	}
     }
 
     # Append the debugdir to the separate debug directory search path.
     append_debug_dir $debugdir
 
+    # Load the core file.
     gdb_test "core-file $corefile" "Program terminated with .*" \
 	"load core file"
+
+    # What do we expect the name of the executable to appear as?
     if {$symlink} {
-	set expected_file [file join $dirname [file tail $progname]]
+	set expected_file [file join $dirname [lindex $filenames 0]]
     } else {
-	set expected_file $buildid
+	set expected_file [file join $debugdir [lindex $all_buildids 0]]
+    }
+    check_exec_file $expected_file
+
+    # Check that all of the expected shared libraries have been found.
+    if {$shared} {
+	if {$symlink} {
+	    set expected_files [lmap item [lrange $filenames 1 end] {
+		file join $dirname $item
+	    }]
+	} else {
+	    set expected_files [lmap item [lrange $all_buildids 1 end] {
+		file join $debugdir $item
+	    }]
+	}
+	check_shlib_files $expected_files
     }
-    check_exec_file [file join $debugdir $expected_file]
 }
 
 foreach_with_prefix mode { exec shared } {
-    # Build the executable.
-    set progname ${binfile}-$mode
+    # Build the executable and optionally, any shared libraries.
     set build_proc build_corefile_buildid_${mode}
-    if { ![$build_proc $progname] } {
-	return -1
+    set build_artefacts [$build_proc $mode]
+    if { [llength $build_artefacts] == 0 } {
+	return
     }
 
-    # Generate a corefile.
+    # Generate a corefile.  The executable is the first item in
+    # BUILD_ARTEFACTS.
+    set progname [lindex $build_artefacts 0]
     set corefile [create_core_file $progname]
     if { $corefile eq "" } {
-	return -1
-    }
-
-    # Get the build-id filename without ".debug" on the end.  This
-    # will have the format: '.build-id/xx/xxxxx'
-    set buildid [build_id_debug_filename_get $progname ""]
-    if {$buildid == ""} {
-	untested "binary has no build-id"
 	return
     }
-    verbose -log "build-id is $buildid"
 
-    # Create a directory for the non-stripped test.
-    set combined_dirname [standard_output_file ${mode}_non-stripped]
+    # Create a directory for the non-stripped test, copy every build
+    # artefact into this directory.
+    set combined_dirname [standard_output_file ${mode}_not-stripped]
     remote_exec build "mkdir -p $combined_dirname"
-    remote_exec build "cp $progname $combined_dirname"
-
-    # Create a directory for the stripped test.
-    if {[gdb_gnu_strip_debug [standard_output_file $progname] no-debuglink] != 0} {
-	untested "could not strip executable  for [join $suffix \ ]"
-	return
+    foreach filename $build_artefacts {
+	remote_exec build "cp $filename $combined_dirname"
     }
+
+    # Split the debug from each build artefact.
+    foreach filename $build_artefacts {
+	if {[gdb_gnu_strip_debug $filename no-debuglink] != 0} {
+	    untested "could not strip debug from [file tail $filename]"
+	    return
+	}
+    }
+
+    # Create a directory for the stripped test, move the now stripped
+    # binary, and the stripped out debug information for every build
+    # artefact, into this new directory.
     set sepdebug_dirname [standard_output_file ${mode}_stripped]
     remote_exec build "mkdir -p $sepdebug_dirname"
-    remote_exec build "mv $progname $sepdebug_dirname"
-    remote_exec build "mv ${progname}.debug $sepdebug_dirname"
+    foreach filename $build_artefacts {
+	remote_exec build "mv $filename $sepdebug_dirname"
+	remote_exec build "mv ${filename}.debug $sepdebug_dirname"
+    }
+
+    # The build artefacts are all absolute filenames, but now they
+    # have been copied or moved into the two holding areas created
+    # above, it is more useful to have BUILD_ARTEFACTS contain just
+    # the basenames.  Update the list now.
+    set build_artefacts [lmap filename $build_artefacts {
+	file tail $filename
+    }]
 
     # Now do the actual testing part.  Fill out a debug directory with
     # build-id related files (copies or symlinks) and then load the
@@ -262,9 +391,9 @@ foreach_with_prefix mode { exec shared } {
 	}
 
 	foreach_with_prefix symlink { false true } {
-	    locate_exec_from_core_build_id $corefile $buildid \
-		$dirname $progname \
-		$sepdebug $symlink [expr {$mode eq "shared"}]
+	    locate_exec_from_core_build_id $corefile $dirname \
+		$build_artefacts $sepdebug $symlink \
+		[expr {$mode eq "shared"}]
 	}
     }
 }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 3/5] gdb: remove 'num' argument from gdbarch_read_core_file_mappings callback
  2026-03-14 14:32 [PATCH 0/5] Improved support for core files with missing NT_FILE note Andrew Burgess
  2026-03-14 14:32 ` [PATCH 1/5] gdb/testsuite: improve corefile-no-threads.py script Andrew Burgess
  2026-03-14 14:32 ` [PATCH 2/5] gdb/testsuite: restructure gdb.base/corefile-buildid.exp Andrew Burgess
@ 2026-03-14 14:32 ` Andrew Burgess
  2026-03-14 14:32 ` [PATCH 4/5] gdb: remove pre-loop callback from gdbarch_read_core_file_mappings Andrew Burgess
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Andrew Burgess @ 2026-03-14 14:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

The gdbarch_read_core_file_mappings method takes two callback
functions.  The second of these, the loop_cb takes a 'num' parameter
that is never used.  It's not entirely clear what this 'num'
represents, and in later commits I'm going to be tweaking what gets
sent through this callback, and it's not clear to me how 'num' should
be changed.

So let's just remove the 'num' argument, this will make the later
commits easier.
---
 gdb/corelow.c    | 2 +-
 gdb/gdbarch.h    | 3 +--
 gdb/linux-tdep.c | 7 +++----
 3 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/gdb/corelow.c b/gdb/corelow.c
index 84f44bc680e..e540e44474b 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -2138,7 +2138,7 @@ gdb_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd)
 
     /* read_core_file_mappings will invoke this lambda for each mapping
        that it finds.  */
-    [&] (int num, ULONGEST start, ULONGEST end, ULONGEST file_ofs,
+    [&] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
 	 const char *filename, const bfd_build_id *build_id)
       {
 	/* Architecture-specific read_core_mapping methods are expected to
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index fe59846b916..356aae0fee8 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -127,8 +127,7 @@ using read_core_file_mappings_pre_loop_ftype =
   gdb::function_view<void (ULONGEST count)>;
 
 using read_core_file_mappings_loop_ftype =
-  gdb::function_view<void (int num,
-			   ULONGEST start,
+  gdb::function_view<void (ULONGEST start,
 			   ULONGEST end,
 			   ULONGEST file_ofs,
 			   const char *filename,
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 2bf35a2fe00..45606015579 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -1237,7 +1237,6 @@ linux_read_core_file_mappings
   /* Vector to collect proc mappings.  */
   struct proc_mapping
   {
-    int num;
     ULONGEST start;
     ULONGEST end;
     ULONGEST file_ofs;
@@ -1249,7 +1248,7 @@ linux_read_core_file_mappings
   /* Collect proc mappings.  */
   for (int i = 0; i < count; i++)
     {
-      struct proc_mapping m = { .num = i };
+      struct proc_mapping m;
       m.start = bfd_get (addr_size_bits, cbfd, descdata);
       descdata += addr_size;
       m.end = bfd_get (addr_size_bits, cbfd, descdata);
@@ -1278,7 +1277,7 @@ linux_read_core_file_mappings
   for (int i = 0; i < count; i++)
     {
       const auto &m = proc_mappings[i];
-      loop_cb (m.num, m.start, m.end, m.file_ofs, m.filename, m.build_id);
+      loop_cb (m.start, m.end, m.file_ofs, m.filename, m.build_id);
     }
 }
 
@@ -1303,7 +1302,7 @@ linux_core_info_proc_mappings (struct gdbarch *gdbarch, struct bfd *cbfd,
 	current_uiout->table_header (0, ui_left, "objfile", "File");
 	current_uiout->table_body ();
       },
-    [=] (int num, ULONGEST start, ULONGEST end, ULONGEST file_ofs,
+    [=] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
 	 const char *filename, const bfd_build_id *build_id)
       {
 	ui_out_emit_tuple tuple_emitter (current_uiout, nullptr);
-- 
2.25.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 4/5] gdb: remove pre-loop callback from gdbarch_read_core_file_mappings
  2026-03-14 14:32 [PATCH 0/5] Improved support for core files with missing NT_FILE note Andrew Burgess
                   ` (2 preceding siblings ...)
  2026-03-14 14:32 ` [PATCH 3/5] gdb: remove 'num' argument from gdbarch_read_core_file_mappings callback Andrew Burgess
@ 2026-03-14 14:32 ` Andrew Burgess
  2026-03-14 14:32 ` [PATCH 5/5] gdb/linux: handle missing NT_FILE note when opening core files Andrew Burgess
  2026-04-23 22:06 ` [PATCH 0/5] Improved support for core files with missing NT_FILE note Keith Seitz
  5 siblings, 0 replies; 9+ messages in thread
From: Andrew Burgess @ 2026-03-14 14:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Currently only one target, Linux, implements
gdbarch_read_core_file_mappings, with
linux_read_core_file_mappings. There is one use of
gdbarch_read_core_file_mappings in corelow.c, and one direct use of
linux_read_core_file_mappings in linux-tdep.c.

The gdbarch_read_core_file_mappings takes two callbacks, a pre-loop
callback, which is called once, then a loop callback which is called
multiple times for each mapping that is discovered.

The only user of the pre-loop callback is in linux-tdep.c.  Within
corelow.c, the pre-loop callback is not used.

In the next commit I plan to change linux_read_core_file_mappings, and
as a result of this change the use of linux_read_core_file_mappings in
linux-tdep.c will no longer be able to make use of the pre-loop
callback.  This means that, after the next commit, there will be no
users of the pre-loop callback.

Additionally, the pre-loop callback takes an argument, the number of
mappings found.

After the next commit it is no longer clear what number we should pass
here as the next commit will introduce the idea of there being two
types of mapping, anonymous and non-anonymous.  Should the number
passed to the pre-loop callback be the combined total?  Or should we
count each separately?

I could try to answer this question.

Or I could just delete the pre-loop callback from
gdbarch_read_core_file_mappings.

This commit takes the second approach and deletes the callback.

As part of this work I've updated linux_core_info_proc_mappings, which
is the function that calls linux_read_core_file_mappings, so that the
pre-loop callback is no longer used.  The lambda capture on the loop
callback needed to change from [=] to [&] with this commit so
`emitter` from the enclosing scope can be modified.

There is one subtle change of behaviour in
linux_core_info_proc_mappings after this commit.  Previously,
linux_core_info_proc_mappings would print the table header so long as
the core file had a valid NT_FILE note, even if that note contained no
actual file mappings.

With the removal of the pre-loop callback I had a choice, either
always print the table header, or only print the table header if I saw
some entries being printed.  I selected the second choice as that
seemed like the smallest change, but there is a change here.  If a
user has a core file with an NT_FILE note containing no mapped files,
then the table header will no longer be printed.  Hopefully this isn't
too disruptive.

This is a refactoring commit in preparation for the next one.
---
 gdb/arch-utils.c          |  1 -
 gdb/arch-utils.h          |  1 -
 gdb/corelow.c             | 10 ++--------
 gdb/gdbarch-gen.c         |  4 ++--
 gdb/gdbarch-gen.h         |  4 ++--
 gdb/gdbarch.h             |  5 +----
 gdb/gdbarch_components.py |  1 -
 gdb/linux-tdep.c          | 41 +++++++++++++++------------------------
 8 files changed, 23 insertions(+), 44 deletions(-)

diff --git a/gdb/arch-utils.c b/gdb/arch-utils.c
index 9d3dbfa2d81..f649cfae82c 100644
--- a/gdb/arch-utils.c
+++ b/gdb/arch-utils.c
@@ -1077,7 +1077,6 @@ void
 default_read_core_file_mappings
   (struct gdbarch *gdbarch,
    struct bfd *cbfd,
-   read_core_file_mappings_pre_loop_ftype pre_loop_cb,
    read_core_file_mappings_loop_ftype loop_cb)
 {
 }
diff --git a/gdb/arch-utils.h b/gdb/arch-utils.h
index 670136c9cac..7d16b219106 100644
--- a/gdb/arch-utils.h
+++ b/gdb/arch-utils.h
@@ -375,7 +375,6 @@ extern std::string default_get_pc_address_flags (const frame_info_ptr &frame,
 extern void default_read_core_file_mappings
   (struct gdbarch *gdbarch,
    struct bfd *cbfd,
-   read_core_file_mappings_pre_loop_ftype pre_loop_cb,
    read_core_file_mappings_loop_ftype loop_cb);
 
 /* Default implementation of gdbarch_core_parse_exec_context.  Returns
diff --git a/gdb/corelow.c b/gdb/corelow.c
index e540e44474b..d63dd5f8f0d 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -2130,14 +2130,8 @@ gdb_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd)
   /* See linux_read_core_file_mappings() in linux-tdep.c for an example
      read_core_file_mappings method.  */
   gdbarch_read_core_file_mappings (gdbarch, cbfd,
-    /* After determining the number of mappings, read_core_file_mappings
-       will invoke this lambda.  */
-    [&] (ULONGEST)
-      {
-      },
-
-    /* read_core_file_mappings will invoke this lambda for each mapping
-       that it finds.  */
+    /* gdbarch_read_core_file_mappings will invoke this lambda for each
+       mapping that it finds.  */
     [&] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
 	 const char *filename, const bfd_build_id *build_id)
       {
diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c
index 573f6eefc96..f3a038b4a51 100644
--- a/gdb/gdbarch-gen.c
+++ b/gdb/gdbarch-gen.c
@@ -5202,13 +5202,13 @@ set_gdbarch_get_pc_address_flags (struct gdbarch *gdbarch,
 }
 
 void
-gdbarch_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd, read_core_file_mappings_pre_loop_ftype pre_loop_cb, read_core_file_mappings_loop_ftype loop_cb)
+gdbarch_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd, read_core_file_mappings_loop_ftype loop_cb)
 {
   gdb_assert (gdbarch != NULL);
   gdb_assert (gdbarch->read_core_file_mappings != NULL);
   if (gdbarch_debug >= 2)
     gdb_printf (gdb_stdlog, "gdbarch_read_core_file_mappings called\n");
-  gdbarch->read_core_file_mappings (gdbarch, cbfd, pre_loop_cb, loop_cb);
+  gdbarch->read_core_file_mappings (gdbarch, cbfd, loop_cb);
 }
 
 void
diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h
index 1ff02b1067c..dbc0a070b7c 100644
--- a/gdb/gdbarch-gen.h
+++ b/gdb/gdbarch-gen.h
@@ -1691,8 +1691,8 @@ extern void set_gdbarch_get_pc_address_flags (struct gdbarch *gdbarch, gdbarch_g
 
 /* Read core file mappings */
 
-typedef void (gdbarch_read_core_file_mappings_ftype) (struct gdbarch *gdbarch, struct bfd *cbfd, read_core_file_mappings_pre_loop_ftype pre_loop_cb, read_core_file_mappings_loop_ftype loop_cb);
-extern void gdbarch_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd, read_core_file_mappings_pre_loop_ftype pre_loop_cb, read_core_file_mappings_loop_ftype loop_cb);
+typedef void (gdbarch_read_core_file_mappings_ftype) (struct gdbarch *gdbarch, struct bfd *cbfd, read_core_file_mappings_loop_ftype loop_cb);
+extern void gdbarch_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd, read_core_file_mappings_loop_ftype loop_cb);
 extern void set_gdbarch_read_core_file_mappings (struct gdbarch *gdbarch, gdbarch_read_core_file_mappings_ftype *read_core_file_mappings);
 
 /* Return true if the target description for all threads should be read from the
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 356aae0fee8..7d0f46ab8e2 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -121,10 +121,7 @@ enum class memtag_type
   allocation,
 };
 
-/* Callback types for 'read_core_file_mappings' gdbarch method.  */
-
-using read_core_file_mappings_pre_loop_ftype =
-  gdb::function_view<void (ULONGEST count)>;
+/* Callback type for 'read_core_file_mappings' gdbarch method.  */
 
 using read_core_file_mappings_loop_ftype =
   gdb::function_view<void (ULONGEST start,
diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py
index 46283c1bd7d..e15163957ee 100644
--- a/gdb/gdbarch_components.py
+++ b/gdb/gdbarch_components.py
@@ -2717,7 +2717,6 @@ Read core file mappings
     name="read_core_file_mappings",
     params=[
         ("struct bfd *", "cbfd"),
-        ("read_core_file_mappings_pre_loop_ftype", "pre_loop_cb"),
         ("read_core_file_mappings_loop_ftype", "loop_cb"),
     ],
     predefault="default_read_core_file_mappings",
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 45606015579..0eaf9597ad8 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -1134,11 +1134,6 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
 
    CBFD is the BFD of the core file.
 
-   PRE_LOOP_CB is the callback function to invoke prior to starting
-   the loop which processes individual entries.  This callback will
-   only be executed after the note has been examined in enough
-   detail to verify that it's not malformed in some way.
-
    LOOP_CB is the callback function that will be executed once
    for each mapping.  */
 
@@ -1146,7 +1141,6 @@ static void
 linux_read_core_file_mappings
   (struct gdbarch *gdbarch,
    struct bfd *cbfd,
-   read_core_file_mappings_pre_loop_ftype pre_loop_cb,
    read_core_file_mappings_loop_ftype  loop_cb)
 {
   /* Ensure that ULONGEST is big enough for reading 64-bit core files.  */
@@ -1232,7 +1226,6 @@ linux_read_core_file_mappings
     }
 
   cbfd->build_id = orig_build_id;
-  pre_loop_cb (count);
 
   /* Vector to collect proc mappings.  */
   struct proc_mapping
@@ -1274,11 +1267,8 @@ linux_read_core_file_mappings
 	       });
 
   /* Call loop_cb with sorted proc mappings.  */
-  for (int i = 0; i < count; i++)
-    {
-      const auto &m = proc_mappings[i];
-      loop_cb (m.start, m.end, m.file_ofs, m.filename, m.build_id);
-    }
+  for (const auto &m : proc_mappings)
+    loop_cb (m.start, m.end, m.file_ofs, m.filename, m.build_id);
 }
 
 /* Implement "info proc mappings" for corefile CBFD.  */
@@ -1290,21 +1280,22 @@ linux_core_info_proc_mappings (struct gdbarch *gdbarch, struct bfd *cbfd,
   std::optional<ui_out_emit_table> emitter;
 
   linux_read_core_file_mappings (gdbarch, cbfd,
-    [&] (ULONGEST count)
-      {
-	gdb_printf (_("Mapped address spaces:\n\n"));
-	emitter.emplace (current_uiout, 5, -1, "ProcMappings");
-	int width = gdbarch_addr_bit (gdbarch) == 32 ? 10 : 18;
-	current_uiout->table_header (width, ui_left, "start", "Start Addr");
-	current_uiout->table_header (width, ui_left, "end", "End Addr");
-	current_uiout->table_header (width, ui_left, "size", "Size");
-	current_uiout->table_header (width, ui_left, "offset", "Offset");
-	current_uiout->table_header (0, ui_left, "objfile", "File");
-	current_uiout->table_body ();
-      },
-    [=] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
+    [&] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
 	 const char *filename, const bfd_build_id *build_id)
       {
+	if (!emitter.has_value ())
+	  {
+	    gdb_printf (_("Mapped address spaces:\n\n"));
+	    emitter.emplace (current_uiout, 5, -1, "ProcMappings");
+	    int width = gdbarch_addr_bit (gdbarch) == 32 ? 10 : 18;
+	    current_uiout->table_header (width, ui_left, "start", "Start Addr");
+	    current_uiout->table_header (width, ui_left, "end", "End Addr");
+	    current_uiout->table_header (width, ui_left, "size", "Size");
+	    current_uiout->table_header (width, ui_left, "offset", "Offset");
+	    current_uiout->table_header (0, ui_left, "objfile", "File");
+	    current_uiout->table_body ();
+	  }
+
 	ui_out_emit_tuple tuple_emitter (current_uiout, nullptr);
 	current_uiout->field_core_addr ("start", gdbarch, start);
 	current_uiout->field_core_addr ("end", gdbarch, end);
-- 
2.25.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 5/5] gdb/linux: handle missing NT_FILE note when opening core files
  2026-03-14 14:32 [PATCH 0/5] Improved support for core files with missing NT_FILE note Andrew Burgess
                   ` (3 preceding siblings ...)
  2026-03-14 14:32 ` [PATCH 4/5] gdb: remove pre-loop callback from gdbarch_read_core_file_mappings Andrew Burgess
@ 2026-03-14 14:32 ` Andrew Burgess
  2026-04-23 22:08   ` Keith Seitz
  2026-04-23 22:06 ` [PATCH 0/5] Improved support for core files with missing NT_FILE note Keith Seitz
  5 siblings, 1 reply; 9+ messages in thread
From: Andrew Burgess @ 2026-03-14 14:32 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This patch originated from this mailing list discussion:

  https://inbox.sourceware.org/gdb-patches/b9b5bf03c59b58e02ca27b522338c6103d5ae49f.camel@gnu.org

The user has some core files which lack an NT_FILE note.  They
wondered why GDB was still unable to find the shared libraries based
on their build-id.

The reason right now is that GDB only records the build-id information
for mappings based on the entries in the NT_FILE note.  With the
entries in this note we build several lookup tables; a filename to
build-id table, a soname (extracted from the file if it is a shared
library) to build-id table, and an address range to build-id table.

When a shared library is being loaded we perform a lookup using two
pieces of information; the shared library's filename, and an address
that we know is within the shared library.  If either of these give a
build-id, then we can use that build-id to ensure GDB loads the
shared library that matches the core file.

If the NT_FILE note is missing then none of the lookup tables are
created, and so the shared library build-id lookup fails, meaning that
all GDB can do is look for the shared library by name on the local
file system.  This often results in the wrong library version being
loaded, or the library not being found at all.

However, Linux core files also have the segment table.  This table
gives address ranges.  The segment table doesn't tell us what file was
mapped in, or the offset within the file that was mapped in.  But if
we go back to the three lookup tables, we can use the segment table to
build the address to build-id lookup table, and that would be enough
to allow GDB to find the build-id for a shared library in most cases.

So, here's what this patch does: linux_read_core_file_mappings (in
linux-tdep.c) is updated to first parse the NT_FILE note as it
currently does.  But after this we also walk the segment table (BFD
actually converts these into sections with the LOAD flag set), and if
a segment has a build-id, and doesn't correspond to an entry found in
the NT_FILE note, we create an anonymous mapping.  An anonymous
mapping is just like a mapping from the NT_FILE note, but without a
filename and file offset.  This mapping is passed through the callback
just like the traditional, non-anonymous, mappings.

Then in corelow.c various functions are updated in order to handle
anonymous mappings.

Back in linux-tdep.c, function linux_core_info_proc_mappings gets a
small update to handle anonymous mappings.

The corefile-buildid.exp test is updated to remove the NT_FILE notes
and rerun the tests.  This should make no difference as all this test
is checking is that GDB is able to find and load the shared libraries
and executable based on their build-ids; this is something we can do
fine now without the NT_FILE note.

I have also had to update the Python core file API documentation after
this commit.  Previously we claimed that CorefileMappedFile.filename
would never be empty, but this is now possible.  Luckily, this API has
not yet been in a released version of GDB, so this minor tweak isn't
going to break any existing user code.  I did consider having
CorefileMappedFile.filename be a non-empty string or None, but I
couldn't see much value in this, so I just documented that the string
could be empty, and what this means.

The py-corefile.exp test needed a minor update to filter out anonymous
mappings (those without a filename), this matches the behaviour of the
builtin 'info proc mappings' command.
---
 gdb/corelow.c                               |  56 ++--
 gdb/doc/python.texi                         |  10 +-
 gdb/gdbcore.h                               |  28 +-
 gdb/linux-tdep.c                            | 304 +++++++++++++-------
 gdb/testsuite/gdb.base/corefile-buildid.exp |  27 ++
 gdb/testsuite/gdb.python/py-corefile.py     |   4 +
 6 files changed, 295 insertions(+), 134 deletions(-)

diff --git a/gdb/corelow.c b/gdb/corelow.c
index d63dd5f8f0d..d1412c4903a 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -405,12 +405,25 @@ core_target::build_file_mappings ()
   gdb::unordered_string_map<struct bfd *> bfd_map;
   gdb::unordered_set<std::string> unavailable_paths;
 
-  /* All files mapped into the core file.  The key is the filename.  */
+  /* All files mapped into the core file.  */
   std::vector<core_mapped_file> mapped_files
     = gdb_read_core_file_mappings (m_core_gdbarch, this->core_bfd ());
 
   for (const core_mapped_file &file_data : mapped_files)
     {
+      /* A mapping without a filename can still have a build-id.  Tracking
+	 these mappings is useful as when the shared libraries are loaded,
+	 if the shared library is within this anonymous region, we can
+	 validate the build-id of the shared library being loaded.  */
+      if (file_data.filename.empty ())
+	{
+	  gdb_assert (file_data.build_id != nullptr);
+	  std::vector<mem_range> ranges = file_data.mem_ranges ();
+	  m_mapped_file_info.add (nullptr, nullptr, nullptr,
+				  std::move (ranges), file_data.build_id);
+	  continue;
+	}
+
       /* If this mapped file is marked as the main executable then record
 	 the filename as we can use this later.  */
       if (file_data.is_main_exec && m_expected_exec_filename.empty ())
@@ -476,10 +489,6 @@ core_target::build_file_mappings ()
 	    }
 	}
 
-      std::vector<mem_range> ranges;
-      for (const core_mapped_file::region &region : file_data.regions)
-	ranges.emplace_back (region.start, region.end - region.start);
-
       if (expanded_fname == nullptr
 	  || abfd == nullptr
 	  || !bfd_check_format (abfd.get (), bfd_object))
@@ -592,7 +601,7 @@ core_target::build_file_mappings ()
 	 libraries.  */
       if (file_data.build_id != nullptr)
 	{
-	  normalize_mem_ranges (&ranges);
+	  std::vector<mem_range> ranges = file_data.mem_ranges ();
 
 	  const char *actual_filename = nullptr;
 	  gdb::unique_xmalloc_ptr<char> soname;
@@ -1963,7 +1972,6 @@ mapped_file_info::add (const char *soname,
 		       const bfd_build_id *build_id)
 {
   gdb_assert (build_id != nullptr);
-  gdb_assert (expected_filename != nullptr);
 
   if (soname != nullptr)
     {
@@ -1983,13 +1991,17 @@ mapped_file_info::add (const char *soname,
 	m_soname_to_build_id_map[soname] = build_id;
     }
 
-  /* When the core file is initially opened and the mapped files are
-     parsed, we group the build-id information based on the file name.  As
-     a consequence, we should see each EXPECTED_FILENAME value exactly
-     once.  This means that each insertion should always succeed.  */
-  const auto inserted
-    = m_filename_to_build_id_map.emplace (expected_filename, build_id).second;
-  gdb_assert (inserted);
+  /* Ignore empty filenames.  */
+  if (expected_filename != nullptr)
+    {
+      /* When the core file is initially opened and the mapped files are
+	 parsed, we group the build-id information based on the file name.  As
+	 a consequence, we should see each EXPECTED_FILENAME value exactly
+	 once.  This means that each insertion should always succeed.  */
+      const auto inserted
+	= m_filename_to_build_id_map.emplace (expected_filename, build_id).second;
+      gdb_assert (inserted);
+    }
 
   /* Setup the reverse build-id to file name map.  */
   if (actual_filename != nullptr)
@@ -2135,9 +2147,19 @@ gdb_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd)
     [&] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
 	 const char *filename, const bfd_build_id *build_id)
       {
-	/* Architecture-specific read_core_mapping methods are expected to
-	   weed out non-file-backed mappings.  */
-	gdb_assert (filename != nullptr);
+	if (filename == nullptr)
+	  {
+	    /* A mapping without a filename MUST have a build-id, and the
+	       file_ofs MUST be 0, but the file_ofs is actually meaningless
+	       in this case.  */
+	    gdb_assert (build_id != nullptr);
+	    gdb_assert (file_ofs == 0);
+
+	    results.emplace_back ();
+	    results.back ().build_id = build_id;
+	    results.back ().regions.emplace_back (start, end, file_ofs);
+	    return;
+	  }
 
 	/* Add this mapped region to the data for FILENAME.  */
 	auto iter = mapped_files.find (filename);
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 2df3b7c0423..6653601f4e2 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -9016,8 +9016,10 @@ Core Files In Python
 A @code{gdb.CorefileMappedFile} object has the following attributes:
 
 @defvar CorefileMappedFile.filename
-This read only attribute contains a non-empty string, the file name of
-the mapped file.
+This read only attribute contains a string, the file name of the
+mapped file.  This string can be empty if @value{GDBN} was unable to
+find the name of the mapped file.  If this string is empty then
+@code{CorefileMappedFile.build_id} will not be @code{None}.
 @end defvar
 
 @defvar CorefileMappedFile.build_id
@@ -9057,7 +9059,9 @@ Core Files In Python
 
 @defvar CorefileMappedFileRegion.file_offset
 This read only attribute contains the offset within the mapped file
-for this mapping.
+for this mapping.  This attribute will be @code{0} if the containing
+@code{CorefileMappedFile.filename} is empty, in which case this
+attribute has no meaning.
 @end defvar
 
 @node Python Auto-loading
diff --git a/gdb/gdbcore.h b/gdb/gdbcore.h
index 89586e0c6ec..3fd91770c54 100644
--- a/gdb/gdbcore.h
+++ b/gdb/gdbcore.h
@@ -261,7 +261,9 @@ core_target_find_mapped_file (const char *filename,
 
 /* Type holding information about a single file mapped into the inferior
    at the point when the core file was created.  Associates a build-id
-   with the list of regions the file is mapped into.  */
+   with the list of regions the file is mapped into.  It is acceptable to
+   have a core_mapped_file with an empty filename, so long as we have a
+   build-id for the mapping.  */
 struct core_mapped_file
 {
   /* Type for a region of a file that was mapped into the inferior when
@@ -281,15 +283,22 @@ struct core_mapped_file
     /* The inferior address immediately after the mapped region.  */
     CORE_ADDR end;
 
-    /* The offset within the mapped file for this content.  */
+    /* The offset within the mapped file for this content.  If the
+       filename of the mapping is empty then this field will be 0, but has
+       no meaning.  */
     CORE_ADDR file_ofs;
   };
 
-  /* The filename as recorded in the core file.  */
+  /* The filename as recorded in the core file.  This can be empty meaning
+     that GDB was unable to find a filename for this mapping.  If the
+     filename is empty then the build_id field MUST be non-NULL.  If this
+     is empty then the region::file_ofs fields will all be 0, and have no
+     meaning.  */
   std::string filename;
 
   /* If not nullptr, then this is the build-id associated with this
-     file.  */
+     mapping.  If the filename field is empty, then this MUST be
+     non-NULL.  */
   const bfd_build_id *build_id = nullptr;
 
   /* All the mapped regions of this file.  */
@@ -297,6 +306,17 @@ struct core_mapped_file
 
   /* True if this is the main executable.  */
   bool is_main_exec = false;
+
+  /* Convert the REGIONS to a vector of mem_range objects.  */
+  std::vector<mem_range>
+  mem_ranges () const
+  {
+    std::vector<mem_range> ranges;
+    for (const core_mapped_file::region &region : this->regions)
+      ranges.emplace_back (region.start, region.end - region.start);
+    normalize_mem_ranges (&ranges);
+    return ranges;
+  }
 };
 
 extern std::vector<core_mapped_file> gdb_read_core_file_mappings
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 0eaf9597ad8..b9bfa80af4d 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -1116,6 +1116,33 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
     }
 }
 
+/* Return a map from the start address of any LOAD segment in CBFD to the
+   associated build-id.  The map only contains entries for those segments
+   where a build-id is found, so the build-id will never be NULL, but not
+   every segment will have an entry in the map.  */
+
+static gdb::unordered_map<CORE_ADDR, const bfd_build_id *>
+linux_read_build_ids_from_core_file_mappings (struct bfd *cbfd)
+{
+  gdb::unordered_map<CORE_ADDR, const bfd_build_id *> vma_to_build_id_map;
+
+  auto restore_cbfd_build_id = make_scoped_restore (&cbfd->build_id);
+
+  /* Search for solib build-ids in the core file.  Each time one is found,
+     map the start vma of the corresponding elf header to the build-id.  */
+  for (bfd_section *sec = cbfd->sections; sec != nullptr; sec = sec->next)
+    {
+      cbfd->build_id = nullptr;
+
+      if (sec->flags & SEC_LOAD
+	  && (get_elf_backend_data (cbfd)->elf_backend_core_find_build_id
+	      (cbfd, (bfd_vma) sec->filepos)))
+	vma_to_build_id_map[sec->vma] = cbfd->build_id;
+    }
+
+  return vma_to_build_id_map;
+}
+
 /* Implementation of `gdbarch_read_core_file_mappings', as defined in
    gdbarch.h.
 
@@ -1132,103 +1159,39 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
 	long file_ofs
       followed by COUNT filenames in ASCII: "FILE1" NUL "FILE2" NUL...
 
+   In addition to reading the NT_FILE note, this function also considers
+   all of the LOADable segments (which BFD turns into sections).  If a
+   segment is loadable, has a build-id, and isn't covered by an NT_FILE
+   entry, then we consider it an anonymous mapping.  Tracking these is
+   useful when we load shared libraries.  If a shared library corresponds
+   to an anonymous mapping then we can validate the build-id of the shared
+   library.  This is useful when core files are created without an NT_FILE
+   note.
+
    CBFD is the BFD of the core file.
 
    LOOP_CB is the callback function that will be executed once
    for each mapping.  */
 
 static void
-linux_read_core_file_mappings
-  (struct gdbarch *gdbarch,
-   struct bfd *cbfd,
-   read_core_file_mappings_loop_ftype  loop_cb)
+linux_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd,
+			       read_core_file_mappings_loop_ftype  loop_cb)
 {
   /* Ensure that ULONGEST is big enough for reading 64-bit core files.  */
   static_assert (sizeof (ULONGEST) >= 8);
 
-  /* It's not required that the NT_FILE note exists, so return silently
-     if it's not found.  Beyond this point though, we'll complain
-     if problems are found.  */
-  asection *section = bfd_get_section_by_name (cbfd, ".note.linuxcore.file");
-  if (section == nullptr)
-    return;
+  /* Map from the start address of LOAD segments to the associated
+     build-id, but only for segments that have a build-id.  */
+  gdb::unordered_map<CORE_ADDR, const bfd_build_id *> vma_map
+    = linux_read_build_ids_from_core_file_mappings (cbfd);
 
-  unsigned int addr_size_bits = gdbarch_addr_bit (gdbarch);
-  unsigned int addr_size = addr_size_bits / 8;
-  size_t note_size = bfd_section_size (section);
+  /* Track the start address of each mapping found.  This allows us to
+     quickly drop duplicates when processing the NT_FILE note then the LOAD
+     segments.  */
+  gdb::unordered_set<CORE_ADDR> mapping_start_addresses;
 
-  if (note_size < 2 * addr_size)
-    {
-      warning (_("malformed core note - too short for header"));
-      return;
-    }
-
-  gdb::byte_vector contents (note_size);
-  if (!bfd_get_section_contents (cbfd, section, contents.data (), 0,
-				 note_size))
-    {
-      warning (_("could not get core note contents"));
-      return;
-    }
-
-  gdb_byte *descdata = contents.data ();
-  char *descend = (char *) descdata + note_size;
-
-  if (descdata[note_size - 1] != '\0')
-    {
-      warning (_("malformed note - does not end with \\0"));
-      return;
-    }
-
-  ULONGEST count = bfd_get (addr_size_bits, cbfd, descdata);
-  descdata += addr_size;
-
-  ULONGEST page_size = bfd_get (addr_size_bits, cbfd, descdata);
-  descdata += addr_size;
-
-  if (note_size < 2 * addr_size + count * 3 * addr_size)
-    {
-      warning (_("malformed note - too short for supplied file count"));
-      return;
-    }
-
-  char *filenames = (char *) descdata + count * 3 * addr_size;
-
-  /* Make sure that the correct number of filenames exist.  Complain
-     if there aren't enough or are too many.  */
-  char *f = filenames;
-  for (int i = 0; i < count; i++)
-    {
-      if (f >= descend)
-	{
-	  warning (_("malformed note - filename area is too small"));
-	  return;
-	}
-      f += strnlen (f, descend - f) + 1;
-    }
-  /* Complain, but don't return early if the filename area is too big.  */
-  if (f != descend)
-    warning (_("malformed note - filename area is too big"));
-
-  const bfd_build_id *orig_build_id = cbfd->build_id;
-  gdb::unordered_map<ULONGEST, const bfd_build_id *> vma_map;
-
-  /* Search for solib build-ids in the core file.  Each time one is found,
-     map the start vma of the corresponding elf header to the build-id.  */
-  for (bfd_section *sec = cbfd->sections; sec != nullptr; sec = sec->next)
-    {
-      cbfd->build_id = nullptr;
-
-      if (sec->flags & SEC_LOAD
-	  && (get_elf_backend_data (cbfd)->elf_backend_core_find_build_id
-	       (cbfd, (bfd_vma) sec->filepos)))
-	vma_map[sec->vma] = cbfd->build_id;
-    }
-
-  cbfd->build_id = orig_build_id;
-
-  /* Vector to collect proc mappings.  */
-  struct proc_mapping
+  /* Vector to collect all mappings.  */
+  struct a_mapping
   {
     ULONGEST start;
     ULONGEST end;
@@ -1236,38 +1199,155 @@ linux_read_core_file_mappings
     const char *filename;
     const bfd_build_id *build_id;
   };
-  std::vector<struct proc_mapping> proc_mappings;
+  std::vector<struct a_mapping> all_mappings;
 
-  /* Collect proc mappings.  */
-  for (int i = 0; i < count; i++)
+  /* If we find the NT_FILE section below then we need to load its
+     contents, and those contents need to remain live until the end of this
+     function.  This vector will hold the contents.  */
+  gdb::byte_vector contents;
+
+  /* It's not required that the NT_FILE note exists, so return silently
+     if it's not found.  Beyond this point though, we'll complain
+     if problems are found.  */
+  asection *section = bfd_get_section_by_name (cbfd, ".note.linuxcore.file");
+  if (section != nullptr)
     {
-      struct proc_mapping m;
-      m.start = bfd_get (addr_size_bits, cbfd, descdata);
-      descdata += addr_size;
-      m.end = bfd_get (addr_size_bits, cbfd, descdata);
-      descdata += addr_size;
-      m.file_ofs = bfd_get (addr_size_bits, cbfd, descdata) * page_size;
-      descdata += addr_size;
-      m.filename = filenames;
-      filenames += strlen ((char *) filenames) + 1;
+      unsigned int addr_size_bits = gdbarch_addr_bit (gdbarch);
+      unsigned int addr_size = addr_size_bits / 8;
+      size_t note_size = bfd_section_size (section);
 
-      m.build_id = nullptr;
-      auto vma_map_it = vma_map.find (m.start);
-      if (vma_map_it != vma_map.end ())
-	m.build_id = vma_map_it->second;
+      if (note_size < 2 * addr_size)
+	{
+	  warning (_("malformed core note - too short for header"));
+	  return;
+	}
 
-      proc_mappings.push_back (m);
+      contents.resize (note_size);
+      if (!bfd_get_section_contents (cbfd, section, contents.data (), 0,
+				     note_size))
+	{
+	  warning (_("could not get core note contents"));
+	  return;
+	}
+
+      gdb_byte *descdata = contents.data ();
+      char *descend = (char *) descdata + note_size;
+
+      if (descdata[note_size - 1] != '\0')
+	{
+	  warning (_("malformed note - does not end with \\0"));
+	  return;
+	}
+
+      ULONGEST count = bfd_get (addr_size_bits, cbfd, descdata);
+      descdata += addr_size;
+
+      ULONGEST page_size = bfd_get (addr_size_bits, cbfd, descdata);
+      descdata += addr_size;
+
+      if (note_size < 2 * addr_size + count * 3 * addr_size)
+	{
+	  warning (_("malformed note - too short for supplied file count"));
+	  return;
+	}
+
+      char *filenames = (char *) descdata + count * 3 * addr_size;
+
+      /* Make sure that the correct number of filenames exist.  Complain
+	 if there aren't enough or are too many.  */
+      char *f = filenames;
+      for (int i = 0; i < count; i++)
+	{
+	  if (f >= descend)
+	    {
+	      warning (_("malformed note - filename area is too small"));
+	      return;
+	    }
+	  f += strnlen (f, descend - f) + 1;
+	}
+      /* Complain, but don't return early if the filename area is too big.  */
+      if (f != descend)
+	warning (_("malformed note - filename area is too big"));
+
+      /* Collect proc mappings.  */
+      for (int i = 0; i < count; i++)
+	{
+	  struct a_mapping m;
+	  m.start = bfd_get (addr_size_bits, cbfd, descdata);
+	  descdata += addr_size;
+	  m.end = bfd_get (addr_size_bits, cbfd, descdata);
+	  descdata += addr_size;
+	  m.file_ofs = bfd_get (addr_size_bits, cbfd, descdata) * page_size;
+	  descdata += addr_size;
+	  m.filename = filenames;
+	  filenames += strlen ((char *) filenames) + 1;
+
+	  m.build_id = nullptr;
+	  auto vma_map_it = vma_map.find (m.start);
+	  if (vma_map_it != vma_map.end ())
+	    m.build_id = vma_map_it->second;
+
+	  all_mappings.push_back (m);
+	  mapping_start_addresses.insert (m.start);
+	}
     }
 
-  /* Sort proc mappings.  */
-  std::sort (proc_mappings.begin (), proc_mappings.end (),
-	     [] (const proc_mapping &a, const proc_mapping &b)
-	       {
-		 return a.start < b.start;
-	       });
+  /* Now look through the LOAD segments.  These have all been converted to
+     sections with the SEC_LOAD flag by BFD.  If we find a section that
+     contains a build-id, and which wasn't covered by an NT_FILE note, then
+     we create a mapping entry with no filename.  */
+  for (bfd_section *sec = cbfd->sections; sec != nullptr; sec = sec->next)
+    {
+      /* Skip non LOAD segments.  */
+      if ((sec->flags & SEC_LOAD) == 0)
+	continue;
 
-  /* Call loop_cb with sorted proc mappings.  */
-  for (const auto &m : proc_mappings)
+      CORE_ADDR start = bfd_section_vma (sec);
+
+      /* If there is already a mapping at this address (likely from the
+	 NT_FILE processing above) then skip this LOAD segment.  Currently
+	 this is assuming that the mapping from NT_FILE will be the same
+	 size as the LOAD segment, if this ever turns out not to be the
+	 case then we might need to be smarter here and figure out
+	 non-overlapping regions.  But that seems like unnecessary
+	 complexity for now.  */
+      auto mapping_start_addresses_it = mapping_start_addresses.find (start);
+      if (mapping_start_addresses_it != mapping_start_addresses.end ())
+	continue;
+
+      /* This LOAD segment is not covered by an NT_FILE entry, so we are
+	 not going to have a filename associated with the mapping.  Still,
+	 we might be able to find a build-id, and if we can, then tracking
+	 this mapping is still useful as, when we load shared libraries, if
+	 a library sits in this mapping we can validate the library's
+	 build-id.  If we cannot find a build-id for this mapping then
+	 there's no point tracking it, as it tells us nothing of value; no
+	 filename, no build-id.  */
+      const bfd_build_id *build_id = nullptr;
+      auto vma_map_it = vma_map.find (start);
+      if (vma_map_it != vma_map.end ())
+	build_id = vma_map_it->second;
+      if (build_id == nullptr)
+	continue;
+
+      /* Create an anonymous mapping object, one without a filename.  */
+      CORE_ADDR end = start + bfd_section_size (sec);
+
+      struct a_mapping m { .start = start, .end = end,
+	.file_ofs = 0, .filename = nullptr, .build_id = build_id };
+      all_mappings.push_back (m);
+      mapping_start_addresses.insert (start);
+    }
+
+  /* Sort the mappings.  */
+  std::sort (all_mappings.begin (), all_mappings.end (),
+	     [] (const a_mapping &a, const a_mapping &b)
+	     {
+	       return a.start < b.start;
+	     });
+
+  /* Call loop_cb with sorted mappings.  */
+  for (const a_mapping &m : all_mappings)
     loop_cb (m.start, m.end, m.file_ofs, m.filename, m.build_id);
 }
 
@@ -1283,6 +1363,10 @@ linux_core_info_proc_mappings (struct gdbarch *gdbarch, struct bfd *cbfd,
     [&] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
 	 const char *filename, const bfd_build_id *build_id)
       {
+	/* Ignore anonymous mappings.  */
+	if (filename == nullptr)
+	  return;
+
 	if (!emitter.has_value ())
 	  {
 	    gdb_printf (_("Mapped address spaces:\n\n"));
diff --git a/gdb/testsuite/gdb.base/corefile-buildid.exp b/gdb/testsuite/gdb.base/corefile-buildid.exp
index 1dec651e03c..f9e0e072d92 100644
--- a/gdb/testsuite/gdb.base/corefile-buildid.exp
+++ b/gdb/testsuite/gdb.base/corefile-buildid.exp
@@ -21,6 +21,10 @@
 
 standard_testfile .c -shlib-shr.c -shlib.c
 
+# Reuse the Python script from another test.  This provides a command
+# to remove a particular note from a core file.
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/corefile-no-threads.py]
+
 # Create a corefile from PROGNAME.  Return the name of the generated
 # corefile, or the empty string if anything goes wrong.
 #
@@ -345,6 +349,21 @@ foreach_with_prefix mode { exec shared } {
 	return
     }
 
+    if { [allow_python_tests] } {
+	# Create a corefile with no NT_FILE notes.
+	gdb_start
+	set corefile_no_nt_file [standard_output_file ${corefile}-no-nt_file]
+	remote_exec build "cp $corefile $corefile_no_nt_file"
+	gdb_test_no_output "source $::pyfile" "import python scripts"
+	gdb_test "modify-core-file \"$corefile_no_nt_file\" NT_FILE" \
+	    [multi_line \
+		 "Located PT_NOTE segment: \[^\r\n\]+" \
+		 "\\s+Found note with type $hex at file offset $hex\\..*" \
+		 "Successfully updated $decimal note\\(s\\) in \[^\r\n\]+"] \
+	    "update core file"
+	gdb_exit
+    }
+
     # Create a directory for the non-stripped test, copy every build
     # artefact into this directory.
     set combined_dirname [standard_output_file ${mode}_not-stripped]
@@ -394,6 +413,14 @@ foreach_with_prefix mode { exec shared } {
 	    locate_exec_from_core_build_id $corefile $dirname \
 		$build_artefacts $sepdebug $symlink \
 		[expr {$mode eq "shared"}]
+
+	    if { [allow_python_tests] } {
+		with_test_prefix "NT_FILE removed" {
+		    locate_exec_from_core_build_id $corefile_no_nt_file $dirname \
+			$build_artefacts $sepdebug $symlink \
+			[expr {$mode eq "shared"}]
+		}
+	    }
 	}
     }
 }
diff --git a/gdb/testsuite/gdb.python/py-corefile.py b/gdb/testsuite/gdb.python/py-corefile.py
index 43b64085117..22049cdf6ae 100644
--- a/gdb/testsuite/gdb.python/py-corefile.py
+++ b/gdb/testsuite/gdb.python/py-corefile.py
@@ -56,6 +56,10 @@ def info_proc_mappings():
 
     result = []
     for m in mappings:
+        # Ignore anonymous mappings.
+        if m.filename == "":
+            continue
+
         for r in m.regions:
             result.append(Mapping(m, r))
 
-- 
2.25.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/5] Improved support for core files with missing NT_FILE note
  2026-03-14 14:32 [PATCH 0/5] Improved support for core files with missing NT_FILE note Andrew Burgess
                   ` (4 preceding siblings ...)
  2026-03-14 14:32 ` [PATCH 5/5] gdb/linux: handle missing NT_FILE note when opening core files Andrew Burgess
@ 2026-04-23 22:06 ` Keith Seitz
  5 siblings, 0 replies; 9+ messages in thread
From: Keith Seitz @ 2026-04-23 22:06 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

Hi,

On 3/14/26 7:32 AM, Andrew Burgess wrote:
> This series improves GDB's support for core files without an NT_FILE
> note.  When this note is missing GDB is currently unable to associate
> a build-id with any of the shared libraries that loaded as the core
> file is opened.  As a result GDB might load the wrong shared library
> if a local library happens to have the same name (e.g. is a different
> version of the same library), and GDB will not perform a build-id
> based lookup either locally, or through debuginfod, for the missing
> shared library.
> 
> Patches #1 and #2 rework some existing tests.  This gets these tests
> into a state where they can be reused for testing the fixes that come
> later in the series.
> 
> Patches #3 and #4 are minor refactoring patches removing some code
> that will make the later patch in this series harder.

These all look okay to me, and one or two are even pretty obvious.

> Patch #5 is the actual fix.  After this, on GNU/Linux, GDB will be
> able to associate a build-id with a shared library, even if the
> NT_FILE note is missing from a core file.

I will follow-up with a correction in this patch, but otherwise,
everything appears okay.

FWIW, I regression tested this internally on all supported
architectures on F43 and found nothing.

Reviewed-By: Keith Seitz <keiths@redhat.com>

Keith


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 5/5] gdb/linux: handle missing NT_FILE note when opening core files
  2026-03-14 14:32 ` [PATCH 5/5] gdb/linux: handle missing NT_FILE note when opening core files Andrew Burgess
@ 2026-04-23 22:08   ` Keith Seitz
  2026-05-01 19:09     ` Andrew Burgess
  0 siblings, 1 reply; 9+ messages in thread
From: Keith Seitz @ 2026-04-23 22:08 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 3/14/26 7:32 AM, Andrew Burgess wrote:
> Then in corelow.c various functions are updated in order to handle
> anonymous mappings.

Just one note below, otherwise LGTM.

> diff --git a/gdb/corelow.c b/gdb/corelow.c
> index d63dd5f8f0d..d1412c4903a 100644
> --- a/gdb/corelow.c
> +++ b/gdb/corelow.c
> @@ -1963,7 +1972,6 @@ mapped_file_info::add (const char *soname,
>   		       const bfd_build_id *build_id)
>   {
>     gdb_assert (build_id != nullptr);
> -  gdb_assert (expected_filename != nullptr);
>   
>     if (soname != nullptr)
>       {

The comment at the beginning of this method (mapped_file_info::add)
contains:

    EXPECTED_FILENAME is the name of the file that was mapped into the
    inferior as extracted from the core file, this should never be
    nullptr.

This needs to be updated, since null `expected_filename' file names
will now be ignored.

Keith

> @@ -1983,13 +1991,17 @@ mapped_file_info::add (const char *soname,
>   	m_soname_to_build_id_map[soname] = build_id;
>       }
>   
> -  /* When the core file is initially opened and the mapped files are
> -     parsed, we group the build-id information based on the file name.  As
> -     a consequence, we should see each EXPECTED_FILENAME value exactly
> -     once.  This means that each insertion should always succeed.  */
> -  const auto inserted
> -    = m_filename_to_build_id_map.emplace (expected_filename, build_id).second;
> -  gdb_assert (inserted);
> +  /* Ignore empty filenames.  */
> +  if (expected_filename != nullptr)
> +    {
> +      /* When the core file is initially opened and the mapped files are
> +	 parsed, we group the build-id information based on the file name.  As
> +	 a consequence, we should see each EXPECTED_FILENAME value exactly
> +	 once.  This means that each insertion should always succeed.  */
> +      const auto inserted
> +	= m_filename_to_build_id_map.emplace (expected_filename, build_id).second;
> +      gdb_assert (inserted);
> +    }
>   
>     /* Setup the reverse build-id to file name map.  */
>     if (actual_filename != nullptr)


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 5/5] gdb/linux: handle missing NT_FILE note when opening core files
  2026-04-23 22:08   ` Keith Seitz
@ 2026-05-01 19:09     ` Andrew Burgess
  0 siblings, 0 replies; 9+ messages in thread
From: Andrew Burgess @ 2026-05-01 19:09 UTC (permalink / raw)
  To: Keith Seitz, gdb-patches

Keith Seitz <keiths@redhat.com> writes:

> On 3/14/26 7:32 AM, Andrew Burgess wrote:
>> Then in corelow.c various functions are updated in order to handle
>> anonymous mappings.
>
> Just one note below, otherwise LGTM.
>
>> diff --git a/gdb/corelow.c b/gdb/corelow.c
>> index d63dd5f8f0d..d1412c4903a 100644
>> --- a/gdb/corelow.c
>> +++ b/gdb/corelow.c
>> @@ -1963,7 +1972,6 @@ mapped_file_info::add (const char *soname,
>>   		       const bfd_build_id *build_id)
>>   {
>>     gdb_assert (build_id != nullptr);
>> -  gdb_assert (expected_filename != nullptr);
>>   
>>     if (soname != nullptr)
>>       {
>
> The comment at the beginning of this method (mapped_file_info::add)
> contains:
>
>     EXPECTED_FILENAME is the name of the file that was mapped into the
>     inferior as extracted from the core file, this should never be
>     nullptr.
>
> This needs to be updated, since null `expected_filename' file names
> will now be ignored.
>

Thanks for pointing this out.  I fixed this.  I also realised there was
another small improvement that could be made in
gdb_read_core_file_mappings that would better handle the case where an
NT_FILE note was corrupted, so I made that fix and then pushed this
series.

The version of this patch that I pushed can be found below.

Thanks,
Andrew

---

commit 37cb3cd8f896b8d0aa95ced818c6c7b1fb9ddc99
Author: Andrew Burgess <aburgess@redhat.com>
Date:   Wed Mar 11 11:45:42 2026 +0000

    gdb/linux: handle missing NT_FILE note when opening core files
    
    This patch originated from this mailing list discussion:
    
      https://inbox.sourceware.org/gdb-patches/b9b5bf03c59b58e02ca27b522338c6103d5ae49f.camel@gnu.org
    
    The user has some core files which lack an NT_FILE note.  They
    wondered why GDB was still unable to find the shared libraries based
    on their build-id.
    
    The reason right now is that GDB only records the build-id information
    for mappings based on the entries in the NT_FILE note.  With the
    entries in this note we build several lookup tables; a filename to
    build-id table, a soname (extracted from the file if it is a shared
    library) to build-id table, and an address range to build-id table.
    
    When a shared library is being loaded we perform a lookup using two
    pieces of information; the shared library's filename, and an address
    that we know is within the shared library.  If either of these give a
    build-id, then we can use that build-id to ensure GDB loads the
    shared library that matches the core file.
    
    If the NT_FILE note is missing then none of the lookup tables are
    created, and so the shared library build-id lookup fails, meaning that
    all GDB can do is look for the shared library by name on the local
    file system.  This often results in the wrong library version being
    loaded, or the library not being found at all.
    
    However, Linux core files also have the segment table.  This table
    gives address ranges.  The segment table doesn't tell us what file was
    mapped in, or the offset within the file that was mapped in.  But if
    we go back to the three lookup tables, we can use the segment table to
    build the address to build-id lookup table, and that would be enough
    to allow GDB to find the build-id for a shared library in most cases.
    
    So, here's what this patch does: linux_read_core_file_mappings (in
    linux-tdep.c) is updated to first parse the NT_FILE note as it
    currently does.  But after this we also walk the segment table (BFD
    actually converts these into sections with the LOAD flag set), and if
    a segment has a build-id, and doesn't correspond to an entry found in
    the NT_FILE note, we create an anonymous mapping.  An anonymous
    mapping is just like a mapping from the NT_FILE note, but without a
    filename and file offset.  This mapping is passed through the callback
    just like the traditional, non-anonymous, mappings.
    
    Then in corelow.c various functions are updated in order to handle
    anonymous mappings.
    
    Back in linux-tdep.c, function linux_core_info_proc_mappings gets a
    small update to handle anonymous mappings.
    
    The corefile-buildid.exp test is updated to remove the NT_FILE notes
    and rerun the tests.  This should make no difference as all this test
    is checking is that GDB is able to find and load the shared libraries
    and executable based on their build-ids; this is something we can do
    fine now without the NT_FILE note.
    
    I have also had to update the Python core file API documentation after
    this commit.  Previously we claimed that CorefileMappedFile.filename
    would never be empty, but this is now possible.  Luckily, this API has
    not yet been in a released version of GDB, so this minor tweak isn't
    going to break any existing user code.  I did consider having
    CorefileMappedFile.filename be a non-empty string or None, but I
    couldn't see much value in this, so I just documented that the string
    could be empty, and what this means.
    
    The py-corefile.exp test needed a minor update to filter out anonymous
    mappings (those without a filename), this matches the behaviour of the
    builtin 'info proc mappings' command.
    
    Reviewed-By: Keith Seitz <keiths@redhat.com>

diff --git a/gdb/corelow.c b/gdb/corelow.c
index 407515ee45a..008da68155f 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -406,12 +406,25 @@ core_target::build_file_mappings ()
   gdb::unordered_string_map<struct bfd *> bfd_map;
   gdb::unordered_set<std::string> unavailable_paths;
 
-  /* All files mapped into the core file.  The key is the filename.  */
+  /* All files mapped into the core file.  */
   std::vector<core_mapped_file> mapped_files
     = gdb_read_core_file_mappings (m_core_gdbarch, this->core_bfd ());
 
   for (const core_mapped_file &file_data : mapped_files)
     {
+      /* A mapping without a filename can still have a build-id.  Tracking
+	 these mappings is useful as when the shared libraries are loaded,
+	 if the shared library is within this anonymous region, we can
+	 validate the build-id of the shared library being loaded.  */
+      if (file_data.filename.empty ())
+	{
+	  gdb_assert (file_data.build_id != nullptr);
+	  std::vector<mem_range> ranges = file_data.mem_ranges ();
+	  m_mapped_file_info.add (nullptr, nullptr, nullptr,
+				  std::move (ranges), file_data.build_id);
+	  continue;
+	}
+
       /* If this mapped file is marked as the main executable then record
 	 the filename as we can use this later.  */
       if (file_data.is_main_exec && m_expected_exec_filename.empty ())
@@ -477,10 +490,6 @@ core_target::build_file_mappings ()
 	    }
 	}
 
-      std::vector<mem_range> ranges;
-      for (const core_mapped_file::region &region : file_data.regions)
-	ranges.emplace_back (region.start, region.end - region.start);
-
       if (expanded_fname == nullptr
 	  || abfd == nullptr
 	  || !bfd_check_format (abfd.get (), bfd_object))
@@ -593,7 +602,7 @@ core_target::build_file_mappings ()
 	 libraries.  */
       if (file_data.build_id != nullptr)
 	{
-	  normalize_mem_ranges (&ranges);
+	  std::vector<mem_range> ranges = file_data.mem_ranges ();
 
 	  const char *actual_filename = nullptr;
 	  gdb::unique_xmalloc_ptr<char> soname;
@@ -1977,7 +1986,10 @@ maintenance_print_core_file_backed_mappings (const char *args, int from_tty)
    DT_SONAME attribute.
 
    EXPECTED_FILENAME is the name of the file that was mapped into the
-   inferior as extracted from the core file, this should never be nullptr.
+   inferior as extracted from the core file, this can be nullptr if the
+   filename is unknown.  This can happen on Linux if there is no NT_FILE
+   note, and we are building the mapped_file_info using just the segment
+   table from the core file.
 
    ACTUAL_FILENAME is the name of the actual file GDB found to provide the
    mapped file information, this can be nullptr if GDB failed to find a
@@ -2001,7 +2013,6 @@ mapped_file_info::add (const char *soname,
 		       const bfd_build_id *build_id)
 {
   gdb_assert (build_id != nullptr);
-  gdb_assert (expected_filename != nullptr);
 
   if (soname != nullptr)
     {
@@ -2021,13 +2032,17 @@ mapped_file_info::add (const char *soname,
 	m_soname_to_build_id_map[soname] = build_id;
     }
 
-  /* When the core file is initially opened and the mapped files are
-     parsed, we group the build-id information based on the file name.  As
-     a consequence, we should see each EXPECTED_FILENAME value exactly
-     once.  This means that each insertion should always succeed.  */
-  const auto inserted
-    = m_filename_to_build_id_map.emplace (expected_filename, build_id).second;
-  gdb_assert (inserted);
+  /* Ignore empty filenames.  */
+  if (expected_filename != nullptr)
+    {
+      /* When the core file is initially opened and the mapped files are
+	 parsed, we group the build-id information based on the file name.  As
+	 a consequence, we should see each EXPECTED_FILENAME value exactly
+	 once.  This means that each insertion should always succeed.  */
+      const auto inserted
+	= m_filename_to_build_id_map.emplace (expected_filename, build_id).second;
+      gdb_assert (inserted);
+    }
 
   /* Setup the reverse build-id to file name map.  */
   if (actual_filename != nullptr)
@@ -2173,9 +2188,19 @@ gdb_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd)
     [&] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
 	 const char *filename, const bfd_build_id *build_id)
       {
-	/* Architecture-specific read_core_mapping methods are expected to
-	   weed out non-file-backed mappings.  */
-	gdb_assert (filename != nullptr);
+	if (filename == nullptr)
+	  {
+	    /* A mapping without a filename MUST have a build-id, and the
+	       file_ofs MUST be 0, but the file_ofs is actually meaningless
+	       in this case.  */
+	    gdb_assert (build_id != nullptr);
+	    gdb_assert (file_ofs == 0);
+
+	    results.emplace_back ();
+	    results.back ().build_id = build_id;
+	    results.back ().regions.emplace_back (start, end, file_ofs);
+	    return;
+	  }
 
 	/* Add this mapped region to the data for FILENAME.  */
 	auto iter = mapped_files.find (filename);
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 96b46a98091..6cafab78d58 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -9040,8 +9040,10 @@ Core Files In Python
 A @code{gdb.CorefileMappedFile} object has the following attributes:
 
 @defvar CorefileMappedFile.filename
-This read only attribute contains a non-empty string, the file name of
-the mapped file.
+This read only attribute contains a string, the file name of the
+mapped file.  This string can be empty if @value{GDBN} was unable to
+find the name of the mapped file.  If this string is empty then
+@code{CorefileMappedFile.build_id} will not be @code{None}.
 @end defvar
 
 @defvar CorefileMappedFile.build_id
@@ -9081,7 +9083,9 @@ Core Files In Python
 
 @defvar CorefileMappedFileRegion.file_offset
 This read only attribute contains the offset within the mapped file
-for this mapping.
+for this mapping.  This attribute will be @code{0} if the containing
+@code{CorefileMappedFile.filename} is empty, in which case this
+attribute has no meaning.
 @end defvar
 
 @node Python Auto-loading
diff --git a/gdb/gdbcore.h b/gdb/gdbcore.h
index 89586e0c6ec..3fd91770c54 100644
--- a/gdb/gdbcore.h
+++ b/gdb/gdbcore.h
@@ -261,7 +261,9 @@ core_target_find_mapped_file (const char *filename,
 
 /* Type holding information about a single file mapped into the inferior
    at the point when the core file was created.  Associates a build-id
-   with the list of regions the file is mapped into.  */
+   with the list of regions the file is mapped into.  It is acceptable to
+   have a core_mapped_file with an empty filename, so long as we have a
+   build-id for the mapping.  */
 struct core_mapped_file
 {
   /* Type for a region of a file that was mapped into the inferior when
@@ -281,15 +283,22 @@ struct core_mapped_file
     /* The inferior address immediately after the mapped region.  */
     CORE_ADDR end;
 
-    /* The offset within the mapped file for this content.  */
+    /* The offset within the mapped file for this content.  If the
+       filename of the mapping is empty then this field will be 0, but has
+       no meaning.  */
     CORE_ADDR file_ofs;
   };
 
-  /* The filename as recorded in the core file.  */
+  /* The filename as recorded in the core file.  This can be empty meaning
+     that GDB was unable to find a filename for this mapping.  If the
+     filename is empty then the build_id field MUST be non-NULL.  If this
+     is empty then the region::file_ofs fields will all be 0, and have no
+     meaning.  */
   std::string filename;
 
   /* If not nullptr, then this is the build-id associated with this
-     file.  */
+     mapping.  If the filename field is empty, then this MUST be
+     non-NULL.  */
   const bfd_build_id *build_id = nullptr;
 
   /* All the mapped regions of this file.  */
@@ -297,6 +306,17 @@ struct core_mapped_file
 
   /* True if this is the main executable.  */
   bool is_main_exec = false;
+
+  /* Convert the REGIONS to a vector of mem_range objects.  */
+  std::vector<mem_range>
+  mem_ranges () const
+  {
+    std::vector<mem_range> ranges;
+    for (const core_mapped_file::region &region : this->regions)
+      ranges.emplace_back (region.start, region.end - region.start);
+    normalize_mem_ranges (&ranges);
+    return ranges;
+  }
 };
 
 extern std::vector<core_mapped_file> gdb_read_core_file_mappings
diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c
index 43df05d1529..4bc5a2b6948 100644
--- a/gdb/linux-tdep.c
+++ b/gdb/linux-tdep.c
@@ -1091,6 +1091,33 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
     }
 }
 
+/* Return a map from the start address of any LOAD segment in CBFD to the
+   associated build-id.  The map only contains entries for those segments
+   where a build-id is found, so the build-id will never be NULL, but not
+   every segment will have an entry in the map.  */
+
+static gdb::unordered_map<CORE_ADDR, const bfd_build_id *>
+linux_read_build_ids_from_core_file_mappings (struct bfd *cbfd)
+{
+  gdb::unordered_map<CORE_ADDR, const bfd_build_id *> vma_to_build_id_map;
+
+  auto restore_cbfd_build_id = make_scoped_restore (&cbfd->build_id);
+
+  /* Search for solib build-ids in the core file.  Each time one is found,
+     map the start vma of the corresponding elf header to the build-id.  */
+  for (bfd_section *sec = cbfd->sections; sec != nullptr; sec = sec->next)
+    {
+      cbfd->build_id = nullptr;
+
+      if (sec->flags & SEC_LOAD
+	  && (get_elf_backend_data (cbfd)->elf_backend_core_find_build_id
+	      (cbfd, (bfd_vma) sec->filepos)))
+	vma_to_build_id_map[sec->vma] = cbfd->build_id;
+    }
+
+  return vma_to_build_id_map;
+}
+
 /* Implementation of `gdbarch_read_core_file_mappings', as defined in
    gdbarch.h.
 
@@ -1107,103 +1134,39 @@ linux_info_proc (struct gdbarch *gdbarch, const char *args,
 	long file_ofs
       followed by COUNT filenames in ASCII: "FILE1" NUL "FILE2" NUL...
 
+   In addition to reading the NT_FILE note, this function also considers
+   all of the LOADable segments (which BFD turns into sections).  If a
+   segment is loadable, has a build-id, and isn't covered by an NT_FILE
+   entry, then we consider it an anonymous mapping.  Tracking these is
+   useful when we load shared libraries.  If a shared library corresponds
+   to an anonymous mapping then we can validate the build-id of the shared
+   library.  This is useful when core files are created without an NT_FILE
+   note.
+
    CBFD is the BFD of the core file.
 
    LOOP_CB is the callback function that will be executed once
    for each mapping.  */
 
 static void
-linux_read_core_file_mappings
-  (struct gdbarch *gdbarch,
-   struct bfd *cbfd,
-   read_core_file_mappings_loop_ftype  loop_cb)
+linux_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd,
+			       read_core_file_mappings_loop_ftype  loop_cb)
 {
   /* Ensure that ULONGEST is big enough for reading 64-bit core files.  */
   static_assert (sizeof (ULONGEST) >= 8);
 
-  /* It's not required that the NT_FILE note exists, so return silently
-     if it's not found.  Beyond this point though, we'll complain
-     if problems are found.  */
-  asection *section = bfd_get_section_by_name (cbfd, ".note.linuxcore.file");
-  if (section == nullptr)
-    return;
+  /* Map from the start address of LOAD segments to the associated
+     build-id, but only for segments that have a build-id.  */
+  gdb::unordered_map<CORE_ADDR, const bfd_build_id *> vma_map
+    = linux_read_build_ids_from_core_file_mappings (cbfd);
 
-  unsigned int addr_size_bits = gdbarch_addr_bit (gdbarch);
-  unsigned int addr_size = addr_size_bits / 8;
-  size_t note_size = bfd_section_size (section);
+  /* Track the start address of each mapping found.  This allows us to
+     quickly drop duplicates when processing the NT_FILE note then the LOAD
+     segments.  */
+  gdb::unordered_set<CORE_ADDR> mapping_start_addresses;
 
-  if (note_size < 2 * addr_size)
-    {
-      warning (_("malformed core note - too short for header"));
-      return;
-    }
-
-  gdb::byte_vector contents (note_size);
-  if (!bfd_get_section_contents (cbfd, section, contents.data (), 0,
-				 note_size))
-    {
-      warning (_("could not get core note contents"));
-      return;
-    }
-
-  gdb_byte *descdata = contents.data ();
-  char *descend = (char *) descdata + note_size;
-
-  if (descdata[note_size - 1] != '\0')
-    {
-      warning (_("malformed note - does not end with \\0"));
-      return;
-    }
-
-  ULONGEST count = bfd_get (addr_size_bits, cbfd, descdata);
-  descdata += addr_size;
-
-  ULONGEST page_size = bfd_get (addr_size_bits, cbfd, descdata);
-  descdata += addr_size;
-
-  if (note_size < 2 * addr_size + count * 3 * addr_size)
-    {
-      warning (_("malformed note - too short for supplied file count"));
-      return;
-    }
-
-  char *filenames = (char *) descdata + count * 3 * addr_size;
-
-  /* Make sure that the correct number of filenames exist.  Complain
-     if there aren't enough or are too many.  */
-  char *f = filenames;
-  for (int i = 0; i < count; i++)
-    {
-      if (f >= descend)
-	{
-	  warning (_("malformed note - filename area is too small"));
-	  return;
-	}
-      f += strnlen (f, descend - f) + 1;
-    }
-  /* Complain, but don't return early if the filename area is too big.  */
-  if (f != descend)
-    warning (_("malformed note - filename area is too big"));
-
-  const bfd_build_id *orig_build_id = cbfd->build_id;
-  gdb::unordered_map<ULONGEST, const bfd_build_id *> vma_map;
-
-  /* Search for solib build-ids in the core file.  Each time one is found,
-     map the start vma of the corresponding elf header to the build-id.  */
-  for (bfd_section *sec = cbfd->sections; sec != nullptr; sec = sec->next)
-    {
-      cbfd->build_id = nullptr;
-
-      if (sec->flags & SEC_LOAD
-	  && (get_elf_backend_data (cbfd)->elf_backend_core_find_build_id
-	       (cbfd, (bfd_vma) sec->filepos)))
-	vma_map[sec->vma] = cbfd->build_id;
-    }
-
-  cbfd->build_id = orig_build_id;
-
-  /* Vector to collect proc mappings.  */
-  struct proc_mapping
+  /* Vector to collect all mappings.  */
+  struct a_mapping
   {
     ULONGEST start;
     ULONGEST end;
@@ -1211,38 +1174,165 @@ linux_read_core_file_mappings
     const char *filename;
     const bfd_build_id *build_id;
   };
-  std::vector<struct proc_mapping> proc_mappings;
+  std::vector<struct a_mapping> all_mappings;
 
-  /* Collect proc mappings.  */
-  for (int i = 0; i < count; i++)
+  /* If we find the NT_FILE section in the lambda below then we need
+     to load its contents, and those contents need to remain live
+     until the end of this function.  This vector will hold the
+     contents.  */
+  gdb::byte_vector contents;
+
+  /* Read mapping from the NT_FILE note if it exists.  It is not
+     required that this note exist so wrap the parsing within a lambda
+     function, this allows us to return if the note doesn't exist, or
+     is malformed.
+
+     After parsing the NT_FILE note we will also parse the segment
+     table to fill in any additional mappings that the NT_FILE didn't
+     cover, e.g. if NT_FILE was missing or unreadable.  */
+  auto read_mappings_from_nt_file_note = [&] ()
     {
-      struct proc_mapping m;
-      m.start = bfd_get (addr_size_bits, cbfd, descdata);
-      descdata += addr_size;
-      m.end = bfd_get (addr_size_bits, cbfd, descdata);
-      descdata += addr_size;
-      m.file_ofs = bfd_get (addr_size_bits, cbfd, descdata) * page_size;
-      descdata += addr_size;
-      m.filename = filenames;
-      filenames += strlen ((char *) filenames) + 1;
+      asection *section = bfd_get_section_by_name (cbfd, ".note.linuxcore.file");
+      if (section == nullptr)
+	return;
 
-      m.build_id = nullptr;
-      auto vma_map_it = vma_map.find (m.start);
+      unsigned int addr_size_bits = gdbarch_addr_bit (gdbarch);
+      unsigned int addr_size = addr_size_bits / 8;
+      size_t note_size = bfd_section_size (section);
+
+      if (note_size < 2 * addr_size)
+	{
+	  warning (_("malformed core note - too short for header"));
+	  return;
+	}
+
+      contents.resize (note_size);
+      if (!bfd_get_section_contents (cbfd, section, contents.data (), 0,
+				     note_size))
+	{
+	  warning (_("could not get core note contents"));
+	  return;
+	}
+
+      gdb_byte *descdata = contents.data ();
+      char *descend = (char *) descdata + note_size;
+
+      if (descdata[note_size - 1] != '\0')
+	{
+	  warning (_("malformed note - does not end with \\0"));
+	  return;
+	}
+
+      ULONGEST count = bfd_get (addr_size_bits, cbfd, descdata);
+      descdata += addr_size;
+
+      ULONGEST page_size = bfd_get (addr_size_bits, cbfd, descdata);
+      descdata += addr_size;
+
+      if (note_size < 2 * addr_size + count * 3 * addr_size)
+	{
+	  warning (_("malformed note - too short for supplied file count"));
+	  return;
+	}
+
+      char *filenames = (char *) descdata + count * 3 * addr_size;
+
+      /* Make sure that the correct number of filenames exist.  Complain
+	 if there aren't enough or are too many.  */
+      char *f = filenames;
+      for (int i = 0; i < count; i++)
+	{
+	  if (f >= descend)
+	    {
+	      warning (_("malformed note - filename area is too small"));
+	      return;
+	    }
+	  f += strnlen (f, descend - f) + 1;
+	}
+      /* Complain, but don't return early if the filename area is too big.  */
+      if (f != descend)
+	warning (_("malformed note - filename area is too big"));
+
+      /* Collect proc mappings.  */
+      for (int i = 0; i < count; i++)
+	{
+	  struct a_mapping m;
+	  m.start = bfd_get (addr_size_bits, cbfd, descdata);
+	  descdata += addr_size;
+	  m.end = bfd_get (addr_size_bits, cbfd, descdata);
+	  descdata += addr_size;
+	  m.file_ofs = bfd_get (addr_size_bits, cbfd, descdata) * page_size;
+	  descdata += addr_size;
+	  m.filename = filenames;
+	  filenames += strlen ((char *) filenames) + 1;
+
+	  m.build_id = nullptr;
+	  auto vma_map_it = vma_map.find (m.start);
+	  if (vma_map_it != vma_map.end ())
+	    m.build_id = vma_map_it->second;
+
+	  all_mappings.push_back (m);
+	  mapping_start_addresses.insert (m.start);
+	}
+    };
+  read_mappings_from_nt_file_note ();
+
+  /* Now look through the LOAD segments.  These have all been converted to
+     sections with the SEC_LOAD flag by BFD.  If we find a section that
+     contains a build-id, and which wasn't covered by an NT_FILE note, then
+     we create a mapping entry with no filename.  */
+  for (bfd_section *sec = cbfd->sections; sec != nullptr; sec = sec->next)
+    {
+      /* Skip non LOAD segments.  */
+      if ((sec->flags & SEC_LOAD) == 0)
+	continue;
+
+      CORE_ADDR start = bfd_section_vma (sec);
+
+      /* If there is already a mapping at this address (likely from the
+	 NT_FILE processing above) then skip this LOAD segment.  Currently
+	 this is assuming that the mapping from NT_FILE will be the same
+	 size as the LOAD segment, if this ever turns out not to be the
+	 case then we might need to be smarter here and figure out
+	 non-overlapping regions.  But that seems like unnecessary
+	 complexity for now.  */
+      auto mapping_start_addresses_it = mapping_start_addresses.find (start);
+      if (mapping_start_addresses_it != mapping_start_addresses.end ())
+	continue;
+
+      /* This LOAD segment is not covered by an NT_FILE entry, so we are
+	 not going to have a filename associated with the mapping.  Still,
+	 we might be able to find a build-id, and if we can, then tracking
+	 this mapping is still useful as, when we load shared libraries, if
+	 a library sits in this mapping we can validate the library's
+	 build-id.  If we cannot find a build-id for this mapping then
+	 there's no point tracking it, as it tells us nothing of value; no
+	 filename, no build-id.  */
+      const bfd_build_id *build_id = nullptr;
+      auto vma_map_it = vma_map.find (start);
       if (vma_map_it != vma_map.end ())
-	m.build_id = vma_map_it->second;
+	build_id = vma_map_it->second;
+      if (build_id == nullptr)
+	continue;
 
-      proc_mappings.push_back (m);
+      /* Create an anonymous mapping object, one without a filename.  */
+      CORE_ADDR end = start + bfd_section_size (sec);
+
+      struct a_mapping m { .start = start, .end = end,
+	.file_ofs = 0, .filename = nullptr, .build_id = build_id };
+      all_mappings.push_back (m);
+      mapping_start_addresses.insert (start);
     }
 
-  /* Sort proc mappings.  */
-  std::sort (proc_mappings.begin (), proc_mappings.end (),
-	     [] (const proc_mapping &a, const proc_mapping &b)
-	       {
-		 return a.start < b.start;
-	       });
+  /* Sort the mappings.  */
+  std::sort (all_mappings.begin (), all_mappings.end (),
+	     [] (const a_mapping &a, const a_mapping &b)
+	     {
+	       return a.start < b.start;
+	     });
 
-  /* Call loop_cb with sorted proc mappings.  */
-  for (const auto &m : proc_mappings)
+  /* Call loop_cb with sorted mappings.  */
+  for (const a_mapping &m : all_mappings)
     loop_cb (m.start, m.end, m.file_ofs, m.filename, m.build_id);
 }
 
@@ -1258,6 +1348,10 @@ linux_core_info_proc_mappings (struct gdbarch *gdbarch, struct bfd *cbfd,
     [&] (ULONGEST start, ULONGEST end, ULONGEST file_ofs,
 	 const char *filename, const bfd_build_id *build_id)
       {
+	/* Ignore anonymous mappings.  */
+	if (filename == nullptr)
+	  return;
+
 	if (!emitter.has_value ())
 	  {
 	    gdb_printf (_("Mapped address spaces:\n\n"));
diff --git a/gdb/testsuite/gdb.base/corefile-buildid.exp b/gdb/testsuite/gdb.base/corefile-buildid.exp
index 09935c2a86e..5207cc15816 100644
--- a/gdb/testsuite/gdb.base/corefile-buildid.exp
+++ b/gdb/testsuite/gdb.base/corefile-buildid.exp
@@ -21,6 +21,10 @@
 
 standard_testfile .c -shlib-shr.c -shlib.c
 
+# Reuse the Python script from another test.  This provides a command
+# to remove a particular note from a core file.
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/corefile-no-threads.py]
+
 # Create a corefile from PROGNAME.  Return the name of the generated
 # corefile, or the empty string if anything goes wrong.
 #
@@ -345,6 +349,21 @@ foreach_with_prefix mode { exec shared } {
 	return
     }
 
+    if { [allow_python_tests] } {
+	# Create a corefile with no NT_FILE notes.
+	gdb_start
+	set corefile_no_nt_file [standard_output_file ${corefile}-no-nt_file]
+	remote_exec build "cp $corefile $corefile_no_nt_file"
+	gdb_test_no_output "source $::pyfile" "import python scripts"
+	gdb_test "modify-core-file \"$corefile_no_nt_file\" NT_FILE" \
+	    [multi_line \
+		 "Located PT_NOTE segment: \[^\r\n\]+" \
+		 "\\s+Found note with type $hex at file offset $hex\\..*" \
+		 "Successfully updated $decimal note\\(s\\) in \[^\r\n\]+"] \
+	    "update core file"
+	gdb_exit
+    }
+
     # Create a directory for the non-stripped test, copy every build
     # artefact into this directory.
     set combined_dirname [standard_output_file ${mode}_not-stripped]
@@ -394,6 +413,14 @@ foreach_with_prefix mode { exec shared } {
 	    locate_exec_from_core_build_id $corefile $dirname \
 		$build_artefacts $sepdebug $symlink \
 		[expr {$mode eq "shared"}]
+
+	    if { [allow_python_tests] } {
+		with_test_prefix "NT_FILE removed" {
+		    locate_exec_from_core_build_id $corefile_no_nt_file $dirname \
+			$build_artefacts $sepdebug $symlink \
+			[expr {$mode eq "shared"}]
+		}
+	    }
 	}
     }
 }
diff --git a/gdb/testsuite/gdb.python/py-corefile.py b/gdb/testsuite/gdb.python/py-corefile.py
index aadcfc4acb2..de6d85fffc1 100644
--- a/gdb/testsuite/gdb.python/py-corefile.py
+++ b/gdb/testsuite/gdb.python/py-corefile.py
@@ -56,6 +56,10 @@ def info_proc_mappings():
 
     result = []
     for m in mappings:
+        # Ignore anonymous mappings.
+        if m.filename == "":
+            continue
+
         for r in m.regions:
             result.append(Mapping(m, r))
 


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-05-01 19:10 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-14 14:32 [PATCH 0/5] Improved support for core files with missing NT_FILE note Andrew Burgess
2026-03-14 14:32 ` [PATCH 1/5] gdb/testsuite: improve corefile-no-threads.py script Andrew Burgess
2026-03-14 14:32 ` [PATCH 2/5] gdb/testsuite: restructure gdb.base/corefile-buildid.exp Andrew Burgess
2026-03-14 14:32 ` [PATCH 3/5] gdb: remove 'num' argument from gdbarch_read_core_file_mappings callback Andrew Burgess
2026-03-14 14:32 ` [PATCH 4/5] gdb: remove pre-loop callback from gdbarch_read_core_file_mappings Andrew Burgess
2026-03-14 14:32 ` [PATCH 5/5] gdb/linux: handle missing NT_FILE note when opening core files Andrew Burgess
2026-04-23 22:08   ` Keith Seitz
2026-05-01 19:09     ` Andrew Burgess
2026-04-23 22:06 ` [PATCH 0/5] Improved support for core files with missing NT_FILE note Keith Seitz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox