From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id eYBuL7+m0miySQ8AWB0awg (envelope-from ) for ; Tue, 23 Sep 2025 09:55:11 -0400 Authentication-Results: simark.ca; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Z880X5Gs; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id A9E6B1E047; Tue, 23 Sep 2025 09:55:11 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=0.2 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_INVALID,DKIM_SIGNED,MAILING_LIST_MULTI, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=no autolearn_force=no version=4.0.1 Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id B87061E047 for ; Tue, 23 Sep 2025 09:55:09 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 636223858C41 for ; Tue, 23 Sep 2025 13:55:09 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 636223858C41 Authentication-Results: sourceware.org; dkim=fail reason="signature verification failed" (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Z880X5Gs Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 94FEE385843B for ; Tue, 23 Sep 2025 13:44:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 94FEE385843B Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 94FEE385843B Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1758635062; cv=none; b=buOBu4pfDoDTu6ETK1F68vGNUh3TxR0hk1Oh8fKvbEzn6dBY+BJ51nt7ga+na08fHEnbhDlOlJVHwmDuyKvP+eAp4WySbK/9scA8gK1zSrRsxaOprm015U9GBSyWYvxZTOccV0iQYwbl9V3GdQu5w0aBUZ8DHfIi4V7c6vBg0ag= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1758635062; c=relaxed/simple; bh=6Ac6n6kMgyPOXdQhm/EX9Oylw65fr9eXWeMH2bJr9EE=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=d0YLHu9zK8Xr4aaP/cm9J3EiTO4C0US3H5O0YP9eMxte2EYJFi3Mu0NJa4CwbaqvF6K2P55iHd5Iz8kHpAjkVv+EnhnFfCEmuLMQ0nNQ0lZfxmcu7IdPdq49XZ75SyJ817CKws2IZSo4zuBGJoEwYa5uvd6FzdObdtyyqs88RnU= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 94FEE385843B DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1758635062; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=eruMBeNanwl0VliPHxGstkNPvOcBN98hNIObA8x4ueg=; b=Z880X5Gs0rqzOM37KA8CedHRyG7YhkX81hZ7m7OicUjR8d5aQnVrx+JeTg/SQVJ3yDY/VU 5J8hvscqVL0yrIPmGQzM1XWMtrSketHYrJtjKN/NW+KlwzS7DTyg8zbxUgkOPZlqzPUu1n Sh3KOqWKrzky8fQooL5nGtYpZ39GeTE= Received: from mail-wm1-f70.google.com (mail-wm1-f70.google.com [209.85.128.70]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-657-SCBja4s4MHu2Cvy-O5Sh_w-1; Tue, 23 Sep 2025 09:44:20 -0400 X-MC-Unique: SCBja4s4MHu2Cvy-O5Sh_w-1 X-Mimecast-MFC-AGG-ID: SCBja4s4MHu2Cvy-O5Sh_w_1758635060 Received: by mail-wm1-f70.google.com with SMTP id 5b1f17b1804b1-46d8ef3526dso12963565e9.1 for ; Tue, 23 Sep 2025 06:44:20 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1758635059; x=1759239859; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=eX30b7lbe8sLQLPGxiqgdSS/ESXFaGJ0mE7e4/YVI34=; b=ju2AEh4mo5z28apXeuLGGnQXMgI87sPDyomCIcNmS4pSOMhiqNtqAFRkso2OuIP4dJ VYZbdiCSNDhAaJDfPtZju+vTzF42OZrOktc9XzBYmcFboCfSWVFtjBCyx+MSohSLh0wx d6c7JoaRiZZLqnVnl2wwO26hn/idh8QkxdHGtepDDajuE+WAT5lLVuqI+Kt0DL474POl 3wIVb8CIjexZyhBawKvUzO4Ka51g9toB9LsujSRUodLMDA0S5kcGytDAYMpXSKToX/CY ulFUzfFPgDwPnIfuDGca3Mzf5aY8Cw07AARx4mrIj3dM2Gj5F56BwGu8MVPpvEOShhHf +rkg== X-Gm-Message-State: AOJu0YzemD73rOGGLm79cIDqMxcvU9RGppwVKSU9r5zmJVPt4SdU3V+7 lE4oRN7pN/4lewa6Bv12N5hojaHddu324qkAzI4/Uyfvd+hF0j/nyd/tfWDIaj7gHmL2buNOWhS 9HKJB6iIfkcVmrBc7NCbCGULDNXcZiK52V6haMreU4Jsgom6mp11Zgx9gw+0Bk/oTfWbslntEt9 UP42ey0Q0FfaDjm37ADEbxUrmBLP5FcipdfEs5f6CP7AGiXtA= X-Gm-Gg: ASbGncvJjyioQowBtf//QBtCMpAK1RaHLysVi/EePPFOYXrbULTPEO/Wppf0iZk9Wkc mQhYM7otU83sGJ5e+hcSdVY6liLhJLMLz3jcL1S602WIRLcT6K8IyOs8gDiYEe66ZkNEpTsPaGf CjTemBxx+EkQprhC1bQJFhYVBp5/nOfUGKgJpsCFQsFmeJa5CZ1DW9TgEDpELbGUiSaG59tW61B 2fOPDqm1nIezHpaCwuH8ctUsD3VWMV2Gkn6vi9uR16pU5PPifjxPlxn1NKKHIZAb9R8sXbRdOZD 8M4pktRcY/SUsGQdUGS9cOkpiHBtGOsVGho= X-Received: by 2002:a05:600c:5493:b0:46e:2109:f435 with SMTP id 5b1f17b1804b1-46e2109f67cmr19211055e9.11.1758635058809; Tue, 23 Sep 2025 06:44:18 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGT+jVVLgqGB6ZyFDK5LjtzlPx1CLPfEFRLbGjiNlp4Ol2DzxPLhXVCKJm4diQLMdWgZqPa3g== X-Received: by 2002:a05:600c:5493:b0:46e:2109:f435 with SMTP id 5b1f17b1804b1-46e2109f67cmr19210585e9.11.1758635057926; Tue, 23 Sep 2025 06:44:17 -0700 (PDT) Received: from localhost ([31.111.84.207]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-46e15d1610fsm62387795e9.7.2025.09.23.06.44.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 23 Sep 2025 06:44:17 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCHv2 3/3] gdb/python: add Corefile.mapped_files method Date: Tue, 23 Sep 2025 14:44:08 +0100 Message-ID: <9ab589510f784da2752b72d0b1385afa33aca406.1758634958.git.aburgess@redhat.com> X-Mailer: git-send-email 2.47.1 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: H4KMS7HGkkyuIP12wpbBGgDaf7E7kxOzMRwhfQQQHzA_1758635060 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org 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 --- gdb/NEWS | 21 ++ gdb/doc/python.texi | 61 ++++ gdb/python/py-corefile.c | 397 +++++++++++++++++++++++ gdb/testsuite/gdb.python/py-corefile.exp | 58 ++++ gdb/testsuite/gdb.python/py-corefile.py | 144 ++++++++ 5 files changed, 681 insertions(+) create mode 100644 gdb/testsuite/gdb.python/py-corefile.py diff --git a/gdb/NEWS b/gdb/NEWS index 07838e41bd2..5cdd81a6ff3 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -45,6 +45,27 @@ single-inf-arg in qSupported contains the gdb.Corefile object if a core file is loaded into the inferior, otherwise, this contains None. + ** 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. + *** Changes in GDB 17 * Debugging Linux programs that use x86-64 or x86-64 with 32-bit pointer diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index d25567e8943..b37d84a989e 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -8667,12 +8667,73 @@ Core Files In Python 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/python/py-corefile.c b/gdb/python/py-corefile.c index f199a2c82be..757369fa0a3 100644 --- a/gdb/python/py-corefile.c +++ b/gdb/python/py-corefile.c @@ -21,6 +21,8 @@ #include "progspace.h" #include "observable.h" #include "inferior.h" +#include "gdbcore.h" +#include "gdbsupport/rsp-low.h" /* A gdb.Corefile object. */ @@ -37,11 +39,61 @@ struct corefile_object /* 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. */ @@ -94,6 +146,7 @@ gdbpy_core_file_from_inferior (inferior *inf) 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; @@ -168,6 +221,123 @@ cfpy_is_valid (PyObject *self, PyObject *args) 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 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 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 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; +} + /* Callback from gdb::observers::core_file_changed. The core file in PSPACE has been changed. */ @@ -191,6 +361,7 @@ cfpy_dealloc (PyObject *obj) gdb_assert (corefile->inferior == nullptr); Py_XDECREF (corefile->dict); + Py_XDECREF (corefile->mapped_files); Py_TYPE (obj)->tp_free (obj); } @@ -215,6 +386,114 @@ cfpy_repr (PyObject *self) +/* 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 () { @@ -224,6 +503,12 @@ gdbpy_initialize_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; } @@ -245,6 +530,10 @@ 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 } }; @@ -289,3 +578,111 @@ PyTypeObject corefile_object_type = 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/testsuite/gdb.python/py-corefile.exp b/gdb/testsuite/gdb.python/py-corefile.exp index e9254bd9e78..3b57cc0e250 100644 --- a/gdb/testsuite/gdb.python/py-corefile.exp +++ b/gdb/testsuite/gdb.python/py-corefile.exp @@ -176,3 +176,61 @@ with_test_prefix "remove second inferior" { 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 + + set remote_python_file \ + [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] + + # Load the Python script into GDB. + gdb_test "source $remote_python_file" "^Success" \ + "source python script" + + # Load the core file. + gdb_test "core-file $corefile" ".*" \ + "load core file" + + # 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))" \ + "^" + 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))" \ + "^" + gdb_test "python regions\[0\] = None" \ + "'tuple' object does not support item assignment" +} diff --git a/gdb/testsuite/gdb.python/py-corefile.py b/gdb/testsuite/gdb.python/py-corefile.py new file mode 100644 index 00000000000..cffd037a23b --- /dev/null +++ b/gdb/testsuite/gdb.python/py-corefile.py @@ -0,0 +1,144 @@ +# 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 . + +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() + + +print("Success") -- 2.47.1