From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id naLHGrL4FmrCQSAAWB0awg (envelope-from ) for ; Wed, 27 May 2026 09:59:14 -0400 Received: by simark.ca (Postfix, from userid 112) id 5261B1E0A3; Wed, 27 May 2026 09:59:14 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.3 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id 755721E062 for ; Wed, 27 May 2026 09:59:12 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id E8E864BA2E25 for ; Wed, 27 May 2026 13:59:11 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E8E864BA2E25 Received: from mail-wr1-f41.google.com (mail-wr1-f41.google.com [209.85.221.41]) by sourceware.org (Postfix) with ESMTPS id B38E94BA2E13 for ; Wed, 27 May 2026 13:58:41 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org B38E94BA2E13 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=palves.net Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org B38E94BA2E13 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=209.85.221.41 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779890322; cv=none; b=DRixtS9tZnshnpRDZvooudOKvoPwENrJE2STOobq/jH7mp4iOGEiil2OCzGUCQt8iPnybIAVYV8xhaH4O7O5Qo43gwx2gHSVadoU3X8yaNCZtnDwYQIXXKgRglepT4mFjjD7g/Fz+bsjVXQtk6PVV5RYQ7FFZ/T9lmK9RrKSmic= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1779890322; c=relaxed/simple; bh=vwHPAids7+YwfABWAifathZmP5dMwdwsHITLYy/wwzs=; h=Message-ID:Date:MIME-Version:Subject:To:From; b=kw5TGlw3LgLPHLRtlX+basYXAykrlioe65DxSkEC+3QKofbzPHwdUCpirSj0/nxfhs2uV+Na3Af0mjYeJ5OQXeag7RlGRhNNsVX/i5x6nG/xw07h+CJFwoIRYdCt5g/roDbtDfmUgN5XdzKPdiT566Qunto5a40i8GUBwN81Wcs= ARC-Authentication-Results: i=1; sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B38E94BA2E13 Received: by mail-wr1-f41.google.com with SMTP id ffacd0b85a97d-43fe608cb92so7208898f8f.2 for ; Wed, 27 May 2026 06:58:41 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779890321; x=1780495121; h=content-transfer-encoding:in-reply-to:content-language:from :references:cc:to:subject:user-agent:mime-version:date:message-id :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=1bVGQhtWSsM17NI/HGaERDjvJMEOKycbPmmfyOJKt7c=; b=C9r/qw8XMQHpvvA24+EjRkysaEW+X6W5+/xJLtNsySLhuQ0Yn2ZpWsr5i+l3em6R6H 80RXfgWN2yYYxGtoW2XIE6zwlhcRoXzGd0FNifzEhlny1BwfwniPoPRNgWI0o36WBCnB 25ua8D2kvisYQ43qpsjWjmBMiujQdqyc9snIs52l5NPmivkaaUy+Ohz6Ex8lwp61ihoB 93X1BpyNmnRW41pQ+yVTGZKeoRc5T9ZAv0d+UjetSMAfreWmaCGytkKB0tWMgPOM7mbr IpQRu3p0Ixgff573HdeQyt+H++i/mAZACNplUiJUhSMgCF2RY1JubXvzFcuU0vn+okvv /gwQ== X-Gm-Message-State: AOJu0YzapT7CFsrCjdcIdk9zRyccVUuAKbR4K5JIjjWJouFMk1coG85K ngxiGv4wZXtuOLyGAWdiDFP2+Nh6EqP9XOHHE9ojG6gsEFMXhaOTfqwMI7Za5nZ5 X-Gm-Gg: Acq92OEpGrzcXDDww9dPFmDzqqDEdQLCIS1539MbrF1VlK+9k3637TBqtUJl+oBVEhf /dc1pv5PG0ol+tO5BoW99EZJuRMfKV+Lf8wjBJTsbpJYQ7KP+YC0ety9PBysXcTwnsLeGjQiRvy 5Io73Dc5Sc9lKV0dnYmCT4xvKLzeShKMrS+5UxCdn3LQaHhK8IGR/FtNKDEtBz/QzSYsdj5dj4B BoB4J1sGZYvuKjmMjFrWwldDdxM83xiIAV081cyXSJf55ekzHSwGpwIRFJTF4b9E0PspVnA/Lhx Brol9lNGds7HIsujEAbA4GfVgMhem4o5UXyzC2qnWNNcNNGF407iPLvORMTpX2MdqoZJzNBF42U 3O0sAHf9WcfiSjQpKUv9QrwF7VmUFWNpgi+WQLXcwOcCEt5UeFh9VgpOg0N6DQL8k8pqsy57ceT otnoJ88li5m74u9zz0QJC60GWp1kv2hnMCR9oIofUYegBqnCWNhLI2i08QfbAt5Bew5jlJKRhcV Lsl X-Received: by 2002:a05:600c:c3cd:20b0:48a:58ae:993b with SMTP id 5b1f17b1804b1-490428c97a1mr270182575e9.16.1779890320503; Wed, 27 May 2026 06:58:40 -0700 (PDT) Received: from ?IPV6:2001:8a0:fae3:2600:5120:d74c:7a35:7b68? ([2001:8a0:fae3:2600:5120:d74c:7a35:7b68]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-490809d275esm18547695e9.33.2026.05.27.06.58.39 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 27 May 2026 06:58:40 -0700 (PDT) Message-ID: Date: Wed, 27 May 2026 14:58:33 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v2 08/11] Windows gdb+gdbserver: Decode Cygwin ExitProcess codes To: Eli Zaretskii Cc: gdb-patches@sourceware.org References: <20260525191829.984105-1-pedro@palves.net> <20260525191829.984105-9-pedro@palves.net> <86h5nuquew.fsf@gnu.org> From: Pedro Alves Content-Language: en-US In-Reply-To: <86h5nuquew.fsf@gnu.org> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org On 2026-05-26 12:31, Eli Zaretskii wrote: >> From: Pedro Alves >> 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 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