From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id l7UGAu0LvGrdZxkAWB0awg (envelope-from ) for ; Tue, 29 Sep 2026 15:05:17 -0400 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=WRrjmavn; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id D154C1E06B; Tue, 29 Sep 2026 15:05:16 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [IPv6:2620:52:6:3111::32]) (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 2DC061E01F for ; Tue, 29 Sep 2026 15:05:15 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 0381F4B9DB4F for ; Tue, 29 Sep 2026 19:05:14 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0381F4B9DB4F Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=WRrjmavn 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 9DC964BA79BE for ; Tue, 29 Sep 2026 19:03:56 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 9DC964BA79BE 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 9DC964BA79BE Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1790708636; cv=none; b=bQ0tU1ig2Im7A/5pSU7pCmLH6FtbUda89l5aqbDjjy2C/V/h0uyg+5aJgJ/CSkYefre3ViAM436+lJeu/DiikVKa2V3P7HSoJgIuXaYGH1WJNp7zkvr1c/wOLGuh+nEtDdGNG9yPpwHiao0MzAAqfPg7585dkbT8PyBcEB6FCD4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1790708636; c=relaxed/simple; bh=SoOjbt4psmWR1C0NV0hLyFaYDRFn4qzmAEsWbtti9a8=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=fIwc74yYho2AH+YWhj1ITmnSTZf/vFeyAJCvJCpG72Sbh09a7kJ2idjP+o2siGTUcnvVYpFUWhFUjT3mpWFIy0orhMh9SB6jW4ZM+/C4I+6PbBH0Qppi0q8swRtSfBq7oDtt/8BtgAXK5EPd8ehPvzHthxktBb2/dbK/1NUAoek= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=WRrjmavn DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9DC964BA79BE DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1790708636; 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; bh=nGV+dhmIrRGRs1I1rjQnhfOvpAwEQVsbKjG+F6IywUg=; b=WRrjmavnsSAA9nfsDaZ3mxQARydGPyTbJC9dcIzdVAgcjyn0f0l8bZDREg7OfDS/s1yE2t bcTQ757WmXsP0eu/CIOgDANjPKDtqQJGmlpcrxWLn3uCUrWI8bYdN8Lc1zRnnWEeyFOeKM 1paDU9HmfuXGTKIge8o7pZRu1jTwiJg= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-551-OE6fpE-tMcmiSyH9GrcNFA-1; Tue, 29 Sep 2026 15:03:53 -0400 X-MC-Unique: OE6fpE-tMcmiSyH9GrcNFA-1 X-Mimecast-MFC-AGG-ID: OE6fpE-tMcmiSyH9GrcNFA_1790708632 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 45FF21840C68 for ; Tue, 29 Sep 2026 19:03:52 +0000 (UTC) Received: from ahajkova-thinkpadt14gen4.rmtcz.csb (headnet05.pony-001.prod.iad2.dc.redhat.com [10.2.32.117]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 47F2230001BB; Tue, 29 Sep 2026 19:03:50 +0000 (UTC) From: =?UTF-8?q?Alexandra=20H=C3=A1jkov=C3=A1?= To: gdb-patches@sourceware.org Cc: ahajkova@redhat.com Subject: [RFC PATCH v2] gdb: Add drgn-why-sleeping command Date: Tue, 29 Sep 2026 21:00:06 +0200 Message-ID: <20260929190349.312877-1-ahajkova@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: ZGybQpVqDD0_59buCxvspav3FEDq1nY9Fe33Mo1-H08_1790708632 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 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 . + +"""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