Search code examples
cgdbforkfifomi

Why is read() blocking in FIFO loop for GDB/MI process


I'm trying to work with the GDB/MI interface, binding shortcuts to commands, but I'm getting blocked on a read() call. I've tried several different variations but can't seem to get it to work. I'm new to pipes and forks.

#define READ        0
#define WRITE       1
#define BUFFER_SIZE 1096

#define DEBUGGER_IN_FIFO    "/tmp/debugger_in"
#define DEBUGGER_OUT_FIFO   "/tmp/debugger_out"

char *cmd_dbg[] = {"gdb", "-q", "-i", "mi", "./prog", NULL};

char *cmd_run        = "-exec-run\n";
char *cmd_breakpoint = "-break-insert main\n";
char *cmd_continue   = "-exec-continue\n";
char *cmd_exit       = "-gdb-exit\n";

int main (void)
{
    pid_t pid;
    int debugger_input,
        debugger_output;
    char buffer [BUFFER_SIZE];
    ssize_t num_read;
    int ch, c;

    unlink (DEBUGGER_IN_FIFO);
    unlink (DEBUGGER_OUT_FIFO);
    mkfifo (DEBUGGER_IN_FIFO, 0777);
    mkfifo (DEBUGGER_OUT_FIFO, 0777);

    pid = fork();
    switch (pid) {
        case -1:
            fprintf (stderr, "fork");
            break;

        case 0:

            debugger_output = open (DEBUGGER_OUT_FIFO, O_WRONLY);
            dup2 (debugger_output, STDOUT_FILENO);
            close (debugger_output);

            debugger_input = open (DEBUGGER_IN_FIFO, O_RDONLY | O_NONBLOCK); 
            dup2 (debugger_input, STDIN_FILENO);
            close (debugger_input);

            execvp (cmd_dbg[0], cmd_dbg);
            perror ("execvp");
            exit (EXIT_FAILURE);
            
        default:

            for (;;) {

                printf ("READ START\n");

                for (;;) {
                    debugger_output = open(DEBUGGER_OUT_FIFO, O_RDONLY);
                    num_read = read(debugger_output, buffer, BUFFER_SIZE);
                    if (num_read == -1) {
                        fprintf (stderr, "read");
                    }
                    buffer[num_read] = '\0';
                    if (strstr(buffer, "(gdb)")) {
                        printf("%s", buffer);
                        break;
                    }
                    printf("%s", buffer);
                }
                close(debugger_output);

                printf ("READ END\n");

                ch = getchar();
                while ((c = getchar()) != '\n' && c != EOF);
                printf ("WRITE START %c\n", ch);

                debugger_input = open (DEBUGGER_IN_FIFO, O_WRONLY | O_NONBLOCK);
                switch (ch) {
                    case 'b':
                        write (debugger_input, cmd_breakpoint, strlen (cmd_breakpoint));
                        break;
                    case 'r':
                        write (debugger_input, cmd_run, strlen (cmd_run));
                        break;
                    case 'c':
                        write (debugger_input, cmd_continue, strlen (cmd_continue));
                        break;
                    case 'e':
                        write (debugger_input, cmd_exit, strlen (cmd_exit));
                        break;
                    default:
                        break;
                }
                close (debugger_input);

                printf ("WRITE END\n");
            }
    }

    return 0;
}

The output for pressing 'b' and then 'r' repeatedly is as follows with the added printf() statements.

READ START
=thread-group-added,id="i1"
=cmd-param-changed,param="auto-load safe-path",value="/home/jvalcher/"
~"Reading symbols from ./prog...\n"
(gdb)
READ END
b
WRITE START b
WRITE END
READ START
^done,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x0000000000001151",func="main",file="prog.c",fullname="/home/jvalcher/Prototypes/gdb/prog.c",line="9",thread-groups=["i1"],times="0",original-location="main"}
(gdb)
READ END
r
WRITE START r
WRITE END
READ START
r
r
r

The 'b' seems to add a breakpoint but then gets blocked on the following read().


Solution

  • The problem is that you are closing GDB's input stream, this results in GDB receiving EOF, at which point GDB exits.

    You should move the open/close of DEBUGGER_OUT_FIFO and DEBUGGER_IN_FIFO outside your for loop. With this done GDB isn't going to exit.

    Slightly off topic, but the approach you're currently taking isn't going to work. Instead of reading from debugger_input until you see a prompt you need to use something like select and wait for either input from the user, or input from GDB, then, once you have input available from GDB you need to read until there is no more input available, parse the input you got from GDB, and then do something with it.

    For example, with the fix I describe above the output from your application is now:

    READ START
    =thread-group-added,id="i1"
    ~"GDB Version: 15.1\n\n"
    =cmd-param-changed,param="index-cache enabled",value="on"
    =cmd-param-changed,param="print pretty",value="on"
    ~"Reading symbols from ./prog...\n"
    (gdb) 
    READ END
    b
    WRITE START b
    WRITE END
    READ START
    ^done,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x0000000000401198",func="main",file="/tmp/hello.c",fullname="/tmp/hello.c",line="18",thread-groups=["i1"],times="0",original-location="main"}
    (gdb) 
    READ END
    r
    WRITE START r
    WRITE END
    READ START
    =thread-group-started,id="i1",pid="1544364"
    =thread-created,id="1",group-id="i1"
    =library-loaded,id="/lib64/ld-linux-x86-64.so.2",target-name="/lib64/ld-linux-x86-64.so.2",host-name="/lib64/ld-linux-x86-64.so.2",symbols-loaded="0",thread-group="i1",ranges=[{from="0x00007ffff7fd3110",to="0x00007ffff7ff2bb4"}]
    ^running
    *running,thread-id="all"
    (gdb) 
    READ END
    

    However, notice that the last output you capture is the *running,... notification. We expect GDB to immediately hit the breakpoint in main, so there will be more output from GDB available, it's just you stopped reading once you saw the (gdb) prompt.

    This is why you need to have a control loop which waits for output from GDB then drains the output.

    And you'll need to parse the output to spot the notifications so you know what state GDB is in, e.g. is the inferior running and we're waiting for the a stop, or is the inferior stopped and we're waiting for user input.