* [PATCH v2 00/11] Fix a few Cygwin/MinGW problems
@ 2026-05-25 19:18 Pedro Alves
2026-05-25 19:18 ` [PATCH v2 01/11] Adjust gdb.base/exitsignal.exp for MinGW, trigger fault Pedro Alves
` (10 more replies)
0 siblings, 11 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
Here's v2 of the series first posted as v1 at:
https://inbox.sourceware.org/gdb-patches/20260522001626.393908-1-pedro@palves.net/T/
This started out as trying to make gdb.python/py-events.exp work on
Cygwin, which revealed a few other problems. Some patches from v1
have been merged already, so fewer problems now. OTOH, testing more I
saw some more problems, so...
- gdb.base/exitsignal.exp needs adjustment for MinGW as well. A
different adjustment, though.
- The inferior exit code / termination signal is mishandled on
Windows. Same problem as in v1, except the v1 solution was
incomplete.
- gdb.python/py-events.exp needs a lot of adjustment for Cygwin and
MinGW.
All fixed by this series.
gdb.base/exitsignal.exp and gdb.python/py-events.exp both tested on
x86_64-unknown-linux-gnu and on Cygwin.
gdb.base/exitsignal.exp also tested on x86_64-w64-mingw32.
New in v2:
- Patches 1 through 3 in v1 merged and thus dropped from v2.
- Now with gdb.base/exitsignal.exp fixes for MinGW. Now passes
cleanly there too.
- Exit code patch more complete -- now handles attach case and
non-cygwin inferior cases. gdb.base/exitsignal.exp now covers
those scenarios too.
- Exit status logic refactor moved to its own patch.
- gdb.python/py-events.exp adjustments split into three separate
patches.
Pedro Alves (11):
Adjust gdb.base/exitsignal.exp for MinGW, trigger fault
Adjust gdb.base/exitsignal.exp for MinGW, second-chance SIGSEGV
Adjust gdb.base/exitsignal.exp for MinGW, separate program names
gdb.base/exitsignal.exp: Exit with non-zero
gdb.base/exitsignal.exp: Test attaching too
gdb/testsuite: Add mechanism to compile Windows native programs on
Cygwin
Windows gdb+gdbserver: Share exit status logic
Windows gdb+gdbserver: Decode Cygwin ExitProcess codes
Adjust gdb.python/py-events.exp for Cygwin/MinGW, thread IDs
Adjust gdb.python/py-events.exp for Cygwin/MinGW, no fork
Adjust gdb.python/py-events.exp for Cygwin/MinGW, "info proc" =>
"inferior"
gdb/nat/windows-nat.c | 177 ++++++++++++-
gdb/nat/windows-nat.h | 35 +++
gdb/testsuite/README | 22 ++
.../gdb.base/coredump-filter-build-id.c | 22 ++
.../gdb.base/coredump-filter-build-id.exp | 2 +-
gdb/testsuite/gdb.base/exitsignal.exp | 247 ++++++++++++------
gdb/testsuite/gdb.base/normal.c | 14 +-
gdb/testsuite/gdb.base/segv.c | 9 +-
gdb/testsuite/gdb.python/py-events.c | 3 -
gdb/testsuite/gdb.python/py-events.exp | 83 +++---
gdb/testsuite/lib/gdb.exp | 104 +++++++-
gdb/windows-nat.c | 20 +-
gdbserver/win32-low.cc | 21 +-
13 files changed, 605 insertions(+), 154 deletions(-)
create mode 100644 gdb/testsuite/gdb.base/coredump-filter-build-id.c
base-commit: 13f28e68cc751e4aac96f0ae700d7a708791006c
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 01/11] Adjust gdb.base/exitsignal.exp for MinGW, trigger fault
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 02/11] Adjust gdb.base/exitsignal.exp for MinGW, second-chance SIGSEGV Pedro Alves
` (9 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
gdb.base/segv.c uses raise(SIGSEGV) to generate a SIGSEGV. On native
Windows that does not generate an EXCEPTION_ACCESS_VIOLATION; raise is
a pure userspace construct: it dispatches to the registered SIGSEGV
handler if there is one, otherwise calls abort. GDB therefore never
sees an exception to intercept. E.g.:
...
continue
Continuing.
[Thread 1908.0x3308 (id 2) exited with code 3]
[Inferior 1 (process 1908) exited with code 03]
(gdb) FAIL: gdb.base/exitsignal.exp: trigger SIGSEGV (the program exited)
continue
The program is not being run.
...
Replace the raise with a real null dereference so the kernel actually
raises an access violation.
Note: I confirmed no other tests use segv.c. segv.c and normal.c are
both "owned" by gdb.base/exitsignal.exp.
Change-Id: Ib54d9e6998cf9bfc18dcb5e76d31a9deb0458da4
commit-id: 86fcf2ce
---
gdb/testsuite/gdb.base/segv.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/gdb/testsuite/gdb.base/segv.c b/gdb/testsuite/gdb.base/segv.c
index a3db6d292db..fd43d95e9b2 100644
--- a/gdb/testsuite/gdb.base/segv.c
+++ b/gdb/testsuite/gdb.base/segv.c
@@ -17,13 +17,11 @@
/* This test can be used just to generate a SIGSEGV. */
-#include <signal.h>
-
int
main (int argc, char *argv[])
{
/* Generating a SIGSEGV. */
- raise (SIGSEGV);
+ *(volatile int *) 0;
return 0;
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 02/11] Adjust gdb.base/exitsignal.exp for MinGW, second-chance SIGSEGV
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
2026-05-25 19:18 ` [PATCH v2 01/11] Adjust gdb.base/exitsignal.exp for MinGW, trigger fault Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-26 11:18 ` Eli Zaretskii
2026-05-25 19:18 ` [PATCH v2 03/11] Adjust gdb.base/exitsignal.exp for MinGW, separate program names Pedro Alves
` (8 subsequent siblings)
10 siblings, 1 reply; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
On native Windows a segmentation fault is delivered as a SEH
exception, so GDB stops twice: once for the first-chance exception,
and -- if no SEH handler in the inferior catches it -- again for the
second-chance exception, after which the process dies. The testcase
currently continues only once, resulting in:
...
continue
Continuing.
Thread 1 received signal SIGSEGV, Segmentation fault.
0x00007ff765ac1469 in main (argc=1, argv=0x9f2640) at C:/rocgdb/src.cascais-rsync/gdb/testsuite/gdb.base/segv.c:24
24 *(volatile int *) 0;
(gdb) PASS: gdb.base/exitsignal.exp: trigger SIGSEGV
continue
Continuing.
Thread 1 received signal SIGSEGV, Segmentation fault.
0x00007ff765ac1469 in main (argc=1, argv=0x9f2640) at C:/rocgdb/src.cascais-rsync/gdb/testsuite/gdb.base/segv.c:24
24 *(volatile int *) 0;
(gdb) FAIL: gdb.base/exitsignal.exp: program terminated with SIGSEGV
...
Add a second continue on MinGW to step past the second-chance stop.
With this, gdb.base/exitsignal.exp passes cleanly on MinGW:
-FAIL: gdb.base/exitsignal.exp: program terminated with SIGSEGV
+PASS: gdb.base/exitsignal.exp: trigger SIGSEGV, second-chance
+PASS: gdb.base/exitsignal.exp: program terminated with SIGSEGV
Change-Id: Ibda1540a602b62c26a5b218d930402eccc3ba98f
commit-id: 385755c8
---
gdb/testsuite/gdb.base/exitsignal.exp | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/gdb/testsuite/gdb.base/exitsignal.exp b/gdb/testsuite/gdb.base/exitsignal.exp
index 5099ae1a1d6..341197cf0c8 100644
--- a/gdb/testsuite/gdb.base/exitsignal.exp
+++ b/gdb/testsuite/gdb.base/exitsignal.exp
@@ -53,7 +53,13 @@ gdb_test "print \$_exitcode" " = void" \
gdb_test "continue" "(Thread .*|Program) received signal SIGSEGV.*" \
"trigger SIGSEGV"
-if {[istarget "*-*-cygwin*"]} {
+if {[istarget "*-*-mingw*"]} {
+ # We're debugging a pure Win32 program with no SEH handler. The
+ # previous continue caught the first-chance exception. Now we
+ # catch the second-chance.
+ gdb_test "continue" "Thread .* received signal SIGSEGV.*" \
+ "trigger SIGSEGV, second-chance"
+} elseif {[istarget "*-*-cygwin*"]} {
# Cygwin calls DebugBreak before it lets the process exit.
gdb_test "continue" "Thread .* received signal SIGTRAP.*" \
"trigger try_to_debug SIGTRAP"
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 03/11] Adjust gdb.base/exitsignal.exp for MinGW, separate program names
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
2026-05-25 19:18 ` [PATCH v2 01/11] Adjust gdb.base/exitsignal.exp for MinGW, trigger fault Pedro Alves
2026-05-25 19:18 ` [PATCH v2 02/11] Adjust gdb.base/exitsignal.exp for MinGW, second-chance SIGSEGV Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-27 21:59 ` Thiago Jung Bauermann
2026-05-25 19:18 ` [PATCH v2 04/11] gdb.base/exitsignal.exp: Exit with non-zero Pedro Alves
` (7 subsequent siblings)
10 siblings, 1 reply; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
Currently, gdb.base/exitsignal.exp on MinGW skips half the tests, like
so:
# of expected passes 13
# of untested testcases 1
... because the testcase actually compiles two programs, but the
second program is compiled using the same executable name as the
first, at a time when gdb still has the first executable open,
resulting in:
.../x86_64-w64-mingw32/bin/ld.exe: cannot open output file C:/.../gdb.base/exitsignal/exitsignal.exe: Permission denied
The use of standard_testfile in the middle of a testcase is a bit
surprising. Fix this by moving the second compilation to the top, and
give each executable its own name.
Move the bulk of test code to two procedures, one for testing exiting
with signal, another for testing normal exit.
With this, gdb.base/exitsignal.exp passes cleanly on
x86_64-w64-mingw32.
Change-Id: I9a1af298b0e4f62f38b5c4b14787d7e40a2b5e2c
commit-id: e496754c
---
gdb/testsuite/gdb.base/exitsignal.exp | 201 ++++++++++++++------------
1 file changed, 112 insertions(+), 89 deletions(-)
diff --git a/gdb/testsuite/gdb.base/exitsignal.exp b/gdb/testsuite/gdb.base/exitsignal.exp
index 341197cf0c8..4f4c53e37cb 100644
--- a/gdb/testsuite/gdb.base/exitsignal.exp
+++ b/gdb/testsuite/gdb.base/exitsignal.exp
@@ -23,103 +23,126 @@
require {!target_info exists gdb,nosignals}
-standard_testfile segv.c
+set testfile "exitsignal"
-if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
- return
-}
+set exec1 "segv"
+set srcfile1 ${exec1}.c
+set binfile1 [standard_output_file ${exec1}]
-# Run to main. But, before, change cwd to get the core into the
-# output directory.
-set_inferior_cwd_to_output_dir
+set exec2 "normal"
+set srcfile2 ${exec2}.c
+set binfile2 [standard_output_file ${exec2}]
-if { ![runto_main] } {
- return
+if { [build_executable "failed to build $exec1" ${exec1} "${srcfile1}" \
+ {debug}] == -1 } {
+ return -1
}
-# Get the inferior's PID for later.
-set pid [get_inferior_pid]
-
-# Print $_exitsignal. It should be void now, because nothing
-# happened.
-gdb_test "print \$_exitsignal" " = void" \
- "\$_exitsignal is void before running"
-
-# Just to guarantee, making sure that $_exitcode is also void.
-gdb_test "print \$_exitcode" " = void" \
- "\$_exitcode is void before running"
-
-# Trigger SIGSEGV.
-gdb_test "continue" "(Thread .*|Program) received signal SIGSEGV.*" \
- "trigger SIGSEGV"
-
-if {[istarget "*-*-mingw*"]} {
- # We're debugging a pure Win32 program with no SEH handler. The
- # previous continue caught the first-chance exception. Now we
- # catch the second-chance.
- gdb_test "continue" "Thread .* received signal SIGSEGV.*" \
- "trigger SIGSEGV, second-chance"
-} elseif {[istarget "*-*-cygwin*"]} {
- # Cygwin calls DebugBreak before it lets the process exit.
- gdb_test "continue" "Thread .* received signal SIGTRAP.*" \
- "trigger try_to_debug SIGTRAP"
+if { [build_executable "failed to build $exec2" ${exec2} "${srcfile2}" \
+ {debug}] == -1} {
+ return -1
}
-# Continue until the end.
-gdb_test "continue" "Program terminated with signal SIGSEGV.*" \
- "program terminated with SIGSEGV"
-
-# We don't need the core file, remove it.
-remove_core $pid
-
-# Now, print $_exitsignal again. It should be 11 (SIGSEGV).
-gdb_test "print \$_exitsignal" " = 11" \
- "\$_exitsignal is 11 (SIGSEGV) after SIGSEGV."
-
-# And $_exitcode should still be void, since the inferior died because
-# of a signal, and did not return.
-gdb_test "print \$_exitcode" " = void" \
- "\$_exitcode is still void after SIGSEGV"
-
-# Re-run to main, i.e., restart the executable.
-rerun_to_main
-
-# Print the $_exitsignal again. Even in this normal scenario, it
-# should still contain the signal triggered in the other run.
-gdb_test "print \$_exitsignal" " = 11" \
- "\$_exitsignal is 11 (SIGSEGV) after restarting the inferior"
-
-# And, again, print $_exitcode.
-gdb_test "print \$_exitcode" " = void" \
- "\$_exitcode is still void after restarting the inferior"
-
-# Now we test the behavior of $_exit{code,signal} during a normal
-# inferior execution.
-standard_testfile normal.c
-
-if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
- return
+proc test_signal {} {
+ clean_restart $::exec1
+
+ # Run to main. But, before, change cwd to get the core into the
+ # output directory.
+ set_inferior_cwd_to_output_dir
+
+ if { ![runto_main] } {
+ return
+ }
+
+ # Get the inferior's PID for later.
+ set pid [get_inferior_pid]
+
+ # Print $_exitsignal. It should be void now, because nothing
+ # happened.
+ gdb_test "print \$_exitsignal" " = void" \
+ "\$_exitsignal is void before running"
+
+ # Just to guarantee, making sure that $_exitcode is also void.
+ gdb_test "print \$_exitcode" " = void" \
+ "\$_exitcode is void before running"
+
+ # Trigger SIGSEGV.
+ gdb_test "continue" "(Thread .*|Program) received signal SIGSEGV.*" \
+ "trigger SIGSEGV"
+
+ if {[istarget "*-*-mingw*"]} {
+ # We're debugging a pure Win32 program with no SEH handler. The
+ # previous continue caught the first-chance exception. Now we
+ # catch the second-chance one.
+ gdb_test "continue" "Thread .* received signal SIGSEGV.*" \
+ "trigger SIGSEGV, second-chance"
+ } elseif {[istarget "*-*-cygwin*"]} {
+ # Cygwin calls DebugBreak before it lets the process exit.
+ gdb_test "continue" "Thread .* received signal SIGTRAP.*" \
+ "trigger try_to_debug SIGTRAP"
+ }
+
+ # Continue until the end.
+ gdb_test "continue" "Program terminated with signal SIGSEGV.*" \
+ "program terminated with SIGSEGV"
+
+ # We don't need the core file, remove it.
+ remove_core $pid
+
+ # Now, print $_exitsignal again. It should be 11 (SIGSEGV).
+ gdb_test "print \$_exitsignal" " = 11" \
+ "\$_exitsignal is 11 (SIGSEGV) after SIGSEGV."
+
+ # And $_exitcode should still be void, since the inferior died
+ # because of a signal, and did not return.
+ gdb_test "print \$_exitcode" " = void" \
+ "\$_exitcode is still void after SIGSEGV"
+
+ # Re-run to main, i.e., restart the executable.
+ rerun_to_main
+
+ # Print the $_exitsignal again. Even in this normal scenario, it
+ # should still contain the signal triggered in the other run.
+ gdb_test "print \$_exitsignal" " = 11" \
+ "\$_exitsignal is 11 (SIGSEGV) after restarting the inferior"
+
+ # And, again, print $_exitcode.
+ gdb_test "print \$_exitcode" " = void" \
+ "\$_exitcode is still void after restarting the inferior"
}
-# Checking $_exitsignal and $_exitcode, both should be void before the
-# inferior is executed.
-gdb_test "print \$_exitsignal" " = void" \
- "\$_exitsignal is void before normal inferior is executed"
-gdb_test "print \$_exitcode" " = void" \
- "\$_exitcode is void before normal inferior is executed"
-
-# Run the inferior until the end.
-if { ![runto_main] } {
- return
+# Test the behavior of $_exit{code,signal} during a normal inferior
+# execution.
+proc test_normal {} {
+ clean_restart $::exec2
+
+ # Check $_exitsignal and $_exitcode, both should be void before
+ # the inferior is executed.
+ gdb_test "print \$_exitsignal" " = void" \
+ "\$_exitsignal is void before normal inferior is executed"
+ gdb_test "print \$_exitcode" " = void" \
+ "\$_exitcode is void before normal inferior is executed"
+
+ # Run the inferior until the end.
+ if { ![runto_main] } {
+ return
+ }
+
+ gdb_continue_to_end
+
+ # Check $_exitcode. It should be 0.
+ gdb_test "print \$_exitcode" " = 0" \
+ "\$_exitcode is zero after normal inferior is executed"
+
+ # Check $_exitsignal. It should still be void, since the inferior
+ # has not received any signal.
+ gdb_test "print \$_exitsignal" " = void" \
+ "\$_exitsignal is still void after normal inferior is executed"
}
-gdb_continue_to_end
-
-# Checking $_exitcode. It should be 0.
-gdb_test "print \$_exitcode" " = 0" \
- "\$_exitcode is zero after normal inferior is executed"
-
-# Checking $_exitsignal. It should still be void, since the inferior
-# has not received any signal.
-gdb_test "print \$_exitsignal" " = void" \
- "\$_exitsignal is still void after normal inferior is executed"
+with_test_prefix "signal" {
+ test_signal
+}
+with_test_prefix "normal" {
+ test_normal
+}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 04/11] gdb.base/exitsignal.exp: Exit with non-zero
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
` (2 preceding siblings ...)
2026-05-25 19:18 ` [PATCH v2 03/11] Adjust gdb.base/exitsignal.exp for MinGW, separate program names Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 05/11] gdb.base/exitsignal.exp: Test attaching too Pedro Alves
` (6 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
gdb.base/exitsignal.exp currently tests exiting with exit code 0.
But, testing for 0 is typically more "dangerous" in the sense that
it's easy for some bug in GDB or the target backend to fail to extract
the exit code and return 0. Make it test exit code 1 instead.
gdb.base/coredump-filter-build-id.exp currently reuses normal.c. It
could still use it with this patch, but it seems better to me to
remove the coupling. Simply add a new .c file for that testcase,
which still returns 0.
Change-Id: I49ccfdbbe4be4445172476e7dd4e06142c8a672e
commit-id: d6b526ac
---
.../gdb.base/coredump-filter-build-id.c | 22 +++++++++++++++++++
.../gdb.base/coredump-filter-build-id.exp | 2 +-
gdb/testsuite/gdb.base/exitsignal.exp | 8 +++----
gdb/testsuite/gdb.base/normal.c | 7 ++++--
4 files changed, 32 insertions(+), 7 deletions(-)
create mode 100644 gdb/testsuite/gdb.base/coredump-filter-build-id.c
diff --git a/gdb/testsuite/gdb.base/coredump-filter-build-id.c b/gdb/testsuite/gdb.base/coredump-filter-build-id.c
new file mode 100644
index 00000000000..d78df952cb5
--- /dev/null
+++ b/gdb/testsuite/gdb.base/coredump-filter-build-id.c
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2013-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/>. */
+
+int
+main ()
+{
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.base/coredump-filter-build-id.exp b/gdb/testsuite/gdb.base/coredump-filter-build-id.exp
index d267221e1bf..d8251e5c25f 100644
--- a/gdb/testsuite/gdb.base/coredump-filter-build-id.exp
+++ b/gdb/testsuite/gdb.base/coredump-filter-build-id.exp
@@ -21,7 +21,7 @@
# external tool, eu-unstrip, to verify if the corefile contains
# build-ids.
-standard_testfile "normal.c"
+standard_testfile
# This test is Linux x86_64 only.
if { ![istarget *-*-linux*] } {
diff --git a/gdb/testsuite/gdb.base/exitsignal.exp b/gdb/testsuite/gdb.base/exitsignal.exp
index 4f4c53e37cb..7ae6e19b093 100644
--- a/gdb/testsuite/gdb.base/exitsignal.exp
+++ b/gdb/testsuite/gdb.base/exitsignal.exp
@@ -128,11 +128,11 @@ proc test_normal {} {
return
}
- gdb_continue_to_end
+ gdb_test "continue" " exited with code 01\\\].*" "continue to exit"
- # Check $_exitcode. It should be 0.
- gdb_test "print \$_exitcode" " = 0" \
- "\$_exitcode is zero after normal inferior is executed"
+ # Check $_exitcode. It should be 1.
+ gdb_test "print \$_exitcode" " = 1" \
+ "\$_exitcode is one after normal inferior is executed"
# Check $_exitsignal. It should still be void, since the inferior
# has not received any signal.
diff --git a/gdb/testsuite/gdb.base/normal.c b/gdb/testsuite/gdb.base/normal.c
index dd371746235..ba04c861dad 100644
--- a/gdb/testsuite/gdb.base/normal.c
+++ b/gdb/testsuite/gdb.base/normal.c
@@ -15,10 +15,13 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
-/* This test is just a normal return 0. */
+/* This test is just a normal return 1. */
int
main (int argc, char *argv[])
{
- return 0;
+ /* Non-zero specifically, as it would be otherwise easier for a
+ buggy GDB to report exit code 0 when it shouldn't and that
+ wouldn't be noticed. */
+ return 1;
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 05/11] gdb.base/exitsignal.exp: Test attaching too
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
` (3 preceding siblings ...)
2026-05-25 19:18 ` [PATCH v2 04/11] gdb.base/exitsignal.exp: Exit with non-zero Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 06/11] gdb/testsuite: Add mechanism to compile Windows native programs on Cygwin Pedro Alves
` (5 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
On some targets, like Cygwin (see the following commit), extracting
the exit code works differently depending on whether the inferior was
run by GDB, or GDB attached to an existing process. Extend
gdb.base/exitsignal.exp to test both scenarios.
Note: We add a wait_for_gdb sleep loop to segv.c and normal.c so the
attach path can synchronize with GDB before the inferior runs. For
simplicity, the run path pokes the same variable from setup, so
behavior there is unchanged.
The testcase passes cleanly on Linux, native and gdbserver, and on
MinGW too. Cygwin still requires more fixes.
Change-Id: I9908aef0cea3eaec8134f05e347702baf753d10c
commit-id: b74921d8
---
gdb/testsuite/gdb.base/exitsignal.exp | 86 +++++++++++++++++++++------
gdb/testsuite/gdb.base/normal.c | 7 +++
gdb/testsuite/gdb.base/segv.c | 7 +++
3 files changed, 82 insertions(+), 18 deletions(-)
diff --git a/gdb/testsuite/gdb.base/exitsignal.exp b/gdb/testsuite/gdb.base/exitsignal.exp
index 7ae6e19b093..7684646b546 100644
--- a/gdb/testsuite/gdb.base/exitsignal.exp
+++ b/gdb/testsuite/gdb.base/exitsignal.exp
@@ -21,6 +21,10 @@
# killed by a signal. However, if it was killed by an uncaught
# signal, then there is no way for it to have exited.
+# On some targets, like Cygwin, extracting the exit code works
+# differently depending on whether the inferior was run by GDB, or GDB
+# attached to an existing process. We exercise both cases.
+
require {!target_info exists gdb,nosignals}
set testfile "exitsignal"
@@ -43,16 +47,48 @@ if { [build_executable "failed to build $exec2" ${exec2} "${srcfile2}" \
return -1
}
-proc test_signal {} {
+# Get the inferior under GDB's control in mode HOW ("run" or
+# "attach"), using BINFILE. In "attach" mode, spawn the binary and
+# attach to it; in "run" mode, run to main. In both modes, clear the
+# inferior's wait_for_gdb flag so the spin loop in main exits when
+# execution continues.
+
+proc setup {how binfile} {
+ if {$how == "run"} {
+ if { ![runto_main] } {
+ return
+ }
+ } else {
+ # Change directory so that the core file (for test_signal)
+ # doesn't end up outside the output directory.
+ with_cwd [standard_output_file {}] {
+ set ::test_spawn_id [spawn_wait_for_attach $binfile]
+ }
+ set test_pid [spawn_id_get_pid $::test_spawn_id]
+ gdb_test "attach $test_pid" "Attaching to program.*" \
+ "attach to process"
+ }
+
+ gdb_test "p wait_for_gdb = 0" " = 0"
+}
+
+# Counterpart to setup. In "attach" mode, kill the spawned process
+# started by setup. In "run" mode, nothing to do.
+
+proc teardown {how} {
+ if {$how == "attach"} {
+ kill_wait_spawned_process $::test_spawn_id
+ }
+}
+
+proc test_signal {how} {
clean_restart $::exec1
- # Run to main. But, before, change cwd to get the core into the
- # output directory.
+ # Get the inferior under GDB's control. But, before, change cwd
+ # so the core file ends up in the output directory.
set_inferior_cwd_to_output_dir
- if { ![runto_main] } {
- return
- }
+ setup $how $::binfile1
# Get the inferior's PID for later.
set pid [get_inferior_pid]
@@ -98,8 +134,15 @@ proc test_signal {} {
gdb_test "print \$_exitcode" " = void" \
"\$_exitcode is still void after SIGSEGV"
- # Re-run to main, i.e., restart the executable.
- rerun_to_main
+ # Start over.
+ if {$how == "run"} {
+ rerun_to_main
+ } else {
+ with_test_prefix "reattach" {
+ kill_wait_spawned_process $::test_spawn_id
+ setup $how $::binfile1
+ }
+ }
# Print the $_exitsignal again. Even in this normal scenario, it
# should still contain the signal triggered in the other run.
@@ -109,11 +152,13 @@ proc test_signal {} {
# And, again, print $_exitcode.
gdb_test "print \$_exitcode" " = void" \
"\$_exitcode is still void after restarting the inferior"
+
+ teardown $how
}
# Test the behavior of $_exit{code,signal} during a normal inferior
# execution.
-proc test_normal {} {
+proc test_normal {how} {
clean_restart $::exec2
# Check $_exitsignal and $_exitcode, both should be void before
@@ -124,10 +169,7 @@ proc test_normal {} {
"\$_exitcode is void before normal inferior is executed"
# Run the inferior until the end.
- if { ![runto_main] } {
- return
- }
-
+ setup $how $::binfile2
gdb_test "continue" " exited with code 01\\\].*" "continue to exit"
# Check $_exitcode. It should be 1.
@@ -138,11 +180,19 @@ proc test_normal {} {
# has not received any signal.
gdb_test "print \$_exitsignal" " = void" \
"\$_exitsignal is still void after normal inferior is executed"
-}
-with_test_prefix "signal" {
- test_signal
+ teardown $how
}
-with_test_prefix "normal" {
- test_normal
+
+foreach_with_prefix how {"run" "attach"} {
+ if {$how == "attach" && ![can_spawn_for_attach]} {
+ continue
+ }
+
+ with_test_prefix "signal" {
+ test_signal $how
+ }
+ with_test_prefix "normal" {
+ test_normal $how
+ }
}
diff --git a/gdb/testsuite/gdb.base/normal.c b/gdb/testsuite/gdb.base/normal.c
index ba04c861dad..fbe59653c97 100644
--- a/gdb/testsuite/gdb.base/normal.c
+++ b/gdb/testsuite/gdb.base/normal.c
@@ -17,9 +17,16 @@
/* This test is just a normal return 1. */
+#include <unistd.h>
+
+volatile int wait_for_gdb = 1;
+
int
main (int argc, char *argv[])
{
+ while (wait_for_gdb)
+ sleep (1);
+
/* Non-zero specifically, as it would be otherwise easier for a
buggy GDB to report exit code 0 when it shouldn't and that
wouldn't be noticed. */
diff --git a/gdb/testsuite/gdb.base/segv.c b/gdb/testsuite/gdb.base/segv.c
index fd43d95e9b2..e1c295f50a5 100644
--- a/gdb/testsuite/gdb.base/segv.c
+++ b/gdb/testsuite/gdb.base/segv.c
@@ -17,9 +17,16 @@
/* This test can be used just to generate a SIGSEGV. */
+#include <unistd.h>
+
+volatile int wait_for_gdb = 1;
+
int
main (int argc, char *argv[])
{
+ while (wait_for_gdb)
+ sleep (1);
+
/* Generating a SIGSEGV. */
*(volatile int *) 0;
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 06/11] gdb/testsuite: Add mechanism to compile Windows native programs on Cygwin
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
` (4 preceding siblings ...)
2026-05-25 19:18 ` [PATCH v2 05/11] gdb.base/exitsignal.exp: Test attaching too Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 07/11] Windows gdb+gdbserver: Share exit status logic Pedro Alves
` (4 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
This adds a new "win32" option to gdb_compile that lets us compile
native Windows programs when testing Cygwin. Will be used in a
following patch.
The testsuite tries to find the compiler to use automatically. You
can also explicitly set it with WIN32_CC_FOR_TARGET.
Change-Id: I86e954494cf7f88164cdf1c9127be46d5437986f
commit-id: c71b8812
---
gdb/testsuite/README | 22 ++++++++
gdb/testsuite/lib/gdb.exp | 104 +++++++++++++++++++++++++++++++++++++-
2 files changed, 124 insertions(+), 2 deletions(-)
diff --git a/gdb/testsuite/README b/gdb/testsuite/README
index 60fd322ea4d..479e81f90f5 100644
--- a/gdb/testsuite/README
+++ b/gdb/testsuite/README
@@ -414,6 +414,28 @@ Example:
make check-gdb TESTS="gdb.multi/multi-arch.exp" RUNTESTFLAGS="ARM_CC_FOR_TARGET=arm-linux-gnueabihf-gcc"
+WIN32_CC_FOR_TARGET
+
+The Cygwin ports of GDB and GDBserver include basic support for
+debugging native Windows programs. Some tests exercise this
+particular feature.
+
+By default, the testsuite tries to find a compiler capable of
+generating native Windows executables. If no compiler is found, or if
+the executable generated by the found compiler can't be executed
+correctly, the tests are marked UNSUPPORTED. The list of compiler
+names the testsuite tries can be found in
+gdb/testsuite/lib/gdb.exp:win32_cc_for_target.
+
+You can set WIN32_CC_FOR_TARGET to override the search and explicitly
+specify the compiler to use. This variable should contain the command
+line for the compiler, including the full path to it, if the compiler
+is not in $PATH.
+
+Example:
+
+ make check-gdb TESTS="gdb.base/exitsignal.exp" RUNTESTFLAGS="WIN32_CC_FOR_TARGET=x86_64-w64-mingw32-gcc"
+
Race detection
**************
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 28709004570..e21c1c1224f 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -6128,6 +6128,10 @@ proc gdb_can_simple_compile_nodebug {name code {type object} {compile_flags ""}
global gdb_saved_set_unbuffered_mode_obj
set gdb_saved_set_unbuffered_mode_obj ""
+# The name of the set_unbuffered_mode_saved.o object. Stored in a
+# global variable so it can be overridden in some cases.
+set gdb_saved_set_unbuffered_mode_obj_name set_unbuffered_mode_saved.o
+
# Escape STR sufficiently for use on host commandline.
proc escape_for_host { str } {
@@ -6201,6 +6205,7 @@ proc quote_for_host { args } {
# Fortran 90, Go or Rust.
# - debug: Build with debug information.
# - optimize: Build with optimization.
+# - win32: Build a native Windows program. Cygwin only.
proc gdb_compile {source dest type options} {
global GDB_TESTCASE_OPTIONS
@@ -6212,6 +6217,11 @@ proc gdb_compile {source dest type options} {
set outdir [file dirname $dest]
+ # Handle compiling native Windows programs when testing Cygwin.
+ if { [istarget *-*-cygwin*] && [lsearch -exact $options win32] != -1} {
+ return [gdb_compile_win32 $source $dest $type $options]
+ }
+
# GDB doesn't support minimal symbols in AIX, so fail the compilation
# if nodebug is requested for an AIX target.
if { [istarget *-*-aix*] && [lsearch -exact $options nodebug] != -1} {
@@ -6635,9 +6645,11 @@ proc gdb_compile {source dest type options} {
return $result
}
if {[is_remote host]} {
- set gdb_saved_set_unbuffered_mode_obj set_unbuffered_mode_saved.o
+ set gdb_saved_set_unbuffered_mode_obj \
+ $::gdb_saved_set_unbuffered_mode_obj_name
} else {
- set gdb_saved_set_unbuffered_mode_obj ${objdir}/set_unbuffered_mode_saved.o
+ set gdb_saved_set_unbuffered_mode_obj \
+ ${objdir}/$::gdb_saved_set_unbuffered_mode_obj_name
}
# Link a copy of the output object, because the
# original may be automatically deleted.
@@ -7003,6 +7015,38 @@ proc gdb_compile_openmp {source dest type options} {
return [gdb_compile $source $dest $type $options]
}
+# Build a native Windows program from SOURCE. See prefatory comment
+# for gdb_compile, above, for discussion of the parameters to this
+# proc. This is meant to be used on Cygwin, to smoke test debugging
+# native Windows programs with the Cygwin native backend.
+
+proc gdb_compile_win32 {source dest type options} {
+ if {![istarget *-*-cygwin*]} {
+ error "cygwin procedure called on non-cygwin target"
+ }
+
+ save_vars {
+ ::target_triplet
+ ::CC_FOR_TARGET
+ ::gdb_saved_set_unbuffered_mode_obj
+ ::gdb_saved_set_unbuffered_mode_obj_name
+ } {
+ # So we trigger the set_unbuffered_mode_obj paths.
+ set ::target_triplet "x86_64-w64-mingw32"
+
+ # We need a Win32-specific compilation of the
+ # set_unbuffered_mode object.
+ set ::gdb_saved_set_unbuffered_mode_obj ""
+ set ::gdb_saved_set_unbuffered_mode_obj_name \
+ set_unbuffered_mode_saved_win32.o
+
+ # Do this last, as this tries compiling internally.
+ set ::CC_FOR_TARGET [win32_cc_for_target]
+
+ return [gdb_compile $source $dest $type $options]
+ }
+}
+
# Send a command to GDB.
# For options for TYPE see gdb_stdin_log_write
@@ -11248,6 +11292,62 @@ gdb_caching_proc arm_cc_for_target {} {
return ""
}
+# Return the compiler that can generate native Windows executables.
+# Used when testing basic native Windows support on Cygwin. If
+# WIN32_CC_FOR_TARGET is set, use that. If not, try a few common
+# compiler names, making sure that the executable they produce can
+# run. Note, this assumes global state has been set to target native
+# Windows programs. See gdb_compile_win32.
+
+gdb_caching_proc win32_cc_for_target {} {
+ if {[info exists ::WIN32_CC_FOR_TARGET]} {
+ # If the user specified the compiler explicitly, then don't
+ # check whether the resulting binary runs outside GDB. Assume
+ # that it does, and if it turns out it doesn't, then the user
+ # should get loud FAILs, instead of UNSUPPORTED.
+ return $::WIN32_CC_FOR_TARGET
+ }
+
+ # Fallback to a few common compiler names. Also confirm the
+ # produced binary actually runs on the system before declaring
+ # we've found the right compiler.
+
+ # Only one compiler for now.
+ set compilers {
+ x86_64-w64-mingw32-gcc
+ }
+
+ save_vars {
+ ::CC_FOR_TARGET
+ } {
+ foreach compiler $compilers {
+ if {![is_remote host] && [which $compiler] == 0} {
+ # Avoid "default_target_compile: Can't find
+ # $compiler." warning issued from gdb_compile.
+ continue
+ }
+
+ set ::CC_FOR_TARGET $compiler
+
+ set src { int main() { return 0; } }
+ if {[gdb_simple_compile win32-test-prog $src executable]} {
+ set target_obj [gdb_remote_download target $obj]
+ set result [remote_exec target $target_obj]
+ set status [lindex $result 0]
+ set output [lindex $result 1]
+
+ file delete $obj
+
+ if { $output == "" && $status == 0} {
+ return $compiler
+ }
+ }
+ }
+ }
+
+ return ""
+}
+
# Step until the pattern REGEXP is found. Step at most
# MAX_STEPS times, but stop stepping once REGEXP is found.
# CURRENT matches current location
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 07/11] Windows gdb+gdbserver: Share exit status logic
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
` (5 preceding siblings ...)
2026-05-25 19:18 ` [PATCH v2 06/11] gdb/testsuite: Add mechanism to compile Windows native programs on Cygwin Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes Pedro Alves
` (3 subsequent siblings)
10 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
Move the exit status logic added by commit 559e7e5056 ("Improve
process exit status macros on MinGW") from both GDB and GDBserver to a
shared routine used by both.
The next patch extends this routine with Cygwin-specific decoding.
Change-Id: I4bf08c6beff0d1688064a81d49bbdd615643735e
commit-id: 586becd8
---
gdb/nat/windows-nat.c | 24 ++++++++++++++++++++++++
gdb/nat/windows-nat.h | 6 ++++++
gdb/windows-nat.c | 15 +++------------
gdbserver/win32-low.cc | 16 +++-------------
4 files changed, 36 insertions(+), 25 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index b093acda342..92f9394ca6d 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -18,6 +18,8 @@
#include "nat/windows-nat.h"
#include "gdbsupport/common-debug.h"
+#include "gdbsupport/gdb_signals.h"
+#include "gdbsupport/gdb_wait.h"
#include "target/target.h"
#undef GetModuleFileNameEx
@@ -694,6 +696,28 @@ windows_process_info::add_all_dlls ()
/* See nat/windows-nat.h. */
+target_waitstatus
+windows_process_info::exit_process_to_target_status
+ (const EXIT_PROCESS_DEBUG_INFO &info)
+{
+ DWORD exit_code = info.dwExitCode;
+ target_waitstatus tstatus;
+
+ /* If the exit status looks like a fatal exception, but we don't
+ recognize the exception's code, make the original exit status
+ value available, to avoid losing information. */
+ int exit_signal
+ = WIFSIGNALED (exit_code) ? WTERMSIG (exit_code) : -1;
+ if (exit_signal == -1)
+ tstatus.set_exited (exit_code);
+ else
+ tstatus.set_signalled (gdb_signal_from_host (exit_signal));
+
+ return tstatus;
+}
+
+/* See nat/windows-nat.h. */
+
std::string
event_code_to_string (DWORD event_code)
{
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index 52378765438..d2e6adb4f40 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -315,6 +315,12 @@ struct windows_process_info
});
}
+ /* Convert an EXIT_PROCESS_DEBUG_EVENT payload to a target wait
+ status. */
+
+ target_waitstatus exit_process_to_target_status
+ (const EXIT_PROCESS_DEBUG_INFO &info);
+
private:
/* Handle MS_VC_EXCEPTION when processing a stop. MS_VC_EXCEPTION is
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index a284438bd36..26333238cfa 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -62,7 +62,6 @@
#include "complaints.h"
#include "gdbsupport/gdb_tilde_expand.h"
#include "gdbsupport/pathstuff.h"
-#include "gdbsupport/gdb_wait.h"
#include "gdbsupport/symbol.h"
#include "inf-loop.h"
@@ -1610,17 +1609,9 @@ windows_nat_target::get_windows_debug_event
}
else if (windows_process->saw_create == 1)
{
- DWORD exit_status = current_event->u.ExitProcess.dwExitCode;
- /* If the exit status looks like a fatal exception, but we
- don't recognize the exception's code, make the original
- exit status value available, to avoid losing
- information. */
- int exit_signal
- = WIFSIGNALED (exit_status) ? WTERMSIG (exit_status) : -1;
- if (exit_signal == -1)
- ourstatus->set_exited (exit_status);
- else
- ourstatus->set_signalled (gdb_signal_from_host (exit_signal));
+ *ourstatus
+ = (windows_process->exit_process_to_target_status
+ (current_event->u.ExitProcess));
thread_id = current_event->dwThreadId;
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 6f1cf5ed025..550994b0bf2 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -33,7 +33,6 @@
#include <process.h>
#include "gdbsupport/gdb_tilde_expand.h"
#include "gdbsupport/common-inferior.h"
-#include "gdbsupport/gdb_wait.h"
using namespace windows_nat;
@@ -1061,18 +1060,9 @@ get_child_debug_event (DWORD *continue_status,
break;
case EXIT_PROCESS_DEBUG_EVENT:
- {
- DWORD exit_status = current_event->u.ExitProcess.dwExitCode;
- /* If the exit status looks like a fatal exception, but we
- don't recognize the exception's code, make the original
- exit status value available, to avoid losing information. */
- int exit_signal
- = WIFSIGNALED (exit_status) ? WTERMSIG (exit_status) : -1;
- if (exit_signal == -1)
- ourstatus->set_exited (exit_status);
- else
- ourstatus->set_signalled (gdb_signal_from_host (exit_signal));
- }
+ *ourstatus
+ = (windows_process.exit_process_to_target_status
+ (current_event->u.ExitProcess));
continue_last_debug_event (DBG_CONTINUE, debug_threads);
break;
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
` (6 preceding siblings ...)
2026-05-25 19:18 ` [PATCH v2 07/11] Windows gdb+gdbserver: Share exit status logic Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-26 11:31 ` Eli Zaretskii
2026-05-27 22:00 ` Thiago Jung Bauermann
2026-05-25 19:18 ` [PATCH v2 09/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, thread IDs Pedro Alves
` (2 subsequent siblings)
10 siblings, 2 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
On native Cygwin, GDB misreports the inferior's exit reason in several
common cases, resulting in several gdb.base/exitsignal.exp failures:
$ grep FAIL gdb.sum
FAIL: gdb.base/exitsignal.exp: how=run: signal: program terminated with SIGSEGV (the program exited)
FAIL: gdb.base/exitsignal.exp: how=run: signal: $_exitsignal is 11 (SIGSEGV) after SIGSEGV.
FAIL: gdb.base/exitsignal.exp: how=run: signal: $_exitcode is still void after SIGSEGV
FAIL: gdb.base/exitsignal.exp: how=run: signal: $_exitsignal is 11 (SIGSEGV) after restarting the inferior
FAIL: gdb.base/exitsignal.exp: how=run: signal: $_exitcode is still void after restarting the inferior
FAIL: gdb.base/exitsignal.exp: how=run: normal: continue to exit
FAIL: gdb.base/exitsignal.exp: how=run: normal: $_exitcode is one after normal inferior is executed
FAIL: gdb.base/exitsignal.exp: how=run: normal: $_exitsignal is still void after normal inferior is executed
FAIL: gdb.base/exitsignal.exp: how=attach: normal: continue to exit (the program exited)
FAIL: gdb.base/exitsignal.exp: how=attach: normal: $_exitcode is one after normal inferior is executed
For example, from gdb.log, the normal exit case:
...
[Thread 14300.0x4214 (id 1) exited with code 1]
[Thread 14300.0x1b1c (id 4) exited with code 1]
[Thread 14300.0x1e2c (id 2) exited with code 1]
Program terminated with signal SIGHUP, Hangup.
The program no longer exists.
(gdb) FAIL: gdb.base/exitsignal.exp: how=run: normal: continue to exit
The program in fact exited normally with code 1. SIGHUP happens to be
signal 1, and GDB picked the wrong interpretation.
Similarly, for the signal termination case:
...
continue
Continuing.
[Thread 4600.0x3104 (id 4) exited with code 2816]
[Thread 4600.0x2bcc (id 3) exited with code 2816]
[Thread 4600.0x2f44 (id 1) exited with code 2816]
[Inferior 1 (process 4600) exited with code 05400]
(gdb) FAIL: gdb.base/exitsignal.exp: how=run: signal: program terminated with SIGSEGV (the program exited)
Here the inferior died with SIGSEGV, but GDB reported exit decimal
2816 / octal 05400 / hex 0x0B00, which is SIGSEGV swapped into the
high byte of a waitpid exit status.
The problem is that Cygwin waitpid exit status and Windows exit codes
do not have the same encoding, and GDB & GDBserver do not know about
this.
This commit fixes it. It adds a Cygwin-specific branch to the code
that determines the terminating signal and status of a program. The
branch for native Windows/MinGW GDB is left intact, no behavior change
there.
The way to decode the exit codes is a little bit tricky, see detailed
comments added by the patch. To exercise the "raw NTSTATUS error
code" path in windows_process_info::exit_process_to_target_status,
gdb.base/exitsignal.exp is extended to debug a native Windows program
that crashes with a segfault (STATUS_ACCESS_VIOLATION).
With this, gdb.base/exitsignal.exp passes cleanly on Cygwin.
Change-Id: Icaebcc234b71927915c996fd120884604441415b
commit-id: bd0fbb9c
---
gdb/nat/windows-nat.c | 153 +++++++++++++++++++++++++-
gdb/nat/windows-nat.h | 29 +++++
gdb/testsuite/gdb.base/exitsignal.exp | 32 +++++-
gdb/windows-nat.c | 5 +
gdbserver/win32-low.cc | 5 +
5 files changed, 217 insertions(+), 7 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 92f9394ca6d..27bc65225cc 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -654,6 +654,7 @@ windows_process_info::add_dll (LPVOID load_addr)
at which the DLL was loaded is equal to LOAD_ADDR. */
if (!(load_addr != nullptr && mi.lpBaseOfDll != load_addr))
{
+ maybe_note_cygwin1_dll (name);
handle_load_dll (name, mi.lpBaseOfDll);
if (load_addr != nullptr)
return;
@@ -681,7 +682,10 @@ windows_process_info::dll_loaded_event (const DEBUG_EVENT ¤t_event)
by enumerating all the DLLs loaded into the inferior, looking for
one that is loaded at base address = lpBaseOfDll. */
if (dll_name != nullptr)
- handle_load_dll (dll_name, event->lpBaseOfDll);
+ {
+ maybe_note_cygwin1_dll (dll_name);
+ handle_load_dll (dll_name, event->lpBaseOfDll);
+ }
else if (event->lpBaseOfDll != nullptr)
add_dll (event->lpBaseOfDll);
}
@@ -694,6 +698,48 @@ windows_process_info::add_all_dlls ()
add_dll (nullptr);
}
+#ifdef __CYGWIN__
+
+/* See nat/windows-nat.h. */
+
+void
+windows_process_info::maybe_note_cygwin1_dll (const char *dll_path)
+{
+ const char *base = dll_path + strlen (dll_path);
+ while (base > dll_path && base[-1] != '/' && base[-1] != '\\')
+ base--;
+ if (strcasecmp (base, "cygwin1.dll") == 0)
+ cygwin1_dll_loaded = true;
+}
+
+/* See nat/windows-nat.h. */
+
+bool
+inferior_started_by_cygwin (DWORD winpid, bool attaching)
+{
+ /* In the run (non-attach) case this is called early when the
+ inferior has only just reached its first instruction and
+ cygwin1.dll hasn't initialized itself yet -- GDB launched the
+ inferior with raw CreateProcess, not through Cygwin's fork/spawn
+ path, so PID_CYGPARENT is necessarily false, so we can shortcut
+ without calling Cygwin. */
+ if (!attaching)
+ return false;
+
+ /* Note CW_WINPID_TO_CYGWIN_PID never fails. It returns a synthetic
+ pid for non-Cygwin or unknown winpids, in which case CW_GETPINFO
+ returns either a pinfo with PID_CYGPARENT unset, or NULL. */
+ auto cygpid = (pid_t) cygwin_internal (CW_WINPID_TO_CYGWIN_PID, winpid);
+
+ auto *pinfo = (external_pinfo *) cygwin_internal (CW_GETPINFO, cygpid);
+ if (pinfo == nullptr)
+ return false;
+
+ return (pinfo->process_state & PID_CYGPARENT) != 0;
+}
+
+#endif /* __CYGWIN__. */
+
/* See nat/windows-nat.h. */
target_waitstatus
@@ -703,6 +749,110 @@ windows_process_info::exit_process_to_target_status
DWORD exit_code = info.dwExitCode;
target_waitstatus tstatus;
+#ifdef __CYGWIN__
+ /* A Cygwin parent waiting on a Cygwin child via waitpid doesn't go
+ through GetExitCodeProcess / the Win32 exit code at all. It
+ reads the child's wait status directly out of the child's Cygwin
+ pinfo (shared memory), set by pinfo::exit in
+ winsup/cygwin/pinfo.cc. So sys/wait.h macros apply to that value
+ verbatim.
+
+ GDB, however, even though it is itself a Cygwin program, drives
+ its inferiors via the native Win32 debugger API: it spawns them
+ with CreateProcess (DEBUG_PROCESS), not via Cygwin's
+ fork/spawn/posix_spawn, and consumes
+ EXIT_PROCESS_DEBUG_EVENT.dwExitCode from WaitForDebugEvent rather
+ than calling waitpid. That dwExitCode value comes from the
+ inferior's ExitProcess call.
+
+ What that value means depends on two orthogonal things:
+
+ 1. Is the inferior a Cygwin process at all? If not, dwExitCode
+ is a raw Win32 exit value.
+
+ 2. For a Cygwin inferior, was it created through Cygwin's spawn
+ path?
+
+ - If not, cygwin1.dll's pinfo::exit byte-swaps the wait status
+ on the way out, so that the meaningful exit value lands in
+ the low byte where native Win32 consumers (cmd.exe's "echo
+ %errorlevel%", and bare GetExitCodeProcess readers) expect
+ it. This is the case for Cygwin inferiors that we run, via
+ CreateProcess.
+
+ - If yes, cygwin1.dll does not swap. We see this case if we
+ attach to an already-running process with a Cygwin parent.
+
+ See winsup/cygwin/pinfo.cc:
+
+ int exitcode = self->exitcode & 0xffff;
+ if (!self->cygstarted)
+ exitcode = ((exitcode & 0xff) << 8) | ((exitcode >> 8) & 0xff);
+ ...
+ ExitProcess (exitcode);
+ */
+
+ /* The inferior may also exit with a raw NTSTATUS error code, e.g.,
+ STATUS_ACCESS_VIOLATION (0xc0000005), without going through the
+ pinfo::exit at all -- for example, if the unhandled-exception
+ filter didn't run, or for processes that don't link cygwin1.dll.
+ Detect those and map them the same way Cygwin's set_exit_code
+ does in winsup/cygwin/pinfo.cc. */
+ if (exit_code >= 0xc0000000)
+ {
+ gdb_signal sig;
+ switch (exit_code)
+ {
+ case EXCEPTION_ACCESS_VIOLATION:
+ sig = GDB_SIGNAL_SEGV;
+ break;
+ case EXCEPTION_ILLEGAL_INSTRUCTION:
+ sig = GDB_SIGNAL_ILL;
+ break;
+ case STATUS_NO_MEMORY:
+ sig = GDB_SIGNAL_BUS;
+ break;
+ case STATUS_CONTROL_C_EXIT:
+ sig = GDB_SIGNAL_INT;
+ break;
+ default:
+ /* Cygwin maps any other NTSTATUS to exit 127. */
+ tstatus.set_exited (127);
+ return tstatus;
+ }
+ tstatus.set_signalled (sig);
+ return tstatus;
+ }
+
+ if (!this->cygwin1_dll_loaded)
+ {
+ /* Non-Cygwin inferior: dwExitCode is a raw Win32 exit value.
+ Limit to 8 bits, like Cygwin does, matching what happens with
+ Cygwin inferiors. */
+ tstatus.set_exited (exit_code & 0xff);
+ return tstatus;
+ }
+
+ /* Note: when GDB attaches to a Cygwin inferior and the inferior is
+ then killed externally (e.g., taskkill /F with exit code 1), GDB
+ and Cygwin disagree. Cygwin's parent waitpid reports WIFEXITED,
+ code=1; GDB reports SIGHUP (signal 1, no swap below because
+ started_by_cygwin). Cygwin's parent distinguishes "pinfo::exit
+ ran" from "didn't run" via the child's wait pipe and only applies
+ the swap-undo for the former. GDB has only dwExitCode and can't
+ tell. This can't be solved without Cygwin's help. OTOH, such an
+ external termination steps out of Cygwin and arguably falls into
+ undefined-behavior territory, so it is less important than the
+ other cases. */
+
+ int wstatus = exit_code & 0xffff;
+ if (!this->started_by_cygwin)
+ wstatus = ((wstatus & 0xff) << 8) | ((wstatus >> 8) & 0xff);
+ if (!WIFSIGNALED (wstatus))
+ tstatus.set_exited (WEXITSTATUS (wstatus));
+ else
+ tstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstatus)));
+#else
/* If the exit status looks like a fatal exception, but we don't
recognize the exception's code, make the original exit status
value available, to avoid losing information. */
@@ -712,6 +862,7 @@ windows_process_info::exit_process_to_target_status
tstatus.set_exited (exit_code);
else
tstatus.set_signalled (gdb_signal_from_host (exit_signal));
+#endif
return tstatus;
}
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index d2e6adb4f40..2e4db9832ae 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -224,6 +224,23 @@ struct windows_process_info
DWORD process_id = 0;
DWORD main_thread_id = 0;
+#ifdef __CYGWIN__
+ /* True if the inferior was created through Cygwin's spawn path
+ (i.e., its Cygwin pinfo has PID_CYGPARENT set). We need this at
+ exit time, but we cache it early when we start debugging the
+ inferior, because by exit time the inferior's Cygwin pinfo may
+ have been torn down (CW_GETPINFO returns NULL). */
+ bool started_by_cygwin = false;
+
+ /* True if cygwin1.dll is loaded into the inferior. */
+ bool cygwin1_dll_loaded = false;
+
+ /* If DLL_PATH is cygwin1.dll, set cygwin1_dll_loaded to true. */
+ void maybe_note_cygwin1_dll (const char *dll_path);
+#else
+ void maybe_note_cygwin1_dll (const char *) {}
+#endif
+
#ifdef __x86_64__
/* The target is a WOW64 process */
bool wow64_process = false;
@@ -353,6 +370,18 @@ struct windows_process_info
int get_exec_module_filename (char *exe_name_ret, size_t exe_name_max_len);
};
+#ifdef __CYGWIN__
+/* Return true if the process with native Windows pid WINPID was
+ started by a Cygwin parent -- that is, its Cygwin pinfo exists and
+ has PID_CYGPARENT set. Returns false if the process is not a
+ Cygwin process at all, or if its parent is not a Cygwin process.
+
+ ATTACHING indicates whether GDB is attaching to an already-running
+ inferior (true) or has just launched it via CreateProcess
+ (false). */
+extern bool inferior_started_by_cygwin (DWORD winpid, bool attaching);
+#endif
+
/* Return a string version of EVENT_CODE. */
extern std::string event_code_to_string (DWORD event_code);
diff --git a/gdb/testsuite/gdb.base/exitsignal.exp b/gdb/testsuite/gdb.base/exitsignal.exp
index 7684646b546..348c7a72eff 100644
--- a/gdb/testsuite/gdb.base/exitsignal.exp
+++ b/gdb/testsuite/gdb.base/exitsignal.exp
@@ -37,6 +37,10 @@ set exec2 "normal"
set srcfile2 ${exec2}.c
set binfile2 [standard_output_file ${exec2}]
+set exec3 "segv-win32"
+set srcfile3 ${exec1}.c
+set binfile3 [standard_output_file ${exec3}]
+
if { [build_executable "failed to build $exec1" ${exec1} "${srcfile1}" \
{debug}] == -1 } {
return -1
@@ -47,6 +51,16 @@ if { [build_executable "failed to build $exec2" ${exec2} "${srcfile2}" \
return -1
}
+# On Cygwin, also build a pure-Win32 segv binary, used to test that
+# GDB extracts the terminating SIGSEGV out of the 0xc0000005
+# (STATUS_ACCESS_VIOLATION) Windows exit code.
+if {[istarget "*-*-cygwin*"]} {
+ if { [build_executable "failed to build $exec3" ${exec3} "${srcfile3}" \
+ {debug win32}] == -1} {
+ return -1
+ }
+}
+
# Get the inferior under GDB's control in mode HOW ("run" or
# "attach"), using BINFILE. In "attach" mode, spawn the binary and
# attach to it; in "run" mode, run to main. In both modes, clear the
@@ -81,14 +95,14 @@ proc teardown {how} {
}
}
-proc test_signal {how} {
- clean_restart $::exec1
+proc test_signal {how exec binfile} {
+ clean_restart $exec
# Get the inferior under GDB's control. But, before, change cwd
# so the core file ends up in the output directory.
set_inferior_cwd_to_output_dir
- setup $how $::binfile1
+ setup $how $binfile
# Get the inferior's PID for later.
set pid [get_inferior_pid]
@@ -106,7 +120,8 @@ proc test_signal {how} {
gdb_test "continue" "(Thread .*|Program) received signal SIGSEGV.*" \
"trigger SIGSEGV"
- if {[istarget "*-*-mingw*"]} {
+ if {[istarget "*-*-mingw*"]
+ || ([istarget "*-*-cygwin*"] && $binfile == $::binfile3)} {
# We're debugging a pure Win32 program with no SEH handler. The
# previous continue caught the first-chance exception. Now we
# catch the second-chance one.
@@ -140,7 +155,7 @@ proc test_signal {how} {
} else {
with_test_prefix "reattach" {
kill_wait_spawned_process $::test_spawn_id
- setup $how $::binfile1
+ setup $how $binfile
}
}
@@ -190,9 +205,14 @@ foreach_with_prefix how {"run" "attach"} {
}
with_test_prefix "signal" {
- test_signal $how
+ test_signal $how $exec1 $binfile1
}
with_test_prefix "normal" {
test_normal $how
}
+ if {[istarget "*-*-cygwin*"]} {
+ with_test_prefix "signal, win32" {
+ test_signal $how $exec3 $binfile3
+ }
+ }
}
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 26333238cfa..16269671d95 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1994,6 +1994,11 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
phase, and then process them all in one batch now. */
windows_process->add_all_dlls ();
+#ifdef __CYGWIN__
+ windows_process->started_by_cygwin
+ = inferior_started_by_cygwin (pid, attaching);
+#endif
+
windows_process->windows_initialization_done = 1;
return;
}
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 550994b0bf2..2150045e188 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -368,6 +368,11 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
phase, and then process them all in one batch now. */
windows_process.add_all_dlls ();
+#ifdef __CYGWIN__
+ windows_process.started_by_cygwin
+ = inferior_started_by_cygwin (pid, attached);
+#endif
+
windows_process.child_initialization_done = 1;
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 09/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, thread IDs
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
` (7 preceding siblings ...)
2026-05-25 19:18 ` [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 10/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, no fork Pedro Alves
2026-05-25 19:18 ` [PATCH v2 11/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, "info proc" => "inferior" Pedro Alves
10 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
This commit fixes a couple issues in gdb.python/py-events.exp for
Cygwin and MinGW:
1) - GDB prints Windows thread IDs in hex:
(gdb) thread
[Current thread is 1 (Thread 9528.0xa9c)]
The current code assumes decimal, so we only (incorrectly) extract the
"0" after the dot.
2) - Thread and process ID number spaces are different.
The current code assumes that the extracted thread ID is the same
number as the extracted process ID, which is not true on Windows.
Also tested on x86_64-unknown-linux-gnu.
Change-Id: Iebcc07c3ad0845b548334c0d5177b3ab9e9350cf
commit-id: 8009478e
---
gdb/testsuite/gdb.python/py-events.exp | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/gdb/testsuite/gdb.python/py-events.exp b/gdb/testsuite/gdb.python/py-events.exp
index 16a290c31c2..b07eb2ec9b0 100644
--- a/gdb/testsuite/gdb.python/py-events.exp
+++ b/gdb/testsuite/gdb.python/py-events.exp
@@ -76,17 +76,23 @@ delete_breakpoints
# Test inferior call events
set process_id "invalid"
+set thread_id "invalid"
gdb_test_multiple "thread" "get current thread" {
-re -wrap "process ($decimal).*" {
set process_id $expect_out(1,string)
+ set thread_id $process_id
pass $gdb_test_name
}
-re -wrap "Thread $hex \\(LWP ($decimal)\\).*" {
set process_id $expect_out(1,string)
+ set thread_id $process_id
pass $gdb_test_name
}
- -re -wrap "Thread $decimal\.($decimal).*" {
+ -re -wrap "Thread ($decimal)\.($hex|$decimal).*" {
set process_id $expect_out(1,string)
+ set thread_id $expect_out(2,string)
+ # Convert from hex to decimal.
+ set thread_id [expr {$thread_id}]
pass $gdb_test_name
}
}
@@ -99,9 +105,9 @@ gdb_test_multiple "print do_nothing" "get address of do_nothing" {
}
set expected [list "event type: pre-call"]
-lappend expected "ptid: \\($process_id, $process_id, 0\\)" "address: $addr"
+lappend expected "ptid: \\($process_id, $thread_id, 0\\)" "address: $addr"
lappend expected "event type: post-call"
-lappend expected "ptid: \\($process_id, $process_id, 0\\)" "address: $addr"
+lappend expected "ptid: \\($process_id, $thread_id, 0\\)" "address: $addr"
gdb_test_sequence "call do_nothing()" "" $expected
# Test register changed event
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 10/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, no fork
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
` (8 preceding siblings ...)
2026-05-25 19:18 ` [PATCH v2 09/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, thread IDs Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 11/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, "info proc" => "inferior" Pedro Alves
10 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
The gdb.python/py-events.exp testcase currently uses fork, and relies
on "set detach-on-fork off".
Cygwin does support the fork syscall, but GDB can't follow forks
there, so "detach-on-fork off" has no effect. And also, there is no
fork on native Windows, which makes the testcase unusable on MinGW
currently.
I don't see any reason the testcase needs to use fork or multiple
inferiors. We can replace what those parts were testing by more
focused tests:
- The clear_objfiles event was tested via following a fork. Instead,
test a more directed "file" command.
- Two-inferior quit was being used to test that gdb.ExitedEvent has
no "exit_code". Quitting while an inferior is being debugged makes
GDB kill the inferior. What's really being tested is the kill
path, so write an explicit (single-inferior) kill test.
Tested on x86_64-unknown-linux-gnu.
Change-Id: I21ee8af7b52653c6fdff9b4c1596cdde3cfe751a
commit-id: de2bf164
---
gdb/testsuite/gdb.python/py-events.c | 3 --
gdb/testsuite/gdb.python/py-events.exp | 59 +++++++++++++-------------
2 files changed, 30 insertions(+), 32 deletions(-)
diff --git a/gdb/testsuite/gdb.python/py-events.c b/gdb/testsuite/gdb.python/py-events.c
index b1910f75ed9..6a0b407042a 100644
--- a/gdb/testsuite/gdb.python/py-events.c
+++ b/gdb/testsuite/gdb.python/py-events.c
@@ -15,12 +15,9 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
-#include <unistd.h>
-
extern void do_nothing (void);
int second(){
- fork() ;
return 12;
}
diff --git a/gdb/testsuite/gdb.python/py-events.exp b/gdb/testsuite/gdb.python/py-events.exp
index b07eb2ec9b0..a34a423c014 100644
--- a/gdb/testsuite/gdb.python/py-events.exp
+++ b/gdb/testsuite/gdb.python/py-events.exp
@@ -45,8 +45,6 @@ gdb_breakpoint "main" {temporary}
gdb_test "run" ".*event type: new_objfile.*new objfile name.*" "new objfile notification"
-gdb_test_no_output "set detach-on-fork off" ""
-
gdb_test "test-events" "Event testers registered."
gdb_breakpoint "first"
@@ -217,24 +215,13 @@ proc get_process_id {test} {
return ${process_id}
}
-set process_id [get_process_id "get inferior 1 process id"]
+set process_id [get_process_id "get inferior process id"]
gdb_test "continue" ".*event type: continue.*
-.*clear_objfiles\[\r\n\]*progspace: .*py-events.*
.*event type: exit.*
.*exit code: 12.*
.*exit inf: 1.*
.*exit pid: $process_id.*
-dir ok: True.*" "Inferior 1 terminated."
-
-gdb_test "inferior 2" ".*Switching to inferior 2.*"
-set process_id [get_process_id "get inferior 2 process id"]
-gdb_test "continue" ".*event type: continue.*
-.*event type: exit.*
-.*exit code: 12.*
-.*exit inf: 2.*
-.*exit pid: $process_id.*
-dir ok: True.*" "Inferior 2 terminated."
-
+dir ok: True.*" "inferior terminated"
# Test before_prompt event.
gdb_test_multiline "define new user command" \
@@ -286,29 +273,30 @@ with_test_prefix "inferior continue exit" {
gdb_test "print \$_foo" "= 2" "check foo after start continue"
}
-# Check that when GDB exits, we see gdb.ExitedEvent objects with no
-# 'exit_code' attribute, and that a gdb.GdbExitingEvent is emitted.
-with_test_prefix "gdb exiting: normal" {
+# Check that when GDB kills an inferior, we see gdb.ExitedEvent
+# objects with no 'exit_code' attribute.
+with_test_prefix "kill inferior" {
+ if {![runto_main]} {
+ return
+ }
gdb_test "test-exiting-event normal" "GDB exiting event registered\\."
+ gdb_test "with confirm off -- kill" \
+ "event type: exit\r\nexit code: not-present\r\nexit inf: $decimal\r\nexit pid: $decimal\r\ndir ok: False\r\n.*" \
+ "exit code not present"
+}
+
+# Check that when GDB exits, we see that a gdb.GdbExitingEvent is
+# emitted.
+with_test_prefix "gdb exiting: normal" {
set saw_exiting_event 0
- set saw_inferior_exit 0
- gdb_test_multiple "quit" "" {
- -re "Quit anyway\\? \\(y or n\\) $" {
- send_gdb "y\n"
- exp_continue
- }
+ gdb_test_multiple "with confirm off -- quit" "quit" {
-re "event type: gdb-exiting\r\nexit code: $decimal" {
incr saw_exiting_event
exp_continue
}
- -re "event type: exit\r\nexit code: not-present\r\nexit inf: $decimal\r\nexit pid: $decimal\r\ndir ok: False\r\n" {
- incr saw_inferior_exit
- exp_continue
- }
eof {
gdb_assert { $saw_exiting_event == 1 }
- gdb_assert { $saw_inferior_exit == 2 }
pass $gdb_test_name
}
}
@@ -354,3 +342,16 @@ with_test_prefix "gdb exiting: error" {
}
}
}
+
+# Test clear_objfiles event.
+
+with_test_prefix "clear_objfiles" {
+ clean_restart ${testfile}
+
+ gdb_test_no_output "source ${pyfile}" "load python file"
+ gdb_test "test-objfile-events" "Object file events registered."
+
+ gdb_test "with confirm off -- file" \
+ "event type: clear_objfiles\r\nprogspace: None\r\n.*" \
+ "file cleared"
+}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 11/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, "info proc" => "inferior"
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
` (9 preceding siblings ...)
2026-05-25 19:18 ` [PATCH v2 10/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, no fork Pedro Alves
@ 2026-05-25 19:18 ` Pedro Alves
10 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-25 19:18 UTC (permalink / raw)
To: gdb-patches
The testcase is using "info proc" to extract the inferior's process
ID. But "info proc" does not exist on all targets, including Windows.
Switch to using the get_inferior_pid routine from lib/gdb.exp, which
uses "inferior" instead.
With this fixed, the testcase passes cleanly on Cygwin. I haven't
tested on MinGW (I'm not set up for Python testing there currently),
but at least (since the previous patches) the test should be able to
compile & run there now.
Also tested on x86_64-unknown-linux-gnu.
Change-Id: If6ff482ceb011d9afe5ed40ef7e4e2f2cad8cae8
commit-id: 172fe262
---
gdb/testsuite/gdb.python/py-events.exp | 14 +-------------
1 file changed, 1 insertion(+), 13 deletions(-)
diff --git a/gdb/testsuite/gdb.python/py-events.exp b/gdb/testsuite/gdb.python/py-events.exp
index a34a423c014..35681cb178e 100644
--- a/gdb/testsuite/gdb.python/py-events.exp
+++ b/gdb/testsuite/gdb.python/py-events.exp
@@ -203,19 +203,7 @@ gdb_test_multiple "continue" $test {
gdb_test_no_output "delete $second_breakpoint"
-#test exited event.
-proc get_process_id {test} {
- global gdb_prompt
- gdb_test_multiple "info proc" $test {
- -re "process (\\d+).*$gdb_prompt $" {
- set process_id $expect_out(1,string)
- pass $gdb_test_name
- }
- }
- return ${process_id}
-}
-
-set process_id [get_process_id "get inferior process id"]
+set process_id [get_inferior_pid]
gdb_test "continue" ".*event type: continue.*
.*event type: exit.*
.*exit code: 12.*
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 02/11] Adjust gdb.base/exitsignal.exp for MinGW, second-chance SIGSEGV
2026-05-25 19:18 ` [PATCH v2 02/11] Adjust gdb.base/exitsignal.exp for MinGW, second-chance SIGSEGV Pedro Alves
@ 2026-05-26 11:18 ` Eli Zaretskii
2026-05-27 12:56 ` Pedro Alves
0 siblings, 1 reply; 20+ messages in thread
From: Eli Zaretskii @ 2026-05-26 11:18 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 25 May 2026 20:18:20 +0100
>
> On native Windows a segmentation fault is delivered as a SEH
> exception
Is this correct for all native Windows targets, including 32-bit x86,
or only for x86_64?
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes
2026-05-25 19:18 ` [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes Pedro Alves
@ 2026-05-26 11:31 ` Eli Zaretskii
2026-05-27 13:58 ` Pedro Alves
2026-05-27 22:00 ` Thiago Jung Bauermann
1 sibling, 1 reply; 20+ messages in thread
From: Eli Zaretskii @ 2026-05-26 11:31 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
> From: Pedro Alves <pedro@palves.net>
> Date: Mon, 25 May 2026 20:18:26 +0100
>
> +void
> +windows_process_info::maybe_note_cygwin1_dll (const char *dll_path)
> +{
> + const char *base = dll_path + strlen (dll_path);
> + while (base > dll_path && base[-1] != '/' && base[-1] != '\\')
> + base--;
> + if (strcasecmp (base, "cygwin1.dll") == 0)
> + cygwin1_dll_loaded = true;
> +}
I wonder if this should also detect the MSYS2 DLL, since (AFAIU) MSYS2
is a fork of Cygwin. But I don't know what is the status of GDB
support for debugging MSYS2 executables.
> + /* The inferior may also exit with a raw NTSTATUS error code, e.g.,
> + STATUS_ACCESS_VIOLATION (0xc0000005), without going through the
> + pinfo::exit at all -- for example, if the unhandled-exception
> + filter didn't run, or for processes that don't link cygwin1.dll.
> + Detect those and map them the same way Cygwin's set_exit_code
> + does in winsup/cygwin/pinfo.cc. */
> + if (exit_code >= 0xc0000000)
> + {
> + gdb_signal sig;
> + switch (exit_code)
> + {
> + case EXCEPTION_ACCESS_VIOLATION:
> + sig = GDB_SIGNAL_SEGV;
> + break;
> + case EXCEPTION_ILLEGAL_INSTRUCTION:
> + sig = GDB_SIGNAL_ILL;
> + break;
> + case STATUS_NO_MEMORY:
> + sig = GDB_SIGNAL_BUS;
> + break;
> + case STATUS_CONTROL_C_EXIT:
> + sig = GDB_SIGNAL_INT;
> + break;
> + default:
> + /* Cygwin maps any other NTSTATUS to exit 127. */
> + tstatus.set_exited (127);
> + return tstatus;
> + }
> + tstatus.set_signalled (sig);
> + return tstatus;
> + }
This seems to be a subset of what windows_status_to_termsig already
does, or thereabouts. Did you intentionally used separate and
slightly different code, and if so, why? Perhaps the reason should be
in the commentary?
> + /* Note: when GDB attaches to a Cygwin inferior and the inferior is
> + then killed externally (e.g., taskkill /F with exit code 1), GDB
> + and Cygwin disagree. Cygwin's parent waitpid reports WIFEXITED,
> + code=1; GDB reports SIGHUP (signal 1, no swap below because
> + started_by_cygwin). Cygwin's parent distinguishes "pinfo::exit
> + ran" from "didn't run" via the child's wait pipe and only applies
> + the swap-undo for the former. GDB has only dwExitCode and can't
> + tell. This can't be solved without Cygwin's help. OTOH, such an
> + external termination steps out of Cygwin and arguably falls into
> + undefined-behavior territory, so it is less important than the
> + other cases. */
This should be arguably reported to Cygwin developers, but until they
fix this, I wonder whether assuming that SIGHUP is much more rare than
TASKKILL (or any other way of natively killing a program on Windows),
and handle 1 as an exit code rather than a signal, will be more useful
in practice?
Thanks.
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 02/11] Adjust gdb.base/exitsignal.exp for MinGW, second-chance SIGSEGV
2026-05-26 11:18 ` Eli Zaretskii
@ 2026-05-27 12:56 ` Pedro Alves
0 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-05-27 12:56 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: gdb-patches
Hi!
On 2026-05-26 12:18, Eli Zaretskii wrote:
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 25 May 2026 20:18:20 +0100
>>
>> On native Windows a segmentation fault is delivered as a SEH
>> exception
>
> Is this correct for all native Windows targets, including 32-bit x86,
> or only for x86_64?
>
Yes. SEH is the Windows OS-level exception mechanism and has been part of NT since 3.1. Windows 95 had it too.
On every supported architecture.
On Windows, an access violation is turned by the kernel into an EXCEPTION_RECORD with code EXCEPTION_ACCESS_VIOLATION and dispatched through SEH.
16-bit Windows had no such luck, but we don't support that.
Are you perhaps thinking of table-based SEH in x86-64 vs the linked-list style that existed in 32-bit x86?
Both schemes are still SEH. GDB sees the same first/second-chance pair for a null deref whether the inferior is 32-bit or 64-bit Win32.
Pedro Alves
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes
2026-05-26 11:31 ` Eli Zaretskii
@ 2026-05-27 13:58 ` Pedro Alves
2026-05-27 14:12 ` Eli Zaretskii
0 siblings, 1 reply; 20+ messages in thread
From: Pedro Alves @ 2026-05-27 13:58 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: gdb-patches
On 2026-05-26 12:31, Eli Zaretskii wrote:
>> From: Pedro Alves <pedro@palves.net>
>> Date: Mon, 25 May 2026 20:18:26 +0100
>>
>> +void
>> +windows_process_info::maybe_note_cygwin1_dll (const char *dll_path)
>> +{
>> + const char *base = dll_path + strlen (dll_path);
>> + while (base > dll_path && base[-1] != '/' && base[-1] != '\\')
>> + base--;
>> + if (strcasecmp (base, "cygwin1.dll") == 0)
>> + cygwin1_dll_loaded = true;
>> +}
>
> I wonder if this should also detect the MSYS2 DLL, since (AFAIU) MSYS2
> is a fork of Cygwin.
Yes it is. It's Cygwin plus a number of local patches.
> But I don't know what is the status of GDB support for debugging MSYS2 executables.
There's CYGWIN_DLL_NAME symbol, but it's not consistently used throughout, and it only
exists in gdb/windows-nat.c, while this code is in gdb/nat/windows-nat.c (shared with gdbserver).
I'll normalize all this in a follow up patch, move CYGWIN_DLL_NAME somewhere that can be
shared, probably gdb/nat/windows-nat.h.
I'd expect msys2 gdb to have a local patch that changes CYGWIN_DLL_NAME to
point at the msys dll, but I haven't checked.
A couple years ago someone tried to add gdb support for msys2 upstream, but
then retracted it because msys2 is trying to align better with cygwin:
https://github.com/msys2/MSYS2-packages/issues/3012
>
>> + /* The inferior may also exit with a raw NTSTATUS error code, e.g.,
>> + STATUS_ACCESS_VIOLATION (0xc0000005), without going through the
>> + pinfo::exit at all -- for example, if the unhandled-exception
>> + filter didn't run, or for processes that don't link cygwin1.dll.
>> + Detect those and map them the same way Cygwin's set_exit_code
>> + does in winsup/cygwin/pinfo.cc. */
>> + if (exit_code >= 0xc0000000)
>> + {
>> + gdb_signal sig;
>> + switch (exit_code)
>> + {
>> + case EXCEPTION_ACCESS_VIOLATION:
>> + sig = GDB_SIGNAL_SEGV;
>> + break;
>> + case EXCEPTION_ILLEGAL_INSTRUCTION:
>> + sig = GDB_SIGNAL_ILL;
>> + break;
>> + case STATUS_NO_MEMORY:
>> + sig = GDB_SIGNAL_BUS;
>> + break;
>> + case STATUS_CONTROL_C_EXIT:
>> + sig = GDB_SIGNAL_INT;
>> + break;
>> + default:
>> + /* Cygwin maps any other NTSTATUS to exit 127. */
>> + tstatus.set_exited (127);
>> + return tstatus;
>> + }
>> + tstatus.set_signalled (sig);
>> + return tstatus;
>> + }
>
> This seems to be a subset of what windows_status_to_termsig already
> does, or thereabouts. Did you intentionally used separate and
> slightly different code, and if so, why? Perhaps the reason should be
> in the commentary?
Yes, intentional. This is aligning with the exit code that a Cygwin parent
sees out of waitpid. This part of the comment was aluding to it, but only
says the "what":
"Detect those and map them the same way Cygwin's set_exit_code does in winsup/cygwin/pinfo.cc"
I'll extend it with the "why" too:
/* The inferior may also exit with a raw NTSTATUS error code, e.g.,
STATUS_ACCESS_VIOLATION (0xc0000005), without going through the
pinfo::exit at all -- for example, if the unhandled-exception
filter didn't run (e.g., the inferior was killed before
installing one), or for inferiors that don't link cygwin1.dll.
Detect those and map them the same way Cygwin's set_exit_code
does in winsup/cygwin/pinfo.cc, so Cygwin GDB sees the same
status a Cygwin parent's waitpid would. */
>
>> + /* Note: when GDB attaches to a Cygwin inferior and the inferior is
>> + then killed externally (e.g., taskkill /F with exit code 1), GDB
>> + and Cygwin disagree. Cygwin's parent waitpid reports WIFEXITED,
>> + code=1; GDB reports SIGHUP (signal 1, no swap below because
>> + started_by_cygwin). Cygwin's parent distinguishes "pinfo::exit
>> + ran" from "didn't run" via the child's wait pipe and only applies
>> + the swap-undo for the former. GDB has only dwExitCode and can't
>> + tell. This can't be solved without Cygwin's help. OTOH, such an
>> + external termination steps out of Cygwin and arguably falls into
>> + undefined-behavior territory, so it is less important than the
>> + other cases. */
>
> This should be arguably reported to Cygwin developers, but until they
> fix this, I wonder whether assuming that SIGHUP is much more rare than
> TASKKILL (or any other way of natively killing a program on Windows),
> and handle 1 as an exit code rather than a signal, will be more useful
> in practice?
The thing is that Cygwin GDB is mainly used to debug Cygwin programs,
and this case in question is about when the inferior is a Cygwin inferior.
You should want Cygwin GDB to report the Cygwin exit code or termination
status, i.e., "live within the Cygwin bubble".
"taskkill" is a native Windows tool, it completely side-steps Cygwin. The
proper way to kill a program within Cygwin is to kill it with a Unix signal,
with "kill -9" or something like that. Sacrificing correct Cygwin termination
signal reporting when the program you're debugging is killed with
a Cygwin signal seems like the wrong trade-off.
Here's what I've now updated the comment to:
/* Note: when GDB attaches to a Cygwin inferior and the inferior is
then killed externally (e.g., taskkill /F with exit code 1), GDB
and Cygwin disagree. Cygwin's parent waitpid reports WIFEXITED,
code=1; GDB reports SIGHUP (signal 1, no swap below because
started_by_cygwin). Cygwin's parent distinguishes "pinfo::exit
ran" from "didn't run" via the child's wait pipe and only applies
the swap-undo for the former. GDB has only dwExitCode and can't
tell. This can't be solved without Cygwin's help. However, such
an external termination steps out of Cygwin and falls outside
Cygwin's contract, so it matters less than the cases where the
inferior exits through Cygwin's own mechanisms. */
BTW, while experimenting with this the other day, I noticed that Cygwin/bash itself
also loses the external-kill TerminateProcess exit code too in some cases. I think
those are Cygwin bugs, but it goes to show how it's not a GDB-only thing.
I had thought about how Cygwin could help before writing that "need Cygwin help"
comment. In a nutshell, make the CW_GETPINFO info include the Cygwin exit code,
so GDB could just ask Cygwin what is the exit code that Cygwin reports to the parent.
The complication is that the pinfo structure is already potentially gone
when GDB gets the exit process debug event. I have some ideas to handle that,
around making it possible for GDB to grab a handle to the pinfo mapping
when it starts debugging the process.
But I need to think it over, and maybe prototype it, before reaching out
to the Cygwin list.
In any case, any Cygwin change will take a long while, and I think we will want my
proposed code as fallback for a (potentially long) while even if Cygwin gives
us a better mechanism.
Here's the updated patch. Only the comments changed.
From f5683c590550932e5f83a835792e5dd139bd9837 Mon Sep 17 00:00:00 2001
From: Pedro Alves <pedro@palves.net>
Date: Wed, 20 May 2026 11:45:57 +0100
Subject: [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes
On native Cygwin, GDB misreports the inferior's exit reason in several
common cases, resulting in several gdb.base/exitsignal.exp failures:
$ grep FAIL gdb.sum
FAIL: gdb.base/exitsignal.exp: how=run: signal: program terminated with SIGSEGV (the program exited)
FAIL: gdb.base/exitsignal.exp: how=run: signal: $_exitsignal is 11 (SIGSEGV) after SIGSEGV.
FAIL: gdb.base/exitsignal.exp: how=run: signal: $_exitcode is still void after SIGSEGV
FAIL: gdb.base/exitsignal.exp: how=run: signal: $_exitsignal is 11 (SIGSEGV) after restarting the inferior
FAIL: gdb.base/exitsignal.exp: how=run: signal: $_exitcode is still void after restarting the inferior
FAIL: gdb.base/exitsignal.exp: how=run: normal: continue to exit
FAIL: gdb.base/exitsignal.exp: how=run: normal: $_exitcode is one after normal inferior is executed
FAIL: gdb.base/exitsignal.exp: how=run: normal: $_exitsignal is still void after normal inferior is executed
FAIL: gdb.base/exitsignal.exp: how=attach: normal: continue to exit (the program exited)
FAIL: gdb.base/exitsignal.exp: how=attach: normal: $_exitcode is one after normal inferior is executed
For example, from gdb.log, the normal exit case:
...
[Thread 14300.0x4214 (id 1) exited with code 1]
[Thread 14300.0x1b1c (id 4) exited with code 1]
[Thread 14300.0x1e2c (id 2) exited with code 1]
Program terminated with signal SIGHUP, Hangup.
The program no longer exists.
(gdb) FAIL: gdb.base/exitsignal.exp: how=run: normal: continue to exit
The program in fact exited normally with code 1. SIGHUP happens to be
signal 1, and GDB picked the wrong interpretation.
Similarly, for the signal termination case:
...
continue
Continuing.
[Thread 4600.0x3104 (id 4) exited with code 2816]
[Thread 4600.0x2bcc (id 3) exited with code 2816]
[Thread 4600.0x2f44 (id 1) exited with code 2816]
[Inferior 1 (process 4600) exited with code 05400]
(gdb) FAIL: gdb.base/exitsignal.exp: how=run: signal: program terminated with SIGSEGV (the program exited)
Here the inferior died with SIGSEGV, but GDB reported exit decimal
2816 / octal 05400 / hex 0x0B00, which is SIGSEGV swapped into the
high byte of a waitpid exit status.
The problem is that Cygwin waitpid exit status and Windows exit codes
do not have the same encoding, and GDB & GDBserver do not know about
this.
This commit fixes it. It adds a Cygwin-specific branch to the code
that determines the terminating signal and status of a program. The
branch for native Windows/MinGW GDB is left intact, no behavior change
there.
The way to decode the exit codes is a little bit tricky, see detailed
comments added by the patch. To exercise the "raw NTSTATUS error
code" path in windows_process_info::exit_process_to_target_status,
gdb.base/exitsignal.exp is extended to debug a native Windows program
that crashes with a segfault (STATUS_ACCESS_VIOLATION).
With this, gdb.base/exitsignal.exp passes cleanly on Cygwin.
Change-Id: Icaebcc234b71927915c996fd120884604441415b
commit-id: bd0fbb9c
---
gdb/nat/windows-nat.c | 155 +++++++++++++++++++++++++-
gdb/nat/windows-nat.h | 29 +++++
gdb/testsuite/gdb.base/exitsignal.exp | 32 +++++-
gdb/windows-nat.c | 5 +
gdbserver/win32-low.cc | 5 +
5 files changed, 219 insertions(+), 7 deletions(-)
diff --git a/gdb/nat/windows-nat.c b/gdb/nat/windows-nat.c
index 92f9394ca6d..e975892f487 100644
--- a/gdb/nat/windows-nat.c
+++ b/gdb/nat/windows-nat.c
@@ -654,6 +654,7 @@ windows_process_info::add_dll (LPVOID load_addr)
at which the DLL was loaded is equal to LOAD_ADDR. */
if (!(load_addr != nullptr && mi.lpBaseOfDll != load_addr))
{
+ maybe_note_cygwin1_dll (name);
handle_load_dll (name, mi.lpBaseOfDll);
if (load_addr != nullptr)
return;
@@ -681,7 +682,10 @@ windows_process_info::dll_loaded_event (const DEBUG_EVENT ¤t_event)
by enumerating all the DLLs loaded into the inferior, looking for
one that is loaded at base address = lpBaseOfDll. */
if (dll_name != nullptr)
- handle_load_dll (dll_name, event->lpBaseOfDll);
+ {
+ maybe_note_cygwin1_dll (dll_name);
+ handle_load_dll (dll_name, event->lpBaseOfDll);
+ }
else if (event->lpBaseOfDll != nullptr)
add_dll (event->lpBaseOfDll);
}
@@ -694,6 +698,48 @@ windows_process_info::add_all_dlls ()
add_dll (nullptr);
}
+#ifdef __CYGWIN__
+
+/* See nat/windows-nat.h. */
+
+void
+windows_process_info::maybe_note_cygwin1_dll (const char *dll_path)
+{
+ const char *base = dll_path + strlen (dll_path);
+ while (base > dll_path && base[-1] != '/' && base[-1] != '\\')
+ base--;
+ if (strcasecmp (base, "cygwin1.dll") == 0)
+ cygwin1_dll_loaded = true;
+}
+
+/* See nat/windows-nat.h. */
+
+bool
+inferior_started_by_cygwin (DWORD winpid, bool attaching)
+{
+ /* In the run (non-attach) case this is called early when the
+ inferior has only just reached its first instruction and
+ cygwin1.dll hasn't initialized itself yet -- GDB launched the
+ inferior with raw CreateProcess, not through Cygwin's fork/spawn
+ path, so PID_CYGPARENT is necessarily false, so we can shortcut
+ without calling Cygwin. */
+ if (!attaching)
+ return false;
+
+ /* Note CW_WINPID_TO_CYGWIN_PID never fails. It returns a synthetic
+ pid for non-Cygwin or unknown winpids, in which case CW_GETPINFO
+ returns either a pinfo with PID_CYGPARENT unset, or NULL. */
+ auto cygpid = (pid_t) cygwin_internal (CW_WINPID_TO_CYGWIN_PID, winpid);
+
+ auto *pinfo = (external_pinfo *) cygwin_internal (CW_GETPINFO, cygpid);
+ if (pinfo == nullptr)
+ return false;
+
+ return (pinfo->process_state & PID_CYGPARENT) != 0;
+}
+
+#endif /* __CYGWIN__. */
+
/* See nat/windows-nat.h. */
target_waitstatus
@@ -703,6 +749,112 @@ windows_process_info::exit_process_to_target_status
DWORD exit_code = info.dwExitCode;
target_waitstatus tstatus;
+#ifdef __CYGWIN__
+ /* A Cygwin parent waiting on a Cygwin child via waitpid doesn't go
+ through GetExitCodeProcess / the Win32 exit code at all. It
+ reads the child's wait status directly out of the child's Cygwin
+ pinfo (shared memory), set by pinfo::exit in
+ winsup/cygwin/pinfo.cc. So sys/wait.h macros apply to that value
+ verbatim.
+
+ GDB, however, even though it is itself a Cygwin program, drives
+ its inferiors via the native Win32 debugger API: it spawns them
+ with CreateProcess (DEBUG_PROCESS), not via Cygwin's
+ fork/spawn/posix_spawn, and consumes
+ EXIT_PROCESS_DEBUG_EVENT.dwExitCode from WaitForDebugEvent rather
+ than calling waitpid. That dwExitCode value comes from the
+ inferior's ExitProcess call.
+
+ What that value means depends on two orthogonal things:
+
+ 1. Is the inferior a Cygwin process at all? If not, dwExitCode
+ is a raw Win32 exit value.
+
+ 2. For a Cygwin inferior, was it created through Cygwin's spawn
+ path?
+
+ - If not, cygwin1.dll's pinfo::exit byte-swaps the wait status
+ on the way out, so that the meaningful exit value lands in
+ the low byte where native Win32 consumers (cmd.exe's "echo
+ %errorlevel%", and bare GetExitCodeProcess readers) expect
+ it. This is the case for Cygwin inferiors that we run, via
+ CreateProcess.
+
+ - If yes, cygwin1.dll does not swap. We see this case if we
+ attach to an already-running process with a Cygwin parent.
+
+ See winsup/cygwin/pinfo.cc:
+
+ int exitcode = self->exitcode & 0xffff;
+ if (!self->cygstarted)
+ exitcode = ((exitcode & 0xff) << 8) | ((exitcode >> 8) & 0xff);
+ ...
+ ExitProcess (exitcode);
+ */
+
+ /* The inferior may also exit with a raw NTSTATUS error code, e.g.,
+ STATUS_ACCESS_VIOLATION (0xc0000005), without going through the
+ pinfo::exit at all -- for example, if the unhandled-exception
+ filter didn't run (e.g., the inferior was killed before
+ installing one), or for inferiors that don't link cygwin1.dll.
+ Detect those and map them the same way Cygwin's set_exit_code
+ does in winsup/cygwin/pinfo.cc, so Cygwin GDB sees the same
+ status a Cygwin parent's waitpid would. */
+ if (exit_code >= 0xc0000000)
+ {
+ gdb_signal sig;
+ switch (exit_code)
+ {
+ case EXCEPTION_ACCESS_VIOLATION:
+ sig = GDB_SIGNAL_SEGV;
+ break;
+ case EXCEPTION_ILLEGAL_INSTRUCTION:
+ sig = GDB_SIGNAL_ILL;
+ break;
+ case STATUS_NO_MEMORY:
+ sig = GDB_SIGNAL_BUS;
+ break;
+ case STATUS_CONTROL_C_EXIT:
+ sig = GDB_SIGNAL_INT;
+ break;
+ default:
+ /* Cygwin maps any other NTSTATUS to exit 127. */
+ tstatus.set_exited (127);
+ return tstatus;
+ }
+ tstatus.set_signalled (sig);
+ return tstatus;
+ }
+
+ if (!this->cygwin1_dll_loaded)
+ {
+ /* Non-Cygwin inferior: dwExitCode is a raw Win32 exit value.
+ Limit to 8 bits, like Cygwin does, matching what happens with
+ Cygwin inferiors. */
+ tstatus.set_exited (exit_code & 0xff);
+ return tstatus;
+ }
+
+ /* Note: when GDB attaches to a Cygwin inferior and the inferior is
+ then killed externally (e.g., taskkill /F with exit code 1), GDB
+ and Cygwin disagree. Cygwin's parent waitpid reports WIFEXITED,
+ code=1; GDB reports SIGHUP (signal 1, no swap below because
+ started_by_cygwin). Cygwin's parent distinguishes "pinfo::exit
+ ran" from "didn't run" via the child's wait pipe and only applies
+ the swap-undo for the former. GDB has only dwExitCode and can't
+ tell. This can't be solved without Cygwin's help. However, such
+ an external termination steps out of Cygwin and falls outside
+ Cygwin's contract, so it matters less than the cases where the
+ inferior exits through Cygwin's own mechanisms. */
+
+ int wstatus = exit_code & 0xffff;
+ if (!this->started_by_cygwin)
+ wstatus = ((wstatus & 0xff) << 8) | ((wstatus >> 8) & 0xff);
+ if (!WIFSIGNALED (wstatus))
+ tstatus.set_exited (WEXITSTATUS (wstatus));
+ else
+ tstatus.set_signalled (gdb_signal_from_host (WTERMSIG (wstatus)));
+#else
/* If the exit status looks like a fatal exception, but we don't
recognize the exception's code, make the original exit status
value available, to avoid losing information. */
@@ -712,6 +864,7 @@ windows_process_info::exit_process_to_target_status
tstatus.set_exited (exit_code);
else
tstatus.set_signalled (gdb_signal_from_host (exit_signal));
+#endif
return tstatus;
}
diff --git a/gdb/nat/windows-nat.h b/gdb/nat/windows-nat.h
index d2e6adb4f40..2e4db9832ae 100644
--- a/gdb/nat/windows-nat.h
+++ b/gdb/nat/windows-nat.h
@@ -224,6 +224,23 @@ struct windows_process_info
DWORD process_id = 0;
DWORD main_thread_id = 0;
+#ifdef __CYGWIN__
+ /* True if the inferior was created through Cygwin's spawn path
+ (i.e., its Cygwin pinfo has PID_CYGPARENT set). We need this at
+ exit time, but we cache it early when we start debugging the
+ inferior, because by exit time the inferior's Cygwin pinfo may
+ have been torn down (CW_GETPINFO returns NULL). */
+ bool started_by_cygwin = false;
+
+ /* True if cygwin1.dll is loaded into the inferior. */
+ bool cygwin1_dll_loaded = false;
+
+ /* If DLL_PATH is cygwin1.dll, set cygwin1_dll_loaded to true. */
+ void maybe_note_cygwin1_dll (const char *dll_path);
+#else
+ void maybe_note_cygwin1_dll (const char *) {}
+#endif
+
#ifdef __x86_64__
/* The target is a WOW64 process */
bool wow64_process = false;
@@ -353,6 +370,18 @@ struct windows_process_info
int get_exec_module_filename (char *exe_name_ret, size_t exe_name_max_len);
};
+#ifdef __CYGWIN__
+/* Return true if the process with native Windows pid WINPID was
+ started by a Cygwin parent -- that is, its Cygwin pinfo exists and
+ has PID_CYGPARENT set. Returns false if the process is not a
+ Cygwin process at all, or if its parent is not a Cygwin process.
+
+ ATTACHING indicates whether GDB is attaching to an already-running
+ inferior (true) or has just launched it via CreateProcess
+ (false). */
+extern bool inferior_started_by_cygwin (DWORD winpid, bool attaching);
+#endif
+
/* Return a string version of EVENT_CODE. */
extern std::string event_code_to_string (DWORD event_code);
diff --git a/gdb/testsuite/gdb.base/exitsignal.exp b/gdb/testsuite/gdb.base/exitsignal.exp
index 7684646b546..348c7a72eff 100644
--- a/gdb/testsuite/gdb.base/exitsignal.exp
+++ b/gdb/testsuite/gdb.base/exitsignal.exp
@@ -37,6 +37,10 @@ set exec2 "normal"
set srcfile2 ${exec2}.c
set binfile2 [standard_output_file ${exec2}]
+set exec3 "segv-win32"
+set srcfile3 ${exec1}.c
+set binfile3 [standard_output_file ${exec3}]
+
if { [build_executable "failed to build $exec1" ${exec1} "${srcfile1}" \
{debug}] == -1 } {
return -1
@@ -47,6 +51,16 @@ if { [build_executable "failed to build $exec2" ${exec2} "${srcfile2}" \
return -1
}
+# On Cygwin, also build a pure-Win32 segv binary, used to test that
+# GDB extracts the terminating SIGSEGV out of the 0xc0000005
+# (STATUS_ACCESS_VIOLATION) Windows exit code.
+if {[istarget "*-*-cygwin*"]} {
+ if { [build_executable "failed to build $exec3" ${exec3} "${srcfile3}" \
+ {debug win32}] == -1} {
+ return -1
+ }
+}
+
# Get the inferior under GDB's control in mode HOW ("run" or
# "attach"), using BINFILE. In "attach" mode, spawn the binary and
# attach to it; in "run" mode, run to main. In both modes, clear the
@@ -81,14 +95,14 @@ proc teardown {how} {
}
}
-proc test_signal {how} {
- clean_restart $::exec1
+proc test_signal {how exec binfile} {
+ clean_restart $exec
# Get the inferior under GDB's control. But, before, change cwd
# so the core file ends up in the output directory.
set_inferior_cwd_to_output_dir
- setup $how $::binfile1
+ setup $how $binfile
# Get the inferior's PID for later.
set pid [get_inferior_pid]
@@ -106,7 +120,8 @@ proc test_signal {how} {
gdb_test "continue" "(Thread .*|Program) received signal SIGSEGV.*" \
"trigger SIGSEGV"
- if {[istarget "*-*-mingw*"]} {
+ if {[istarget "*-*-mingw*"]
+ || ([istarget "*-*-cygwin*"] && $binfile == $::binfile3)} {
# We're debugging a pure Win32 program with no SEH handler. The
# previous continue caught the first-chance exception. Now we
# catch the second-chance one.
@@ -140,7 +155,7 @@ proc test_signal {how} {
} else {
with_test_prefix "reattach" {
kill_wait_spawned_process $::test_spawn_id
- setup $how $::binfile1
+ setup $how $binfile
}
}
@@ -190,9 +205,14 @@ foreach_with_prefix how {"run" "attach"} {
}
with_test_prefix "signal" {
- test_signal $how
+ test_signal $how $exec1 $binfile1
}
with_test_prefix "normal" {
test_normal $how
}
+ if {[istarget "*-*-cygwin*"]} {
+ with_test_prefix "signal, win32" {
+ test_signal $how $exec3 $binfile3
+ }
+ }
}
diff --git a/gdb/windows-nat.c b/gdb/windows-nat.c
index 26333238cfa..16269671d95 100644
--- a/gdb/windows-nat.c
+++ b/gdb/windows-nat.c
@@ -1994,6 +1994,11 @@ windows_nat_target::do_initial_windows_stuff (DWORD pid, bool attaching)
phase, and then process them all in one batch now. */
windows_process->add_all_dlls ();
+#ifdef __CYGWIN__
+ windows_process->started_by_cygwin
+ = inferior_started_by_cygwin (pid, attaching);
+#endif
+
windows_process->windows_initialization_done = 1;
return;
}
diff --git a/gdbserver/win32-low.cc b/gdbserver/win32-low.cc
index 550994b0bf2..2150045e188 100644
--- a/gdbserver/win32-low.cc
+++ b/gdbserver/win32-low.cc
@@ -368,6 +368,11 @@ do_initial_child_stuff (HANDLE proch, DWORD pid, int attached)
phase, and then process them all in one batch now. */
windows_process.add_all_dlls ();
+#ifdef __CYGWIN__
+ windows_process.started_by_cygwin
+ = inferior_started_by_cygwin (pid, attached);
+#endif
+
windows_process.child_initialization_done = 1;
}
--
2.53.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes
2026-05-27 13:58 ` Pedro Alves
@ 2026-05-27 14:12 ` Eli Zaretskii
0 siblings, 0 replies; 20+ messages in thread
From: Eli Zaretskii @ 2026-05-27 14:12 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
> Date: Wed, 27 May 2026 14:58:33 +0100
> Cc: gdb-patches@sourceware.org
> From: Pedro Alves <pedro@palves.net>
>
> Here's the updated patch. Only the comments changed.
Thanks, I'm okay with your conclusions, and the comments LGTM.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 03/11] Adjust gdb.base/exitsignal.exp for MinGW, separate program names
2026-05-25 19:18 ` [PATCH v2 03/11] Adjust gdb.base/exitsignal.exp for MinGW, separate program names Pedro Alves
@ 2026-05-27 21:59 ` Thiago Jung Bauermann
2026-06-12 14:00 ` Pedro Alves
0 siblings, 1 reply; 20+ messages in thread
From: Thiago Jung Bauermann @ 2026-05-27 21:59 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
I have just one nit, with two instances in this patch and another
instance in another patch.
Pedro Alves <pedro@palves.net> writes:
> diff --git a/gdb/testsuite/gdb.base/exitsignal.exp b/gdb/testsuite/gdb.base/exitsignal.exp
> index 341197cf0c8..4f4c53e37cb 100644
> --- a/gdb/testsuite/gdb.base/exitsignal.exp
> +++ b/gdb/testsuite/gdb.base/exitsignal.exp
> @@ -23,103 +23,126 @@
>
> require {!target_info exists gdb,nosignals}
>
> -standard_testfile segv.c
> +set testfile "exitsignal"
>
> -if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
> - return
> -}
> +set exec1 "segv"
> +set srcfile1 ${exec1}.c
> +set binfile1 [standard_output_file ${exec1}]
>
> -# Run to main. But, before, change cwd to get the core into the
> -# output directory.
> -set_inferior_cwd_to_output_dir
> +set exec2 "normal"
> +set srcfile2 ${exec2}.c
> +set binfile2 [standard_output_file ${exec2}]
>
> -if { ![runto_main] } {
> - return
> +if { [build_executable "failed to build $exec1" ${exec1} "${srcfile1}" \
> + {debug}] == -1 } {
> + return -1
The current style for testcases is to not return any value from
top-level.
> }
>
> -# Get the inferior's PID for later.
> -set pid [get_inferior_pid]
> -
> -# Print $_exitsignal. It should be void now, because nothing
> -# happened.
> -gdb_test "print \$_exitsignal" " = void" \
> - "\$_exitsignal is void before running"
> -
> -# Just to guarantee, making sure that $_exitcode is also void.
> -gdb_test "print \$_exitcode" " = void" \
> - "\$_exitcode is void before running"
> -
> -# Trigger SIGSEGV.
> -gdb_test "continue" "(Thread .*|Program) received signal SIGSEGV.*" \
> - "trigger SIGSEGV"
> -
> -if {[istarget "*-*-mingw*"]} {
> - # We're debugging a pure Win32 program with no SEH handler. The
> - # previous continue caught the first-chance exception. Now we
> - # catch the second-chance.
> - gdb_test "continue" "Thread .* received signal SIGSEGV.*" \
> - "trigger SIGSEGV, second-chance"
> -} elseif {[istarget "*-*-cygwin*"]} {
> - # Cygwin calls DebugBreak before it lets the process exit.
> - gdb_test "continue" "Thread .* received signal SIGTRAP.*" \
> - "trigger try_to_debug SIGTRAP"
> +if { [build_executable "failed to build $exec2" ${exec2} "${srcfile2}" \
> + {debug}] == -1} {
> + return -1
The current style for testcases is to not return any value from
top-level.
--
Thiago
(he/him)
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes
2026-05-25 19:18 ` [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes Pedro Alves
2026-05-26 11:31 ` Eli Zaretskii
@ 2026-05-27 22:00 ` Thiago Jung Bauermann
1 sibling, 0 replies; 20+ messages in thread
From: Thiago Jung Bauermann @ 2026-05-27 22:00 UTC (permalink / raw)
To: Pedro Alves; +Cc: gdb-patches
Here's the other instance of the nit.
Pedro Alves <pedro@palves.net> writes:
> diff --git a/gdb/testsuite/gdb.base/exitsignal.exp b/gdb/testsuite/gdb.base/exitsignal.exp
> index 7684646b546..348c7a72eff 100644
> --- a/gdb/testsuite/gdb.base/exitsignal.exp
> +++ b/gdb/testsuite/gdb.base/exitsignal.exp
> @@ -37,6 +37,10 @@ set exec2 "normal"
> set srcfile2 ${exec2}.c
> set binfile2 [standard_output_file ${exec2}]
>
> +set exec3 "segv-win32"
> +set srcfile3 ${exec1}.c
> +set binfile3 [standard_output_file ${exec3}]
> +
> if { [build_executable "failed to build $exec1" ${exec1} "${srcfile1}" \
> {debug}] == -1 } {
> return -1
> @@ -47,6 +51,16 @@ if { [build_executable "failed to build $exec2" ${exec2} "${srcfile2}" \
> return -1
> }
>
> +# On Cygwin, also build a pure-Win32 segv binary, used to test that
> +# GDB extracts the terminating SIGSEGV out of the 0xc0000005
> +# (STATUS_ACCESS_VIOLATION) Windows exit code.
> +if {[istarget "*-*-cygwin*"]} {
> + if { [build_executable "failed to build $exec3" ${exec3} "${srcfile3}" \
> + {debug win32}] == -1} {
> + return -1
The current style for testcases is to not return any value from
top-level.
--
Thiago
(he/him)
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 03/11] Adjust gdb.base/exitsignal.exp for MinGW, separate program names
2026-05-27 21:59 ` Thiago Jung Bauermann
@ 2026-06-12 14:00 ` Pedro Alves
0 siblings, 0 replies; 20+ messages in thread
From: Pedro Alves @ 2026-06-12 14:00 UTC (permalink / raw)
To: Thiago Jung Bauermann; +Cc: gdb-patches
On 2026-05-27 22:59, Thiago Jung Bauermann wrote:
> I have just one nit, with two instances in this patch and another
> instance in another patch.
>
Thank you Thiago. I fixed those, and merged the series.
(I also looked for any other hit, there was none.)
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-06-12 14:00 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-25 19:18 [PATCH v2 00/11] Fix a few Cygwin/MinGW problems Pedro Alves
2026-05-25 19:18 ` [PATCH v2 01/11] Adjust gdb.base/exitsignal.exp for MinGW, trigger fault Pedro Alves
2026-05-25 19:18 ` [PATCH v2 02/11] Adjust gdb.base/exitsignal.exp for MinGW, second-chance SIGSEGV Pedro Alves
2026-05-26 11:18 ` Eli Zaretskii
2026-05-27 12:56 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 03/11] Adjust gdb.base/exitsignal.exp for MinGW, separate program names Pedro Alves
2026-05-27 21:59 ` Thiago Jung Bauermann
2026-06-12 14:00 ` Pedro Alves
2026-05-25 19:18 ` [PATCH v2 04/11] gdb.base/exitsignal.exp: Exit with non-zero Pedro Alves
2026-05-25 19:18 ` [PATCH v2 05/11] gdb.base/exitsignal.exp: Test attaching too Pedro Alves
2026-05-25 19:18 ` [PATCH v2 06/11] gdb/testsuite: Add mechanism to compile Windows native programs on Cygwin Pedro Alves
2026-05-25 19:18 ` [PATCH v2 07/11] Windows gdb+gdbserver: Share exit status logic Pedro Alves
2026-05-25 19:18 ` [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes Pedro Alves
2026-05-26 11:31 ` Eli Zaretskii
2026-05-27 13:58 ` Pedro Alves
2026-05-27 14:12 ` Eli Zaretskii
2026-05-27 22:00 ` Thiago Jung Bauermann
2026-05-25 19:18 ` [PATCH v2 09/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, thread IDs Pedro Alves
2026-05-25 19:18 ` [PATCH v2 10/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, no fork Pedro Alves
2026-05-25 19:18 ` [PATCH v2 11/11] Adjust gdb.python/py-events.exp for Cygwin/MinGW, "info proc" => "inferior" Pedro Alves
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox