From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id iTnhEdYVt2j+ThIAWB0awg (envelope-from ) for ; Tue, 02 Sep 2025 12:05:42 -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=WZ09iUAG; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 3FA071E057; Tue, 02 Sep 2025 12:05:42 -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.5 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_INVALID,DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_DNSWL_LOW, 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 D6EB61E057 for ; Tue, 02 Sep 2025 12:05:38 -0400 (EDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 82A373856DD1 for ; Tue, 2 Sep 2025 16:05:38 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 82A373856DD1 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=WZ09iUAG 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 12BE8385EC04 for ; Tue, 2 Sep 2025 16:04:01 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 12BE8385EC04 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 12BE8385EC04 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=1756829041; cv=none; b=TgMkZa3B7aMQ5DVXUR3nNWcen8lptgSSBNdRAkqFgLx9CunQ5As/BQzk5DfE9jKC9oYQ5/urjQF1BCYTDevoiF1rViWY3ybhvi8kLRNCtaNTOkkdqyAXDOR+vCuoOgzRbv+Zcu7cEB7D9xb+HbzIBi0G6RZm9qjxu+9p62rT1rk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1756829041; c=relaxed/simple; bh=j2PDogNAxZ5N2kgNoyJI3VmmQaJDVzJ/+LVKVLWFTtQ=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=ZxXQAExQl6H6qMqCNn8ysW5H4hWVpIuoe0el559ZQQ/CfTQId2X2ZVa5opgA7JgwilHALb180IE0+780WiNVLy7VCLI+XU8KxZSQ0uVkMpric7iLfSeqbAZFkTufCzY8nBhm567YHAP1oHGoXNSyQ/pXNK3OgeFG7UD7t5PCHso= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 12BE8385EC04 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1756829040; 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=3X2+a2Q7uJ9eLJY1SIFixbkSbHpZhpeMeDAtd5wfSUo=; b=WZ09iUAGMnIkw6YAFzapsa6JWeVKWzFGU+xKAhuglJu1kwCl6GUXbXxkRZapMXD9e+2nrx Xti5nEnupSWLAuabUQS44fNTnhZfLSAXGU04Ky6zR+xhUQ0uzw9Fu1HFNWS+Q/6zmiH+WK ApV4d0VmEUGCtGG208v4mZEFixrlQdc= Received: from mail-wm1-f71.google.com (mail-wm1-f71.google.com [209.85.128.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-583-EBLzm_GWMBOiZhV55cCkpA-1; Tue, 02 Sep 2025 12:03:59 -0400 X-MC-Unique: EBLzm_GWMBOiZhV55cCkpA-1 X-Mimecast-MFC-AGG-ID: EBLzm_GWMBOiZhV55cCkpA_1756829038 Received: by mail-wm1-f71.google.com with SMTP id 5b1f17b1804b1-45b883aa405so23519425e9.2 for ; Tue, 02 Sep 2025 09:03:59 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1756829038; x=1757433838; 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=zgYOQzocVPHA/ir9x0vRFoIMBPOw920bPrg9XOFHLv0=; b=O7IoRXfcdsmYjpQGXER6lXdfgkCZ7eQzze0d4A17BbU4zz2HsFkyGOvsmdX1P0qjBg T5bgWdf2vYFe/KQeYFc0LKPi32wiFpTpXzgK9T8c1SHwXkk92MIrEjWh1KatLiTyo6i/ +PWsd9b77g2I2mndG6kp4hMed1G4OrEKADvbmitiw6RK+qiAkn63LfoVFUpVrSewNz8B MFN7bKCyaQRzp1i2UAmAP55qRQ3mpYPvcfDNwTsEsegWECwMhym63n6u6Qo3L8tFs7DE ULrxaY1HuUN2VaoKOnUdl0h1MZ/d0VIhYrKXXNv5EgiJxSlCTuBlYKreGyEuv3xPKcG/ Ic7Q== X-Gm-Message-State: AOJu0YyEVJHdrWO6kGgdL5kiUIy5RQYR26MZ9GMthULiH8E7t6ZSR8cj NlPqQLwwMdCAlBTdYLRShzrSX6p5XsVH/kiq2OfjXA44TQqnTym5+djuJX9uv0Vb27YYdYgwFTG Bps9biX3N6pSkUmDKRxJzsZR6yMoY2dDrZkrGjyUNpMfi74v99o20CO76v4JfutViuP89YGQByc zH7FvAdWq9TYdTyZlFWVRpIjl1yEEccz3QV6J5zjTq5iMGcEI= X-Gm-Gg: ASbGncvmfGv2XWaNXrOP6rTihzPq+tnxqSHraDdFbolu8qEO9RCTFhGfs5yQ7djK1L8 svE9dHUdPiZPwOF8D6ENGPlhy+1IwnaqiWin5LzsIr59p8DjcFG+du5dkIc+V1BAs+2Hd+qob0E htJdguH6XVLwD1iwZwFeHuNQMUPX24ZXJdcdCIrjWvt7uEFxqLj2CrbnMCuhKIwosttUsmpzSGS QSHCSapmNjMyYjfRc2qgfZkdQCzcSFI+UDeGb2gTssP39O/IOdI0wgT5rdlubevIA8cBrUq6r6U v5l4mzruQpjRrrRVNAgoqWTj+SFTNnExbhc= X-Received: by 2002:a05:600c:3153:b0:455:f59e:fd9b with SMTP id 5b1f17b1804b1-45b85570dbdmr127742265e9.24.1756829036946; Tue, 02 Sep 2025 09:03:56 -0700 (PDT) X-Google-Smtp-Source: AGHT+IHMWY+6DIR0WUDXZEr2/Ti/KtgfWT76y7s0z3FdSxGpYu9paOgc1QGf6E6pJSij2a1TnoF5HQ== X-Received: by 2002:a05:600c:3153:b0:455:f59e:fd9b with SMTP id 5b1f17b1804b1-45b85570dbdmr127741435e9.24.1756829035982; Tue, 02 Sep 2025 09:03:55 -0700 (PDT) Received: from localhost ([31.111.84.207]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-45b7e8ab14esm220457375e9.21.2025.09.02.09.03.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Sep 2025 09:03:55 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCH 1/3] gdb/python: introduce gdb.Corefile API Date: Tue, 2 Sep 2025 17:03:49 +0100 Message-ID: <974b7081b12926872eddc6a68a3dec36fb1d06d6.1756828929.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: dTv8_fYaAiMse_cmDkLxbH4wozN4FfG6bgrAdr6_5Ec_1756829038 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 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) python print(core.is_valid()) True (gdb) core-file No core file now. (gdb) python print(core) (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 --- gdb/Makefile.in | 1 + gdb/NEWS | 10 + gdb/corelow.c | 7 + gdb/doc/python.texi | 49 ++++ gdb/observable.c | 1 + gdb/observable.h | 6 + gdb/python/py-corefile.c | 293 +++++++++++++++++++++++ gdb/python/py-inferior.c | 18 ++ gdb/python/python-internal.h | 8 + gdb/testsuite/gdb.python/py-corefile.c | 25 ++ gdb/testsuite/gdb.python/py-corefile.exp | 169 +++++++++++++ 11 files changed, 587 insertions(+) create mode 100644 gdb/python/py-corefile.c create mode 100644 gdb/testsuite/gdb.python/py-corefile.c create mode 100644 gdb/testsuite/gdb.python/py-corefile.exp diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 7654fb1aa04..7a38bd6bed1 100644 --- 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 index 2d410a75265..98e322d7be6 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -174,6 +174,16 @@ info threads [-gid] [-stopped] [-running] [ID]... ** New gdb.warning() function that takes a string and prints it as a warning, with GDB's standard 'warning' prefix. + ** 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). + + ** 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. + * Guile API ** New type for dealing with colors. diff --git a/gdb/corelow.c b/gdb/corelow.c index af534bfb26f..111999c202c 100644 --- 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 @@ -678,6 +679,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 ()); } } @@ -1277,6 +1281,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 diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 6fa22851f6a..3a6022f22e7 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -233,6 +233,7 @@ Python API * 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 @@ -3607,6 +3608,15 @@ Inferiors In Python 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 () @@ -8605,6 +8615,45 @@ Missing Objfiles In Python @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 + +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. + @node Python Auto-loading @subsection Python Auto-loading @cindex Python auto-loading diff --git a/gdb/observable.c b/gdb/observable.c index 1233a1943a6..8439f11c15d 100644 --- 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 index 4d913010c56..5f064cf1fc8 100644 --- a/gdb/observable.h +++ b/gdb/observable.h @@ -260,6 +260,12 @@ extern observable free_program_space; extern observable 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 core_file_changed; + } /* namespace observers */ } /* namespace gdb */ diff --git a/gdb/python/py-corefile.c b/gdb/python/py-corefile.c new file mode 100644 index 00000000000..25fdb31963c --- /dev/null +++ b/gdb/python/py-corefile.c @@ -0,0 +1,293 @@ +/* Python interface to core files. + + Copyright (C) 2010-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 . */ + +#include "python-internal.h" +#include "progspace.h" +#include "observable.h" +#include "inferior.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; +}; + +extern PyTypeObject corefile_object_type + CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("corefile_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; + gdbpy_ref object (obj); + object->inferior = nullptr; + } +}; + +/* Store a gdb.Corefile object in an inferior's registry. */ + +static const registry::key + 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 object + ((corefile_object *) PyObject_New (corefile_object, + &corefile_object_type)); + if (object == nullptr) + return nullptr; + + object->dict = PyDict_New (); + if (object->dict == nullptr) + return nullptr; + 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 NULL; \ + } \ + } 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; +} + +/* Callback from gdb::observers::core_file_changed. The core file in + PSPACE has been changed. */ + +static void +cfpy_corefile_changed (inferior *inf) +{ + if (!gdb_python_initialized) + return; + + gdbpy_enter enter_py; + + /* Get any existing corefile_object for PSPACE. */ + corefile_object *object = cfpy_inferior_corefile_data_key.get (inf); + + /* If we have an object, then... */ + if (object != nullptr) + { + /* Clearing the inferior pointer marks the gdb.Corefile as invalid. */ + object->inferior = nullptr; + + /* We're discarding our reference to the gdb.Corefile. */ + Py_XDECREF ((PyObject *) object); + cfpy_inferior_corefile_data_key.set (inf, nullptr); + } +} + +/* 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_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 ())); +} + + + +static int CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION +gdbpy_initialize_corefile (void) +{ + gdb::observers::core_file_changed.attach (cfpy_corefile_changed, + "py-corefile"); + + if (gdbpy_type_ready (&corefile_object_type) < 0) + return -1; + + return 0; +} + +GDBPY_INITIALIZE_FILE (gdbpy_initialize_corefile); + + + +static gdb_PyGetSetDef corefile_getset[] = +{ + { "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." }, + { nullptr } +}; + +PyTypeObject corefile_object_type = +{ + PyVarObject_HEAD_INIT (NULL, 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 */ +}; diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c index 2aa11d3160d..d926923915a 100644 --- 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 index f61a1753ac4..1649bb83812 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -1113,6 +1113,14 @@ extern std::optional 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.python/py-corefile.c b/gdb/testsuite/gdb.python/py-corefile.c new file mode 100644 index 00000000000..1334ff65143 --- /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 . */ + +#include + +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 index 00000000000..a65c7c9b8b5 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-corefile.exp @@ -0,0 +1,169 @@ +# 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 . + +# This file is part of the GDB testsuite. It tests the core file +# support in Python. + +require isnative + +load_lib gdb-python.exp + +require allow_python_tests + +standard_testfile + +if {[build_executable "build executable" $testfile $srcfile] == -1} { + return +} + +set corefile [core_find $binfile] +if {$corefile == ""} { + unsupported "couldn't create or find corefile" + return +} + +# Create a copy of the corefile. +set other_corefile [standard_output_file ${testfile}-other.core] +remote_exec build "cp $corefile $other_corefile" + +clean_restart + +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" + +gdb_test "core-file $corefile" ".*" \ + "load core file" + +set file_re [string_to_regexp $corefile] +gdb_test "python print(inf.corefile)" "^" \ + "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_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.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" + +gdb_test "core-file" "^No core file now\\." "unload current core file" + +gdb_test "python print(core1.is_valid())" "^False" \ + "Corefile.is_valid() is False after corefile is unloaded" + +gdb_test "python print(core1)" "^" \ + "print an invalid gdb.Corefile object" + +gdb_test "python print(core1.filename)" \ + [multi_line \ + "Python Exception : 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" { + gdb_test "core-file $corefile" ".*" \ + "load core file" + + 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. +gdb_test "core $other_corefile" ".*" \ + "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" + + gdb_test "core-file" + + gdb_test "python print(core2.filename)" \ + [multi_line \ + "Python Exception : 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 : 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" +} -- 2.47.1