2957 lines
99 KiB
Diff
2957 lines
99 KiB
Diff
From FEDORA_PATCHES Mon Sep 17 00:00:00 2001
|
||
From: Michal Kolar <mkolar@redhat.com>
|
||
Date: Wed, 24 Jun 2026 21:25:33 +0000
|
||
Subject: gdb-backport-dap-core-file-support.patch
|
||
|
||
;; Backport of upstream commit e492fb22b70
|
||
;; gdb: backport DAP core file support
|
||
|
||
Back-port the following upstream commits to enable core file support
|
||
within GDB's DAP protocol implementation:
|
||
|
||
* 0a481bb9a6d gdb/dap: add support for opening core files
|
||
* 4ecaac39c26 gdb/python: new events.corefile_changed event
|
||
* ecac42af735 Always propagate exceptions in DAP
|
||
* fc8e5a565b3 gdb: make structured core file mappings processing global
|
||
* f69c1d03c4d gdb/python: add Corefile.mapped_files method
|
||
* 7862554bcf4 gdb/python: introduce gdb.Corefile API
|
||
|
||
The commit messages for each individual commit follow:
|
||
|
||
~~~ 0a481bb9a6d:
|
||
|
||
gdb/dap: add support for opening core files
|
||
|
||
This patch adds core file support to GDB's DAP interface.
|
||
|
||
Core files are supported as a GDB specific argument to 'attach', the
|
||
new argument is 'coreFile', the name of the core file to debug.
|
||
|
||
I think handling core files via attach makes the most sense; attach is
|
||
for connecting to existing processes, but these targets are (usually)
|
||
stopped as soon as GDB attaches, and that's what a core file looks
|
||
like, a target that was running, but is now stopped. It just happens
|
||
that core file targets are special in that the target cannot be
|
||
resumed again, nor can the user modify the program state (e.g. write
|
||
to memory or registers).
|
||
|
||
Prior to starting this work I took a look at what lldb does. The
|
||
documentation is not super clear, but this page seems to indicate that
|
||
lldb might also use the 'coreFile' argument to 'attach':
|
||
|
||
https://lldb.llvm.org/use/lldbdap.html#configuration-settings-reference
|
||
|
||
Like I said, it's not very clear, but search for "coreFile" and you'll
|
||
see it mentioned, just once, under the "attach" header. In order to
|
||
be compatible with lldb I used the same argument name with the same
|
||
capitalisation.
|
||
|
||
The new argument is added to the documentation and mentioned in NEWS.
|
||
|
||
I had to make some changes to testsuite/lib/dap-support.exp to support
|
||
this new feature. There's a new dap_corefile proc to handle setting
|
||
up the initial connection. This seemed cleaner that overloading
|
||
dap_attach, even though under the hood it is still an 'attach' request
|
||
that gets sent.
|
||
|
||
The new test tries to write to memory and registers with the core file
|
||
target in place, neither of these requests succeed, which is what we
|
||
want, but the exceptions are logged into the dap log file. The
|
||
dap_shutdown proc calls dap_check_log_file to check the log for
|
||
exceptions, and these two exceptions are spotted and trigger a FAIL.
|
||
To avoid this I've added a new "expected_exception_count" argument
|
||
for dap_shutdown. Now we check that we see the expected number of
|
||
exceptions. We don't check for the specific exception types right
|
||
now, but as the test is already checking that the expected requests
|
||
fail, I think we're OK.
|
||
|
||
Approved-By: Tom Tromey <tom@tromey.com>
|
||
|
||
~~~ 4ecaac39c26:
|
||
|
||
gdb/python: new events.corefile_changed event
|
||
|
||
Add a new Python event registry, events.corefile_changed. This event
|
||
is emitted each time the corefile within an inferior changes.
|
||
|
||
The event object has a single 'inferior' attribute which is the
|
||
gdb.Inferior object for which the core file changed. The user can
|
||
then inspect Inferior.corefile to see details about the new core file,
|
||
or this will be None if the core file was removed from the inferior.
|
||
|
||
I've updated the existing test to cover this new event.
|
||
|
||
The new test covers both the corefile_changed event, but also monitors
|
||
the exited event. This ties into the work done in the previous
|
||
commit where we use whether the inferior has exited or not as a guard
|
||
for whether core_target::exit_core_file_inferior should be called.
|
||
Unloading a core file should result in a single corefile_changed event
|
||
and a single exited event.
|
||
|
||
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
|
||
|
||
~~~ ecac42af735:
|
||
|
||
Always propagate exceptions in DAP
|
||
|
||
This changes the DAP exec_and_log function to always transform an
|
||
exception into a DAPException and propagate it.
|
||
|
||
As the bug points out, we haven't always wrapped calls when
|
||
appropriate. I think it's better to cause the request to fail by
|
||
default; if any spot truly needs to ignore errors, that is readily
|
||
done at the point of call.
|
||
|
||
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33346
|
||
|
||
~~~ fc8e5a565b3:
|
||
|
||
gdb: make structured core file mappings processing global
|
||
|
||
In corelow.c, within core_target::build_file_mappings, we have code
|
||
that wraps around a call to gdbarch_read_core_file_mappings and
|
||
provides more structure to the results.
|
||
|
||
Specifically, gdbarch_read_core_file_mappings calls a callback once
|
||
for every region of every mapped file. The wrapper code groups all of
|
||
the mappings for one file into an instance of 'struct mapped_file',
|
||
this allows all of the mapped regions to be associated with the
|
||
build-id and filename of a file.
|
||
|
||
In the next commit I plan to make this information available via the
|
||
Python API, and so I need to allow access to this structured wrapping
|
||
outside of corelow.c.
|
||
|
||
This commit renames 'struct mapped_file' to 'struct core_mapped_file'
|
||
and moves the struct into gdbcore.h. Then a new global function
|
||
gdb_read_core_file_mappings is created into which I move the code to
|
||
build the structured data.
|
||
|
||
Then corelow.c is updated to call gdb_read_core_file_mappings.
|
||
|
||
This commit does not extend the Python API, that is for the next
|
||
commit.
|
||
|
||
There should be no user visible changes after this commit.
|
||
|
||
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32844
|
||
|
||
Approved-By: Tom Tromey <tom@tromey.com>
|
||
|
||
~~~ f69c1d03c4d:
|
||
|
||
gdb/python: add Corefile.mapped_files method
|
||
|
||
Add a new Corefile.mapped_files method which returns a list of
|
||
gdb.CorefileMappedFile objects.
|
||
|
||
Each gdb.CorefileMappedFile object represents a file that was mapped
|
||
into the process when the core file was created.
|
||
|
||
A gdb.CorefileMappedFile has attributes:
|
||
|
||
+ filename -- A string, the name of the mapped file.
|
||
+ build_id -- A string or None, the build-id of the mapped file if
|
||
GDB could find it (None if not).
|
||
+ is_main_executable -- A boolean, True if this mapping is the main
|
||
executable.
|
||
+ regions -- A list containing the regions of this file that were
|
||
mapped into the process.
|
||
|
||
The 'regions' list is a list of gdb.CorefileMappedFileRegion objects,
|
||
each of these objects has the following attributes:
|
||
|
||
+ start -- the start address within the inferior.
|
||
+ end -- the end address within the inferior.
|
||
+ file_offset -- the offset within the mapped file for this mapping.
|
||
|
||
There are docs and tests.
|
||
|
||
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32844
|
||
|
||
Approved-By: Tom Tromey <tom@tromey.com>
|
||
|
||
~~~ 7862554bcf4:
|
||
|
||
gdb/python: introduce gdb.Corefile API
|
||
|
||
This commit starts adding some core file related features to the
|
||
Python API.
|
||
|
||
In this initial commit I've tried to keep the changes as small as
|
||
possible for easy review.
|
||
|
||
There's a new Python class gdb.Corefile, which represents a loaded
|
||
core file. This API doesn't allow the user to create their own
|
||
gdb.Corefile objects, a core file must be loaded using the 'core-file'
|
||
command, then a gdb.Corefile object can be obtained by querying the
|
||
inferior in which the core file was loaded.
|
||
|
||
There's a new attribute gdb.Inferior.corefile, this is None when no
|
||
core file is loaded, or contains a gdb.Corefile object if a core file
|
||
has been loaded.
|
||
|
||
Currently, the gdb.Corefile object has one attribute, and one method,
|
||
these are:
|
||
|
||
gdb.Corefile.filename -- the file name of the loaded core file.
|
||
|
||
gdb.Corefile.is_valid() -- indicates if a gdb.Corefile object is
|
||
valid or not. See notes below.
|
||
|
||
A gdb.Corefile object is only valid while the corresponding core file
|
||
is loaded into an inferior. Unloading the core file, or loading a
|
||
different one will cause a gdb.Corefile object to become invalid. For
|
||
example:
|
||
|
||
(gdb) core-file /tmp/core.54313
|
||
... snip ...
|
||
(gdb) python core=gdb.selected_inferior().corefile
|
||
(gdb) python print(core)
|
||
<gdb.Corefile inferior=1 filename='/tmp/core.54313'>
|
||
(gdb) python print(core.is_valid())
|
||
True
|
||
(gdb) core-file
|
||
No core file now.
|
||
(gdb) python print(core)
|
||
<gdb.Corefile (invalid)>
|
||
(gdb) python print(core.is_valid())
|
||
False
|
||
(gdb)
|
||
|
||
In order to track changes to the core file, there is a new observable
|
||
'core_file_changed', which accounts for the changes in corelow.c,
|
||
observable,c, and observable.h. Currently, this observable is not
|
||
visible as a Python event.
|
||
|
||
I chose to access the core file via the inferior even though the core
|
||
file BFD object is actually stored within the program_space. As such,
|
||
it might seem that the natural choice would be to add the attribute as
|
||
gdb.Progspace.corefile.
|
||
|
||
For background reading on my choice, please see:
|
||
|
||
https://inbox.sourceware.org/gdb-patches/577f2c47793acb501c2611c0e6c7ea379f774830.1668789658.git.aburgess@redhat.com
|
||
|
||
This patch was never merged, it is still on my backlog, but the
|
||
observation in that work is that some targets are not really
|
||
shareable. For example, the core_target (corelow.c) stores
|
||
information about the loaded core file within the target instance. As
|
||
such, each target instance represents a single loaded core file.
|
||
|
||
Except that the BFD part of the core file is stored in the
|
||
program_space, which is a little weird.
|
||
|
||
During review, Tom made the observation, that maybe we should
|
||
investigate moving the core file BFD into the core_target. I'm
|
||
inclined to agree with this as a direction of travel.
|
||
|
||
All this leaves us with two observations:
|
||
|
||
1. Currently, loading a core file into an inferior, then using
|
||
'add-inferior' will try to share the core_target between
|
||
inferiors. This is broken, and can trigger GDB crashes. The
|
||
obvious fix, without reworking core_target, is just to prevent
|
||
this sharing, making core_target per-inferior.
|
||
|
||
2. Having the core file information split between the core_target
|
||
instance, and the BFD stored in the program_space is a little
|
||
weird, and is really just historical. Planning for a future
|
||
where the BFD is also stored in the core_target might be wise.
|
||
|
||
So, if we imagine that the BFD is (one day) moved into the
|
||
core_target, and that the core_target really becomes non-shareable,
|
||
then it is, I think, clearer that the corefile attribute should live
|
||
on the gdb.Inferior object, not the gdb.Progspace object.
|
||
|
||
There's testing for all the functionality added in this commit.
|
||
|
||
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=32844
|
||
|
||
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
|
||
Approved-By: Tom Tromey <tom@tromey.com>
|
||
|
||
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
|
||
--- a/gdb/Makefile.in
|
||
+++ b/gdb/Makefile.in
|
||
@@ -399,6 +399,7 @@ SUBDIR_PYTHON_SRCS = \
|
||
python/py-color.c \
|
||
python/py-connection.c \
|
||
python/py-continueevent.c \
|
||
+ python/py-corefile.c \
|
||
python/py-dap.c \
|
||
python/py-disasm.c \
|
||
python/py-event.c \
|
||
diff --git a/gdb/NEWS b/gdb/NEWS
|
||
--- a/gdb/NEWS
|
||
+++ b/gdb/NEWS
|
||
@@ -13,6 +13,8 @@
|
||
|
||
** GDB now supports the "completions" request.
|
||
|
||
+ ** The attach request now accepts the coreFile parameter.
|
||
+
|
||
* "set style" commands now supports numeric format for basic colors
|
||
from 0 to 255 and #RRGGBB format for TrueColor.
|
||
|
||
@@ -176,6 +178,32 @@ info threads [-gid] [-stopped] [-running] [ID]...
|
||
unavailability like gdb.Value.is_optimized_out checks for
|
||
optimized out values.
|
||
|
||
+ ** New gdb.Corefile class which represents a loaded core file. This
|
||
+ has an attribute Corefile.filename, the file name of the loaded
|
||
+ core file, and a method Corefile.is_valid(), which returns False
|
||
+ when a Corefile object becomes invalid (e.g. when the core file
|
||
+ is unloaded). There is also Corefile.mapped_files() which
|
||
+ returns a list of CorefileMappedFile objects, representing files
|
||
+ that were mapped into the core file when it was created.
|
||
+
|
||
+ ** New gdb.CorefileMappedFile type representing a file that was
|
||
+ mapped when the core file was created. Has read-only attributes
|
||
+ filename (string), build_id (string), is_main_executable
|
||
+ (boolean), and regions (list of CorefileMappedFileRegion objects).
|
||
+
|
||
+ ** New gdb.CorefileMappedFileRegion type, which represents a mapped
|
||
+ region of a file (see gdb.CorefileMappedFile above). Has
|
||
+ read-only attributes start, end, and file_offset.
|
||
+
|
||
+ ** New Inferior.corefile attribute. This read only attribute
|
||
+ contains the gdb.Corefile object if a core file is loaded into
|
||
+ the inferior, otherwise, this contains None.
|
||
+
|
||
+ ** New event registry gdb.events.corefile_changed, which emits a
|
||
+ CorefileChangedEvent whenever the core file associated with an
|
||
+ inferior changes. The event has an 'inferior' attribute which is
|
||
+ the gdb.Inferior in which the core file has changed.
|
||
+
|
||
* Guile API
|
||
|
||
** New type <gdb:color> for dealing with colors.
|
||
diff --git a/gdb/corelow.c b/gdb/corelow.c
|
||
--- a/gdb/corelow.c
|
||
+++ b/gdb/corelow.c
|
||
@@ -53,6 +53,7 @@
|
||
#include "xml-tdesc.h"
|
||
#include "memtag.h"
|
||
#include "cli/cli-style.h"
|
||
+#include "observable.h"
|
||
|
||
#ifndef O_LARGEFILE
|
||
#define O_LARGEFILE 0
|
||
@@ -365,108 +366,27 @@ core_target::core_target ()
|
||
void
|
||
core_target::build_file_mappings ()
|
||
{
|
||
- /* 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. */
|
||
- struct mapped_file
|
||
- {
|
||
- /* Type for a region of a file that was mapped into the inferior when
|
||
- the core file was generated. */
|
||
- struct region
|
||
- {
|
||
- /* Constructor. See member variables for argument descriptions. */
|
||
- region (CORE_ADDR start_, CORE_ADDR end_, CORE_ADDR file_ofs_)
|
||
- : start (start_),
|
||
- end (end_),
|
||
- file_ofs (file_ofs_)
|
||
- { /* Nothing. */ }
|
||
-
|
||
- /* The inferior address for the start of the mapped region. */
|
||
- CORE_ADDR start;
|
||
-
|
||
- /* The inferior address immediately after the mapped region. */
|
||
- CORE_ADDR end;
|
||
-
|
||
- /* The offset within the mapped file for this content. */
|
||
- CORE_ADDR file_ofs;
|
||
- };
|
||
-
|
||
- /* If not nullptr, then this is the build-id associated with this
|
||
- file. */
|
||
- const bfd_build_id *build_id = nullptr;
|
||
-
|
||
- /* If true then we have seen multiple different build-ids associated
|
||
- with the same filename. The build_id field will have been set back
|
||
- to nullptr, and we should not set build_id in future. */
|
||
- bool ignore_build_id_p = false;
|
||
-
|
||
- /* All the mapped regions of this file. */
|
||
- std::vector<region> regions;
|
||
- };
|
||
-
|
||
gdb::unordered_map<std::string, struct bfd *> bfd_map;
|
||
gdb::unordered_set<std::string> unavailable_paths;
|
||
|
||
/* All files mapped into the core file. The key is the filename. */
|
||
- gdb::unordered_map<std::string, mapped_file> mapped_files;
|
||
-
|
||
- /* See linux_read_core_file_mappings() in linux-tdep.c for an example
|
||
- read_core_file_mappings method. */
|
||
- gdbarch_read_core_file_mappings (m_core_gdbarch,
|
||
- current_program_space->core_bfd (),
|
||
-
|
||
- /* 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. */
|
||
- [&] (int num, 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);
|
||
+ std::vector<core_mapped_file> mapped_files
|
||
+ = gdb_read_core_file_mappings (m_core_gdbarch,
|
||
+ current_program_space->core_bfd ());
|
||
|
||
- /* Add this mapped region to the data for FILENAME. */
|
||
- mapped_file &file_data = mapped_files[filename];
|
||
- file_data.regions.emplace_back (start, end, file_ofs);
|
||
- if (build_id != nullptr && !file_data.ignore_build_id_p)
|
||
- {
|
||
- if (file_data.build_id == nullptr)
|
||
- file_data.build_id = build_id;
|
||
- else if (!build_id_equal (build_id, file_data.build_id))
|
||
- {
|
||
- warning (_("Multiple build-ids found for %ps"),
|
||
- styled_string (file_name_style.style (), filename));
|
||
- file_data.build_id = nullptr;
|
||
- file_data.ignore_build_id_p = true;
|
||
- }
|
||
- }
|
||
- });
|
||
-
|
||
- /* Get the build-id of the core file. */
|
||
- const bfd_build_id *core_build_id
|
||
- = build_id_bfd_get (current_program_space->core_bfd ());
|
||
-
|
||
- for (const auto &[filename, file_data] : mapped_files)
|
||
+ for (const core_mapped_file &file_data : mapped_files)
|
||
{
|
||
- /* If this mapped file has the same build-id as was discovered for
|
||
- the core-file itself, then we assume this is the main
|
||
- executable. Record the filename as we can use this later. */
|
||
- if (file_data.build_id != nullptr
|
||
- && m_expected_exec_filename.empty ()
|
||
- && build_id_equal (file_data.build_id, core_build_id))
|
||
- m_expected_exec_filename = filename;
|
||
+ /* 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 ())
|
||
+ m_expected_exec_filename = file_data.filename;
|
||
|
||
/* Use exec_file_find() to do sysroot expansion. It'll
|
||
also strip the potential sysroot "target:" prefix. If
|
||
there is no sysroot, an equivalent (possibly more
|
||
canonical) pathname will be provided. */
|
||
gdb::unique_xmalloc_ptr<char> expanded_fname
|
||
- = exec_file_find (filename.c_str (), nullptr);
|
||
+ = exec_file_find (file_data.filename.c_str (), nullptr);
|
||
|
||
bool build_id_mismatch = false;
|
||
if (expanded_fname != nullptr && file_data.build_id != nullptr)
|
||
@@ -508,7 +428,7 @@ core_target::build_file_mappings ()
|
||
{
|
||
abfd = find_objfile_by_build_id (current_program_space,
|
||
file_data.build_id,
|
||
- filename.c_str ());
|
||
+ file_data.filename.c_str ());
|
||
|
||
if (abfd != nullptr)
|
||
{
|
||
@@ -526,7 +446,7 @@ core_target::build_file_mappings ()
|
||
}
|
||
|
||
std::vector<mem_range> ranges;
|
||
- for (const mapped_file::region ®ion : file_data.regions)
|
||
+ for (const core_mapped_file::region ®ion : file_data.regions)
|
||
ranges.emplace_back (region.start, region.end - region.start);
|
||
|
||
if (expanded_fname == nullptr
|
||
@@ -544,7 +464,7 @@ core_target::build_file_mappings ()
|
||
bool content_is_in_core_file_p = true;
|
||
|
||
/* Record all regions for this file as unavailable. */
|
||
- for (const mapped_file::region ®ion : file_data.regions)
|
||
+ for (const core_mapped_file::region ®ion : file_data.regions)
|
||
{
|
||
/* Check to see if the region is available within the core
|
||
file. */
|
||
@@ -576,33 +496,33 @@ core_target::build_file_mappings ()
|
||
if (build_id_mismatch)
|
||
{
|
||
if (expanded_fname == nullptr
|
||
- || filename == expanded_fname.get ())
|
||
+ || file_data.filename == expanded_fname.get ())
|
||
warning (_("File %ps doesn't match build-id from core-file "
|
||
"during file-backed mapping processing"),
|
||
styled_string (file_name_style.style (),
|
||
- filename.c_str ()));
|
||
+ file_data.filename.c_str ()));
|
||
else
|
||
warning (_("File %ps which was expanded to %ps, doesn't match "
|
||
"build-id from core-file during file-backed "
|
||
"mapping processing"),
|
||
styled_string (file_name_style.style (),
|
||
- filename.c_str ()),
|
||
+ file_data.filename.c_str ()),
|
||
styled_string (file_name_style.style (),
|
||
expanded_fname.get ()));
|
||
}
|
||
else if (!content_is_in_core_file_p)
|
||
{
|
||
if (expanded_fname == nullptr
|
||
- || filename == expanded_fname.get ())
|
||
+ || file_data.filename == expanded_fname.get ())
|
||
warning (_("Can't open file %ps during file-backed mapping "
|
||
"note processing"),
|
||
styled_string (file_name_style.style (),
|
||
- filename.c_str ()));
|
||
+ file_data.filename.c_str ()));
|
||
else
|
||
warning (_("Can't open file %ps which was expanded to %ps "
|
||
"during file-backed mapping note processing"),
|
||
styled_string (file_name_style.style (),
|
||
- filename.c_str ()),
|
||
+ file_data.filename.c_str ()),
|
||
styled_string (file_name_style.style (),
|
||
expanded_fname.get ()));
|
||
}
|
||
@@ -616,7 +536,7 @@ core_target::build_file_mappings ()
|
||
abfd.get ());
|
||
|
||
/* Create sections for each mapped region. */
|
||
- for (const mapped_file::region ®ion : file_data.regions)
|
||
+ for (const core_mapped_file::region ®ion : file_data.regions)
|
||
{
|
||
/* Make new BFD section. All sections have the same name,
|
||
which is permitted by bfd_make_section_anyway(). */
|
||
@@ -652,7 +572,7 @@ core_target::build_file_mappings ()
|
||
soname = gdb_bfd_read_elf_soname (actual_filename);
|
||
}
|
||
|
||
- m_mapped_file_info.add (soname.get (), filename.c_str (),
|
||
+ m_mapped_file_info.add (soname.get (), file_data.filename.c_str (),
|
||
actual_filename, std::move (ranges),
|
||
file_data.build_id);
|
||
}
|
||
@@ -678,6 +598,9 @@ core_target::clear_core ()
|
||
clear_solib (current_program_space);
|
||
|
||
current_program_space->cbfd.reset (nullptr);
|
||
+
|
||
+ /* Notify that the core file has changed. */
|
||
+ gdb::observers::core_file_changed.notify (current_inferior ());
|
||
}
|
||
}
|
||
|
||
@@ -1278,6 +1201,9 @@ core_target_open (const char *arg, int from_tty)
|
||
exception_print (gdb_stderr, except);
|
||
}
|
||
}
|
||
+
|
||
+ /* Notify that the core file has changed. */
|
||
+ gdb::observers::core_file_changed.notify (current_inferior ());
|
||
}
|
||
|
||
void
|
||
@@ -2156,6 +2082,103 @@ mapped_file_info::lookup (const char *filename,
|
||
|
||
/* See gdbcore.h. */
|
||
|
||
+std::vector<core_mapped_file>
|
||
+gdb_read_core_file_mappings (struct gdbarch *gdbarch, struct bfd *cbfd)
|
||
+{
|
||
+ std::vector<core_mapped_file> results;
|
||
+
|
||
+ /* A map entry used while building RESULTS. */
|
||
+ struct map_entry
|
||
+ {
|
||
+ explicit map_entry (core_mapped_file *ptr)
|
||
+ : file_data (ptr)
|
||
+ { /* Nothing. */ }
|
||
+
|
||
+ /* Points to an entry in RESULTS, this allows entries to be quickly
|
||
+ looked up and updated as new mappings are read. */
|
||
+ core_mapped_file *file_data = nullptr;
|
||
+
|
||
+ /* If true then we have seen multiple different build-ids associated
|
||
+ with the filename of FILE_DATA. The FILE_DATA->build_id field will
|
||
+ have been set to nullptr, and we should not set FILE_DATA->build_id
|
||
+ in future. */
|
||
+ bool ignore_build_id_p = false;
|
||
+ };
|
||
+
|
||
+ /* All files mapped into the core file. The key is the filename. */
|
||
+ gdb::unordered_map<std::string, map_entry> mapped_files;
|
||
+
|
||
+ /* Get the build-id of the core file. At least on Linux, this will be
|
||
+ the build-id for the main executable. If other targets add the
|
||
+ gdbarch_read_core_file_mappings method, then it might turn out that
|
||
+ this logic is no longer true, in which case this might need to move
|
||
+ into the gdbarch_read_core_file_mappings method. */
|
||
+ const bfd_build_id *core_build_id = build_id_bfd_get (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. */
|
||
+ [&] (int num, 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);
|
||
+
|
||
+ /* Add this mapped region to the data for FILENAME. */
|
||
+ auto iter = mapped_files.find (filename);
|
||
+ if (iter == mapped_files.end ())
|
||
+ {
|
||
+ /* Create entry in results list. */
|
||
+ results.emplace_back ();
|
||
+
|
||
+ /* The entry to be added to the lookup map. */
|
||
+ map_entry entry (&results.back ());
|
||
+ entry.file_data->filename = filename;
|
||
+
|
||
+ /* Add entry to the quick lookup map and update ITER. */
|
||
+ auto inserted_result
|
||
+ = mapped_files.insert ({filename, std::move (entry)});
|
||
+ gdb_assert (inserted_result.second);
|
||
+ iter = inserted_result.first;
|
||
+ }
|
||
+
|
||
+ core_mapped_file &file_data = *iter->second.file_data;
|
||
+ bool &ignore_build_id_p = iter->second.ignore_build_id_p;
|
||
+
|
||
+ file_data.regions.emplace_back (start, end, file_ofs);
|
||
+ if (build_id != nullptr && !ignore_build_id_p)
|
||
+ {
|
||
+ if (file_data.build_id == nullptr)
|
||
+ file_data.build_id = build_id;
|
||
+ else if (!build_id_equal (build_id, file_data.build_id))
|
||
+ {
|
||
+ warning (_("Multiple build-ids found for %ps"),
|
||
+ styled_string (file_name_style.style (), filename));
|
||
+ file_data.build_id = nullptr;
|
||
+ ignore_build_id_p = true;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (build_id != nullptr
|
||
+ && core_build_id != nullptr
|
||
+ && build_id_equal (build_id, core_build_id))
|
||
+ file_data.is_main_exec = true;
|
||
+ });
|
||
+
|
||
+ return results;
|
||
+}
|
||
+
|
||
+/* See gdbcore.h. */
|
||
+
|
||
std::optional <core_target_mapped_file_info>
|
||
core_target_find_mapped_file (const char *filename,
|
||
std::optional<CORE_ADDR> addr)
|
||
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
|
||
--- a/gdb/doc/gdb.texinfo
|
||
+++ b/gdb/doc/gdb.texinfo
|
||
@@ -40029,10 +40029,10 @@ will set a temporary breakpoint at the program's first instruction, using
|
||
the same approach as the @code{starti} command. @xref{Starting}.
|
||
@end table
|
||
|
||
-@value{GDBN} defines some parameters that can be passed to the
|
||
-@code{attach} request. Either @code{pid} or @code{target} must be
|
||
-specified, but if both are specified then @code{target} will be
|
||
-ignored.
|
||
+@value{GDBN} defines some additional parameters that can be passed to
|
||
+the @code{attach} request. One of @code{pid}, @code{target}, or
|
||
+@code{coreFile} must be specified. If multiple are specified, they
|
||
+are checked for in that order, and the first one found is used.
|
||
|
||
@table @code
|
||
@item pid
|
||
@@ -40048,6 +40048,10 @@ should be supplied.
|
||
@item target
|
||
The target to which @value{GDBN} should connect. This is a string and
|
||
is passed to the @code{target remote} command. @xref{Connecting}.
|
||
+
|
||
+@item coreFile
|
||
+A string that specifies a core file to use. This corresponds to the
|
||
+@kbd{core-file} command. @xref{core-file command}.
|
||
@end table
|
||
|
||
In response to the @code{disassemble} request, DAP allows the client
|
||
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
|
||
--- a/gdb/doc/python.texi
|
||
+++ b/gdb/doc/python.texi
|
||
@@ -233,6 +233,7 @@ optional arguments while skipping others. Example:
|
||
* Disassembly In Python:: Instruction Disassembly In Python
|
||
* Missing Debug Info In Python:: Handle missing debug info from Python.
|
||
* Missing Objfiles In Python:: Handle objfiles from Python.
|
||
+* Core Files In Python:: Python representation of core files.
|
||
@end menu
|
||
|
||
@node Basic Python
|
||
@@ -3626,6 +3627,15 @@ necessary quoting for the shell; when a sequence is assigned, the
|
||
quoting is applied by @value{GDBN}.
|
||
@end defvar
|
||
|
||
+@defvar Inferior.corefile
|
||
+If a core file has been loaded into this inferior (@pxref{core-file
|
||
+command}), then this contains a @code{gdb.Corefile} object that
|
||
+represents the loaded core file (@pxref{Core Files In Python}).
|
||
+
|
||
+If no core file has been loaded into this inferior, then this
|
||
+attribute contains @code{None}.
|
||
+@end defvar
|
||
+
|
||
A @code{gdb.Inferior} object has the following methods:
|
||
|
||
@defun Inferior.is_valid ()
|
||
@@ -4094,6 +4104,17 @@ file is updated first, so when this event is emitted, the executable
|
||
filename will have changed, but the symbol filename might still hold
|
||
its previous value.
|
||
|
||
+@item events.corefile_changed
|
||
+Emits @code{gdb.CorefileChangedEvent} which indicates that the core
|
||
+file associated with a @code{gdb.Inferior} has changed, either a new
|
||
+core file has been loaded, or the existing core file has been
|
||
+unloaded (@pxref{Core Files In Python}).
|
||
+
|
||
+@defvar CorefileChangedEvent.inferior
|
||
+The @code{gdb.Inferior} in which the core file has changed
|
||
+(@pxref{Inferiors In Python}).
|
||
+@end defvar
|
||
+
|
||
@item events.new_progspace
|
||
This is emitted when @value{GDBN} adds a new program space
|
||
(@pxref{Progspaces In Python,,Program Spaces In Python}). The event
|
||
@@ -8624,6 +8645,106 @@ handlers, all of the matching handlers are enabled. The
|
||
@code{enabled} field of each matching handler is set to @code{True}.
|
||
@end table
|
||
|
||
+@node Core Files In Python
|
||
+@subsubsection Core Files In Python
|
||
+@cindex python, core files
|
||
+
|
||
+When a core file is loaded into an inferior (@pxref{Inferiors In
|
||
+Python}) for examination (@pxref{core-file command}), information
|
||
+about the core file is contained in a @code{gdb.Corefile} object.
|
||
+
|
||
+The @code{gdb.Corefile} for an inferior can be accessed using the
|
||
+@code{Inferior.corefile} attribute. This will be @code{None} if
|
||
+no core file is loaded.
|
||
+
|
||
+A @code{gdb.Corefile} object has the following attributes:
|
||
+
|
||
+@defvar Corefile.filename
|
||
+This read only attribute contains a non-empty string, the file name of
|
||
+the core file. Attempting to access this attribute on an invalid
|
||
+@code{gdb.Corefile} object will raise a @code{RuntimeError} exception.
|
||
+@end defvar
|
||
+
|
||
+A @code{gdb.Corefile} object has the following methods:
|
||
+
|
||
+@defun Corefile.is_valid ()
|
||
+Returns @code{True} if the @code{gdb.Corefile} object is valid,
|
||
+@code{False} if not. A @code{gdb.Corefile} object will become invalid
|
||
+when the core file is unloaded from the inferior using the
|
||
+@kbd{core-file} command (@pxref{core-file command}), or if the
|
||
+inferior in which the core file is loaded is deleted. All other
|
||
+@code{gdb.Corefile} methods and attributes will throw an exception if
|
||
+it is invalid at the time the method is called, or the attribute
|
||
+accessed.
|
||
+@end defun
|
||
+
|
||
+@defun Corefile.mapped_files ()
|
||
+Return a list of @code{gdb.CorefileMappedFile} (see below) objects
|
||
+representing files that were mapped into the process when the core
|
||
+file was created. This information is read from the @samp{NT_FILE}
|
||
+core file note on Linux. Not every target supports accessing this
|
||
+information, for targets without support, an empty list will be
|
||
+returned.
|
||
+@end defun
|
||
+
|
||
+One may add arbitrary attributes to @code{gdb.Corefile} objects in the
|
||
+usual Python way. This is useful if, for example, one needs to do
|
||
+some extra record keeping associated with the corefile.
|
||
+@xref{choosing attribute names}, for guidance on selecting a suitable
|
||
+name for new attributes.
|
||
+
|
||
+The @code{Corefile.mapped_files ()} method returns a list of
|
||
+@code{gdb.CorefileMappedFile} objects. Each of these objects
|
||
+represents a file that was fully, or partially, mapped into the
|
||
+processes address space when the core file was created.
|
||
+
|
||
+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.
|
||
+@end defvar
|
||
+
|
||
+@defvar CorefileMappedFile.build_id
|
||
+This read only attribute contains a non-empty string or @code{None}.
|
||
+This is the build-id of the mapped file extracted from the core file,
|
||
+or @code{None} if there was no build-id, or @value{GDBN} was unable to
|
||
+extract the build-id.
|
||
+@end defvar
|
||
+
|
||
+@defvar CorefileMappedFile.is_main_executable
|
||
+This read only attribute is @code{True} if @value{GDBN} believes this
|
||
+mapping represents the main executable for which this core file was
|
||
+created. This will be @code{False} for all other mappings.
|
||
+@end defvar
|
||
+
|
||
+@defvar CorefileMappedFile.regions
|
||
+This read only attribute contains a list of
|
||
+@code{gdb.CorefileMappedFileRegion} objects. Each of these objects
|
||
+describes a region of the file that was mapped into the process when
|
||
+the core file was created, further details are given below.
|
||
+@end defvar
|
||
+
|
||
+The @code{gdb.CorefileMappedFileRegion} object describes which part of
|
||
+a file that was mapped into a process when the core file was created.
|
||
+
|
||
+A @code{gdb.CorefileMappedFile} object has the following attributes:
|
||
+
|
||
+@defvar CorefileMappedFileRegion.start
|
||
+This read only attribute contains the start address of this mapping
|
||
+within the inferior.
|
||
+@end defvar
|
||
+
|
||
+@defvar CorefileMappedFileRegion.end
|
||
+This read only attribute contains end address of this mapping within
|
||
+the inferior.
|
||
+@end defvar
|
||
+
|
||
+@defvar CorefileMappedFileRegion.file_offset
|
||
+This read only attribute contains the offset within the mapped file
|
||
+for this mapping.
|
||
+@end defvar
|
||
+
|
||
@node Python Auto-loading
|
||
@subsection Python Auto-loading
|
||
@cindex Python auto-loading
|
||
diff --git a/gdb/gdbcore.h b/gdb/gdbcore.h
|
||
--- a/gdb/gdbcore.h
|
||
+++ b/gdb/gdbcore.h
|
||
@@ -258,4 +258,47 @@ std::optional<core_target_mapped_file_info>
|
||
core_target_find_mapped_file (const char *filename,
|
||
std::optional<CORE_ADDR> addr);
|
||
|
||
+/* 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. */
|
||
+struct core_mapped_file
|
||
+{
|
||
+ /* Type for a region of a file that was mapped into the inferior when
|
||
+ the core file was generated. */
|
||
+ struct region
|
||
+ {
|
||
+ /* Constructor. See member variables for argument descriptions. */
|
||
+ region (CORE_ADDR start_, CORE_ADDR end_, CORE_ADDR file_ofs_)
|
||
+ : start (start_),
|
||
+ end (end_),
|
||
+ file_ofs (file_ofs_)
|
||
+ { /* Nothing. */ }
|
||
+
|
||
+ /* The inferior address for the start of the mapped region. */
|
||
+ CORE_ADDR start;
|
||
+
|
||
+ /* The inferior address immediately after the mapped region. */
|
||
+ CORE_ADDR end;
|
||
+
|
||
+ /* The offset within the mapped file for this content. */
|
||
+ CORE_ADDR file_ofs;
|
||
+ };
|
||
+
|
||
+ /* The filename as recorded in the core file. */
|
||
+ std::string filename;
|
||
+
|
||
+ /* If not nullptr, then this is the build-id associated with this
|
||
+ file. */
|
||
+ const bfd_build_id *build_id = nullptr;
|
||
+
|
||
+ /* All the mapped regions of this file. */
|
||
+ std::vector<region> regions;
|
||
+
|
||
+ /* True if this is the main executable. */
|
||
+ bool is_main_exec = false;
|
||
+};
|
||
+
|
||
+extern std::vector<core_mapped_file> gdb_read_core_file_mappings
|
||
+ (struct gdbarch *gdbarch, struct bfd *cbfd);
|
||
+
|
||
#endif /* GDB_GDBCORE_H */
|
||
diff --git a/gdb/observable.c b/gdb/observable.c
|
||
--- a/gdb/observable.c
|
||
+++ b/gdb/observable.c
|
||
@@ -76,6 +76,7 @@ DEFINE_OBSERVABLE (target_post_wait);
|
||
DEFINE_OBSERVABLE (new_program_space);
|
||
DEFINE_OBSERVABLE (free_program_space);
|
||
DEFINE_OBSERVABLE (tui_enabled);
|
||
+DEFINE_OBSERVABLE (core_file_changed);
|
||
|
||
} /* namespace observers */
|
||
} /* namespace gdb */
|
||
diff --git a/gdb/observable.h b/gdb/observable.h
|
||
--- a/gdb/observable.h
|
||
+++ b/gdb/observable.h
|
||
@@ -260,6 +260,12 @@ extern observable <program_space */* pspace */> free_program_space;
|
||
|
||
extern observable<bool /* enabled */> tui_enabled;
|
||
|
||
+/* The core file loaded into the program space inferior INF has changed.
|
||
+ The process of changing has completed, i.e. when unloading, the unload
|
||
+ is now complete. When loading a new core file, the load is complete,
|
||
+ shared libraries have been loaded, registers and threads read in, etc. */
|
||
+extern observable<inferior */* inf */> core_file_changed;
|
||
+
|
||
} /* namespace observers */
|
||
|
||
} /* namespace gdb */
|
||
diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py
|
||
--- a/gdb/python/lib/gdb/dap/events.py
|
||
+++ b/gdb/python/lib/gdb/dap/events.py
|
||
@@ -161,7 +161,7 @@ _expected_pause = False
|
||
|
||
|
||
@in_gdb_thread
|
||
-def exec_and_expect_stop(cmd, expected_pause=False, propagate_exception=False):
|
||
+def exec_and_expect_stop(cmd, expected_pause=False):
|
||
"""A wrapper for exec_and_log that sets the continue-suppression flag.
|
||
|
||
When EXPECTED_PAUSE is True, a stop that looks like a pause (e.g.,
|
||
@@ -174,7 +174,7 @@ def exec_and_expect_stop(cmd, expected_pause=False, propagate_exception=False):
|
||
# continuing.
|
||
_suppress_cont = not expected_pause
|
||
# FIXME if the call fails should we clear _suppress_cont?
|
||
- exec_and_log(cmd, propagate_exception)
|
||
+ exec_and_log(cmd)
|
||
|
||
|
||
# Map from gdb stop reasons to DAP stop reasons. Some of these can't
|
||
@@ -276,6 +276,31 @@ def _on_inferior_call(event):
|
||
send_event("stopped", obj)
|
||
|
||
|
||
+@in_gdb_thread
|
||
+def _on_corefile_changed(event):
|
||
+ # Ignore events relating to corefile being unloaded.
|
||
+ if event.inferior.corefile is None:
|
||
+ return
|
||
+
|
||
+ # Corefiles are usually attached via the 'attach' request, which
|
||
+ # sets the global _expected_stop_reason to 'attach'. It is
|
||
+ # because of this that it is safe to forward to _on_stop, as when
|
||
+ # _expected_stop_reason is set _on_stop doesn't read the
|
||
+ # event.details, which EVENT doesn't have.
|
||
+ #
|
||
+ # However, if the user loads a core file via some mechanism other
|
||
+ # than the 'attach' request, e.g. they use the repl to issue a GDB
|
||
+ # 'core-file' command, then when we get here _expected_stop_reason
|
||
+ # will not be set.
|
||
+ #
|
||
+ # So, in either case, set _expected_stop_reason now.
|
||
+ global _expected_stop_reason
|
||
+ _expected_stop_reason = "attach"
|
||
+
|
||
+ # A corefile was loaded, announce that the inferior has stopped.
|
||
+ _on_stop(event)
|
||
+
|
||
+
|
||
gdb.events.stop.connect(_on_stop)
|
||
gdb.events.exited.connect(_on_exit)
|
||
gdb.events.new_thread.connect(_new_thread)
|
||
@@ -284,3 +309,4 @@ gdb.events.cont.connect(_cont)
|
||
gdb.events.new_objfile.connect(_new_objfile)
|
||
gdb.events.free_objfile.connect(_objfile_removed)
|
||
gdb.events.inferior_call.connect(_on_inferior_call)
|
||
+gdb.events.corefile_changed.connect(_on_corefile_changed)
|
||
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
|
||
--- a/gdb/python/lib/gdb/dap/launch.py
|
||
+++ b/gdb/python/lib/gdb/dap/launch.py
|
||
@@ -59,12 +59,16 @@ class _LaunchOrAttachDeferredRequest(DeferredRequest):
|
||
super().reschedule()
|
||
|
||
|
||
+# Handle whitespace, quotes, and backslashes here. Exactly what
|
||
+# to quote depends on libiberty's buildargv and safe-ctype.
|
||
+def escape_filename(filename):
|
||
+ return re.sub("[ \t\n\r\f\v\\\\'\"]", "\\\\\\g<0>", filename)
|
||
+
|
||
+
|
||
# A wrapper for the 'file' command that correctly quotes its argument.
|
||
@in_gdb_thread
|
||
def file_command(program):
|
||
- # Handle whitespace, quotes, and backslashes here. Exactly what
|
||
- # to quote depends on libiberty's buildargv and safe-ctype.
|
||
- program = re.sub("[ \t\n\r\f\v\\\\'\"]", "\\\\\\g<0>", program)
|
||
+ program = escape_filename(program)
|
||
exec_and_log("file " + program)
|
||
|
||
|
||
@@ -132,6 +136,7 @@ def attach(
|
||
program: Optional[str] = None,
|
||
pid: Optional[int] = None,
|
||
target: Optional[str] = None,
|
||
+ coreFile: Optional[str] = None,
|
||
**args,
|
||
):
|
||
# The actual attach is handled by this function.
|
||
@@ -143,11 +148,14 @@ def attach(
|
||
cmd = "attach " + str(pid)
|
||
elif target is not None:
|
||
cmd = "target remote " + target
|
||
+ elif coreFile is not None:
|
||
+ cmd = "core-file " + escape_filename(coreFile)
|
||
else:
|
||
- raise DAPException("attach requires either 'pid' or 'target'")
|
||
+ raise DAPException("attach requires either 'pid', 'target', or 'coreFile'")
|
||
expect_process("attach")
|
||
expect_stop("attach")
|
||
exec_and_log(cmd)
|
||
+
|
||
# Attach response does not have a body.
|
||
return None
|
||
|
||
diff --git a/gdb/python/lib/gdb/dap/next.py b/gdb/python/lib/gdb/dap/next.py
|
||
--- a/gdb/python/lib/gdb/dap/next.py
|
||
+++ b/gdb/python/lib/gdb/dap/next.py
|
||
@@ -76,7 +76,7 @@ def step_in(
|
||
@request("stepOut")
|
||
def step_out(*, threadId: int, singleThread: bool = False, **args):
|
||
_handle_thread_step(threadId, singleThread, True)
|
||
- exec_and_expect_stop("finish &", propagate_exception=True)
|
||
+ exec_and_expect_stop("finish &")
|
||
|
||
|
||
@request("continue")
|
||
diff --git a/gdb/python/lib/gdb/dap/server.py b/gdb/python/lib/gdb/dap/server.py
|
||
--- a/gdb/python/lib/gdb/dap/server.py
|
||
+++ b/gdb/python/lib/gdb/dap/server.py
|
||
@@ -19,6 +19,7 @@ import inspect
|
||
import json
|
||
import threading
|
||
from contextlib import contextmanager
|
||
+from typing import Optional
|
||
|
||
import gdb
|
||
|
||
@@ -610,11 +611,29 @@ def terminate(**args):
|
||
exec_and_log("kill")
|
||
|
||
|
||
+@in_gdb_thread
|
||
+def _disconnect_or_kill(terminate: Optional[bool]):
|
||
+ inf = gdb.selected_inferior()
|
||
+ if inf.connection is None:
|
||
+ # Nothing to do here.
|
||
+ return
|
||
+ if terminate is None:
|
||
+ # The default depends on whether the inferior was attached or
|
||
+ # launched.
|
||
+ terminate = not inf.was_attached
|
||
+
|
||
+ if inf.corefile is not None:
|
||
+ exec_and_log("core-file")
|
||
+ elif terminate:
|
||
+ exec_and_log("kill")
|
||
+ elif inf.was_attached:
|
||
+ exec_and_log("detach")
|
||
+
|
||
+
|
||
@request("disconnect", on_dap_thread=True, expect_stopped=False)
|
||
@capability("supportTerminateDebuggee")
|
||
-def disconnect(*, terminateDebuggee: bool = False, **args):
|
||
- if terminateDebuggee:
|
||
- send_gdb_with_response("kill")
|
||
+def disconnect(*, terminateDebuggee: Optional[bool] = None, **args):
|
||
+ send_gdb_with_response(lambda: _disconnect_or_kill(terminateDebuggee))
|
||
_server.shutdown()
|
||
|
||
|
||
diff --git a/gdb/python/lib/gdb/dap/startup.py b/gdb/python/lib/gdb/dap/startup.py
|
||
--- a/gdb/python/lib/gdb/dap/startup.py
|
||
+++ b/gdb/python/lib/gdb/dap/startup.py
|
||
@@ -204,7 +204,7 @@ def log_stack(level=LogLevel.DEFAULT):
|
||
|
||
|
||
@in_gdb_thread
|
||
-def exec_and_log(cmd, propagate_exception=False):
|
||
+def exec_and_log(cmd):
|
||
"""Execute the gdb command CMD.
|
||
If logging is enabled, log the command and its output."""
|
||
log("+++ " + cmd)
|
||
@@ -213,10 +213,10 @@ def exec_and_log(cmd, propagate_exception=False):
|
||
if output != "":
|
||
log(">>> " + output)
|
||
except gdb.error as e:
|
||
- if propagate_exception:
|
||
- raise DAPException(str(e)) from e
|
||
- else:
|
||
- log_stack()
|
||
+ # Don't normally want to see this, as it interferes with the
|
||
+ # test suite.
|
||
+ log_stack(LogLevel.FULL)
|
||
+ raise DAPException(str(e)) from e
|
||
|
||
|
||
@in_gdb_thread
|
||
diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def
|
||
--- a/gdb/python/py-all-events.def
|
||
+++ b/gdb/python/py-all-events.def
|
||
@@ -46,3 +46,4 @@ GDB_PY_DEFINE_EVENT(executable_changed)
|
||
GDB_PY_DEFINE_EVENT(new_progspace)
|
||
GDB_PY_DEFINE_EVENT(free_progspace)
|
||
GDB_PY_DEFINE_EVENT(tui_enabled)
|
||
+GDB_PY_DEFINE_EVENT(corefile_changed)
|
||
diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/python/py-corefile.c
|
||
@@ -0,0 +1,723 @@
|
||
+/* Python interface to core files.
|
||
+
|
||
+ Copyright (C) 2025 Free Software Foundation, Inc.
|
||
+
|
||
+ This file is part of GDB.
|
||
+
|
||
+ 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/>. */
|
||
+
|
||
+#include "python-internal.h"
|
||
+#include "progspace.h"
|
||
+#include "observable.h"
|
||
+#include "inferior.h"
|
||
+#include "gdbcore.h"
|
||
+#include "gdbsupport/rsp-low.h"
|
||
+#include "py-event.h"
|
||
+
|
||
+/* A gdb.Corefile object. */
|
||
+
|
||
+struct corefile_object
|
||
+{
|
||
+ PyObject_HEAD
|
||
+
|
||
+ /* The inferior this core file is attached to. This will be set to NULL
|
||
+ when the inferior is deleted, or if a different core file is loaded
|
||
+ for the inferior. When this is NULL the gdb.Corefile object is
|
||
+ considered invalid.*/
|
||
+ struct inferior *inferior;
|
||
+
|
||
+ /* Dictionary holding user-added attributes. This is the __dict__
|
||
+ attribute of the object. This is an owning reference. */
|
||
+ PyObject *dict;
|
||
+
|
||
+ /* A Tuple of gdb.CorefileMappedFile objects. This tuple is only created
|
||
+ the first time the user calls gdb.Corefile.mapped_files(), the result
|
||
+ is cached here. If this pointer is not NULL then this is an owning
|
||
+ pointer (i.e. this owns a reference to the Tuple). */
|
||
+ PyObject *mapped_files;
|
||
+};
|
||
+
|
||
+extern PyTypeObject corefile_object_type
|
||
+ CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("corefile_object");
|
||
+
|
||
+/* A gdb.CorefileMapped object. */
|
||
+
|
||
+struct corefile_mapped_file_object
|
||
+{
|
||
+ PyObject_HEAD
|
||
+
|
||
+ /* The name of a file that was mapped when the core file was created.
|
||
+ This is a 'str' object. */
|
||
+ PyObject *filename;
|
||
+
|
||
+ /* The build-id of a file that was mapped when the core file was
|
||
+ created. This is either a 'str' if the file had a build-id, or
|
||
+ 'None' if there was no build-id for this file. */
|
||
+ PyObject *build_id;
|
||
+
|
||
+ /* A List of gdb.CorefileMappedFileRegion objects. */
|
||
+ PyObject *regions;
|
||
+
|
||
+ /* True if this represents the main executable from which the core file
|
||
+ was created. */
|
||
+ bool is_main_exec_p;
|
||
+};
|
||
+
|
||
+extern PyTypeObject corefile_mapped_file_object_type
|
||
+ CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("corefile_mapped_file_object");
|
||
+
|
||
+/* A gdb.CorefileMappedFileRegion object. */
|
||
+
|
||
+struct corefile_mapped_file_region_object
|
||
+{
|
||
+ PyObject_HEAD
|
||
+
|
||
+ /* The start and end addresses for this mapping, these are addresses
|
||
+ within the inferior's address space. */
|
||
+ CORE_ADDR start;
|
||
+ CORE_ADDR end;
|
||
+
|
||
+ /* The offset within the mapped file for this mapping. */
|
||
+ ULONGEST file_offset;
|
||
+};
|
||
+
|
||
+extern PyTypeObject corefile_mapped_file_region_object_type
|
||
+ CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("corefile_mapped_file_region_object");
|
||
+
|
||
+/* Clear the inferior pointer in a Corefile object OBJ when an inferior is
|
||
+ deleted. */
|
||
+
|
||
+struct inferior_corefile_deleter
|
||
+{
|
||
+ void operator() (corefile_object *obj)
|
||
+ {
|
||
+ if (!gdb_python_initialized)
|
||
+ return;
|
||
+
|
||
+ gdbpy_enter enter_py;
|
||
+
|
||
+ /* When OBJECT goes out of scope this will Py_DECREF on OBJ. */
|
||
+ gdbpy_ref<corefile_object> object (obj);
|
||
+
|
||
+ /* Clearing the inferior pointer marks the gdb.Corefile as invalid. */
|
||
+ object->inferior = nullptr;
|
||
+ }
|
||
+};
|
||
+
|
||
+/* Store a gdb.Corefile object in an inferior's registry. */
|
||
+
|
||
+static const registry<inferior>::key<corefile_object,
|
||
+ inferior_corefile_deleter>
|
||
+ cfpy_inferior_corefile_data_key;
|
||
+
|
||
+/* See python-internal.h. */
|
||
+
|
||
+gdbpy_ref<>
|
||
+gdbpy_core_file_from_inferior (inferior *inf)
|
||
+{
|
||
+ gdb_assert (inf != nullptr);
|
||
+ gdb_assert (inf->pspace != nullptr);
|
||
+
|
||
+ program_space *pspace = inf->pspace;
|
||
+
|
||
+ if (pspace->core_bfd () == nullptr)
|
||
+ return gdbpy_ref<>::new_reference (Py_None);
|
||
+
|
||
+ PyObject *result = (PyObject *) cfpy_inferior_corefile_data_key.get (inf);
|
||
+ if (result == nullptr)
|
||
+ {
|
||
+ gdbpy_ref<corefile_object> object
|
||
+ (PyObject_New (corefile_object, &corefile_object_type));
|
||
+ if (object == nullptr)
|
||
+ return nullptr;
|
||
+
|
||
+ /* Ensure the 'inferior' field is set to NULL. If the PyDict_New
|
||
+ call fails then the gdb.Corefile will be discarded and
|
||
+ cfpy_dealloc will be called, which requires that the 'inferior' be
|
||
+ set to NULL. */
|
||
+ object->inferior = nullptr;
|
||
+ object->mapped_files = nullptr;
|
||
+ object->dict = PyDict_New ();
|
||
+ if (object->dict == nullptr)
|
||
+ return nullptr;
|
||
+
|
||
+ /* Now that the gdb.Corefile has been successfully initialised and we
|
||
+ know that it is going to be passed back to the user, move it out
|
||
+ of the invalid state by setting the 'inferior' field to a non NULL
|
||
+ value. */
|
||
+ object->inferior = inf;
|
||
+ cfpy_inferior_corefile_data_key.set (inf, object.get ());
|
||
+ result = (PyObject *) object.release ();
|
||
+ }
|
||
+
|
||
+ return gdbpy_ref<>::new_reference (result);
|
||
+}
|
||
+
|
||
+/* Return true if OBJ is valid. */
|
||
+
|
||
+static bool
|
||
+cfpy_corefile_object_is_valid (const corefile_object *obj)
|
||
+{
|
||
+ if (obj->inferior == nullptr)
|
||
+ return false;
|
||
+
|
||
+ gdb_assert (obj->inferior->pspace != nullptr);
|
||
+
|
||
+ return obj->inferior->pspace->core_bfd () != nullptr;
|
||
+}
|
||
+
|
||
+/* Require that COREFILE_OBJ be a valid core file. A valid core file
|
||
+ object has a valid program space, and the program space has a core file
|
||
+ loaded into it. */
|
||
+#define CFPY_REQUIRE_VALID(corefile_obj) \
|
||
+ do { \
|
||
+ if (!cfpy_corefile_object_is_valid (corefile_obj)) \
|
||
+ { \
|
||
+ PyErr_SetString (PyExc_RuntimeError, \
|
||
+ _("Corefile no longer exists.")); \
|
||
+ return nullptr; \
|
||
+ } \
|
||
+ } while (0)
|
||
+
|
||
+/* Read the gdb.Corefile.filename attribute. */
|
||
+
|
||
+static PyObject *
|
||
+cfpy_get_filename (PyObject *self, void *closure)
|
||
+{
|
||
+ corefile_object *obj = (corefile_object *) self;
|
||
+
|
||
+ CFPY_REQUIRE_VALID (obj);
|
||
+
|
||
+ /* If the program space's core file had been cleared, then this Corefile
|
||
+ object would have been invalidated. */
|
||
+ bfd *abfd = obj->inferior->pspace->core_bfd ();
|
||
+ gdb_assert (abfd != nullptr);
|
||
+
|
||
+ return host_string_to_python_string (bfd_get_filename (abfd)).release ();
|
||
+}
|
||
+
|
||
+/* Implementation of gdb.Corefile.is_valid (self) -> Boolean.
|
||
+ Returns True if this core file object is associated with a program space
|
||
+ that still exists, an the program space still has a core file loaded. */
|
||
+
|
||
+static PyObject *
|
||
+cfpy_is_valid (PyObject *self, PyObject *args)
|
||
+{
|
||
+ corefile_object *obj = (corefile_object *) self;
|
||
+
|
||
+ if (!cfpy_corefile_object_is_valid (obj))
|
||
+ Py_RETURN_FALSE;
|
||
+
|
||
+ Py_RETURN_TRUE;
|
||
+}
|
||
+
|
||
+/* Implement gdb.Corefile.mapped_files (). Return a List of
|
||
+ gdb.CorefileMappedFile objects. The list is created the first time
|
||
+ this method is called, and then cached within the gdb.Corefile object,
|
||
+ future calls just return a reference to the same list. */
|
||
+
|
||
+static PyObject *
|
||
+cfpy_mapped_files (PyObject *self, PyObject *args)
|
||
+{
|
||
+ corefile_object *obj = (corefile_object *) self;
|
||
+
|
||
+ CFPY_REQUIRE_VALID (obj);
|
||
+
|
||
+ /* If we have already created the List then just return another reference
|
||
+ to the existing list. */
|
||
+ if (obj->mapped_files != nullptr)
|
||
+ {
|
||
+ Py_INCREF (obj->mapped_files);
|
||
+ return obj->mapped_files;
|
||
+ }
|
||
+
|
||
+ /* Get all the mapping data from GDB. */
|
||
+ std::vector<core_mapped_file> mapped_files;
|
||
+ try
|
||
+ {
|
||
+ mapped_files
|
||
+ = gdb_read_core_file_mappings (obj->inferior->arch (),
|
||
+ current_program_space->core_bfd ());
|
||
+ }
|
||
+ catch (const gdb_exception &except)
|
||
+ {
|
||
+ return gdbpy_handle_gdb_exception (nullptr, except);
|
||
+ }
|
||
+
|
||
+ /* Create a new list to hold the results. */
|
||
+ gdbpy_ref<> tuple (PyTuple_New (mapped_files.size ()));
|
||
+ if (tuple == nullptr)
|
||
+ return nullptr;
|
||
+
|
||
+ /* Create each gdb.CorefileMappedFile object. */
|
||
+ Py_ssize_t tuple_idx = 0;
|
||
+ for (const core_mapped_file &file : mapped_files)
|
||
+ {
|
||
+ /* The filename 'str' object. */
|
||
+ gdbpy_ref<> filename
|
||
+ = host_string_to_python_string (file.filename.c_str ());
|
||
+ if (filename == nullptr)
|
||
+ return nullptr;
|
||
+
|
||
+ /* The build-id object. Either a 'str' or 'None'. */
|
||
+ gdbpy_ref<> build_id;
|
||
+ if (file.build_id != nullptr)
|
||
+ {
|
||
+ std::string hex_form = bin2hex (file.build_id->data,
|
||
+ file.build_id->size);
|
||
+
|
||
+ build_id
|
||
+ = host_string_to_python_string (hex_form.c_str ());
|
||
+ if (build_id == nullptr)
|
||
+ return nullptr;
|
||
+ }
|
||
+ else
|
||
+ build_id = gdbpy_ref<>::new_reference (Py_None);
|
||
+
|
||
+ /* List to hold all the gdb.CorefileMappedFileRegion objects. */
|
||
+ gdbpy_ref<> regions (PyTuple_New (file.regions.size ()));
|
||
+ if (regions == nullptr)
|
||
+ return nullptr;
|
||
+
|
||
+ /* Create all the gdb.CorefileMappedFileRegion objects. */
|
||
+ Py_ssize_t regions_idx = 0;
|
||
+ for (const core_mapped_file::region &r : file.regions)
|
||
+ {
|
||
+ /* Actually create the object. */
|
||
+ gdbpy_ref<corefile_mapped_file_region_object> region_obj
|
||
+ (PyObject_New (corefile_mapped_file_region_object,
|
||
+ &corefile_mapped_file_region_object_type));
|
||
+ if (region_obj == nullptr)
|
||
+ return nullptr;
|
||
+
|
||
+ /* Initialise the object. */
|
||
+ region_obj->start = r.start;
|
||
+ region_obj->end = r.end;
|
||
+ region_obj->file_offset = r.file_ofs;
|
||
+
|
||
+ /* Add to the gdb.CorefileMappedFileRegion list. */
|
||
+ if (PyTuple_SetItem (regions.get (), regions_idx++,
|
||
+ (PyObject *) region_obj.release ()) < 0)
|
||
+ return nullptr;
|
||
+ }
|
||
+
|
||
+ /* Actually create the gdb.CorefileMappedFile object. */
|
||
+ gdbpy_ref<corefile_mapped_file_object> entry
|
||
+ (PyObject_New (corefile_mapped_file_object,
|
||
+ &corefile_mapped_file_object_type));
|
||
+ if (entry == nullptr)
|
||
+ return nullptr;
|
||
+
|
||
+ /* Initialise the object. */
|
||
+ entry->filename = filename.release ();
|
||
+ entry->build_id = build_id.release ();
|
||
+ entry->regions = regions.release ();
|
||
+ entry->is_main_exec_p = file.is_main_exec;
|
||
+
|
||
+ /* Add to the gdb.CorefileMappedFile list. */
|
||
+ if (PyTuple_SetItem (tuple.get (), tuple_idx++,
|
||
+ (PyObject *) entry.release ()) < 0)
|
||
+ return nullptr;
|
||
+ }
|
||
+
|
||
+ /* No errors. Move the reference currently in LIST into the Corefile
|
||
+ object itself. Then create a new reference and hand this back to the
|
||
+ user. */
|
||
+ obj->mapped_files = tuple.release ();
|
||
+ Py_INCREF (obj->mapped_files);
|
||
+ return obj->mapped_files;
|
||
+}
|
||
+
|
||
+/* Emit a CorefileChangedEvent event, INF is the inferior in which the core
|
||
+ file changed. Return 0 on success, or a negative value on error. */
|
||
+
|
||
+static int
|
||
+emit_corefile_changed_event (inferior *inf)
|
||
+{
|
||
+ /* If there are no listeners then we are done. */
|
||
+ if (evregpy_no_listeners_p (gdb_py_events.corefile_changed))
|
||
+ return 0;
|
||
+
|
||
+ gdbpy_ref<> event_obj
|
||
+ = create_event_object (&corefile_changed_event_object_type);
|
||
+ if (event_obj == nullptr)
|
||
+ return -1;
|
||
+
|
||
+ gdbpy_ref<inferior_object> inf_obj = inferior_to_inferior_object (inf);
|
||
+ if (inf_obj == nullptr
|
||
+ || evpy_add_attribute (event_obj.get (), "inferior",
|
||
+ (PyObject *) inf_obj.get ()) < 0)
|
||
+ return -1;
|
||
+
|
||
+ return evpy_emit_event (event_obj.get (), gdb_py_events.corefile_changed);
|
||
+}
|
||
+
|
||
+/* Callback from gdb::observers::core_file_changed. The core file for
|
||
+ INF has been changed. */
|
||
+
|
||
+static void
|
||
+cfpy_corefile_changed (inferior *inf)
|
||
+{
|
||
+ /* It's safe to do this even if Python is not initialized, but there
|
||
+ should be nothing to clear in that case. */
|
||
+ cfpy_inferior_corefile_data_key.clear (inf);
|
||
+
|
||
+ if (!gdb_python_initialized)
|
||
+ return;
|
||
+
|
||
+ gdbpy_enter enter_py;
|
||
+
|
||
+ if (emit_corefile_changed_event (inf) < 0)
|
||
+ gdbpy_print_stack ();
|
||
+}
|
||
+
|
||
+/* Called when a gdb.Corefile is destroyed. */
|
||
+
|
||
+static void
|
||
+cfpy_dealloc (PyObject *obj)
|
||
+{
|
||
+ corefile_object *corefile = (corefile_object *) obj;
|
||
+
|
||
+ /* Every gdb.Corefile is cached in an inferior's registry. The only way
|
||
+ for a gdb.Corefile to be deallocated is to remove the object reference
|
||
+ from the registry (and dec its ref count), but before we do that, we
|
||
+ set the object's inferior pointer to NULL. */
|
||
+ gdb_assert (corefile->inferior == nullptr);
|
||
+
|
||
+ Py_XDECREF (corefile->dict);
|
||
+ Py_XDECREF (corefile->mapped_files);
|
||
+
|
||
+ Py_TYPE (obj)->tp_free (obj);
|
||
+}
|
||
+
|
||
+/* __repr__ implementation for gdb.Corefile. */
|
||
+
|
||
+static PyObject *
|
||
+cfpy_repr (PyObject *self)
|
||
+{
|
||
+ corefile_object *obj = (corefile_object *) self;
|
||
+
|
||
+ if (!cfpy_corefile_object_is_valid (obj))
|
||
+ return gdb_py_invalid_object_repr (self);
|
||
+
|
||
+ program_space *pspace = obj->inferior->pspace;
|
||
+ gdb_assert (pspace != nullptr);
|
||
+ return PyUnicode_FromFormat ("<%s inferior=%d filename='%s'>",
|
||
+ Py_TYPE (self)->tp_name,
|
||
+ obj->inferior->num,
|
||
+ bfd_get_filename (pspace->core_bfd ()));
|
||
+}
|
||
+
|
||
+
|
||
+
|
||
+/* Called when a gdb.CorefileMappedFile is destroyed. */
|
||
+
|
||
+static void
|
||
+cfmfpy_dealloc (PyObject *obj)
|
||
+{
|
||
+ corefile_mapped_file_object *mapped_file
|
||
+ = (corefile_mapped_file_object *) obj;
|
||
+
|
||
+ Py_XDECREF (mapped_file->filename);
|
||
+ Py_XDECREF (mapped_file->build_id);
|
||
+ Py_XDECREF (mapped_file->regions);
|
||
+
|
||
+ Py_TYPE (obj)->tp_free (obj);
|
||
+}
|
||
+
|
||
+/* Read the gdb.CorefileMappedFile.filename attribute. */
|
||
+
|
||
+static PyObject *
|
||
+cfmfpy_get_filename (PyObject *self, void *closure)
|
||
+{
|
||
+ corefile_mapped_file_object *obj
|
||
+ = (corefile_mapped_file_object *) self;
|
||
+
|
||
+ gdb_assert (obj->filename != nullptr);
|
||
+
|
||
+ Py_INCREF (obj->filename);
|
||
+ return obj->filename;
|
||
+}
|
||
+
|
||
+/* Read the gdb.CorefileMappedFile.build_id attribute. */
|
||
+
|
||
+static PyObject *
|
||
+cfmfpy_get_build_id (PyObject *self, void *closure)
|
||
+{
|
||
+ corefile_mapped_file_object *obj
|
||
+ = (corefile_mapped_file_object *) self;
|
||
+
|
||
+ gdb_assert (obj->build_id != nullptr);
|
||
+
|
||
+ Py_INCREF (obj->build_id);
|
||
+ return obj->build_id;
|
||
+}
|
||
+
|
||
+/* Read the gdb.CorefileMappedFile.regions attribute. */
|
||
+
|
||
+static PyObject *
|
||
+cfmfpy_get_regions (PyObject *self, void *closure)
|
||
+{
|
||
+ corefile_mapped_file_object *obj
|
||
+ = (corefile_mapped_file_object *) self;
|
||
+
|
||
+ gdb_assert (obj->regions != nullptr);
|
||
+
|
||
+ Py_INCREF (obj->regions);
|
||
+ return obj->regions;
|
||
+}
|
||
+
|
||
+/* Read the gdb.CorefileMappedFile.is_main_executable attribute. */
|
||
+
|
||
+static PyObject *
|
||
+cfmf_is_main_exec (PyObject *self, void *closure)
|
||
+{
|
||
+ corefile_mapped_file_object *obj
|
||
+ = (corefile_mapped_file_object *) self;
|
||
+
|
||
+ if (obj->is_main_exec_p)
|
||
+ Py_RETURN_TRUE;
|
||
+ else
|
||
+ Py_RETURN_FALSE;
|
||
+}
|
||
+
|
||
+
|
||
+
|
||
+/* Read the gdb.CorefileMappedFileRegion.start attribute. */
|
||
+
|
||
+static PyObject *
|
||
+cfmfrpy_get_start (PyObject *self, void *closure)
|
||
+{
|
||
+ corefile_mapped_file_region_object *obj
|
||
+ = (corefile_mapped_file_region_object *) self;
|
||
+
|
||
+ return gdb_py_object_from_ulongest (obj->start).release ();
|
||
+}
|
||
+
|
||
+/* Read the gdb.CorefileMappedFileRegion.end attribute. */
|
||
+
|
||
+static PyObject *
|
||
+cfmfrpy_get_end (PyObject *self, void *closure)
|
||
+{
|
||
+ corefile_mapped_file_region_object *obj
|
||
+ = (corefile_mapped_file_region_object *) self;
|
||
+
|
||
+ return gdb_py_object_from_ulongest (obj->end).release ();
|
||
+}
|
||
+
|
||
+/* Read the gdb.CorefileMappedFileRegion.file_offset attribute. */
|
||
+
|
||
+static PyObject *
|
||
+cfmfrpy_get_file_offset (PyObject *self, void *closure)
|
||
+{
|
||
+ corefile_mapped_file_region_object *obj
|
||
+ = (corefile_mapped_file_region_object *) self;
|
||
+
|
||
+ return gdb_py_object_from_ulongest (obj->file_offset).release ();
|
||
+}
|
||
+
|
||
+
|
||
+
|
||
+static int
|
||
+gdbpy_initialize_corefile ()
|
||
+{
|
||
+ gdb::observers::core_file_changed.attach (cfpy_corefile_changed,
|
||
+ "py-corefile");
|
||
+
|
||
+ if (gdbpy_type_ready (&corefile_object_type) < 0)
|
||
+ return -1;
|
||
+
|
||
+ if (gdbpy_type_ready (&corefile_mapped_file_object_type) < 0)
|
||
+ return -1;
|
||
+
|
||
+ if (gdbpy_type_ready (&corefile_mapped_file_region_object_type) < 0)
|
||
+ return -1;
|
||
+
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+GDBPY_INITIALIZE_FILE (gdbpy_initialize_corefile);
|
||
+
|
||
+
|
||
+
|
||
+static gdb_PyGetSetDef corefile_getset[] =
|
||
+{
|
||
+ { "__dict__", gdb_py_generic_dict, nullptr,
|
||
+ "The __dict__ for the gdb.Corefile.", &corefile_object_type },
|
||
+ { "filename", cfpy_get_filename, nullptr,
|
||
+ "The filename of a valid Corefile object.", nullptr },
|
||
+ { nullptr }
|
||
+};
|
||
+
|
||
+static PyMethodDef corefile_object_methods[] =
|
||
+{
|
||
+ { "is_valid", cfpy_is_valid, METH_NOARGS,
|
||
+ "is_valid () -> Boolean.\n\
|
||
+Return true if this Corefile is valid, false if not." },
|
||
+ { "mapped_files", cfpy_mapped_files, METH_NOARGS,
|
||
+ "mapped_files () -> List of mapping tuples.\n\
|
||
+Return a list of tuples. Each tuple represents a mapping from the\
|
||
+core file." },
|
||
+ { nullptr }
|
||
+};
|
||
+
|
||
+PyTypeObject corefile_object_type =
|
||
+{
|
||
+ PyVarObject_HEAD_INIT (nullptr, 0)
|
||
+ "gdb.Corefile", /*tp_name*/
|
||
+ sizeof (corefile_object), /*tp_basicsize*/
|
||
+ 0, /*tp_itemsize*/
|
||
+ cfpy_dealloc, /*tp_dealloc*/
|
||
+ 0, /*tp_print*/
|
||
+ 0, /*tp_getattr*/
|
||
+ 0, /*tp_setattr*/
|
||
+ 0, /*tp_compare*/
|
||
+ cfpy_repr, /*tp_repr*/
|
||
+ 0, /*tp_as_number*/
|
||
+ 0, /*tp_as_sequence*/
|
||
+ 0, /*tp_as_mapping*/
|
||
+ 0, /*tp_hash */
|
||
+ 0, /*tp_call*/
|
||
+ 0, /*tp_str*/
|
||
+ 0, /*tp_getattro*/
|
||
+ 0, /*tp_setattro*/
|
||
+ 0, /*tp_as_buffer*/
|
||
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
|
||
+ "GDB corefile object", /* tp_doc */
|
||
+ 0, /* tp_traverse */
|
||
+ 0, /* tp_clear */
|
||
+ 0, /* tp_richcompare */
|
||
+ 0, /* tp_weaklistoffset */
|
||
+ 0, /* tp_iter */
|
||
+ 0, /* tp_iternext */
|
||
+ corefile_object_methods, /* tp_methods */
|
||
+ 0, /* tp_members */
|
||
+ corefile_getset, /* tp_getset */
|
||
+ 0, /* tp_base */
|
||
+ 0, /* tp_dict */
|
||
+ 0, /* tp_descr_get */
|
||
+ 0, /* tp_descr_set */
|
||
+ offsetof (corefile_object, dict), /* tp_dictoffset */
|
||
+ 0, /* tp_init */
|
||
+ 0, /* tp_alloc */
|
||
+ 0, /* tp_new */
|
||
+};
|
||
+
|
||
+static gdb_PyGetSetDef corefile_mapped_file_object_getset[] =
|
||
+{
|
||
+ { "filename", cfmfpy_get_filename, nullptr,
|
||
+ "The filename of a CorefileMappedFile object.", nullptr },
|
||
+ { "build_id", cfmfpy_get_build_id, nullptr,
|
||
+ "The build-id of a CorefileMappedFile object or None.", nullptr },
|
||
+ { "regions", cfmfpy_get_regions, nullptr,
|
||
+ "The list of regions from a CorefileMappedFile object.", nullptr },
|
||
+ { "is_main_executable", cfmf_is_main_exec, nullptr,
|
||
+ "True for the main executable mapping, otherwise False.", nullptr },
|
||
+ { nullptr }
|
||
+};
|
||
+
|
||
+PyTypeObject corefile_mapped_file_object_type =
|
||
+{
|
||
+ PyVarObject_HEAD_INIT (NULL, 0)
|
||
+ "gdb.CorefileMappedFile", /*tp_name*/
|
||
+ sizeof (corefile_mapped_file_object), /*tp_basicsize*/
|
||
+ 0, /*tp_itemsize*/
|
||
+ cfmfpy_dealloc, /*tp_dealloc*/
|
||
+ 0, /*tp_print*/
|
||
+ 0, /*tp_getattr*/
|
||
+ 0, /*tp_setattr*/
|
||
+ 0, /*tp_compare*/
|
||
+ 0, /*tp_repr*/
|
||
+ 0, /*tp_as_number*/
|
||
+ 0, /*tp_as_sequence*/
|
||
+ 0, /*tp_as_mapping*/
|
||
+ 0, /*tp_hash */
|
||
+ 0, /*tp_call*/
|
||
+ 0, /*tp_str*/
|
||
+ 0, /*tp_getattro*/
|
||
+ 0, /*tp_setattro*/
|
||
+ 0, /*tp_as_buffer*/
|
||
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
|
||
+ "GDB corefile mapped file object", /* tp_doc */
|
||
+ 0, /* tp_traverse */
|
||
+ 0, /* tp_clear */
|
||
+ 0, /* tp_richcompare */
|
||
+ 0, /* tp_weaklistoffset */
|
||
+ 0, /* tp_iter */
|
||
+ 0, /* tp_iternext */
|
||
+ 0, /* tp_methods */
|
||
+ 0, /* tp_members */
|
||
+ corefile_mapped_file_object_getset, /* tp_getset */
|
||
+ 0, /* tp_base */
|
||
+ 0, /* tp_dict */
|
||
+ 0, /* tp_descr_get */
|
||
+ 0, /* tp_descr_set */
|
||
+ 0, /* tp_dictoffset */
|
||
+ 0, /* tp_init */
|
||
+ 0, /* tp_alloc */
|
||
+ 0, /* tp_new */
|
||
+};
|
||
+
|
||
+static gdb_PyGetSetDef corefile_mapped_file_region_object_getset[] =
|
||
+{
|
||
+ { "start", cfmfrpy_get_start, nullptr,
|
||
+ "The start address of a CorefileMappedFileRegion object.", nullptr },
|
||
+ { "end", cfmfrpy_get_end, nullptr,
|
||
+ "The end address of a CorefileMappedFileRegion object.", nullptr },
|
||
+ { "file_offset", cfmfrpy_get_file_offset, nullptr,
|
||
+ "The file offset of a CorefileMappedFileRegion object.", nullptr },
|
||
+ { nullptr }
|
||
+};
|
||
+
|
||
+PyTypeObject corefile_mapped_file_region_object_type =
|
||
+{
|
||
+ PyVarObject_HEAD_INIT (NULL, 0)
|
||
+ "gdb.CorefileMappedFileRegion", /*tp_name*/
|
||
+ sizeof (corefile_mapped_file_region_object), /*tp_basicsize*/
|
||
+ 0, /*tp_itemsize*/
|
||
+ 0, /*tp_dealloc*/
|
||
+ 0, /*tp_print*/
|
||
+ 0, /*tp_getattr*/
|
||
+ 0, /*tp_setattr*/
|
||
+ 0, /*tp_compare*/
|
||
+ 0, /*tp_repr*/
|
||
+ 0, /*tp_as_number*/
|
||
+ 0, /*tp_as_sequence*/
|
||
+ 0, /*tp_as_mapping*/
|
||
+ 0, /*tp_hash */
|
||
+ 0, /*tp_call*/
|
||
+ 0, /*tp_str*/
|
||
+ 0, /*tp_getattro*/
|
||
+ 0, /*tp_setattro*/
|
||
+ 0, /*tp_as_buffer*/
|
||
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
|
||
+ "GDB corefile mapped file region object", /* tp_doc */
|
||
+ 0, /* tp_traverse */
|
||
+ 0, /* tp_clear */
|
||
+ 0, /* tp_richcompare */
|
||
+ 0, /* tp_weaklistoffset */
|
||
+ 0, /* tp_iter */
|
||
+ 0, /* tp_iternext */
|
||
+ 0, /* tp_methods */
|
||
+ 0, /* tp_members */
|
||
+ corefile_mapped_file_region_object_getset, /* tp_getset */
|
||
+ 0, /* tp_base */
|
||
+ 0, /* tp_dict */
|
||
+ 0, /* tp_descr_get */
|
||
+ 0, /* tp_descr_set */
|
||
+ 0, /* tp_dictoffset */
|
||
+ 0, /* tp_init */
|
||
+ 0, /* tp_alloc */
|
||
+ 0, /* tp_new */
|
||
+};
|
||
diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def
|
||
--- a/gdb/python/py-event-types.def
|
||
+++ b/gdb/python/py-event-types.def
|
||
@@ -145,3 +145,8 @@ GDB_PY_DEFINE_EVENT_TYPE (tui_enabled,
|
||
"TuiEnabledEvent",
|
||
"GDB TUI enabled event object",
|
||
event_object_type);
|
||
+
|
||
+GDB_PY_DEFINE_EVENT_TYPE (corefile_changed,
|
||
+ "CorefileChangedEvent",
|
||
+ "GDB corefile changed event",
|
||
+ event_object_type);
|
||
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
|
||
--- a/gdb/python/py-inferior.c
|
||
+++ b/gdb/python/py-inferior.c
|
||
@@ -973,6 +973,22 @@ infpy_get_main_name (PyObject *self, void *closure)
|
||
return host_string_to_python_string (name).release ();
|
||
}
|
||
|
||
+/* Implement the Inferior.corefile getter. Returns a gdb.Corefile
|
||
+ object, or None. */
|
||
+
|
||
+static PyObject *
|
||
+infpy_get_core_file (PyObject *self, void *closure)
|
||
+{
|
||
+ inferior_object *inf = (inferior_object *) self;
|
||
+
|
||
+ INFPY_REQUIRE_VALID (inf);
|
||
+
|
||
+ inferior *inferior = inf->inferior;
|
||
+ gdb_assert (inferior != nullptr);
|
||
+
|
||
+ return gdbpy_core_file_from_inferior (inferior).release ();
|
||
+}
|
||
+
|
||
static void
|
||
infpy_dealloc (PyObject *obj)
|
||
{
|
||
@@ -1062,6 +1078,8 @@ static gdb_PyGetSetDef inferior_object_getset[] =
|
||
{ "progspace", infpy_get_progspace, NULL, "Program space of this inferior" },
|
||
{ "main_name", infpy_get_main_name, nullptr,
|
||
"Name of 'main' function, if known.", nullptr },
|
||
+ { "corefile", infpy_get_core_file, nullptr,
|
||
+ "The corefile loaded in to this inferior, or None.", nullptr },
|
||
{ NULL }
|
||
};
|
||
|
||
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
|
||
--- a/gdb/python/python-internal.h
|
||
+++ b/gdb/python/python-internal.h
|
||
@@ -1113,6 +1113,14 @@ extern std::optional<int> gdbpy_print_insn (struct gdbarch *gdbarch,
|
||
CORE_ADDR address,
|
||
disassemble_info *info);
|
||
|
||
+/* Return the gdb.Corefile object representing the core file loaded into
|
||
+ the program space of INF, or None if there is no core file loaded. INF
|
||
+ must not be NULL. If an error occurs then NULL is returned, and a
|
||
+ suitable Python error will be set. */
|
||
+
|
||
+extern gdbpy_ref<> gdbpy_core_file_from_inferior (inferior *inf);
|
||
+
|
||
+
|
||
/* A wrapper for PyType_Ready that also automatically registers the
|
||
type in the appropriate module. Returns 0 on success, -1 on error.
|
||
If MOD is supplied, then the type is added to that module. If MOD
|
||
diff --git a/gdb/testsuite/gdb.dap/attach-fail.exp b/gdb/testsuite/gdb.dap/attach-fail.exp
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.dap/attach-fail.exp
|
||
@@ -0,0 +1,32 @@
|
||
+# Copyright 2025 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/>.
|
||
+
|
||
+# Test "attach" failure in DAP.
|
||
+
|
||
+require can_spawn_for_attach allow_dap_tests
|
||
+
|
||
+load_lib dap-support.exp
|
||
+
|
||
+# Passing an empty remote name here will guarantee a failure without
|
||
+# trying to find a real remote.
|
||
+set id [dap_target_remote {""}]
|
||
+
|
||
+dap_check_request_and_response "configurationDone" configurationDone
|
||
+
|
||
+set resp [lindex [dap_read_response attach $id] 0]
|
||
+gdb_assert {[dict get $resp success] == "false"} \
|
||
+ "attach failed"
|
||
+
|
||
+dap_shutdown
|
||
diff --git a/gdb/testsuite/gdb.dap/corefile.c b/gdb/testsuite/gdb.dap/corefile.c
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.dap/corefile.c
|
||
@@ -0,0 +1,45 @@
|
||
+/* This testcase is part of GDB, the GNU debugger.
|
||
+
|
||
+ Copyright 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/>. */
|
||
+
|
||
+#include <stdlib.h>
|
||
+
|
||
+int global_var = 0;
|
||
+
|
||
+void
|
||
+baz (void)
|
||
+{
|
||
+ abort ();
|
||
+}
|
||
+
|
||
+void
|
||
+bar (void)
|
||
+{
|
||
+ baz ();
|
||
+}
|
||
+
|
||
+void
|
||
+foo (void)
|
||
+{
|
||
+ bar ();
|
||
+}
|
||
+
|
||
+int
|
||
+main (void)
|
||
+{
|
||
+ foo ();
|
||
+ return 0;
|
||
+}
|
||
diff --git a/gdb/testsuite/gdb.dap/corefile.exp b/gdb/testsuite/gdb.dap/corefile.exp
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.dap/corefile.exp
|
||
@@ -0,0 +1,196 @@
|
||
+# Copyright 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/>.
|
||
+
|
||
+# Test using "attach" in DAP for opening a core file.
|
||
+
|
||
+require allow_dap_tests
|
||
+
|
||
+load_lib dap-support.exp
|
||
+
|
||
+standard_testfile
|
||
+
|
||
+if {[build_executable ${testfile}.exp $testfile] == -1} {
|
||
+ return
|
||
+}
|
||
+
|
||
+set corefile [core_find $binfile {}]
|
||
+if {$corefile == ""} {
|
||
+ untested "unable to create or find corefile"
|
||
+ return
|
||
+}
|
||
+
|
||
+set other_corefile [standard_output_file "${testfile} with spaces.core"]
|
||
+remote_exec build "cp \"$corefile\" \"$other_corefile\""
|
||
+
|
||
+# Test that attaching to a core file works at all.
|
||
+set attach_id [dap_corefile $corefile $binfile]
|
||
+
|
||
+dap_check_request_and_response "configurationDone" configurationDone
|
||
+
|
||
+dap_check_response "attach response" attach $attach_id
|
||
+
|
||
+dap_wait_for_event_and_check "stopped" stopped \
|
||
+ "body reason" attach
|
||
+
|
||
+# Try 'continue', this should fail.
|
||
+set obj [dap_request_and_response continue \
|
||
+ {o threadId [i 1]}]
|
||
+set response [lindex $obj 0]
|
||
+gdb_assert { [dict get $response success] == "false" } \
|
||
+ "continue with core file target"
|
||
+
|
||
+# Get a backtrace from the core file.
|
||
+set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
|
||
+ {o threadId [i 1]}] 0]
|
||
+set frame_id [dict get [lindex [dict get $bt body stackFrames] 0] id]
|
||
+
|
||
+# Get all scopes for frame 0. Search through scopes to find the
|
||
+# register scope.
|
||
+set scopes [dap_check_request_and_response "get scopes" scopes \
|
||
+ [format {o frameId [i %d]} $frame_id]]
|
||
+set scopes [dict get [lindex $scopes 0] body scopes]
|
||
+set reg_scope ""
|
||
+foreach s $scopes {
|
||
+ if {[dict get $s name] == "Registers"} {
|
||
+ set reg_scope $s
|
||
+ }
|
||
+}
|
||
+gdb_assert { $reg_scope ne "" } "found register scope"
|
||
+
|
||
+# Read all the registers from the register scope.
|
||
+set num [dict get $reg_scope variablesReference]
|
||
+set reply [lindex [dap_check_request_and_response "fetch all registers" \
|
||
+ "variables" \
|
||
+ [format {o variablesReference [i %d] count [i %d]} $num\
|
||
+ [dict get $reg_scope namedVariables]]] 0]
|
||
+
|
||
+# Find the name and value of a register, we'll use this to try setting
|
||
+# a register below.
|
||
+set regs [dict get $reply body variables]
|
||
+gdb_assert {[llength $regs] > 0} "got at least one register"
|
||
+set reg_name [dict get [lindex $regs 0] name]
|
||
+set reg_value [dict get [lindex $regs 0] value]
|
||
+
|
||
+set obj [dap_request_and_response setExpression \
|
||
+ {o expression [s global_var] value [s 23]}]
|
||
+set response [lindex $obj 0]
|
||
+gdb_assert { [dict get $response success] == "false" } \
|
||
+ "set global variable fails"
|
||
+set expected_exception_count 1
|
||
+
|
||
+# Try setting a register, this should fail as registers are not
|
||
+# writable for a core file target. We need to write back a different
|
||
+# register value, so we add one to the current value. This means we
|
||
+# can only run the test if the current register value is an integer.
|
||
+if {[string is integer -strict $reg_value]} {
|
||
+ set new_value [expr {$reg_value + 1}]
|
||
+ set obj [dap_request_and_response setExpression \
|
||
+ {o expression [s \$$reg_name] value [s $new_value]}]
|
||
+ set response [lindex $obj 0]
|
||
+ gdb_assert { [dict get $response success] == "false" } \
|
||
+ "set register fails"
|
||
+ incr expected_exception_count
|
||
+}
|
||
+
|
||
+dap_shutdown false $expected_exception_count
|
||
+
|
||
+# Reconnect to the core file. This time when we shutdown we will
|
||
+# request that the target be terminated, GDB should still just
|
||
+# disconnect though as core file targets cannot be killed.
|
||
+with_test_prefix "reattach" {
|
||
+ set attach_id [dap_corefile $corefile $binfile]
|
||
+
|
||
+ dap_check_request_and_response "configurationDone" configurationDone
|
||
+
|
||
+ dap_check_response "attach response" attach $attach_id
|
||
+
|
||
+ dap_wait_for_event_and_check "stopped" stopped \
|
||
+ "body reason" attach
|
||
+
|
||
+ # Request the target be terminated. This doesn't make sense for
|
||
+ # core file targets (which cannot be killed), but GDB should
|
||
+ # handle this gracefully and just disconnect.
|
||
+ dap_shutdown true
|
||
+}
|
||
+
|
||
+# Test loading a core file with spaces in its name.
|
||
+with_test_prefix "core file with spaces" {
|
||
+ gdb_exit
|
||
+
|
||
+ # Test that attaching to a core file works at all.
|
||
+ set attach_id [dap_corefile $other_corefile]
|
||
+
|
||
+ dap_check_request_and_response "configurationDone" configurationDone
|
||
+
|
||
+ dap_check_response "attach response" attach $attach_id
|
||
+
|
||
+ dap_wait_for_event_and_check "stopped" stopped \
|
||
+ "body reason" attach
|
||
+
|
||
+ # Use the repl to issue an 'info inferiors' command.
|
||
+ set obj [dap_check_request_and_response "command repl" \
|
||
+ evaluate {o expression [s "info inferiors"] context [s repl]}]
|
||
+ set response [lindex $obj 0]
|
||
+ set result [dict get $response body result]
|
||
+ set result [string map {\\n \n \\t \t} $result]
|
||
+ verbose -log "Info Inferiors Output:\n$result\n\n"
|
||
+
|
||
+ # Check that the output contains the header line, the executable
|
||
+ # name, and the core file name. We didn't pass the executable
|
||
+ # name when loading the core file, but GDB should have been able
|
||
+ # to find the executable from the core file.
|
||
+ gdb_assert { [regexp "Num\\s+Description\\s+Connection\\s+Executable" $result] } \
|
||
+ "info inferiors column headers found"
|
||
+
|
||
+ gdb_assert { [regexp "[string_to_regexp $binfile]\\s*\n" $result] } \
|
||
+ "executable name was set during core file load"
|
||
+
|
||
+ dap_shutdown
|
||
+}
|
||
+
|
||
+# Test loading a core file via the repl.
|
||
+with_test_prefix "load core file via repl" {
|
||
+ gdb_exit
|
||
+
|
||
+ if {[dap_initialize] == ""} {
|
||
+ return
|
||
+ }
|
||
+
|
||
+ # Use the repl to issue a 'core-file' command.
|
||
+ set obj [dap_check_request_and_response "command repl" \
|
||
+ evaluate [format {o expression [s "core-file %s"] context [s repl]} $corefile]]
|
||
+ set response [lindex $obj 0]
|
||
+ set result [dict get $response body result]
|
||
+ set result [string map {\\n \n \\t \t} $result]
|
||
+ verbose -log "Core-File Command Output:\n$result\n\n"
|
||
+
|
||
+ # By the time we got the response from the 'core-file' command,
|
||
+ # the stopped event, sent when we attach to a core file, should
|
||
+ # already have been seen, check for it now.
|
||
+ set ok false
|
||
+ foreach d [lindex $obj 1] {
|
||
+ if {[dict get $d type] != "event"
|
||
+ || [dict get $d event] != "stopped"} {
|
||
+ continue
|
||
+ }
|
||
+ if {[dict get $d body reason] == "attach"} {
|
||
+ set ok true
|
||
+ break
|
||
+ }
|
||
+ }
|
||
+ gdb_assert { $ok } "saw stopped event"
|
||
+
|
||
+ dap_shutdown
|
||
+}
|
||
diff --git a/gdb/testsuite/gdb.python/py-corefile.c b/gdb/testsuite/gdb.python/py-corefile.c
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.python/py-corefile.c
|
||
@@ -0,0 +1,25 @@
|
||
+/* Copyright 2025 Free Software Foundation, Inc.
|
||
+
|
||
+ This file is part of GDB.
|
||
+
|
||
+ 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/>. */
|
||
+
|
||
+#include <stdlib.h>
|
||
+
|
||
+int
|
||
+main (void)
|
||
+{
|
||
+ /* With correct ulimit, etc. this should cause a core dump. */
|
||
+ abort ();
|
||
+}
|
||
diff --git a/gdb/testsuite/gdb.python/py-corefile.exp b/gdb/testsuite/gdb.python/py-corefile.exp
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.python/py-corefile.exp
|
||
@@ -0,0 +1,365 @@
|
||
+# Copyright (C) 2025 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/>.
|
||
+
|
||
+# This file is part of the GDB testsuite. It tests the core file
|
||
+# support in Python.
|
||
+
|
||
+require isnative
|
||
+require {!is_remote host}
|
||
+
|
||
+load_lib gdb-python.exp
|
||
+
|
||
+require allow_python_tests
|
||
+
|
||
+standard_testfile
|
||
+
|
||
+if {[build_executable "build executable" $testfile $srcfile] == -1} {
|
||
+ return
|
||
+}
|
||
+
|
||
+set remote_python_file \
|
||
+ [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
|
||
+
|
||
+set corefile [core_find $binfile]
|
||
+if {$corefile == ""} {
|
||
+ unsupported "couldn't create or find corefile"
|
||
+ return
|
||
+}
|
||
+
|
||
+# Helper proc to run the 'core-file' command. Takes optional arguments:
|
||
+#
|
||
+# -corefile FILENAME : Load FILENAME as the new core file. If this
|
||
+# argument is not given then the current core
|
||
+# file will be unloaded.
|
||
+#
|
||
+# -inferior NUM : The inferior in which the corefile is being changed.
|
||
+# This is used to match the corefile_changed events
|
||
+# that will be emitted.
|
||
+#
|
||
+# -prefix STRING : A test prefix, to make test names unique.
|
||
+#
|
||
+# -replacement : There's already a core file loaded when this command
|
||
+# is being run.
|
||
+proc core_file_cmd { args } {
|
||
+ parse_some_args {
|
||
+ {corefile ""}
|
||
+ {inferior 1}
|
||
+ {prefix ""}
|
||
+ {replacement}
|
||
+ }
|
||
+
|
||
+ if { $prefix eq "" } {
|
||
+ if { $corefile eq "" } {
|
||
+ set prefix "unload corefile"
|
||
+ } else {
|
||
+ set prefix "load corefile"
|
||
+ }
|
||
+ }
|
||
+
|
||
+ with_test_prefix $prefix {
|
||
+ gdb_test "events corefile_changed check" \
|
||
+ "^No corefile_changed event has been seen\\." \
|
||
+ "no corefile event has been seen"
|
||
+
|
||
+ gdb_test "events exited check" \
|
||
+ "^No exited event has been seen\\." \
|
||
+ "no exited event has been seen"
|
||
+
|
||
+ if { $corefile eq "" } {
|
||
+ gdb_test "core-file" "^No core file now\\." "unload current core file"
|
||
+
|
||
+ gdb_test "events corefile_changed check" \
|
||
+ "Event 1/1, Inferior $inferior, Corefile None" \
|
||
+ "expected corefile event has been seen"
|
||
+
|
||
+ gdb_test "events exited check" \
|
||
+ "Event 1/1, Inferior $inferior, Exit Code None" \
|
||
+ "expected exited event has been seen"
|
||
+ } else {
|
||
+ gdb_test "core-file $corefile" ".*" "load core file"
|
||
+
|
||
+ if { $replacement } {
|
||
+ gdb_test "events corefile_changed check" \
|
||
+ [multi_line \
|
||
+ "Event 1/2, Inferior $inferior, Corefile None" \
|
||
+ "Event 2/2, Inferior $inferior, Corefile [string_to_regexp $corefile]"] \
|
||
+ "expected corefile event has been seen"
|
||
+
|
||
+ gdb_test "events exited check" \
|
||
+ "Event 1/1, Inferior $inferior, Exit Code None" \
|
||
+ "expected exited event has been seen"
|
||
+ } else {
|
||
+ gdb_test "events corefile_changed check" \
|
||
+ "Event 1/1, Inferior $inferior, Corefile [string_to_regexp $corefile]" \
|
||
+ "expected corefile event has been seen"
|
||
+
|
||
+ gdb_test "events exited check" \
|
||
+ "^No exited event has been seen\\." \
|
||
+ "no exited event was emitted"
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ gdb_test_no_output -nopass "events corefile_changed reset"
|
||
+ gdb_test_no_output -nopass "events exited reset"
|
||
+}
|
||
+
|
||
+# A helper proc runs clean_restart passing through ARGS, and then loads the
|
||
+# test's Python script.
|
||
+proc clean_restart_and_load_py_script { args } {
|
||
+ clean_restart {*}$args
|
||
+
|
||
+ # Load the Python script into GDB.
|
||
+ gdb_test "source $::remote_python_file" "^Success" \
|
||
+ "source python script"
|
||
+}
|
||
+
|
||
+# Create a copy of the corefile.
|
||
+set other_corefile [standard_output_file ${testfile}-other.core]
|
||
+remote_exec build "cp $corefile $other_corefile"
|
||
+
|
||
+clean_restart_and_load_py_script
|
||
+
|
||
+gdb_test_no_output "python inf = gdb.selected_inferior()" \
|
||
+ "capture current inferior"
|
||
+
|
||
+gdb_test "python print(inf.corefile)" "^None" \
|
||
+ "Inferior.corefile is None before loading a core file"
|
||
+
|
||
+core_file_cmd -corefile $corefile
|
||
+
|
||
+set file_re [string_to_regexp $corefile]
|
||
+gdb_test "python print(inf.corefile)" "^<gdb\\.Corefile inferior=1 filename='$file_re'>" \
|
||
+ "Inferior.corefile is a valid object after loading a core file"
|
||
+
|
||
+gdb_test_no_output "python core1=inf.corefile" "capture gdb.Corefile object"
|
||
+
|
||
+gdb_test "python print(core1.__dict__)" "^\\{\\}" \
|
||
+ "print Corefile.__dict__ when empty"
|
||
+
|
||
+gdb_test_no_output "python core1._my_attribute = \"Hello\"" \
|
||
+ "write new attribute into Corefile object"
|
||
+
|
||
+gdb_test "python print(core1._my_attribute)" "^Hello" \
|
||
+ "immediately read new attribute"
|
||
+
|
||
+gdb_test "python print(core1.__dict__)" "^\\{'_my_attribute': 'Hello'\\}" \
|
||
+ "print Corefile.__dict__ after adding an attribute"
|
||
+
|
||
+gdb_test "python print(core1.filename)" "^$file_re" \
|
||
+ "Corefile.filename attribute works as expected"
|
||
+
|
||
+gdb_test "python print(core1.is_valid())" "^True" \
|
||
+ "Corefile.is_valid() is True while corefile is loaded"
|
||
+
|
||
+core_file_cmd
|
||
+
|
||
+gdb_test "python print(core1.is_valid())" "^False" \
|
||
+ "Corefile.is_valid() is False after corefile is unloaded"
|
||
+
|
||
+gdb_test "python print(core1.__dict__)" "^\\{'_my_attribute': 'Hello'\\}" \
|
||
+ "print Corefile.__dict__ with attribute when invalid"
|
||
+
|
||
+gdb_test "python print(core1)" "^<gdb\\.Corefile \\(invalid\\)>" \
|
||
+ "print an invalid gdb.Corefile object"
|
||
+
|
||
+gdb_test "python print(core1.filename)" \
|
||
+ [multi_line \
|
||
+ "Python Exception <class 'RuntimeError'>: Corefile no longer exists\\." \
|
||
+ "Error occurred in Python: Corefile no longer exists\\."] \
|
||
+ "error when reading filename from invalid Corefile"
|
||
+
|
||
+gdb_test "python print(inf.corefile)" "^None" \
|
||
+ "Inferior.corefile is None again after corefile unload"
|
||
+
|
||
+gdb_test "python print(core1._my_attribute)" "^Hello" \
|
||
+ "read new attribute from invalid core file"
|
||
+
|
||
+# Create a second inferior.
|
||
+gdb_test "add-inferior"
|
||
+gdb_test "inferior 2"
|
||
+
|
||
+with_test_prefix "in second inferior" {
|
||
+ core_file_cmd -corefile $corefile -inferior 2
|
||
+
|
||
+ gdb_test "python print(inf.corefile)" "^None" \
|
||
+ "first inferior still has no core file"
|
||
+
|
||
+ gdb_test_no_output "python core2=gdb.selected_inferior().corefile" \
|
||
+ "capture gdb.Corefile object"
|
||
+
|
||
+ # The _my_attribute was added to CORE1, not CORE2. Check it
|
||
+ # doesn't somehow appear on CORE2.
|
||
+ gdb_test "python print(core2._my_attribute)" \
|
||
+ "AttributeError.*: 'gdb\\.Corefile' object has no attribute '_my_attribute'" \
|
||
+ "try to read attribute that doesn't exist"
|
||
+
|
||
+ gdb_test "python print(core2.filename)" "^$file_re" \
|
||
+ "Corefile.filename attribute works as expected"
|
||
+
|
||
+ gdb_test "inferior 1"
|
||
+}
|
||
+
|
||
+# Read the name of the core file from the second program space while
|
||
+# the current program space is the first one.
|
||
+gdb_test "python print(core2.filename)" "^$file_re" \
|
||
+ "Corefile.filename attribute works from different progspace"
|
||
+
|
||
+# Load the other corefile into the first inferior.
|
||
+core_file_cmd -corefile $other_corefile \
|
||
+ -prefix "load other corefile into inferior 1"
|
||
+
|
||
+# Delete the second inferior. We need to switch to the second
|
||
+# inferior and unload its corefile before we can do that. Then,
|
||
+# switch back to the first inferior, delete the second, and try to
|
||
+# read the filename of the core file from the (now deleted) second
|
||
+# inferior. We should get an error about the gdb.Corefile being
|
||
+# invalid.
|
||
+with_test_prefix "remove second inferior" {
|
||
+ gdb_test "inferior 2"
|
||
+
|
||
+ gdb_test "python print(inf.corefile.filename)" \
|
||
+ "^[string_to_regexp $other_corefile]" \
|
||
+ "read inferior 1 corefile when in inferior 2"
|
||
+
|
||
+ gdb_test_no_output "python core1=inf.corefile" \
|
||
+ "capture inferior 1 gdb.Corefile while in inferior 2"
|
||
+
|
||
+ # This is a new CORE1 object, check that _my_attribute is gone.
|
||
+ gdb_test "python print(core1._my_attribute)" \
|
||
+ "AttributeError.*: 'gdb\\.Corefile' object has no attribute '_my_attribute'" \
|
||
+ "try to read attribute that doesn't exist"
|
||
+
|
||
+ core_file_cmd -inferior 2
|
||
+
|
||
+ gdb_test "python print(core2.filename)" \
|
||
+ [multi_line \
|
||
+ "Python Exception <class 'RuntimeError'>: Corefile no longer exists\\." \
|
||
+ "Error occurred in Python: Corefile no longer exists\\."] \
|
||
+ "error when reading filename from invalid Corefile"
|
||
+
|
||
+ gdb_test "inferior 1"
|
||
+
|
||
+ gdb_test "remove-inferiors 2"
|
||
+
|
||
+ gdb_test "python print(core2.is_valid())" "^False" \
|
||
+ "Corefile.is_valid() is False after corefile is unloaded, and Progspace is deleted"
|
||
+
|
||
+ gdb_test "python print(core2.filename)" \
|
||
+ [multi_line \
|
||
+ "Python Exception <class 'RuntimeError'>: Corefile no longer exists\\." \
|
||
+ "Error occurred in Python: Corefile no longer exists\\."] \
|
||
+ "error when reading filename of an invalid Corefile, from deleted program space"
|
||
+
|
||
+ gdb_test "python print(core1.is_valid())" "^True" \
|
||
+ "check inferior 1 core file is still valid"
|
||
+}
|
||
+
|
||
+# Test the Corefile.mapped_files() API. The Python script that is
|
||
+# sourced here implements 'info proc mappings' in Python using the
|
||
+# mapped_files API. The output from the built-in command, and the
|
||
+# Python command should be identical.
|
||
+with_test_prefix "test mapped files data" {
|
||
+ clean_restart_and_load_py_script
|
||
+
|
||
+ # Load the core file.
|
||
+ core_file_cmd -corefile $corefile
|
||
+
|
||
+ # Two files to write the output to.
|
||
+ set out_1 [standard_output_file ${gdb_test_file_name}-out-1.txt]
|
||
+ set out_2 [standard_output_file ${gdb_test_file_name}-out-2.txt]
|
||
+
|
||
+ # Run the built-in command, then the new Python command, capture
|
||
+ # the output.
|
||
+ gdb_test "pipe info proc mappings | tee $out_1" ".*" \
|
||
+ "capture built-in mappings output"
|
||
+ gdb_test "pipe info proc py-mappings | tee $out_2" ".*" \
|
||
+ "capture Python based mappings data"
|
||
+
|
||
+ # Check the output is identical.
|
||
+ gdb_test "shell diff -s $out_1 $out_2" \
|
||
+ "Files \[^\r\n\]+-out-1.txt and \[^\r\n\]+-out-2.txt are identical" \
|
||
+ "diff input and output one"
|
||
+
|
||
+ # Check build-ids within the core file mapping data.
|
||
+ gdb_test "check-build-ids" "^PASS"
|
||
+
|
||
+ # Check the is_main_executable flag in the mapping data.
|
||
+ gdb_test "check-main-executable" "^PASS"
|
||
+
|
||
+ # Check that the mapped files "list" is actually an immutable
|
||
+ # tuple.
|
||
+ gdb_test_no_output "python core = gdb.selected_inferior().corefile"
|
||
+ gdb_test_no_output "python mapped_files = core.mapped_files()"
|
||
+ gdb_test "python print(type(mapped_files))" \
|
||
+ "^<class 'tuple'>"
|
||
+ gdb_test "python mapped_files\[0\] = None" \
|
||
+ "'tuple' object does not support item assignment"
|
||
+ gdb_test "python print(mapped_files\[0\] is None)" "^False"
|
||
+
|
||
+ # And same for the list of regions for a mapped file.
|
||
+ gdb_test_no_output "python regions = mapped_files\[0\].regions"
|
||
+ gdb_test "python print(type(regions))" \
|
||
+ "^<class 'tuple'>"
|
||
+ gdb_test "python regions\[0\] = None" \
|
||
+ "'tuple' object does not support item assignment"
|
||
+}
|
||
+
|
||
+# Load a core file. GDB should figure out which file is being debugged.
|
||
+# Then use 'start' to run this executable, this will replace the core file
|
||
+# target. At least on Linux, this replacement is done without calling
|
||
+# target_detach. This test checks that the expected core file changed and
|
||
+# inferior exited events are still seen.
|
||
+with_test_prefix "start from corefile" {
|
||
+ if { [gdb_protocol_is_native] } {
|
||
+ clean_restart_and_load_py_script
|
||
+
|
||
+ # Load the core file.
|
||
+ core_file_cmd -corefile $corefile
|
||
+
|
||
+ # Check GDB figured out the executable.
|
||
+ gdb_test "info inferiors 1" \
|
||
+ "\[^\r\n\]+[string_to_regexp $binfile]\\s*" \
|
||
+ "check executable was detected correctly"
|
||
+
|
||
+ gdb_test "start" \
|
||
+ "Temporary breakpoint $::decimal, main \\(\\).*" \
|
||
+
|
||
+ gdb_test "events corefile_changed check" \
|
||
+ "Event 1/1, Inferior 1, Corefile None" \
|
||
+ "expected corefile event has been seen"
|
||
+
|
||
+ gdb_test "events exited check" \
|
||
+ "Event 1/1, Inferior 1, Exit Code None" \
|
||
+ "expected exited event has been seen"
|
||
+
|
||
+ gdb_test_no_output -nopass "events corefile_changed reset"
|
||
+ gdb_test_no_output -nopass "events exited reset"
|
||
+ }
|
||
+}
|
||
+
|
||
+# Load a core file, then load a different core file to replace it.
|
||
+# Check that the events that are emitted are as expected.
|
||
+with_test_prefix "load one core file over another" {
|
||
+ clean_restart_and_load_py_script
|
||
+
|
||
+ # Load the core file.
|
||
+ core_file_cmd -corefile $corefile \
|
||
+ -prefix "load first corefile"
|
||
+
|
||
+ core_file_cmd -corefile $other_corefile \
|
||
+ -prefix "load second corefile" \
|
||
+ -replacement
|
||
+}
|
||
diff --git a/gdb/testsuite/gdb.python/py-corefile.py b/gdb/testsuite/gdb.python/py-corefile.py
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/gdb/testsuite/gdb.python/py-corefile.py
|
||
@@ -0,0 +1,261 @@
|
||
+# Copyright (C) 2025 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
|
||
+
|
||
+
|
||
+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():
|
||
+ print("Mapped address spaces:")
|
||
+ print("")
|
||
+ 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:
|
||
+ 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
|
||
+ print(
|
||
+ format_str
|
||
+ % (
|
||
+ "0x%016x" % r.start,
|
||
+ "0x%016x" % r.end,
|
||
+ "0x%-16x" % sz,
|
||
+ "0x%-16x" % r.offset,
|
||
+ "%s" % r.filename,
|
||
+ )
|
||
+ )
|
||
+
|
||
+
|
||
+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()
|
||
+
|
||
+
|
||
+class CheckBuildIds(gdb.Command):
|
||
+ def __init__(self):
|
||
+ gdb.Command.__init__(self, "check-build-ids", gdb.COMMAND_DATA)
|
||
+
|
||
+ def invoke(self, args, from_tty):
|
||
+ inf = gdb.selected_inferior()
|
||
+ objfiles = inf.progspace.objfiles()
|
||
+
|
||
+ path_to_build_id = {}
|
||
+
|
||
+ for o in objfiles:
|
||
+ if not o.is_file or o.build_id is None:
|
||
+ continue
|
||
+ p = pathlib.Path(o.filename).resolve()
|
||
+ b = o.build_id
|
||
+ path_to_build_id[p] = b
|
||
+
|
||
+ count = 0
|
||
+ core_mapped_files = inf.corefile.mapped_files()
|
||
+ for m in core_mapped_files:
|
||
+ p = pathlib.Path(m.filename).resolve()
|
||
+ b = m.build_id
|
||
+
|
||
+ if p in path_to_build_id:
|
||
+ count += 1
|
||
+ assert path_to_build_id[p] == b, "build-id mismatch for %s" % p
|
||
+
|
||
+ assert count > 0, "no mapped files checked"
|
||
+
|
||
+ print("PASS")
|
||
+
|
||
+
|
||
+CheckBuildIds()
|
||
+
|
||
+
|
||
+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")
|
||
diff --git a/gdb/testsuite/lib/dap-support.exp b/gdb/testsuite/lib/dap-support.exp
|
||
--- a/gdb/testsuite/lib/dap-support.exp
|
||
+++ b/gdb/testsuite/lib/dap-support.exp
|
||
@@ -362,6 +362,22 @@ proc dap_attach {pid {prog ""}} {
|
||
return [dap_send_request attach $args]
|
||
}
|
||
|
||
+# Start gdb, send a DAP initialize request, and then an attach request
|
||
+# specifying COREFILE as the core file to attach to. Returns the
|
||
+# empty string on failure, or the attach request sequence ID.
|
||
+proc dap_corefile {corefile {prog ""}} {
|
||
+ if {[dap_initialize "startup - initialize"] == ""} {
|
||
+ return ""
|
||
+ }
|
||
+
|
||
+ set args [format {o coreFile [s "%s"]} $corefile]
|
||
+ if {$prog != ""} {
|
||
+ append args [format { program [s "%s"]} $prog]
|
||
+ }
|
||
+
|
||
+ return [dap_send_request attach $args]
|
||
+}
|
||
+
|
||
# Start gdb, send a DAP initialize request, and then an attach request
|
||
# specifying TARGET as the remote target. Returns the empty string on
|
||
# failure, or the attach request sequence ID.
|
||
@@ -372,26 +388,33 @@ proc dap_target_remote {target} {
|
||
return [dap_send_request attach [format {o target [s %s]} $target]]
|
||
}
|
||
|
||
-# Read the most recent DAP log file and check it for exceptions.
|
||
-proc dap_check_log_file {} {
|
||
+# Read the most recent DAP log file and check it for exceptions. We
|
||
+# expect to see exactly EXPECTED_EXCEPTION_COUNT exceptions in the log.
|
||
+proc dap_check_log_file { {expected_exception_count 0} } {
|
||
set fd [open [current_dap_log_file]]
|
||
set contents [read $fd]
|
||
close $fd
|
||
|
||
- set ok 1
|
||
+ set exception_count 0
|
||
foreach line [split $contents "\n"] {
|
||
if {[regexp "^Traceback" $line]} {
|
||
- set ok 0
|
||
- break
|
||
+ incr exception_count
|
||
+ if { $exception_count > $expected_exception_count} {
|
||
+ break
|
||
+ }
|
||
}
|
||
}
|
||
|
||
- if {$ok} {
|
||
+ if {$exception_count == $expected_exception_count} {
|
||
pass "exceptions in log file"
|
||
} else {
|
||
verbose -log -- "--- DAP LOG START ---"
|
||
verbose -log -- $contents
|
||
verbose -log -- "--- DAP LOG END ---"
|
||
+ if { $expected_exception_count > 0 } {
|
||
+ verbose -log -- [join [list "Expected $expected_exception_count" \
|
||
+ "exception(s), saw $exception_count"]]
|
||
+ }
|
||
fail "exceptions in log file"
|
||
}
|
||
}
|
||
@@ -407,8 +430,9 @@ proc dap_check_log_file_re { re } {
|
||
}
|
||
|
||
# Cleanly shut down gdb. TERMINATE is passed as the terminateDebuggee
|
||
-# parameter to the request.
|
||
-proc dap_shutdown {{terminate false}} {
|
||
+# parameter to the request. The EXPECTED_EXCEPTION_COUNT is the
|
||
+# number of exceptions that we expect to see in the latest DAP log.
|
||
+proc dap_shutdown {{terminate false} {expected_exception_count 0}} {
|
||
dap_check_request_and_response "shutdown" disconnect \
|
||
[format {o terminateDebuggee [l %s]} $terminate]
|
||
|
||
@@ -420,7 +444,7 @@ proc dap_shutdown {{terminate false}} {
|
||
|
||
clear_gdb_spawn_id
|
||
|
||
- dap_check_log_file
|
||
+ dap_check_log_file $expected_exception_count
|
||
}
|
||
|
||
# Search the event list EVENTS for an output event matching the regexp
|