Add drg_why_sleeping.py

Message ID 20260831205514.1987454-2-ahajkova@redhat.com
State New
Headers
Series Add drg_why_sleeping.py |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm success Test passed

Commit Message

Alexandra Hájková Aug. 31, 2026, 8:54 p.m. UTC
  ---
 .../lib/gdb/command/drgn_why_sleeping.py      | 72 ++++++++++++++++++
 sleep_test.c                                  | 76 +++++++++++++++++++
 2 files changed, 148 insertions(+)
 create mode 100644 gdb/python/lib/gdb/command/drgn_why_sleeping.py
 create mode 100644 sleep_test.c
  

Comments

Tom Tromey Sept. 1, 2026, 4:52 p.m. UTC | #1
>>>>> "Alexandra" == Alexandra Hájková <ahajkova@redhat.com> writes:

Hi.  Thanks for the patch.

I'm not sure this is really something gdb ought to ship.

Alexandra>  sleep_test.c                                  | 76 +++++++++++++++++++

This file shouldn't be here.

Alexandra> +           (gdb) source ~/binutils-gdb/gdb/python/lib/gdb/command/drgn_why_sleeping.py

Normally commands are auto-installed.

Alexandra> +           (gdb) drgn_why_sleeping

This isn't a very gdb-ish name.  At the very least gdb usually uses "-"
as a separator; I always thought maybe it inherited a Lispy style from
the RMS days.  But maybe a prefix command would be better anyhow.

Alexandra> +            raise gdb.GdbError(
Alexandra> +                (
Alexandra> +                    "Can't find selected thread. "
Alexandra> +                )
Alexandra> +            )

Extra parens?

Alexandra> +                    "Can't read /proc/kcore, %s. Try running GDB as root. " % str(e)

We can't ever recommend running gdb as root.

I don't normally like to make drastic pronouncements like that, but gdb
is nowhere near hardened enough for this kind of thing.  In fact it's
more accurate to say that gdb doesn't even try, it's ridiculously
exposed.

Tom
  
Alexandra Hájková Sept. 2, 2026, 3:29 p.m. UTC | #2
On Tue, Sep 1, 2026 at 6:54 PM Tom Tromey <tom@tromey.com> wrote:

> >>>>> "Alexandra" == Alexandra Hájková <ahajkova@redhat.com> writes:
>
> Hi.  Thanks for the patch.
>
> I'm not sure this is really something gdb ought to ship.
>
> Alexandra>  sleep_test.c                                  | 76
> +++++++++++++++++++
>
> This file shouldn't be here.
>
> Alexandra> +           (gdb) source
> ~/binutils-gdb/gdb/python/lib/gdb/command/drgn_why_sleeping.py
>
> Normally commands are auto-installed.
>
Yeah, sorry, I originally didn't specify --data-directory so it ended up not
sourced for me.

>
> Alexandra> +           (gdb) drgn_why_sleeping
>
> This isn't a very gdb-ish name.  At the very least gdb usually uses "-"
> as a separator; I always thought maybe it inherited a Lispy style from
> the RMS days.  But maybe a prefix command would be better anyhow.
>

I'm happy to change the name, it was just the first one that came to mind.

>
>
> Extra parens?
>
> Alexandra> +                    "Can't read /proc/kcore, %s. Try running
> GDB as root. " % str(e)
>
> We can't ever recommend running gdb as root.
>
So,  you recommend just saying we can't read kcore without saying
anything about running GDB as root making things work. Sure, let's
make this bit secret.


> I don't normally like to make drastic pronouncements like that, but gdb
> is nowhere near hardened enough for this kind of thing.  In fact it's
> more accurate to say that gdb doesn't even try, it's ridiculously
> exposed.
>

So,  you recommend just saying we can't read kcore without saying
anything about running GDB as root making things work. Sure, let's
make this bit secret.




>
> Tom
>
>
  

Patch

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..ea11a73faae
--- /dev/null
+++ b/gdb/python/lib/gdb/command/drgn_why_sleeping.py
@@ -0,0 +1,72 @@ 
+# 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 gdb
+import drgn
+from drgn.helpers.linux.pid import find_task
+from drgn.helpers.linux.sched import task_state_to_char
+
+class DrgnWhySleeping(gdb.Command):
+    """Look at kernel stack and show what is the thread blocked at.
+
+    Usage: Run GDB as root to be able to read from /proc/kcore.
+           The command needs debugging symbols for the running kernel.
+           (gdb) source ~/binutils-gdb/gdb/python/lib/gdb/command/drgn_why_sleeping.py
+           (gdb) info threads
+           (gdb) thread n
+           (gdb) drgn_why_sleeping
+    """
+
+    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):
+        if gdb.selected_thread() is None:
+            raise gdb.GdbError(
+                (
+                    "Can't find selected thread. "
+                )
+            )
+        tid = gdb.selected_thread().ptid[1]
+
+        try:
+            prog = drgn.program_from_kernel()
+        except Exception as e:
+            raise gdb.GdbError(
+                    "Can't read /proc/kcore, %s. Try running GDB as root. " % str(e)
+                    )
+        task = find_task(prog, tid)
+        task_state = task_state_to_char(task)
+        stack = prog.stack_trace(task)
+        print(f"Task state: {task_state}")
+        if arg_str:
+            for frame in stack:
+                if frame.name == arg_str:
+                    print(frame.name)
+                    for var in frame.locals():
+                        print(f"  {var} = {frame[var]}")
+                    break
+            else:
+                print("Frame not found: %s" % arg_str)
+        else:
+            for frame in stack:
+                print(frame.name)
+
+DrgnWhySleeping()
diff --git a/sleep_test.c b/sleep_test.c
new file mode 100644
index 00000000000..c324e55944c
--- /dev/null
+++ b/sleep_test.c
@@ -0,0 +1,76 @@ 
+/*
+ * sleep_test.c - reproducer for testing drgn_why_sleeping GDB command
+ *
+ * Creates three threads, each blocked in a different kind of interruptible
+ * sleep, then prints the PID and waits so GDB can attach.
+ *
+ * Compile: gcc -g -o sleep_test sleep_test.c -lpthread
+ */
+
+#include <stdio.h>
+#include <pthread.h>
+#include <unistd.h>
+
+static pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  cond    = PTHREAD_COND_INITIALIZER;
+static int pipe_fds[2];
+
+/* Blocked trying to acquire mutex_a, which main holds. */
+static void *mutex_waiter(void *arg)
+{
+    printf("[thread 1] blocking on mutex_a...\n");
+    fflush(stdout);
+    pthread_mutex_lock(&mutex_a);
+    /* never reached during the test */
+    pthread_mutex_unlock(&mutex_a);
+    return NULL;
+}
+
+/* Blocked in pthread_cond_wait; nobody will ever signal cond. */
+static void *cond_waiter(void *arg)
+{
+    pthread_mutex_lock(&mutex_b);
+    printf("[thread 2] blocking on cond...\n");
+    fflush(stdout);
+    pthread_cond_wait(&cond, &mutex_b);
+    /* never reached during the test */
+    pthread_mutex_unlock(&mutex_b);
+    return NULL;
+}
+
+/* Blocked in read(); nobody will write to the pipe. */
+static void *pipe_reader(void *arg)
+{
+    char buf[16];
+    printf("[thread 3] blocking on pipe read...\n");
+    fflush(stdout);
+    read(pipe_fds[0], buf, sizeof(buf));
+    /* never reached during the test */
+    return NULL;
+}
+
+int main(void)
+{
+    pthread_t t1, t2, t3;
+
+    pipe(pipe_fds);
+
+    /* Hold mutex_a before creating thread 1 so it blocks immediately. */
+    pthread_mutex_lock(&mutex_a);
+
+    pthread_create(&t1, NULL, mutex_waiter, NULL);
+    pthread_create(&t2, NULL, cond_waiter,  NULL);
+    pthread_create(&t3, NULL, pipe_reader,  NULL);
+
+    /* Give threads time to reach their blocking calls. */
+    sleep(1);
+
+    printf("\n[main] PID %d ready — attach GDB now\n", getpid());
+    fflush(stdout);
+
+    /* Block here so GDB has time to attach. */
+    pause();
+
+    return 0;
+}