* [PATCH RFC 0/6] Handle Android APK files
@ 2026-10-22 18:28 Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 1/6] Add py-file.c Tom Tromey
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Tom Tromey @ 2026-10-22 18:28 UTC (permalink / raw)
To: gdb-patches; +Cc: Tom Tromey
While working on an Android port, we noticed that the Android
gdbserver could report a shared library coming from an APK, like
/some/path/file.apk!/inner/path/libwhatever.so
This "!/" syntax presumably came from Java as the same thing was
typical there for referring to files inside of jars.
This series adds this capability. I considered a few ways of doing
this, including writing a zip (an APK, like a jar, is really just a
zip file) reader. In the end I thought it was better to rely on
Python, first because the zip reader there is already debugged, and
secondly because it doesn't involve any new dependencies (Python's zip
reader is part of the standard library).
I'm sending this first as an RFC. Here I chose to extend the missing
objfile handler code to allow the scenario where a missing objfile
does not have a build-id. This seemed like a straightforward
extension to me, but I thought it would be best to run this past
everyone before writing up the documentation and tests.
Another question is what to do with the Android handler. I've
unconditionally enabled it here, because I don't think there's
currently a way for gdb to know it is targeting Android; but also
because TBH this seems reasaonbly safe, in the sense that nobody is
really going to name a file "/usr/lib/haha.apk!/youlose.so". Still,
this is worth some discussion.
Finally, I wrote most of this before starting on the Python safety
API. So, while this series uses the old-style approach, if this seems
generally acceptable, I'll update it after writing the tests.
Signed-off-by: Tom Tromey <tromey@adacore.com>
---
Tom Tromey (6):
Add py-file.c
Add py-iovec.c
Allow missing build-id when finding an objfile
Allow missing objfile handler to return a file
Call ext_lang_find_objfile_from_buildid from solib_bfd_fopen
Add gdb.handler.android module
gdb/Makefile.in | 2 +
gdb/build-id.c | 4 +-
gdb/data-directory/Makefile.in | 2 +
gdb/extension.c | 2 +-
gdb/extension.h | 21 +++
gdb/python/lib/gdb/__init__.py | 21 ++-
gdb/python/lib/gdb/handler/__init__.py | 14 ++
gdb/python/lib/gdb/handler/android.py | 43 +++++
gdb/python/lib/gdb/missing_objfile.py | 28 ++++
gdb/python/py-file.c | 298 +++++++++++++++++++++++++++++++++
gdb/python/py-iovec.c | 130 ++++++++++++++
gdb/python/python-internal.h | 2 +
gdb/python/python.c | 45 +++--
gdb/solib.c | 18 +-
gdb/symfile-debug.c | 20 ++-
15 files changed, 617 insertions(+), 33 deletions(-)
---
base-commit: 638f6237867858a849f3d6ffa1a1f8d6cd669636
change-id: 20260521-android-apk-file-2fe1721342fc
Best regards,
--
Tom Tromey <tromey@adacore.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [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
end of thread, other threads:[~2026-10-22 18:30 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH RFC 3/6] Allow missing build-id when finding an objfile Tom Tromey
2026-10-22 18:28 ` [PATCH RFC 4/6] Allow missing objfile handler to return a file 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox