* [RFC PATCH v2] gdb: Add drgn-why-sleeping command
@ 2026-09-29 19:00 Alexandra Hájková
0 siblings, 0 replies; only message in thread
From: Alexandra Hájková @ 2026-09-29 19:00 UTC (permalink / raw)
To: gdb-patches; +Cc: ahajkova
When debugging a multithreaded program, we can use `info threads`
to inspect the thread in interruptible sleep. But we'll only
see the user space frame and not where the thread is waiting inside
the kernel or its kernel call stack. Calling drgn provides this
information.
Add the drgn-why-sleeping Python command, to make it possible for
GDB to call drgn. Drgn reads from /proc/kcore and using the selected
thread's ID finds its kernel task and its kernel stack.
Without arguments, the command prints the kernel stack. An optional
frame name selects that frame's local variables.
Use Stephen Brennan's open_via_sudo helper when permission to open
/proc/kcore is denied. The helper opens the file in a transient
privileged process and passes its descriptor back over a Unix socket.
GDB itself remains unprivileged, with access to kernel memory through
that descriptor.
---
V2:
- rename the command to drg-why-sleeping
- remove the reproducer from the patch
- use Stephen Brennan's sudo helper to open /proc/kcore
- import drgn only when the command is invoked
- only accept native Linux targets
gdb/data-directory/Makefile.in | 1 +
.../lib/gdb/command/drgn_why_sleeping.py | 158 ++++++++++++++++++
2 files changed, 159 insertions(+)
create mode 100644 gdb/python/lib/gdb/command/drgn_why_sleeping.py
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 7257da9cd11..c368910fdf4 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -88,6 +88,7 @@ PYTHON_FILE_LIST = \
gdb/unwinder.py \
gdb/xmethod.py \
gdb/command/__init__.py \
+ gdb/command/drgn_why_sleeping.py \
gdb/command/explore.py \
gdb/command/frame_filters.py \
gdb/command/missing_files.py \
diff --git a/gdb/python/lib/gdb/command/drgn_why_sleeping.py b/gdb/python/lib/gdb/command/drgn_why_sleeping.py
new file mode 100644
index 00000000000..2d03dcab3ee
--- /dev/null
+++ b/gdb/python/lib/gdb/command/drgn_why_sleeping.py
@@ -0,0 +1,158 @@
+# GDB 'drgn-why-sleeping' command.
+# 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/>.
+
+"""Implementation of the GDB 'drgn-why-sleeping' command using the GDB Python API."""
+
+import os
+import sys
+
+import gdb
+
+class DrgnWhySleeping(gdb.Command):
+ """Show a thread's kernel stack or selected kernel-frame variables.
+
+ Usage: drgn-why-sleeping [FRAME [VARIABLE.FIELD ...]]
+
+ Requires drgn and debugging information for the running kernel.
+ May prompt for sudo permission to open /proc/kcore.
+
+ Enable non-stop mode before starting or attaching to the inferior.
+ Resume the selected thread before inspecting its sleeping kernel stack:
+ In non-stop mode, "continue &" resumes the selected thread in the
+ background and immediately returns to the GDB prompt. This lets the
+ command inspect the thread while it sleeps inside the system call,
+ rather than while it is stopped by GDB.
+
+ (gdb) set non-stop on
+ (gdb) attach PID
+ (gdb) info threads
+ (gdb) thread THREAD_NUM
+ (gdb) bt
+ (gdb) continue &
+ (gdb) drgn-why-sleeping
+
+ A thread stopped by GDB may show ptrace_stop instead of its original
+ blocking location. Variables may be unavailable due to optimization.
+
+ (gdb) drgn-why-sleeping FRAME
+ (gdb) drgn-why-sleeping FRAME FIELD ...
+
+ Without arguments, print the selected thread's kernel stack.
+ With FRAME, print all available local variable names and values.
+ With FIELD arguments, print only the requested variables or
+ dot-separated member paths.
+
+ Example:
+ (gdb) drgn-why-sleeping vfs_read file.f_op count file.f_flags
+
+ This prints the file operations, requested read size, and file flags.
+ Variables unavailable due to optimization are reported individually.
+ """
+
+ def __init__(self):
+ super(DrgnWhySleeping, self).__init__(
+ name="drgn-why-sleeping", command_class=gdb.COMMAND_STATUS, prefix=False
+ )
+
+ def invoke(self, arg_str, from_tty):
+ try:
+ import drgn
+ from drgn.helpers.linux.pid import find_task
+ from drgn.helpers.linux.sched import task_state_to_char
+ except ImportError as error:
+ raise gdb.GdbError(f"This command requires drgn: {error}") from error
+ if not sys.platform.startswith("linux"):
+ raise gdb.GdbError("This command requires a Linux host.")
+ try:
+ from drgn.internal.sudohelper import open_via_sudo
+ except ImportError as error:
+ raise gdb.GdbError(
+ "This command requires a drgn version providing "
+ "drgn.internal.sudohelper.open_via_sudo."
+ ) from error
+ args = gdb.string_to_argv(arg_str)
+
+ thread = gdb.selected_thread()
+ if thread is None:
+ raise gdb.GdbError("Select a live Linux thread first.")
+
+ connection = gdb.selected_inferior().connection
+ if connection is None or connection.type != "native":
+ raise gdb.GdbError("This command supports only local native debugging.")
+ if thread.is_exited():
+ raise gdb.GdbError("The selected thread has exited.")
+
+ tid = thread.ptid[1]
+ if tid <= 0:
+ raise gdb.GdbError(
+ "The selected thread has no valid Linux LWP ID."
+ )
+ try:
+ prog = drgn.Program()
+ try:
+ prog.set_kernel()
+ except PermissionError:
+ fd = open_via_sudo("/proc/kcore", os.O_RDONLY)
+ prog.set_core_dump(fd)
+
+ prog.load_debug_info(default=True)
+ except Exception as e:
+ raise gdb.GdbError(f"Could not initialize drgn: {e}") from e
+ task = find_task(prog, tid)
+ if not task:
+ raise gdb.GdbError(
+ f"Linux task with LWP ID {tid} was not found; "
+ "the thread may have exited."
+ )
+
+ task_state = task_state_to_char(task)
+ stack = prog.stack_trace(task)
+ print(f"Task state: {task_state}")
+ if not args:
+ for frame in stack:
+ print(frame.name)
+ return
+
+ frame_name = args[0]
+ for frame in stack:
+ if frame.name == frame_name:
+ break
+ else:
+ print(f"Frame not found: {frame_name}")
+ return
+
+ print(frame.name)
+ fields = args[1:] or frame.locals()
+
+ for field in fields:
+ try:
+ parts = field.split(".")
+ value = frame[parts[0]]
+ for member in parts[1:]:
+ value = value.member_(member)
+ print(f" {field} = {value.format_(dereference=False)}")
+ except (
+ KeyError,
+ AttributeError,
+ LookupError,
+ TypeError,
+ drgn.FaultError,
+ drgn.ObjectAbsentError,
+ ) as error:
+ print(f" {field}: unavailable ({error})")
+
+
+DrgnWhySleeping()
--
2.55.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-09-29 19:05 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-29 19:00 [RFC PATCH v2] gdb: Add drgn-why-sleeping command Alexandra Hájková
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox