Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Matthieu Longo <matthieu.longo@arm.com>
To: <gdb-patches@sourceware.org>, Tom Tromey <tom@tromey.com>
Cc: Matthieu Longo <matthieu.longo@arm.com>
Subject: [PATCH v1 3/4] gdb/python: migrate Python initialization to use the new config API (PEP 741)
Date: Thu, 9 Apr 2026 11:51:54 +0100	[thread overview]
Message-ID: <20260409105155.1416274-4-matthieu.longo@arm.com> (raw)
In-Reply-To: <20260409105155.1416274-1-matthieu.longo@arm.com>

GDB currently initializes CPython using the PyConfig C API introduced in
Python 3.8 (PEP 597). From an ABI stability perspective, this API has a
major drawback: it exposes a plain structure whose fields may be added or
removed between Python versions. As a result, it was excluded from the
Python limited API.

Python 3.14 introduced a new configuration API (PEP 741) that avoids
exposing plain structures and instead, operates via opaque pointers.
This design makes it much more suitable for inclusion in the Python
limited API. Indeed, this was the original intent of the PEP-741 author.
However, CPython maintainers ultimately decided otherwise.

Since GDB aims at using the CPython stable API to avoid rebuilding for
each Python version, the absence of a configuration API in the limited
C API constitutes a blocker. Nevertheless, this can be worked around by
using PEP-741 configuration API, whose design is compatible with the
limited C API. It is relatively safe to assume that this API will stay
around for some time.

In this perspective, this patch adds support for using the PEP-741 config
API starting from Python 3.14. When Py_LIMITED_API is defined, the
required functions are exposed as external symbols via a workaround header.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=23830
---
 gdb/python/python.c | 128 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 127 insertions(+), 1 deletion(-)

diff --git a/gdb/python/python.c b/gdb/python/python.c
index 85bd88cd243..1a0ff29d1d4 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -2216,6 +2216,100 @@ static bool python_dont_write_bytecode_at_python_initialization;
    to passing `-E' to the python program.  */
 static bool python_ignore_environment = false;
 
+#if PY_VERSION_HEX >= 0x030e0000
+namespace {
+
+/* A class wrapper around the new configuration functions introduced by
+   PEP 741 in Python 3.14.  */
+class gdb_PyInitializer
+{
+public:
+  gdb_PyInitializer ();
+  ~gdb_PyInitializer ();
+
+  void set_opt (const char *opt_name, bool value);
+  void set_opt (const char *opt_name, const char *value);
+
+  /* Initialize Python with the current config.
+     Note: this function should be called before the object goes out of scope
+     or an assertion will be triggered in the destructor at runtime.  Setting
+     options value after this call does not have any effect.  */
+  void initialize ();
+
+private:
+  [[noreturn]] void handle_error ();
+
+  PyInitConfig *m_config;
+  bool m_err;
+  bool m_py_initialized;
+};
+
+gdb_PyInitializer::gdb_PyInitializer ()
+  : m_config (PyInitConfig_Create ()),
+    m_err (false),
+    m_py_initialized (false)
+{
+  if (m_config == nullptr)
+  {
+    throw_error (GENERIC_ERROR,
+		 _("Python initialization failed: memory allocation failed"));
+  }
+}
+
+gdb_PyInitializer::~gdb_PyInitializer ()
+{
+  /* If the condition below is false, the calling context probably forgot to
+     call initialize().  */
+  gdb_assert (m_err || m_py_initialized);
+
+  if (m_config)
+    PyInitConfig_Free (m_config);
+}
+
+[[noreturn]] void
+gdb_PyInitializer::handle_error ()
+{
+  m_err = true;
+
+  const char *err_msg;
+  if (PyInitConfig_GetError (m_config, &err_msg) == 1)
+    throw_error (GENERIC_ERROR,
+		 _("Python initialization failed: %s"),
+		 err_msg);
+
+  int exit_code;
+  gdb_assert (PyInitConfig_GetExitCode (m_config, &exit_code) != 0);
+  throw_error (GENERIC_ERROR,
+	       _("Python initialization failed with exit status: %d"),
+	       exit_code);
+}
+
+void
+gdb_PyInitializer::set_opt (const char *opt_name, bool value)
+{
+  if (PyInitConfig_SetInt (m_config, opt_name, value) < 0)
+    handle_error ();
+}
+
+void
+gdb_PyInitializer::set_opt (const char *opt_name, const char *value)
+{
+  if (PyInitConfig_SetStr (m_config, opt_name, value) < 0)
+    handle_error ();
+}
+
+void
+gdb_PyInitializer::initialize ()
+{
+  if (!m_py_initialized && Py_InitializeFromInitConfig (m_config) < 0)
+    handle_error ();
+  m_py_initialized = true;
+}
+
+} /* namespace anonymous */
+
+#endif /* PY_VERSION_HEX >= 0x030e0000 */
+
 /* Implement 'show python ignore-environment'.  */
 
 static void
@@ -2549,7 +2643,8 @@ py_initialize ()
   Py_IgnoreEnvironmentFlag
     = python_ignore_environment_at_python_initialization ? 1 : 0;
   return py_initialize_catch_abort ();
-#else
+
+#elif PY_VERSION_HEX < 0x030e0000
   PyConfig config;
 
   PyConfig_InitPythonConfig (&config);
@@ -2585,6 +2680,37 @@ py_initialize ()
 
   py_isinitialized = true;
   return true;
+
+#else
+  try
+  {
+    gdb_PyInitializer py_config;
+
+    py_config.set_opt ("program_name", progname.get ());
+    py_config.set_opt ("write_bytecode",
+		       !python_dont_write_bytecode_at_python_initialization);
+    py_config.set_opt ("isolated", false);
+    py_config.set_opt ("configure_locale", true);
+    py_config.set_opt ("pathconfig_warnings", true);
+    py_config.set_opt ("parse_argv", true);
+    py_config.set_opt ("safe_path", false);
+    py_config.set_opt ("configure_c_stdio", true);
+    py_config.set_opt ("stdio_encoding", "utf-8");
+    py_config.set_opt ("stdio_errors", "strict");
+    py_config.set_opt ("install_signal_handlers", true);
+    py_config.set_opt ("use_environment",
+		       !python_ignore_environment_at_python_initialization);
+    py_config.set_opt ("user_site_directory", true);
+
+    py_config.initialize ();
+    py_isinitialized = true;
+  }
+  catch (const gdb_exception_error &exc)
+  {
+    gdb_printf ("%s\n", exc.what ());
+    py_isinitialized = false;
+  }
+  return py_isinitialized;
 #endif
 }
 
-- 
2.53.0


  parent reply	other threads:[~2026-04-09 10:54 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-09 10:51 [PATCH v1 0/4] gdb/python: more fixes again for Python limited C API support Matthieu Longo
2026-04-09 10:51 ` [PATCH v1 1/4] gdb/python: add gdbpy_borrowed_ref Matthieu Longo
2026-04-09 10:51 ` [PATCH v1 2/4] gdb/python: eval_python_command returns both exit code and result Matthieu Longo
2026-04-09 10:51 ` Matthieu Longo [this message]
2026-04-09 10:51 ` [PATCH v1 4/4] gdb/python: work around missing symbols not yet part of Python limited API Matthieu Longo
2026-04-15  9:18 ` [PATCH v1 0/4] gdb/python: more fixes again for Python limited C API support Matthieu Longo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260409105155.1416274-4-matthieu.longo@arm.com \
    --to=matthieu.longo@arm.com \
    --cc=gdb-patches@sourceware.org \
    --cc=tom@tromey.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox