* [PATCH RFC 1/6] Add py-file.c
2026-10-22 18:28 [PATCH RFC 0/6] Handle Android APK files Tom Tromey
@ 2026-10-22 18:28 ` Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 2/6] Add py-iovec.c Tom Tromey
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Tom Tromey @ 2026-10-22 18:28 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This patch adds py-file.c, a Python wrapper for gdb's target file API.
---
gdb/Makefile.in | 1 +
gdb/python/py-file.c | 298 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 299 insertions(+)
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 8cd12460000..4260c90d425 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -403,6 +403,7 @@ SUBDIR_PYTHON_SRCS = \
python/py-evtregistry.c \
python/py-evts.c \
python/py-exitedevent.c \
+ python/py-file.c \
python/py-finishbreakpoint.c \
python/py-frame.c \
python/py-framefilter.c \
diff --git a/gdb/python/py-file.c b/gdb/python/py-file.c
new file mode 100644
index 00000000000..cd178190bfc
--- /dev/null
+++ b/gdb/python/py-file.c
@@ -0,0 +1,298 @@
+/* Python interface to target files
+
+ Copyright (C) 2026 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 "inferior.h"
+#include "target.h"
+
+/* These have the same values as the Python os.SEEK_* constants.
+ Normally this would be bad but it seems like these are fairly well
+ documented. */
+enum constants : int
+{
+ SET = 0,
+ CUR = 1,
+ END = 2,
+};
+
+struct target_file_object : public PyObject
+{
+ target_fd fd;
+ LONGEST offset;
+ LONGEST file_length;
+
+ void close ()
+ {
+ if (fd != target_fd::INVALID)
+ {
+ fileio_error ignore;
+ target_fileio_close (fd, &ignore);
+ }
+ }
+};
+
+static_assert (gdb::is_python_allocatable_v<target_file_object>);
+
+extern PyTypeObject target_file_object_type;
+
+static void
+tf_dealloc (PyObject *obj)
+{
+ target_file_object *tf = (target_file_object *) obj;
+ tf->close ();
+}
+
+static bool
+check_valid (target_file_object *tf)
+{
+ if (tf->fd == target_fd::INVALID)
+ {
+ PyErr_SetString (PyExc_ValueError, "file closed");
+ return false;
+ }
+ return true;
+}
+
+static PyObject *
+tf_read (PyObject *self, PyObject *args)
+{
+ target_file_object *tf = (target_file_object *) self;
+ if (!check_valid (tf))
+ return nullptr;
+
+ long nbytes = -1;
+ if (!PyArg_ParseTuple (args, "|l", &nbytes))
+ return nullptr;
+
+ long chunk = nbytes == -1 ? 1024 : nbytes;
+ gdb::byte_vector result (chunk);
+ int n_read = 0;
+ try
+ {
+ while (true)
+ {
+ fileio_error err;
+ int this_read = target_fileio_pread (tf->fd, &result[n_read], chunk,
+ tf->offset, &err);
+ if (this_read == -1)
+ {
+ errno = fileio_error_to_host (err);
+ perror_with_name ("read failed");
+ }
+ n_read += this_read;
+ tf->offset += this_read;
+ if (nbytes != -1 || this_read == 0)
+ break;
+
+ result.resize (result.size () + chunk);
+ }
+ }
+ catch (const gdb_exception &exc)
+ {
+ return gdbpy_handle_gdb_exception (nullptr, exc);
+ }
+
+ return PyBytes_FromStringAndSize ((char *) result.data (), n_read);
+}
+
+static PyObject *
+tf_seek (PyObject *self, PyObject *args)
+{
+ target_file_object *tf = (target_file_object *) self;
+ if (!check_valid (tf))
+ return nullptr;
+
+ Py_ssize_t value;
+ int what = SET;
+ if (!PyArg_ParseTuple (args, "n|i", &value, &what))
+ return nullptr;
+
+ LONGEST new_offset = tf->offset;
+ switch (what)
+ {
+ case SET:
+ new_offset = value;
+ break;
+
+ case CUR:
+ new_offset += value;
+ break;
+
+ case END:
+ new_offset = tf->file_length + value;
+ break;
+
+ default:
+ PyErr_SetString (PyExc_ValueError, "invalid seek constant");
+ return nullptr;
+ }
+
+ /* Check the new value. Note we allow the new offset to exactly
+ equal the file length. */
+ if (new_offset < 0 || new_offset > tf->file_length)
+ {
+ PyErr_SetString (PyExc_ValueError, "invalid seek offset");
+ return nullptr;
+ }
+
+ tf->offset = new_offset;
+ return gdb_py_object_from_ulongest (tf->offset).release ();
+}
+
+static PyObject *
+tf_seekable (PyObject *self, PyObject *args)
+{
+ return py_true ().release ();
+}
+
+static PyObject *
+tf_tell (PyObject *self, PyObject *args)
+{
+ target_file_object *tf = (target_file_object *) self;
+ if (!check_valid (tf))
+ return nullptr;
+ return gdb_py_object_from_ulongest (tf->offset).release ();
+}
+
+static PyObject *
+tf_close (PyObject *self, PyObject *args)
+{
+ target_file_object *tf = (target_file_object *) self;
+ tf->close ();
+ return py_none ().release ();
+}
+
+static PyObject *
+tf_new (PyTypeObject *type, PyObject *args, PyObject *keywords)
+{
+ gdbpy_ref<target_file_object> self
+ ((target_file_object *) type->tp_alloc (type, 0));
+ if (self != nullptr)
+ self->fd = target_fd::INVALID;
+ return self.release ();
+}
+
+static int
+tf_init (PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ static const char *keywords[] = { "filename", nullptr };
+ const char *filename = nullptr;
+
+ target_file_object *tf = (target_file_object *) self;
+ /* __init__ can be called twice, but we just reject it for this
+ type. */
+ if (tf->fd != target_fd::INVALID)
+ {
+ PyErr_SetString (PyExc_ValueError,
+ "cannot call __init__ twice for gdb.TargetFile");
+ return -1;
+ }
+
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "s", keywords,
+ &filename))
+ return -1;
+
+ try
+ {
+ fileio_error err;
+ tf->fd = target_fileio_open (current_inferior (), filename,
+ FILEIO_O_RDONLY, 0, true, &err);
+ if (tf->fd == target_fd::INVALID)
+ {
+ errno = fileio_error_to_host (err);
+ perror_with_name ("open failed");
+ }
+
+ struct stat buf;
+ if (target_fileio_fstat (tf->fd, &buf, &err))
+ {
+ tf->close ();
+ errno = fileio_error_to_host (err);
+ perror_with_name ("stat failed while opening");
+ }
+
+ tf->file_length = buf.st_size;
+ }
+ catch (const gdb_exception &exc)
+ {
+ return gdbpy_handle_gdb_exception (-1, exc);
+ }
+
+ return 0;
+}
+
+static PyMethodDef target_file_object_methods[] =
+{
+ { "read", tf_read, METH_VARARGS, "read bytes" },
+ { "seek", tf_seek, METH_VARARGS, "seek to position" },
+ { "seekable", tf_seekable, METH_VARARGS, "seek to position" },
+ { "tell", tf_tell, METH_NOARGS, "return current position" },
+ { "close", tf_close, METH_NOARGS, "close the file" },
+ { nullptr }
+};
+
+PyTypeObject target_file_object_type =
+{
+ PyVarObject_HEAD_INIT (NULL, 0)
+ "gdb.TargetFile", /*tp_name*/
+ sizeof (target_file_object), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ tf_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 remote file object", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ target_file_object_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ tf_init, /* tp_init */
+ 0, /* tp_alloc */
+ tf_new, /* tp_new */
+};
+
+static int
+gdbpy_initialize_target_file ()
+{
+ return gdbpy_type_ready (&target_file_object_type);
+}
+
+GDBPY_INITIALIZE_FILE (gdbpy_initialize_target_file);
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC 2/6] Add py-iovec.c
2026-10-22 18:28 [PATCH RFC 0/6] Handle Android APK files Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 1/6] Add py-file.c Tom Tromey
@ 2026-10-22 18:28 ` Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 3/6] Allow missing build-id when finding an objfile Tom Tromey
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Tom Tromey @ 2026-10-22 18:28 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This patch adds py-iovec.c, which provides a way to construct a new
BFD that can access Python objects to get the underlying data. Due to
how subsequent patches are written, Python file-like objects aren't
used directly, but instead a wrapper will be provided.
---
gdb/Makefile.in | 1 +
gdb/python/py-iovec.c | 130 +++++++++++++++++++++++++++++++++++++++++++
gdb/python/python-internal.h | 2 +
3 files changed, 133 insertions(+)
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 4260c90d425..e6c99924451 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -413,6 +413,7 @@ SUBDIR_PYTHON_SRCS = \
python/py-infevents.c \
python/py-infthread.c \
python/py-instruction.c \
+ python/py-iovec.c \
python/py-lazy-string.c \
python/py-linetable.c \
python/py-membuf.c \
diff --git a/gdb/python/py-iovec.c b/gdb/python/py-iovec.c
new file mode 100644
index 00000000000..4bcea6d8a3b
--- /dev/null
+++ b/gdb/python/py-iovec.c
@@ -0,0 +1,130 @@
+/* Python interface to BFD iovec
+
+ Copyright (C) 2026 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 "gdb_bfd.h"
+#include "gdbcore.h"
+#include "python-internal.h"
+
+struct py_iovec : public gdb_bfd_iovec_base
+{
+ py_iovec (gdbpy_ref<> &&file_obj, ULONGEST file_size)
+ : m_file (std::move (file_obj)),
+ m_size (file_size)
+ {
+ }
+
+ ~py_iovec ();
+
+ file_ptr read (bfd *abfd, void *buffer, file_ptr nbytes,
+ file_ptr offset) override;
+
+ int stat (struct bfd *abfd, struct stat *sb) override
+ {
+ /* We only support the size here. */
+ memset (sb, 0, sizeof (*sb));
+ sb->st_size = m_size;
+ return 0;
+ }
+
+ gdbpy_ref<> m_file;
+ ULONGEST m_size;
+};
+
+py_iovec::~py_iovec ()
+{
+ /* Make sure the GIL is held before destruction. */
+ gdbpy_enter enter_py;
+
+ gdbpy_ref<> result = gdbpy_call_method (m_file.get (), "close");
+ if (result == nullptr)
+ gdbpy_print_stack ();
+
+ m_file.reset (nullptr);
+}
+
+file_ptr
+py_iovec::read (bfd *abfd, void *buffer, file_ptr nbytes, file_ptr offset)
+{
+ /* If this read will read all of the file, limit it to just the rest. */
+ if (offset + nbytes > m_size)
+ nbytes = m_size - offset;
+
+ /* If there are no more bytes left, we've reached EOF. */
+ if (nbytes == 0)
+ return 0;
+
+ gdbpy_enter enter_py;
+
+ char *data;
+ Py_ssize_t len;
+ gdbpy_ref<> bytes = gdbpy_call_method (m_file.get (), "read",
+ (gdb_py_ulongest) offset,
+ (gdb_py_ulongest) nbytes);
+ if (bytes == nullptr
+ || PyBytes_AsStringAndSize (bytes.get (), &data, &len) == -1)
+ {
+ gdbpy_print_stack ();
+ bfd_set_error (bfd_error_bad_value);
+ return -1;
+ }
+
+ memcpy (buffer, data, len);
+ return len;
+}
+
+gdb_bfd_ref_ptr
+open_bfd_from_python (gdbpy_ref<> &&file_obj)
+{
+ int has_attr = PyObject_HasAttrString (file_obj.get (), "read");
+ if (has_attr != 1)
+ {
+ /* This may return without the error set, but this scenario is
+ handled by the caller. */
+ return nullptr;
+ }
+
+ gdbpy_ref<> size_obj = gdbpy_call_method (file_obj.get (), "size");
+ if (size_obj == nullptr)
+ return nullptr;
+ gdb_py_ulongest size = gdb_py_long_as_ulongest (size_obj.get ());
+ if (PyErr_Occurred ())
+ return nullptr;
+
+ gdbpy_ref<> name_obj = gdbpy_call_method (file_obj.get (), "filename");
+ if (name_obj == nullptr)
+ return nullptr;
+ gdb::unique_xmalloc_ptr<char> filename
+ = python_string_to_host_string (name_obj.get ());
+ if (filename == nullptr)
+ return nullptr;
+
+ auto iovec = std::make_unique<py_iovec> (std::move (file_obj), size);
+
+ auto open = [&] (bfd *nbfd) -> py_iovec *
+ {
+ return iovec.release ();
+ };
+
+ gdb_bfd_ref_ptr result = gdb_bfd_openr_iovec (filename.get (),
+ gnutarget, open);
+ if (result == nullptr)
+ PyErr_Format (PyExc_OSError, "could not open %s: %s",
+ filename.get (), bfd_errmsg (bfd_get_error ()));
+ return result;
+}
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index 2e064107cbc..bf7eb795061 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -1416,6 +1416,8 @@ class gdbpy_memoizing_registry_storage
extern gdbpy_ref<> eval_python_command
(const char *command, int start_symbol, const char *filename = nullptr);
+extern gdb_bfd_ref_ptr open_bfd_from_python (gdbpy_ref<> &&file_obj);
+
/* The following four functions are refcount-safe wrappers around
Py_RETURN_{NONE,TRUE,FALSE,NOTIMPLEMENTED}. */
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC 3/6] Allow missing build-id when finding an objfile
2026-10-22 18:28 [PATCH RFC 0/6] Handle Android APK files Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 1/6] Add py-file.c Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 2/6] Add py-iovec.c Tom Tromey
@ 2026-10-22 18:28 ` Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 4/6] Allow missing objfile handler to return a file Tom Tromey
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Tom Tromey @ 2026-10-22 18:28 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Currently the "missing objfile" code requires a build-id.
However, in my situation, the build-id is not known. But, aside from
this small detail, the missing objfile code looked close to what I
wanted to implement.
This patch extends the API to allow the build-id to be missing. In
this case, None is passed to Python.
However, because this is an API change, MissingObjfileHandler is also
extended to record whether the build-id is required. And, by default
it is required, so that existing handlers won't be called in this new
situation.
---
gdb/python/lib/gdb/__init__.py | 17 +++++++++++++----
gdb/python/lib/gdb/missing_objfile.py | 4 ++++
gdb/python/python.c | 17 +++++++++++------
3 files changed, 28 insertions(+), 10 deletions(-)
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 1a4aefcd802..f16e0a66c5b 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -382,7 +382,8 @@ def _handle_missing_objfile(pspace, buildid, filename):
pspace: A gdb.Progspace for which the missing objfile handlers
should be run. This is the program space in which an
objfile was found to be missing.
- buildid: A string containing the build-id we're looking for.
+ buildid: A string containing the build-id we're looking for,
+ or None if the buildid is not known.
filename: The filename of the file GDB tried to find but
couldn't. This is not where the file should be
placed if found, in fact, this file might already
@@ -403,9 +404,17 @@ def _handle_missing_objfile(pspace, buildid, filename):
missing objfile.
"""
- return _handle_missing_files(
- pspace, "objfile", lambda h: h(pspace, buildid, filename)
- )
+ # Delay this as long as possible.
+ from gdb.missing_objfile import MissingObjfileHandler
+
+ def maybe_call_handler(handler):
+ if buildid is not None or (
+ isinstance(handler, MissingObjfileHandler) and not handler.buildid_required
+ ):
+ return handler(pspace, buildid, filename)
+ return None
+
+ return _handle_missing_files(pspace, "objfile", maybe_call_handler)
class ParameterPrefix:
diff --git a/gdb/python/lib/gdb/missing_objfile.py b/gdb/python/lib/gdb/missing_objfile.py
index 45661d0f415..fe45a418c6d 100644
--- a/gdb/python/lib/gdb/missing_objfile.py
+++ b/gdb/python/lib/gdb/missing_objfile.py
@@ -30,8 +30,12 @@ class MissingObjfileHandler(MissingFileHandler):
Attributes:
name: Read-only attribute, the name of this handler.
enabled: When true this handler is enabled.
+ buildid_required: When False, this handler can accept a
+ request with buildid=None.
"""
+ buildid_required = True
+
def __call__(self, buildid, filename):
"""Handle a missing objfile when GDB can knows the build-id.
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 5a9c876c197..06ad0876527 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -1950,7 +1950,6 @@ gdbpy_find_objfile_from_buildid (const struct extension_language_defn *extlang,
const char *missing_filename)
{
gdb_assert (pspace != nullptr);
- gdb_assert (build_id != nullptr);
gdb_assert (missing_filename != nullptr);
/* Early exit if Python is not initialised. */
@@ -1960,12 +1959,18 @@ gdbpy_find_objfile_from_buildid (const struct extension_language_defn *extlang,
gdbpy_enter enter_py;
/* Convert BUILD_ID into a Python object. */
- std::string hex_form = bin2hex (build_id->data, build_id->size);
- gdbpy_ref<> pyo_buildid = host_string_to_python_string (hex_form.c_str ());
- if (pyo_buildid == nullptr)
+ gdbpy_ref<> pyo_buildid;
+ if (build_id == nullptr)
+ pyo_buildid = gdbpy_ref<>::new_reference (Py_None);
+ else
{
- gdbpy_print_stack ();
- return {};
+ std::string hex_form = bin2hex (build_id->data, build_id->size);
+ pyo_buildid = host_string_to_python_string (hex_form.c_str ());
+ if (pyo_buildid == nullptr)
+ {
+ gdbpy_print_stack ();
+ return {};
+ }
}
/* Convert MISSING_FILENAME to a Python object. */
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC 4/6] Allow missing objfile handler to return a file
2026-10-22 18:28 [PATCH RFC 0/6] Handle Android APK files Tom Tromey
` (2 preceding siblings ...)
2026-10-22 18:28 ` [PATCH RFC 3/6] Allow missing build-id when finding an objfile Tom Tromey
@ 2026-10-22 18:28 ` Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 5/6] Call ext_lang_find_objfile_from_buildid from solib_bfd_fopen Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 6/6] Add gdb.handler.android module Tom Tromey
5 siblings, 0 replies; 7+ messages in thread
From: Tom Tromey @ 2026-10-22 18:28 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This patch changes the missing objfile handler code so that a handler
can return a "file".
To be pedantic, this returned object isn't a Python file-like object
but instead a small shim; see missing_objfile.BFDFile.
---
gdb/build-id.c | 4 +++-
gdb/extension.c | 2 +-
gdb/extension.h | 21 +++++++++++++++++++++
gdb/python/lib/gdb/__init__.py | 2 ++
gdb/python/lib/gdb/missing_objfile.py | 24 ++++++++++++++++++++++++
gdb/python/python.c | 28 ++++++++++++++++++----------
gdb/symfile-debug.c | 20 +++++++++++---------
7 files changed, 80 insertions(+), 21 deletions(-)
diff --git a/gdb/build-id.c b/gdb/build-id.c
index 123d87e519c..550880d0d9c 100644
--- a/gdb/build-id.c
+++ b/gdb/build-id.c
@@ -403,7 +403,9 @@ find_objfile_by_build_id (program_space *pspace,
ext_lang_missing_file_result ext_result
= ext_lang_find_objfile_from_buildid (pspace, build_id,
expected_filename);
- if (!ext_result.filename ().empty ())
+ if (ext_result.bfd () != nullptr)
+ return ext_result.bfd ();
+ else if (!ext_result.filename ().empty ())
{
/* The extension identified the file for us. */
abfd = gdb_bfd_open (ext_result.filename ().c_str (), gnutarget);
diff --git a/gdb/extension.c b/gdb/extension.c
index de3b3eb69a5..a531d87fd0d 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -1103,7 +1103,7 @@ ext_lang_find_objfile_from_buildid (program_space *pspace,
ext_lang_missing_file_result result
= extlang->ops->find_objfile_from_buildid (extlang, pspace, build_id,
filename);
- if (!result.filename ().empty () || result.try_again ())
+ if (result.valid ())
return result;
}
diff --git a/gdb/extension.h b/gdb/extension.h
index 360c788a41e..4437169215b 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -24,6 +24,7 @@
#include "gdbsupport/array-view.h"
#include <optional>
#include "gdbtypes.h"
+#include "gdb_bfd.h"
struct breakpoint;
struct command_line;
@@ -385,6 +386,11 @@ struct ext_lang_missing_file_result
: m_filename (std::move (filename))
{ /* Nothing. */ }
+ /* Use BFD for the missing debug info. */
+ ext_lang_missing_file_result (gdb_bfd_ref_ptr &&abfd)
+ : m_bfd (std::move (abfd))
+ { /* Nothing. */ }
+
/* The filename where GDB can find the missing debuginfo. This is empty
if the extension didn't suggest a file that can be used. */
const std::string &
@@ -393,6 +399,13 @@ struct ext_lang_missing_file_result
return m_filename;
}
+ /* The BFD where GDB can find the missing debuginfo. This is empty
+ if the extension didn't open a file that can be used. */
+ const gdb_bfd_ref_ptr &bfd () const
+ {
+ return m_bfd;
+ }
+
/* Returns true if GDB should look again for the debug information. */
const bool
try_again () const
@@ -400,10 +413,18 @@ struct ext_lang_missing_file_result
return m_try_again;
}
+ bool valid () const
+ {
+ return m_try_again || !m_filename.empty () || m_bfd != nullptr;
+ }
+
private:
/* The filename where the missing debuginfo can now be found. */
std::string m_filename;
+ /* The BFD where the missing debuginfo can now be found. */
+ gdb_bfd_ref_ptr m_bfd;
+
/* When true GDB will search again for the debuginfo using its standard
techniques. When false GDB will not search again. */
bool m_try_again = false;
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index f16e0a66c5b..bc47585378c 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -402,6 +402,8 @@ def _handle_missing_objfile(pspace, buildid, filename):
installed there then GDB will not find it.
A string: This is the filename of a file containing the
missing objfile.
+ A BFD: A wrapper for a file implementing the BFD protocol.
+ See gdb.missing_objfile.BFDFile.
"""
# Delay this as long as possible.
diff --git a/gdb/python/lib/gdb/missing_objfile.py b/gdb/python/lib/gdb/missing_objfile.py
index fe45a418c6d..f19f6395d4d 100644
--- a/gdb/python/lib/gdb/missing_objfile.py
+++ b/gdb/python/lib/gdb/missing_objfile.py
@@ -17,6 +17,8 @@
MissingObjfileHandler base class, and register_handler function.
"""
+import os
+
import gdb
from gdb.missing_files import MissingFileHandler
@@ -66,6 +68,28 @@ class MissingObjfileHandler(MissingFileHandler):
raise NotImplementedError("MissingObjfileHandler.__call__()")
+class BFDFile:
+ def __init__(self, fd, filename):
+ assert fd.seekable()
+ self._fd = fd
+ self._filename = filename
+ fd.seek(0, os.SEEK_END)
+ self._size = fd.tell()
+
+ def filename(self):
+ return self._filename
+
+ def size(self):
+ return self._size
+
+ def read(self, offset, size):
+ self._fd.seek(offset)
+ return self._fd.read(size)
+
+ def close(self):
+ self._fd.close()
+
+
def register_handler(locus, handler, replace=False):
"""See gdb.missing_files.register_handler."""
gdb.missing_files.register_handler("objfile", locus, handler, replace)
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 06ad0876527..3f7fdc6b186 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -2022,24 +2022,32 @@ gdbpy_find_objfile_from_buildid (const struct extension_language_defn *extlang,
return ext_lang_missing_file_result (try_again);
}
- if (!gdbpy_is_string (pyo_execute_ret.get ()))
+ if (gdbpy_is_string (pyo_execute_ret.get ()))
{
- PyErr_SetString (PyExc_ValueError,
- "return value from _find_objfile_by_buildid should "
- "be None, a bool, or a str");
- gdbpy_print_stack ();
- return {};
+ gdb::unique_xmalloc_ptr<char> filename
+ = python_string_to_host_string (pyo_execute_ret.get ());
+ if (filename == nullptr)
+ {
+ gdbpy_print_stack ();
+ return {};
+ }
+
+ return ext_lang_missing_file_result (std::string (filename.get ()));
}
- gdb::unique_xmalloc_ptr<char> filename
- = python_string_to_host_string (pyo_execute_ret.get ());
- if (filename == nullptr)
+ gdb_bfd_ref_ptr the_bfd = open_bfd_from_python (std::move (pyo_execute_ret));
+ if (the_bfd == nullptr)
{
+ if (!PyErr_Occurred ())
+ PyErr_SetString (PyExc_ValueError,
+ "return value from _find_objfile_by_buildid should "
+ "be None, a bool, a str, or a "
+ "gdb.missing_objfile.BFDFile-like object");
gdbpy_print_stack ();
return {};
}
- return ext_lang_missing_file_result (std::string (filename.get ()));
+ return ext_lang_missing_file_result (std::move (the_bfd));
}
/* Compute the list of active python type printers and store them in
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 2432a709ddf..61914c09a94 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -659,25 +659,27 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
ext_lang_missing_file_result ext_result
= ext_lang_handle_missing_debuginfo (this);
- if (!ext_result.filename ().empty ())
+ if (ext_result.bfd () != nullptr)
+ debug_bfd = ext_result.bfd ();
+ else if (!ext_result.filename ().empty ())
{
/* Extension found a suitable debug file for us. */
debug_bfd
= symfile_bfd_open_no_error (ext_result.filename ().c_str ());
-
- if (debug_bfd != nullptr)
- {
- symbol_file_add_separate (debug_bfd,
- ext_result.filename ().c_str (),
- symfile_flags, this);
- has_dwarf2 = true;
- }
}
else if (ext_result.try_again ())
{
max_attempts = 2;
continue;
}
+
+ if (debug_bfd != nullptr)
+ {
+ symbol_file_add_separate (debug_bfd,
+ bfd_get_filename (debug_bfd.get ()),
+ symfile_flags, this);
+ has_dwarf2 = true;
+ }
}
/* If we still have not got a separate debug symbol file, then
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC 5/6] Call ext_lang_find_objfile_from_buildid from solib_bfd_fopen
2026-10-22 18:28 [PATCH RFC 0/6] Handle Android APK files Tom Tromey
` (3 preceding siblings ...)
2026-10-22 18:28 ` [PATCH RFC 4/6] Allow missing objfile handler to return a file Tom Tromey
@ 2026-10-22 18:28 ` Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 6/6] Add gdb.handler.android module Tom Tromey
5 siblings, 0 replies; 7+ messages in thread
From: Tom Tromey @ 2026-10-22 18:28 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
Change solib_bfd_fopen to call ext_lang_find_objfile_from_buildid.
This way, if the solib is not found directly, Python is given a chance
to find the file.
---
gdb/solib.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/gdb/solib.c b/gdb/solib.c
index 7ea3084bf48..96f2597d120 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -386,7 +386,23 @@ solib_find (const char *in_pathname, int *fd)
gdb_bfd_ref_ptr
solib_bfd_fopen (const char *pathname, int fd)
{
- gdb_bfd_ref_ptr abfd (gdb_bfd_open (pathname, gnutarget, fd));
+ gdb_bfd_ref_ptr abfd = gdb_bfd_open (pathname, gnutarget, fd);
+ if (abfd != nullptr)
+ return abfd;
+
+ if (fd == -1)
+ {
+ if (is_target_filename (pathname))
+ pathname += strlen (TARGET_SYSROOT_PREFIX);
+
+ ext_lang_missing_file_result ext_result
+ = ext_lang_find_objfile_from_buildid (current_program_space, nullptr,
+ pathname);
+ if (ext_result.bfd () != nullptr)
+ abfd = ext_result.bfd ();
+ else if (!ext_result.filename ().empty ())
+ abfd = gdb_bfd_open (ext_result.filename ().c_str (), gnutarget);
+ }
if (abfd == NULL)
error (_("Could not open `%ps' as an executable file: %s"),
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH RFC 6/6] Add gdb.handler.android module
2026-10-22 18:28 [PATCH RFC 0/6] Handle Android APK files Tom Tromey
` (4 preceding siblings ...)
2026-10-22 18:28 ` [PATCH RFC 5/6] Call ext_lang_find_objfile_from_buildid from solib_bfd_fopen Tom Tromey
@ 2026-10-22 18:28 ` Tom Tromey
5 siblings, 0 replies; 7+ messages in thread
From: Tom Tromey @ 2026-10-22 18:28 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
This adds gdb.handler.android and arranges for it to be automatically
loaded. This new module provides a missing objfile handler that knows
how to extract a .so from an Android APK file.
---
gdb/data-directory/Makefile.in | 2 ++
gdb/python/lib/gdb/__init__.py | 2 +-
gdb/python/lib/gdb/handler/__init__.py | 14 +++++++++++
gdb/python/lib/gdb/handler/android.py | 43 ++++++++++++++++++++++++++++++++++
4 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 7257da9cd11..3c272efb982 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -124,6 +124,8 @@ PYTHON_FILE_LIST = \
gdb/function/as_string.py \
gdb/function/caller_is.py \
gdb/function/strfns.py \
+ gdb/handler/__init__.py \
+ gdb/handler/android.py \
gdb/printer/__init__.py
@HAVE_PYTHON_TRUE@PYTHON_FILES = $(PYTHON_FILE_LIST)
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index bc47585378c..2f2bbb98569 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -152,7 +152,7 @@ PYTHONDIR = os.path.dirname(os.path.dirname(__file__))
# Packages to auto-load.
-packages = ["function", "command", "printer"]
+packages = ["function", "command", "printer", "handler"]
# pkgutil.iter_modules is not available prior to Python 2.6. Instead,
# manually iterate the list, collating the Python files in each module
diff --git a/gdb/python/lib/gdb/handler/__init__.py b/gdb/python/lib/gdb/handler/__init__.py
new file mode 100644
index 00000000000..d28abe3fc3f
--- /dev/null
+++ b/gdb/python/lib/gdb/handler/__init__.py
@@ -0,0 +1,14 @@
+# Copyright (C) 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/>.
diff --git a/gdb/python/lib/gdb/handler/android.py b/gdb/python/lib/gdb/handler/android.py
new file mode 100644
index 00000000000..2febf851dd5
--- /dev/null
+++ b/gdb/python/lib/gdb/handler/android.py
@@ -0,0 +1,43 @@
+# Copyright (C) 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/>.
+
+# Missing objfile handler for Android.
+
+import gdb
+import gdb.missing_objfile
+
+
+class _AndroidHandler(gdb.missing_objfile.MissingObjfileHandler):
+ buildid_required = False
+
+ def __call__(self, pspace, buildid, filename):
+ elts = filename.split(".apk!/", maxsplit=1)
+ if len(elts) != 2:
+ return None
+ initial, member = elts
+ apk_fd = gdb.TargetFile(initial + ".apk")
+
+ import zipfile
+
+ apk_file = zipfile.ZipFile(apk_fd)
+ info = apk_file.getinfo(member)
+ member_fd = apk_file.open(info)
+
+ # Use the full filename here so the user can see what is going
+ # on.
+ return gdb.missing_objfile.BFDFile(member_fd, filename)
+
+
+gdb.missing_objfile.register_handler(None, _AndroidHandler("Android"))
--
2.56.0
^ permalink raw reply [flat|nested] 7+ messages in thread