16
votes

I'm on Windows, running GDB on an executable built under MinGW. The program has an infinite loop. I want to find it by hitting Ctrl + C. When I do, both the program and GDB exit. All the help on this subject seems to assume I'm on Linux.

8

8 Answers

9
votes

This is because GDB doesn't handle the Ctrl + C event of the GUI (non-console) program properly.

You can find the workaround in Workaround for GDB Ctrl-C Interrupt.


Update: Given that the mingw.org domain expired, here's the code rescued from the Web Archive:

If you find yourself trying to interrupt a program being debugged by GDB with Ctrl-C and failing then this little program will allow you to issue the break from another session. Just run it passing the Windows pid of the process being debugged and GDB will regain control. [To compile, use gcc -o debugbreak -mno-cygwin -mthreads debugbreak.c ]

/* BEGIN debugbreak.c */

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif

#if _WIN32_WINNT < 0x0501
#error Must target Windows NT 5.0.1 or later for DebugBreakProcess
#endif

#include <Windows.h>
#include <stddef.h>
#include <stdlib.h>

/* Compile with this line:

    gcc -o debugbreak -mno-cygwin -mthreads debugbreak.c

*/

static char errbuffer[256];

static const char *geterrstr(DWORD errcode)
{
    size_t skip = 0;
    DWORD chars;
    chars = FormatMessage(
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, errcode, 0, errbuffer, sizeof(errbuffer)-1, 0);
    errbuffer[sizeof(errbuffer)-1] = 0;
    if (chars) {
        while (errbuffer[chars-1] == '\r' || errbuffer[chars-1] == '\n') {
            errbuffer[--chars] = 0;
        }
    }
    if (chars && errbuffer[chars-1] == '.') errbuffer[--chars] = 0;
    if (chars >= 2 && errbuffer[0] == '%' && errbuffer[1] >= '0'
        && errbuffer[1] <= '9')
    {
        skip = 2;
        while (chars > skip && errbuffer[skip] == ' ') ++skip;
        if (chars >= skip+2 && errbuffer[skip] == 'i'
            && errbuffer[skip+1] == 's')
        {
            skip += 2;
            while (chars > skip && errbuffer[skip] == ' ') ++skip;
        }
    }
    if (chars > skip && errbuffer[skip] >= 'A' && errbuffer[skip] <= 'Z') {
        errbuffer[skip] += 'a' - 'A';
    }
    return errbuffer+skip;
}

int main(int argc, char *argv[])
{
    HANDLE proc;
    unsigned proc_id = 0;
    BOOL break_result;

    if (argc != 2) {
        printf("Usage: debugbreak process_id_number\n");
        return 1;
    }
    proc_id = (unsigned) strtol(argv[1], NULL, 0);
    if (proc_id == 0) {
        printf("Invalid process id %u\n", proc_id);
        return 1;
    }
    proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)proc_id);
    if (proc == NULL) {
        DWORD lastError = GetLastError();
        printf("Failed to open process %u\n", proc_id);
        printf("Error code is %lu (%s)\n", (unsigned long)lastError,
            geterrstr(lastError));
        return 1;
    }
    break_result = DebugBreakProcess(proc);
    if (!break_result) {
        DWORD lastError = GetLastError();
        printf("Failed to debug break process %u\n", proc_id);
        printf("Error code is %lu (%s)\n", (unsigned long)lastError,
            geterrstr(lastError));
        CloseHandle(proc);
        return 1;
    }
    printf("DebugBreak sent successfully to process id %u\n", proc_id);
    CloseHandle(proc);
    return 0;
}

/* END debugbreak.c */

With thanks to Kyle McKay for posting the code to the cygwin mailing list http://cygwin.com/ml/cygwin/2006-06/msg00321.html

5
votes

Which "shell" are you using? If you use the MSYS "rxvt" shell, the behavior is pretty much as you describe. Ctrl-C only works if you are running from a normal Windows command prompt.

2
votes

I've just hit the same problem.

The workaround from the wiki is to run debugbreak with the pid of the debugged process, but ps doesn't show this pid, only pid of gdb. Maybe there is another way to obtain it.

But there is simpler workaround. Just start the program normally (not in gdb), check pid from ps and start gdb with this pid as second argument. When gdb is attached the process stops and I can print backtrace.

1
votes

Here is a solution that works every time:

When GDB starts use this regular expression to capture the inferior process id:

"\[New Thread (\d+)\."

Then use:

hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, PID);
DebugBreakProcess(hProcess);
CloseHandle(hProcess);

Have a look on the following GDB initialization script, it is required for this to work with MinGW on Windows 7 and later:

# =====================================
#  GDB preload initialization commands
# =====================================

# Set Unicode Charset
#set target-charset UCS-2
#set host-charset UCS-2
#set charset UCS-2
#set target-wide-charset UCS-2

# Set UTF-8 Charset
set target-charset UTF-8
set host-charset UTF-8
set charset UTF-8
set target-wide-charset UTF-8

# Request async target mode
set target-async 1

# Do not complain about pending breakpoints
set breakpoint pending on

# Enable All-Stop for all Threads
set non-stop off

# Do not ask for confirmations
set confirm off

# Do not create new console for output/logging
set new-console off

# Turn-off paggination to allow integration with IDE
set pagination off

# Call Stack files (and anywhere else) should be absolute path
set filename-display absolute

# Enable Pretty Print in GDB Panel
set print pretty on

# Enable notification of completion for asynchronous execution commands.
set exec-done-display on

# Show Addresses in objects, required for integration with IDE
set print address on

# Enable Pretty Print for Arrays
set print array on

# Flatten objects, required for integration with IDE
set print object off

# Include static members, required for integration with IDE
set print static-members on

# Show demangled vtable, required for integration with IDE
set print vtbl off
set print demangle on
set demangle-style gnu-v3

# Print full eight-bit characters, required for integration with IDE
set print sevenbit-strings off

# Set path for obj files
path $(TARGET_ROOT)/obj

# Load gdb scripts for STL (string, vector, map, etc.)
source $(PATH_SDK_DEBUGGER)/stl-views-1.0.3.gdb

# List of source code files
dir $(PATH_SDK_COMMON)
dir $(PATH_SDK_FRAMEWORKS)
dir $(PATH_SDK_INCLUDES)
dir $(PROJECT_PATHS.NATIVE_COMMON)

# Load the binary
file $(TARGET_OUTPUT)
1
votes

As Matthew Talbert pointed out, this happens when GDB built with the native MinGW toolchain is used inside MSYS/Cygwin. Starting GDB with winpty worked like a charm as it's a tool designed just for that. It also worked for cdb.exe.

1
votes

If you install the latest MinGW-x64 from https://www.msys2.org/ then Ctrl-C just works.

c:\test>gdb a.exe
GNU gdb (GDB) 7.11.1
. . .
Reading symbols from a.exe...done.
(gdb) r
Starting program: c:\test\a.exe
<Ctrl>-<C>
Thread 5 received signal SIGINT, Interrupt.
[Switching to Thread 17312.0x5614]
0x00007ff97e75d7e3 in TlsGetValue () from C:\WINDOWS\System32\KernelBase.dll
(gdb)

If you're in Cygwin, you need to tell gdb to handle SIGINT:

(gdb) handle SIGINT
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal        Stop      Print   Pass to program Description
SIGINT        Yes       Yes     No              Interrupt
(gdb)
1
votes

I had the same problem. What solved it for me was using gdb with cmd.exe and setting the following option in gdb.

set new-console on

Now I can use Ctrl + c to interrupt both gui and console programs.

0
votes

To find the infinite loop, you could try stepping through the execution until you get to a sequence that repeats indefinitely.

I'm not sure, but I think Ctrl-C should only stop the execution, not gdb itself...

I think there is a "handle" command that you can use to control how the interrupt signal is handled.