I have a gdb debug function my_debug() in a gdb Python script, which I'd like to run while stopped at a breakpoint, but from different contexts/cases:
- either by calling
gdbin a sort of a batch mode (though without using--batchor--batch-silent), where I'd want the output of the debug script dumped to terminal/stdout; e.g. with"${GDB}" myprogram.exe -q -ex="set confirm off" -ex="set pagination off" -ex="source gdbscript.py" -ex "b compute_fibonacci" -ex "r" -ex="python my_debug()" -ex="exit" - or by calling the script in an interactive debug session, where I'd like to fully save its output to a file, without any text printed to terminal; say with:
(gdb) pipe python my_debug() | cat > my_debug_log.txt
I mostly have this working, though there is one problem - I'd like to run the thread X command totally silent/quiet: this works for case 1 - but not for case 2, where I still see "[Switching to thread ...]" message from thread X command being dumped to the file.
I cannot express just how much it pisses me off, that I do everything possible to suppress this text, and it still doesn't happen (at least, not in all cases); it truly causes me irritation to the point of psychological distress! Below is a bash script, test_gdb_ui.sh, that reproduces the problem; my questions are:
- How come the suppression of the text output of
thread Xgdb command works in the 1st case, but not in the 2nd? My guess is it has something to do with stream redirection, but I cannot figure out why at this time - Is there anything I can do to have the output of
thread Xsuppressed every time the gdb script runs - regardless of the context it runs in?
If you run test_gdb_ui.sh, it will create the following folder and files:
test_gdb_uiout/
├── gdbscript.py
└── myprogram.c
... and then it uses gcc to compile the myprogram.c into myprogram.exe. Finally it runs gdb, once as "batch" command, and at last interactively (where you need to paste command lines, as well as exit gdb, yourself).
Just to show the difference: when you run gdb as a "batch" command, you'll see something like:
Backtraces:
Thread <gdb.InferiorThread id=1.1 target-id="LWP 819043">
#0 clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:62
#1 0x00007ffff7ea4911 in __GI___clone_internal (cl_args= ...
...
Thread <gdb.InferiorThread id=1.2 target-id="LWP 819047">
#0 compute_fibonacci (arg=0x7fffffffde90) at myprogram.c:13
#1 0x00007ffff7e12ac3 in start_thread (arg=...
...
... but if you run interactively with redirect to file, you will see in the text file:
Backtraces:
[Switching to thread 1 (LWP 819052)]
#0 __futex_abstimed_wait_common64 (private=128, cancel=true, abstime=0x0, op=265, expected=819056, futex_word=0x7ffff7d7a910) at ./nptl/futex-internal.c:57
Thread <gdb.InferiorThread id=1.1 target-id="LWP 819052">
#0 __futex_abstimed_wait_common64 (private=128, ...
...
[Switching to thread 2 (LWP 819056)]
#0 compute_fibonacci (arg=0x7fffffffde90) at myprogram.c:13
13 fibo_task_t* task = (fibo_task_t*) arg;
Thread <gdb.InferiorThread id=1.2 target-id="LWP 819056">
#0 compute_fibonacci (arg=...
...
(darn it, my eyes and head hurt just from seeing that damn "[Switching to thread ...]" again)
What I've found in my research so far (related to my version of gdb, 12.1):
- The "[Switching to thread ...]" is printed in the
print_selected_thread_framefunction, by something calleduiout(passed as a function argument, of typestruct ui_out*) viauiout->text ("[Switching to thread ");in gdb/thread.c - There is something called
current_ui(in gdb 12.1; in latest versions, it is apparently calledcurrent_uiout), which might be related to aboveuiout, and apparently gets initialized from(stdin, stdout, stderr)(in latest versions,current_uioutgets initialized fromgdb_stdout) gdb/main.c - When running with
--batch-silent, gdb doesgdb_stdout = new null_file();(which is apparently how all output is suppressed) in gdb/main.c
I guess my problem has to do with the interaction of ui_out/gdb_stdout and the "real" stdout - but I cannot say for sure, as I unfortunately find the above confusing enough; so hopefully someone comes along to clarify properly.
Here is test_gdb_ui.sh:
# clean up previous
rm -rf test_gdb_uiout
# start again - generate files
mkdir test_gdb_uiout
cat > test_gdb_uiout/myprogram.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
// Shared structure to pass input/output to thread
typedef struct {
int input;
int result;
} fibo_task_t;
void* compute_fibonacci(void* arg) {
fibo_task_t* task = (fibo_task_t*) arg;
int n = task->input;
if (n <= 1) {
task->result = n;
} else {
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int temp = a + b;
a = b;
b = temp;
}
task->result = b;
}
return NULL;
}
int process_data(int number) {
printf("Processing number in thread: %d\n", number);
pthread_t thread;
fibo_task_t task;
task.input = number;
task.result = 0;
if (pthread_create(&thread, NULL, compute_fibonacci, &task) != 0) {
perror("Failed to create thread");
exit(1);
}
// Wait for thread to finish
if (pthread_join(thread, NULL) != 0) {
perror("Failed to join thread");
exit(1);
}
return task.result;
}
int prepare_data() {
srand((unsigned int) time(NULL));
int number = (rand() % 31) + 5; // Range: 5–35
return process_data(number);
}
int main() {
int result = prepare_data();
printf("Fibonacci result: %d\n", result);
return 0;
}
EOF
cat > test_gdb_uiout/gdbscript.py <<'EOF'
import gdb
import os
import sys
def run_gdb_command_quietly(cmd): # WORKS!
# Save the original terminal file descriptors
original_stdout_fd = os.dup(1)
original_stderr_fd = os.dup(2)
# Open /dev/null for writing
null_fd = os.open(os.devnull, os.O_WRONLY)
# Redirect stdout and stderr to /dev/null
os.dup2(null_fd, 1)
os.dup2(null_fd, 2)
output = gdb.execute(cmd, from_tty=False, to_string=True)
# Restore the original file descriptors
os.dup2(original_stdout_fd, 1)
os.dup2(original_stderr_fd, 2)
# Close the /dev/null file descriptor
os.close(null_fd)
# just in case, return
return output
def my_debug():
print()
print("=== my_debug start " + "="*20)
print("\nRegisters:")
print("PC:")
gdb.execute("p/x $pc")
print("\nGeneral Registers:")
gdb.execute("info registers")
print("\nBacktraces:")
run_gdb_command_quietly("thread 1")
current_thread = gdb.selected_thread()
print(f"\nThread {current_thread}\n")
btstr = gdb.execute("bt", from_tty=False, to_string=True)
print(btstr)
run_gdb_command_quietly("thread 2")
current_thread = gdb.selected_thread()
print(f"\nThread {current_thread}\n")
btstr = gdb.execute("bt", from_tty=False, to_string=True)
print(btstr)
print("=== my_debug end " + "="*20)
print()
EOF
GDB="/src/gdb-v16.2-static/gdb"
set -x
{ echo "Starting test ..."; } 2>/dev/null
cd test_gdb_uiout
{ echo "Compiling myprogram.c"; } 2>/dev/null
gcc -g -Wall myprogram.c -o myprogram.exe
{ echo "Test command-line \"batch\" gdb"; } 2>/dev/null
"${GDB}" myprogram.exe -q -ex="set confirm off" -ex="set pagination off" -ex="source gdbscript.py" -ex "b compute_fibonacci" -ex "r" -ex="python my_debug()" -ex="exit"
{ echo "Test interactive gdb session: gdb will open,
set breakpoints, and run the program up to the
breakpoint; at that point, please copy/paste and
ENTER the line below at the (gdb) prompt:
pipe python my_debug() | cat > my_debug_log.txt
... and to check the resulting file, (copy/paste) ENTER the line below:
!cat my_debug_log.txt
"; } 2>/dev/null
"${GDB}" myprogram.exe -q -ex="set confirm off" -ex="set pagination off" -ex="source gdbscript.py" -ex "b compute_fibonacci" -ex "r"
InferiorThread.switch ()also print that text?set suppress-cli-notifications onshould mostly do what you want. I think there are some edge cases where this doesn't work, e.g. if you switch to the thread that is currently selected it still prints. You can also dowith suppress-cli-notifications on -- thread Xto avoid having to toggle the setting back and forth.