Bug 30771 - GDB needs a silent thread-selecting command akin to "select-frame"
Summary: GDB needs a silent thread-selecting command akin to "select-frame"
Status: RESOLVED FIXED
Alias: None
Product: gdb
Classification: Unclassified
Component: cli (show other bugs)
Version: 8.2.1
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-08-16 15:41 UTC by Moshe Rubin
Modified: 2024-05-08 18:47 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed: 2023-08-16 00:00:00
Project(s) to access:
ssh public key:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Moshe Rubin 2023-08-16 15:41:53 UTC
Problem Description
===================
GDB enables users to select threads and frames from the gdb command line prompt using the "thread n" and "frame n" commands.  When used, these commands display output to the display, e.g.:

<snippet>
(gdb) thread 2
[Switching to thread 2 (Thread 0x155538885700 (LWP 8875))]
#0  0x0000155552be3aa8 in hack_digit (p=0x155538882480) at printf_fp.c:175
175     printf_fp.c: No such file or directory.
(gdb)

(gdb) frame 1
#1  __GI___printf_fp_l (...) at printf_fp.c:962
962     in printf_fp.c
(gdb)
</snippet>

GDB also supports the "up" and "down" commands for selecting a frame relative to the currently selected one.  Like "frame n", they also display output to the screen.

Thankfully, gdb has a silent version of "frame n", i.e. "select-frame n":

<snippet>
#0  0x0000155552be3aa8 in hack_digit (p=0x155538882480) at printf_fp.c:175
175     in printf_fp.c
(gdb) select-frame 1
(gdb)
</snippet>

The value of this silent selection command cannot be overstated.  It enables complex custom commands to make many frame selections without producing display "noise" as an artifact.

The problem is that there is no analog for silently selecting threads.  No matter how one tries, it is impossible to select a thread without getting unwanted output on the display.  Playing with logging commands, set commands, and the like do not help.  Here is an example of a custom command run that executes "thread n" commands internally, producing a messy display output:

<snippet>
(gdb) custom_command
[Switching to thread 3 (Thread 0x155555354b00 (LWP 16327))]
#0  0x0000155554019ad3 in ...
[Switching to thread 4 (Thread 0x15553084e700 (LWP 13823))]
#0  std::_Hashtable<std::pair< ... (5 lines of output)
[Switching to thread 5 (Thread 0x155532695700 (LWP 8874))]
#0  0x0000155552ca5947 in ...
[Switching to thread 6 (Thread 0x155551a19700 (LWP 16826))]
#0  0x0000155554019ad3 in ...
[Switching to thread 1 (Thread 0x155538844700 (LWP 8876))]
#0  __GI_raise (sig=sig@entry=6) at ...
[Switching to thread 2 (Thread 0x155538885700 (LWP 8875))]
#0  0x0000155552be3aa8 in ...
</snippet>

Request
=======
Can a silent gdb-prompt thread-selecting command (e.g., 'select-thread') be added to the already existing 'select-frame'?
Comment 1 Tom Tromey 2023-08-16 17:37:21 UTC
Adding a command would be fine IMO.
However I think it can already be done like:

  pipe thread 5 | cat > /dev/null

Though... are you really on gdb 8.2?
"pipe" wasn't added until later.
Comment 2 Moshe Rubin 2023-08-16 18:15:01 UTC
Thanks or your prompt reply <g>.  I see that "pipe" was introduced in gdb 9.1.  My company currently uses version 8.2.1 across the organization.  You can imagine that IT's changing this will take time.

Your suggestion to use pipe is an interesting one and would answer the immediate need.  I look forward to my organization upgrading to GDB 9.1.  I do think, however, that having "select-thread" as a parallel to "select-frame" is logical and makes the Python code clearer and better understood.
Comment 3 Tom Tromey 2023-08-16 22:27:06 UTC
(In reply to Moshe Rubin from comment #2)

> immediate need.  I look forward to my organization upgrading to GDB 9.1.  I
> do think, however, that having "select-thread" as a parallel to
> "select-frame" is logical and makes the Python code clearer and better
> understood.

Python can use InferiorThread.switch or gdb.execute("thread ...", to_string=True)
Comment 4 Moshe Rubin 2023-08-17 07:20:04 UTC
Hi Tom,

Your intriguing suggestion about using "pipe" (albeit in later versions of gdb) got me thinking.

Checking two versions of gdb 8.2 my organization uses:

    GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
    GNU gdb (GDB) 8.2.1

it turns out that they support an earlier version of "pipe":

    (gdb) help pipe
    Pipe gdb output via a shell command
      (gdb) pipe cmd1 cmd2
          is equivalent to
      $ cmd1 | cmd2

    Examples:
    pipe "p Vehicles::g_cliquer._members" "grep rectRaw"
    pipe "p g_invlut" "tail -10|sort -n"

(This differs from what I found in gdb 13.1:

    (gdb) help pipe
    pipe, |
    Send the output of a gdb command to a shell command.
    Usage: | [COMMAND] | SHELL_COMMAND
    Usage: | -d DELIM COMMAND DELIM SHELL_COMMAND
    Usage: pipe [COMMAND] | SHELL_COMMAND
    Usage: pipe -d DELIM COMMAND DELIM SHELL_COMMAND

    Executes COMMAND and sends its output to SHELL_COMMAND.

    The -d option indicates to use the string DELIM to separate COMMAND
    from SHELL_COMMAND, in alternative to |.  This is useful in
    case COMMAND contains a | character.

    With no COMMAND, repeat the last executed command
    and send its output to SHELL_COMMAND.)

I figured I'd give the older "pipe" a try, and found some intriguing but less than satisfactory results.  Here is a session I had in 8.2.1 (the results in 8.1.1 are identical) with my comments based on the line numbers:

<session>
  1 (gdb) info thread 1 2 3
  2   Id   Target Id         Frame
  3 * 1    Thread 0x155538844700 (LWP 8876) __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
  4   2    Thread 0x155538885700 (LWP 8875) 0x0000155552be3aa8 in hack_digit (p=0x155538882480) at printf_fp.c:175
  5   3    Thread 0x155555354b00 (LWP 16327) 0x0000155554019ad3 in futex_wait_cancelable (...) at ../sysdeps/unix/sysv/linux/futex-internal.h:88
  7
  8 (gdb) pipe "thread 2" "cat > /dev/null"
  9 [Switching to thread 2 (Thread 0x155538885700 (LWP 8875))]
 10 #0  0x0000155552be3aa8 in hack_digit (p=0x155538882480) at printf_fp.c:175
 11
 12 (gdb) pipe "thread 2" "cat > /dev/null"
 13
 14 (gdb) pipe "thread 3" "cat > /dev/null"
 15 [Switching to thread 3 (Thread 0x155555354b00 (LWP 16327))]
 16 #0  0x0000155554019ad3 in futex_wait_cancelable (...) at     ../sysdeps/unix/sysv/linux/futex-internal.h:88
 17
 18 (gdb) pipe "thread 3" "cat > /dev/null"
 19
 20 (gdb) pipe "thread 2" "cat > /dev/null"
 21 [Switching to thread 2 (Thread 0x155538885700 (LWP 8875))]
 22 #0  0x0000155552be3aa8 in hack_digit (p=0x155538882480) at printf_fp.c:175
 23
 24 (gdb) pipe "thread 2" "cat > /dev/null"
 25
 26 (gdb) thread 1
 27 [Switching to thread 1 (Thread 0x155538844700 (LWP 8876))]
 28 #0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
 29 51      ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
 30 (gdb) thread 1
 31 [Switching to thread 1 (Thread 0x155538844700 (LWP 8876))]
 32 #0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
 33 51      in ../sysdeps/unix/sysv/linux/raise.c
 34 (gdb) thread 1
 35 [Switching to thread 1 (Thread 0x155538844700 (LWP 8876))]
 36 #0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
 37 51      in ../sysdeps/unix/sysv/linux/raise.c
 38 (gdb)
 39
</session>

To simplify things, I only show the first three threads.  Here are the findings:

+-------+---------+------------------+----------+--------------------------------------------+
| Lines | Mode    | Thread Selection | Visible? | Comment                                    |
+-------+---------+------------------+----------+--------------------------------------------+
| 3     |         |                  |          | gdb session opens with thread #1 selected  |
| 8-10  | pipe    | #1 -> #2         | yes      |                                            |
| 12    | pipe    | #2 -> #2         | NO       | see lines 26-29 for VISIBLE case (regular) |
| 14-16 | pipe    | #2 -> #3         | yes      |                                            |
| 18    | pipe    | #3 -> #3         | NO       |                                            |
| 20-22 | pipe    | #3 -> #2         | yes      |                                            |
| 24    | pipe    | #2 -> #2         | NO       |                                            |
| 26-29 | regular | #1 -> #1         | yes      |                                            |
+-------+---------+------------------+----------+--------------------------------------------+

Using ">&" instead of ">" to pipe cat's output doesn't make a difference:

    (gdb) pipe "thread 3" "cat >& /dev/null"
    [Switching to thread 3 (Thread 0x155555354b00 (LWP 16327))]
    #0  0x0000155554019ad3 in futex_wait_cancelable (...) at ../sysdeps/unix/sysv/linux/futex-internal.h:88

When I left out the pipe character ">" I expected to see two identical outputs, but it does not:

    (gdb) pipe "thread 3" "cat"
    [Switching to thread 3 (Thread 0x155555354b00 (LWP 16327))]
    #0  0x0000155554019ad3 in futex_wait_cancelable (...) at ../sysdeps/unix/sysv/linux/futex-internal.h:88
    88      in ../sysdeps/unix/sysv/linux/futex-internal.h
    (gdb)

-----------------------------------
Questions re gdb 8.2 "pipe" command:
-----------------------------------

(1) Why is it not silent when selecting between non-identical threads, but silent with identical threads?
(2) Is there a way to get the 8.2 "pipe" to work correctly and silence the output completely?
Comment 5 Tom Tromey 2023-08-17 13:00:02 UTC
At least from the gdb NEWS file, it seems that "pipe" was not in
GDB 8 at all.  Are you sure yours isn't some sort of local command?
I looked at my checkout of the gdb-8 branch and it doesn't have this.
Comment 6 Moshe Rubin 2023-08-18 12:08:30 UTC
You were right - a local "pipe" custom command was getting automatically loaded.  Sorry about that <g>.

Examining its code, and that of other solutions for passing internal command output to an external program (https://stackoverflow.com/a/50359976), they all suffer from the same drawback that they use gdb.execute(cmd, to_string=True) and then pipe the output to the external tool. gdb.execute("thread 2", to_string=True) returns the output as a string, but also displays it to the screen.  The fact that gdb.execute(<cmd>, to_string=True) does not always suppress screen output is a bug and should be fixed.created and fixed.

However, thanks to your comment about InferiorThread.switch(), I believe we have a solution, even for gdb 8.2!

In my case, when gdb opens my core, I want to iterate over all threads in it (i.e., the current inferior) without any screen output.

To determine the number of threads in the current inferior:

    (gdb) python print(len(gdb.selected_inferior().threads()))
    17

From the gdb command prompt, select each thread one at a time by specifying the target thread object and calling the switch() function:

    (gdb) python gdb.selected_inferior().threads()[0].switch()
    (gdb) thread
    [Current thread is 17 (LWP 631)]
    (gdb) python gdb.selected_inferior().threads()[1].switch()
    (gdb) thread
    [Current thread is 16 (LWP 630)]
    (gdb) python gdb.selected_inferior().threads()[2].switch()
    (gdb) thread
    [Current thread is 15 (LWP 629)]
    . . .
    (gdb) python gdb.selected_inferior().threads()[16].switch()
    (gdb) thread
    [Current thread is 1 (LWP 598)]
    (gdb)

The main thing to note is the ability to switch/select a different thread with *no* screen output.

Kudos and thanks to you, Tom, for suggesting this power user method.
Comment 7 Tom Tromey 2024-05-08 18:47:19 UTC
I believe this is resolved.