mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
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>
320 lines
9.5 KiB
Python
320 lines
9.5 KiB
Python
# Copyright (C) 2025-2026 Free Software Foundation, Inc.
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import pathlib
|
|
|
|
import gdb
|
|
|
|
|
|
class Mapping:
|
|
def __init__(self, mapping, region):
|
|
self._mapping = mapping
|
|
self._region = region
|
|
|
|
@property
|
|
def start(self):
|
|
return self._region.start
|
|
|
|
@property
|
|
def end(self):
|
|
return self._region.end
|
|
|
|
@property
|
|
def offset(self):
|
|
return self._region.file_offset
|
|
|
|
@property
|
|
def filename(self):
|
|
return self._mapping.filename
|
|
|
|
|
|
def info_proc_mappings():
|
|
ptr_size = gdb.lookup_type("void").pointer().sizeof
|
|
|
|
print("Mapped address spaces:")
|
|
print("")
|
|
if ptr_size == 4:
|
|
format_str = "%-10s %-10s %-10s %-10s %s "
|
|
else:
|
|
format_str = "%-18s %-18s %-18s %-18s %s "
|
|
print(format_str % ("Start Addr", "End Addr", "Size", "Offset", "File"))
|
|
|
|
core = gdb.selected_inferior().corefile
|
|
mappings = core.mapped_files()
|
|
|
|
result = []
|
|
for m in mappings:
|
|
# Ignore anonymous mappings.
|
|
if m.filename == "":
|
|
continue
|
|
|
|
for r in m.regions:
|
|
result.append(Mapping(m, r))
|
|
|
|
result.sort(key=lambda x: x.start)
|
|
for r in result:
|
|
sz = r.end - r.start
|
|
if ptr_size == 4:
|
|
t = (
|
|
"0x%08x" % r.start,
|
|
"0x%08x" % r.end,
|
|
"0x%-8x" % sz,
|
|
"0x%-8x" % r.offset,
|
|
"%s" % r.filename,
|
|
)
|
|
else:
|
|
t = (
|
|
"0x%016x" % r.start,
|
|
"0x%016x" % r.end,
|
|
"0x%-16x" % sz,
|
|
"0x%-16x" % r.offset,
|
|
"%s" % r.filename,
|
|
)
|
|
print(format_str % t)
|
|
|
|
|
|
class InfoProcPyMappings(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "info proc py-mappings", gdb.COMMAND_DATA)
|
|
|
|
def invoke(self, args, from_tty):
|
|
info_proc_mappings()
|
|
|
|
|
|
InfoProcPyMappings()
|
|
|
|
|
|
# Assume that a core file is currently loaded.
|
|
#
|
|
# Look through all the objfiles for the current inferior, and record
|
|
# any that have a build-id.
|
|
#
|
|
# Then look through the core file mapped files. Look for entries that
|
|
# correspond with the loaded objfiles. For these matching entries,
|
|
# capture the build-id extracted from the core file.
|
|
#
|
|
# Finally, print a table with the build-id from the objfile, the
|
|
# build-id from the core file, and the file name.
|
|
#
|
|
# This is then processed from the test script to check the build-ids
|
|
# match.
|
|
class ShowBuildIds(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "show-build-ids", gdb.COMMAND_DATA)
|
|
|
|
def invoke(self, args, from_tty):
|
|
inf = gdb.selected_inferior()
|
|
objfiles = inf.progspace.objfiles()
|
|
|
|
path_to_build_id = {}
|
|
|
|
# Initial length based on column headings.
|
|
longest_build_id = 18
|
|
|
|
for o in objfiles:
|
|
if not o.is_file or o.build_id is None or o.owner is not None:
|
|
continue
|
|
p = pathlib.Path(o.filename).resolve()
|
|
b = o.build_id
|
|
path_to_build_id[p] = {"objfile": b, "corefile": "missing"}
|
|
if len(b) > longest_build_id:
|
|
longest_build_id = len(b)
|
|
|
|
core_mapped_files = inf.corefile.mapped_files()
|
|
for m in core_mapped_files:
|
|
p = pathlib.Path(m.filename).resolve()
|
|
b = m.build_id
|
|
|
|
if b is not None and len(b) > longest_build_id:
|
|
longest_build_id = len(b)
|
|
|
|
if p in path_to_build_id:
|
|
path_to_build_id[p]["corefile"] = b
|
|
|
|
format_str = (
|
|
"%-" + str(longest_build_id) + "s %-" + str(longest_build_id) + "s %s"
|
|
)
|
|
|
|
def make_title(string, length=0):
|
|
if length > 0:
|
|
padding_len = length - len(string)
|
|
else:
|
|
padding_len = 0
|
|
|
|
padding = " " * padding_len
|
|
style = gdb.Style("title")
|
|
return style.apply(string) + padding
|
|
|
|
print(
|
|
"%s %s %s"
|
|
% (
|
|
make_title("Objfile Build-Id", longest_build_id),
|
|
make_title("Core File Build-Id", longest_build_id),
|
|
make_title("File Name"),
|
|
)
|
|
)
|
|
for p, b in path_to_build_id.items():
|
|
print(format_str % (b["objfile"], b["corefile"], p))
|
|
|
|
|
|
ShowBuildIds()
|
|
|
|
|
|
class CheckMainExec(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "check-main-executable", gdb.COMMAND_DATA)
|
|
|
|
def invoke(self, args, from_tty):
|
|
inf = gdb.selected_inferior()
|
|
pspace = inf.progspace
|
|
exec_filename = pathlib.Path(pspace.executable_filename).resolve()
|
|
|
|
count = 0
|
|
core_mapped_files = inf.corefile.mapped_files()
|
|
for m in core_mapped_files:
|
|
if not m.is_main_executable:
|
|
continue
|
|
|
|
p = pathlib.Path(m.filename).resolve()
|
|
|
|
count += 1
|
|
assert exec_filename == p, "main exec filename mismatch"
|
|
|
|
assert count == 1, "invalid main executable count"
|
|
|
|
print("PASS")
|
|
|
|
|
|
CheckMainExec()
|
|
|
|
|
|
# An 'events' prefix command.
|
|
class events_cmd(gdb.Command):
|
|
"""Information about recent Python events."""
|
|
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "events", gdb.COMMAND_USER, prefix=True)
|
|
|
|
|
|
# An 'events corefile_changed' sub-command.
|
|
class events_corefile_changed_cmd(gdb.Command):
|
|
"""Check recent corefile_changed events.
|
|
|
|
Requires a single argument either 'check' or 'reset'. With
|
|
'check', print details of every recent corefile_changed event.
|
|
With 'reset' clear the list of recent corefile_changed events."""
|
|
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "events corefile_changed", gdb.COMMAND_USER)
|
|
self._events = []
|
|
gdb.events.corefile_changed.connect(lambda e: self._corefile_changed_handler(e))
|
|
|
|
def _corefile_changed_handler(self, event):
|
|
assert isinstance(event, gdb.CorefileChangedEvent)
|
|
inf = event.inferior
|
|
assert isinstance(inf, gdb.Inferior)
|
|
|
|
corefile = inf.corefile
|
|
if corefile is not None:
|
|
assert corefile.is_valid()
|
|
corefile = corefile.filename
|
|
|
|
obj = {"inferior": inf.num, "corefile": corefile}
|
|
self._events.append(obj)
|
|
|
|
def invoke(self, args, from_tty):
|
|
if args == "check":
|
|
if len(self._events) == 0:
|
|
print("No corefile_changed event has been seen.")
|
|
else:
|
|
total = len(self._events)
|
|
for idx, obj in enumerate(self._events, start=1):
|
|
inf_num = obj["inferior"]
|
|
corefile = obj["corefile"]
|
|
|
|
if corefile is None:
|
|
msg = "None"
|
|
else:
|
|
msg = corefile
|
|
|
|
print(
|
|
"Event {}/{}, Inferior {}, Corefile {}".format(
|
|
idx, total, inf_num, msg
|
|
)
|
|
)
|
|
elif args == "reset":
|
|
self._events = []
|
|
else:
|
|
raise gdb.GdbError("Unknown command args: {}".format(args))
|
|
|
|
|
|
# An 'events exited' sub-command.
|
|
class events_exited_cmd(gdb.Command):
|
|
"""Check recent exited events.
|
|
|
|
Requires a single argument either 'check' or 'reset'. With
|
|
'check', print details of every recent exited event. With 'reset'
|
|
clear the list of recent exited events."""
|
|
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "events exited", gdb.COMMAND_USER)
|
|
self._events = []
|
|
gdb.events.exited.connect(lambda e: self._exited_handler(e))
|
|
|
|
def _exited_handler(self, event):
|
|
assert isinstance(event, gdb.ExitedEvent)
|
|
inf = event.inferior
|
|
assert isinstance(inf, gdb.Inferior)
|
|
|
|
if hasattr(event, "exit_code"):
|
|
assert isinstance(event.exit_code, int)
|
|
exit_code = event.exit_code
|
|
else:
|
|
exit_code = None
|
|
|
|
obj = {"inferior": inf.num, "exit_code": exit_code}
|
|
self._events.append(obj)
|
|
|
|
def invoke(self, args, from_tty):
|
|
if args == "check":
|
|
if len(self._events) == 0:
|
|
print("No exited event has been seen.")
|
|
else:
|
|
total = len(self._events)
|
|
for idx, obj in enumerate(self._events, start=1):
|
|
inf_num = obj["inferior"]
|
|
exit_code = obj["exit_code"]
|
|
|
|
if exit_code is None:
|
|
msg = "None"
|
|
else:
|
|
msg = exit_code
|
|
|
|
print(
|
|
"Event {}/{}, Inferior {}, Exit Code {}".format(
|
|
idx, total, inf_num, msg
|
|
)
|
|
)
|
|
elif args == "reset":
|
|
self._events = []
|
|
else:
|
|
raise gdb.GdbError("Unknown command args: {}".format(args))
|
|
|
|
|
|
events_cmd()
|
|
events_corefile_changed_cmd()
|
|
events_exited_cmd()
|
|
|
|
print("Success")
|