* [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command
@ 2026-10-13 17:13 Simon Marchi
2026-10-13 17:13 ` [PATCH 2/2] gdb: preserve shell command exit status in pipe command on EPIPE Simon Marchi
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Simon Marchi @ 2026-10-13 17:13 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
From: Simon Marchi <simon.marchi@polymtl.ca>
Aditya Kamath reported [1] that gdb.base/shell.exp sometimes fails on
AIX, because GDB gets killed by SIGPIPE when running:
|p 123| exit 0
pipe_command opens the shell command with popen and writes the output
of the GDB command to it. If the shell command exits without reading
its input, as is the case here, writing to the pipe raises SIGPIPE,
whose default action is to terminate the process. It is a race: if the
shell command exits before GDB flushes its output, GDB dies.
On Linux, this is usually hidden by the fact that, when initialized by
GDB, Python sets SIGPIPE to be ignored. This happens with all of GDB's
Python initialization paths:
- Python < 3.9: GDB calls Py_Initialize, which calls
Py_InitializeEx(1), meaning install_signal_handlers is enabled.
- Python 3.9 to 3.13: GDB initializes a PyConfig with
PyConfig_InitPythonConfig, which sets install_signal_handlers to 1 by
default, and passes it to Py_InitializeFromConfig.
- Python >= 3.14: GDB uses the PyInitConfig API, which starts from an
isolated configuration (install_signal_handlers set to 0), but
explicitly sets the install_signal_handlers option to true.
Fix it by ignoring SIGPIPE for the duration of pipe_command, using
scoped_ignore_sigpipe. Writes to the pipe then fail with EPIPE, which
stdio_file ignores.
Add a test that pipes a large amount of output to shell commands that
don't read all their input. The output is larger than the pipe buffer,
so GDB necessarily writes to the pipe after the shell command has
exited, regardless of timing.
When Python is available, the test restores the default SIGPIPE action,
to test the behavior of a GDB built without Python. Without the fix,
GDB gets killed by SIGPIPE.
[1] https://inbox.sourceware.org/gdb-patches/DSWPR15MB319817D17B50D16301CEEACAEED69E2@DSWPR15MB319817.namprd15.prod.outlook.com/
Change-Id: Id1ec076550ab94e6efb53d31bfca8e50a7c7c73d
---
gdb/cli/cli-cmds.c | 13 ++++++++++++
gdb/testsuite/gdb.base/shell.exp | 34 ++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c
index 8a85c8132776..bb7a7acbc3ac 100644
--- a/gdb/cli/cli-cmds.c
+++ b/gdb/cli/cli-cmds.c
@@ -59,6 +59,7 @@
#include "gdbsupport/pathstuff.h"
#include "gdbsupport/gdb_tilde_expand.h"
#include "gdbsupport/eintr.h"
+#include "gdbsupport/scoped_ignore_signal.h"
#ifdef TUI
#include "tui/tui.h"
@@ -1158,6 +1159,18 @@ pipe_command (const char *arg, int from_tty)
int exit_status;
{
+ /* If GDB writes to the pipe after the sub-process has exited (for example,
+ with "lots-of-output | head -1"), GDB would receive a SIGPIPE, whose
+ default behavior is to end the process. Ignore SIGPIPE for the duration
+ of the command.
+
+ With SIGPIPE ignored, the writes will fail with EPIPE, which stdio_file
+ ignores.
+
+ Note that if GDB is built with Python, SIGPIPE will already be ignored,
+ since that is done during Python initialization. */
+ scoped_ignore_sigpipe ignore_sigpipe;
+
SCOPE_EXIT { exit_status = pclose (to_shell_command); };
stdio_file pipe_file (to_shell_command);
diff --git a/gdb/testsuite/gdb.base/shell.exp b/gdb/testsuite/gdb.base/shell.exp
index f6b36dc3a7ca..272ce070ce1a 100644
--- a/gdb/testsuite/gdb.base/shell.exp
+++ b/gdb/testsuite/gdb.base/shell.exp
@@ -195,3 +195,37 @@ foreach cmd {"pipe " "| " "|"} {
test_gdb_complete_none \
"${cmd}-d set maint set "
}
+
+# Test piping a lot of output to a shell command that exits without
+# reading its input. The output is larger than the pipe buffer, so GDB
+# necessarily writes to the pipe after the shell command has exited,
+# regardless of timing. GDB must not get killed by SIGPIPE.
+
+proc_with_prefix test_pipe_reader_exits_early {} {
+ clean_restart
+
+ # When Python is initialized, it sets SIGPIPE to be ignored. Restore
+ # the default action, to test the behavior of a GDB built without
+ # Python.
+ if { [allow_python_tests] } {
+ gdb_test_no_output \
+ "python import signal; signal.signal(signal.SIGPIPE, signal.SIG_DFL)" \
+ "restore default SIGPIPE action"
+ }
+
+ # Define the "lots" command, which outputs 1000 lines of 200
+ # characters.
+ set line [string repeat "this is a line of output " 8]
+ gdb_define_cmd "lots" [list \
+ {set $i = 0} \
+ {while $i < 1000} \
+ "echo $line\\n" \
+ {set $i = $i + 1} \
+ {end}]
+
+ gdb_test "pipe lots | exit 0"
+ gdb_test "pipe lots | exit 1"
+ gdb_test "pipe lots | head -1"
+}
+
+test_pipe_reader_exits_early
base-commit: bad8b979b27e050b2b4db16a261f8a78e24b8aa2
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 2/2] gdb: preserve shell command exit status in pipe command on EPIPE
2026-10-13 17:13 [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command Simon Marchi
@ 2026-10-13 17:13 ` Simon Marchi
2026-10-14 12:46 ` Tom Tromey
2026-10-14 9:32 ` [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command Aditya Kamath
2026-10-14 12:46 ` Tom Tromey
2 siblings, 1 reply; 8+ messages in thread
From: Simon Marchi @ 2026-10-13 17:13 UTC (permalink / raw)
To: gdb-patches; +Cc: Simon Marchi
When the shell command of the pipe command exits without reading all of
its input, the pipe command may fail with:
(gdb) pipe COMMAND | head -1
...
shell command "head -1" failed: Broken pipe
pclose flushes the buffered output before closing the stream. If that
flush fails, pclose may return -1 (with errno == EPIPE) instead of the
exit status of the shell command.
Work around this by flusing the stream explicitly before calling pclose,
ignoring errors. With that out of the way, pclose returns the exit
status of the shell command. This makes something like "pipe COMMAND |
head -1" behave like it would in a shell.
Update the test added in the previous commit to check the exit status
of the shell commands.
Change-Id: I939e4610f6c09ac2007a0c58f64480106c74edb2
---
gdb/cli/cli-cmds.c | 9 ++++++++-
gdb/testsuite/gdb.base/shell.exp | 17 +++++++++++++----
2 files changed, 21 insertions(+), 5 deletions(-)
diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c
index bb7a7acbc3ac..7aca84ff09ac 100644
--- a/gdb/cli/cli-cmds.c
+++ b/gdb/cli/cli-cmds.c
@@ -1171,7 +1171,14 @@ pipe_command (const char *arg, int from_tty)
since that is done during Python initialization. */
scoped_ignore_sigpipe ignore_sigpipe;
- SCOPE_EXIT { exit_status = pclose (to_shell_command); };
+ SCOPE_EXIT
+ {
+ /* Flush explicitly, ignoring errors, so that a failure to write
+ buffered output (for instance, with EPIPE) doesn't make pclose fail
+ and hide the exit status of the shell command. */
+ fflush (to_shell_command);
+ exit_status = pclose (to_shell_command);
+ };
stdio_file pipe_file (to_shell_command);
diff --git a/gdb/testsuite/gdb.base/shell.exp b/gdb/testsuite/gdb.base/shell.exp
index 272ce070ce1a..2e6c63585eb0 100644
--- a/gdb/testsuite/gdb.base/shell.exp
+++ b/gdb/testsuite/gdb.base/shell.exp
@@ -199,7 +199,8 @@ foreach cmd {"pipe " "| " "|"} {
# Test piping a lot of output to a shell command that exits without
# reading its input. The output is larger than the pipe buffer, so GDB
# necessarily writes to the pipe after the shell command has exited,
-# regardless of timing. GDB must not get killed by SIGPIPE.
+# regardless of timing. GDB must not get killed by SIGPIPE, and must
+# report the exit status of the shell command.
proc_with_prefix test_pipe_reader_exits_early {} {
clean_restart
@@ -223,9 +224,17 @@ proc_with_prefix test_pipe_reader_exits_early {} {
{set $i = $i + 1} \
{end}]
- gdb_test "pipe lots | exit 0"
- gdb_test "pipe lots | exit 1"
- gdb_test "pipe lots | head -1"
+ gdb_test_no_output "pipe lots | exit 0"
+ gdb_test "p \$_shell_exitcode" " = 0" "exit 0 exitcode"
+ gdb_test "p \$_shell_exitsignal" " = void" "exit 0 exitsignal"
+
+ gdb_test_no_output "pipe lots | exit 1"
+ gdb_test "p \$_shell_exitcode" " = 1" "exit 1 exitcode"
+ gdb_test "p \$_shell_exitsignal" " = void" "exit 1 exitsignal"
+
+ gdb_test "pipe lots | head -1" "^$line"
+ gdb_test "p \$_shell_exitcode" " = 0" "head -1 exitcode"
+ gdb_test "p \$_shell_exitsignal" " = void" "head -1 exitsignal"
}
test_pipe_reader_exits_early
--
2.56.0
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command
2026-10-13 17:13 [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command Simon Marchi
2026-10-13 17:13 ` [PATCH 2/2] gdb: preserve shell command exit status in pipe command on EPIPE Simon Marchi
@ 2026-10-14 9:32 ` Aditya Kamath
2026-10-14 12:42 ` Tom Tromey
2026-10-14 12:46 ` Tom Tromey
2 siblings, 1 reply; 8+ messages in thread
From: Aditya Kamath @ 2026-10-14 9:32 UTC (permalink / raw)
To: Simon Marchi, gdb-patches
Cc: Simon Marchi, Ulrich Weigand, SANGAMESH MALLAYYA
[-- Attachment #1: Type: text/plain, Size: 2424 bytes --]
Hi Simon,
Thanks for the quick fix.
I tested both patches on AIX 7.3 with a 64-bit GDB configured without
Python, running gdb.base/shell.exp with
"gmake check RUNTESTFLAGS=gdb.base/shell.exp". I ran it with the test
programs built with both -maix64 and -maix32.
expected unexpected unresolved
passes failures
original GDB + new test 112 4 9
patches 1 + 2, -maix64 121 4 0
patches 1 + 2, -maix32 121 4 0
Without the fix, the new test reproduces the problem every time. The log
shows:
(gdb) pipe lots | exit 0
ERROR: GDB process no longer exists
GDB process exited with wait status ... CHILDKILLED SIGPIPE {write on pipe with no readers}
After that GDB is gone, so the 9 checks in test_pipe_reader_exits_early
are all reported as UNRESOLVED. This is the same failure that gave me
75 unresolved results in a full 32-bit run of gdb.base. There it
happened on the first pipe test, so the rest of shell.exp was lost. With
the patches applied the test passes and there are no unresolved results.
The 4 remaining failures in shell.exp are not caused by your patches:
1: shell interrupt exitcode
2: shell interrupt exitsignal
3: pipe interrupt exitcode
4: pipe interrupt exitsignal
They fail the same way without the patches. The tests run "kill -2 $$"
under the shell and expect $_shell_exitsignal = 2. On AIX, /bin/sh and
/usr/bin/ksh are ksh88, which catches SIGINT and calls exit (130)
(128 + 2) instead of dying from the signal. GDB then sees a normal exit,
sets $_shell_exitcode = 130 and leaves $_shell_exitsignal void. I
checked the raw wait status of each shell:
/usr/bin/ksh exited, code 130
/usr/bin/ksh93 killed by signal 2
/usr/bin/bash killed by signal 2
So GDB reports what happened correctly, and the test assumes a shell
that dies from the signal. "shell" and "!" use $SHELL, and running with
SHELL=/usr/bin/bash makes those two [1 and 2] tests pass.
Do you think it would make sense to handle the ksh88 behaviour in a separate test-suite patch, by accepting $_shell_exitcode = 130 and $_shell_exitsignal = void on AIX??
Otherwise, patches 1 and 2 works on AIX.
Thanks,
Aditya
[-- Attachment #2: Type: text/html, Size: 10488 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command
2026-10-14 9:32 ` [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command Aditya Kamath
@ 2026-10-14 12:42 ` Tom Tromey
2026-10-14 12:56 ` Simon Marchi
0 siblings, 1 reply; 8+ messages in thread
From: Tom Tromey @ 2026-10-14 12:42 UTC (permalink / raw)
To: Aditya Kamath
Cc: Simon Marchi, gdb-patches, Simon Marchi, Ulrich Weigand,
SANGAMESH MALLAYYA
>>>>> Aditya Kamath <Aditya.Kamath1@ibm.com> writes:
> Do you think it would make sense to handle the ksh88 behaviour in a
> separate test-suite patch, by accepting $_shell_exitcode = 130 and
> $_shell_exitsignal = void on AIX??
I think that would be reasonable.
thanks,
Tom
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command
2026-10-14 12:42 ` Tom Tromey
@ 2026-10-14 12:56 ` Simon Marchi
0 siblings, 0 replies; 8+ messages in thread
From: Simon Marchi @ 2026-10-14 12:56 UTC (permalink / raw)
To: Tom Tromey, Aditya Kamath
Cc: gdb-patches, Simon Marchi, Ulrich Weigand, SANGAMESH MALLAYYA
On 10/14/26 8:42 AM, Tom Tromey wrote:
>>>>>> Aditya Kamath <Aditya.Kamath1@ibm.com> writes:
>
>> Do you think it would make sense to handle the ksh88 behaviour in a
>> separate test-suite patch, by accepting $_shell_exitcode = 130 and
>> $_shell_exitsignal = void on AIX??
>
> I think that would be reasonable.
>
> thanks,
> Tom
I agree, just make sure that document in the test that this specific
outcome is expected for shell ksh88 on AIX.
Simon
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command
2026-10-13 17:13 [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command Simon Marchi
2026-10-13 17:13 ` [PATCH 2/2] gdb: preserve shell command exit status in pipe command on EPIPE Simon Marchi
2026-10-14 9:32 ` [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command Aditya Kamath
@ 2026-10-14 12:46 ` Tom Tromey
2026-10-14 12:57 ` Simon Marchi
2 siblings, 1 reply; 8+ messages in thread
From: Tom Tromey @ 2026-10-14 12:46 UTC (permalink / raw)
To: Simon Marchi; +Cc: gdb-patches, Simon Marchi
>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
Simon> Fix it by ignoring SIGPIPE for the duration of pipe_command, using
Simon> scoped_ignore_sigpipe. Writes to the pipe then fail with EPIPE, which
Simon> stdio_file ignores.
Makes sense.
Approved-By: Tom Tromey <tom@tromey.com>
Tom
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command
2026-10-14 12:46 ` Tom Tromey
@ 2026-10-14 12:57 ` Simon Marchi
0 siblings, 0 replies; 8+ messages in thread
From: Simon Marchi @ 2026-10-14 12:57 UTC (permalink / raw)
To: Tom Tromey, Simon Marchi; +Cc: gdb-patches
On 10/14/26 8:46 AM, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simon.marchi@efficios.com> writes:
>
> Simon> Fix it by ignoring SIGPIPE for the duration of pipe_command, using
> Simon> scoped_ignore_sigpipe. Writes to the pipe then fail with EPIPE, which
> Simon> stdio_file ignores.
>
> Makes sense.
> Approved-By: Tom Tromey <tom@tromey.com>
>
> Tom
Thanks, pushed both.
Simon
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-14 12:57 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-13 17:13 [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command Simon Marchi
2026-10-13 17:13 ` [PATCH 2/2] gdb: preserve shell command exit status in pipe command on EPIPE Simon Marchi
2026-10-14 12:46 ` Tom Tromey
2026-10-14 9:32 ` [PATCH 1/2] gdb: don't let SIGPIPE kill GDB in the pipe command Aditya Kamath
2026-10-14 12:42 ` Tom Tromey
2026-10-14 12:56 ` Simon Marchi
2026-10-14 12:46 ` Tom Tromey
2026-10-14 12:57 ` Simon Marchi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox