You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GDB/MI进程FIFO循环中read()调用阻塞问题排查

GDB/MI接口read()调用阻塞问题排查与修复

问题描述

我正在使用GDB/MI接口,尝试为命令绑定快捷键,但遇到了read()调用阻塞的问题。已尝试多种修改方式但无法解决,本人对管道与fork技术较为陌生。

相关代码

#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;
}

运行输出

添加printf()语句后,反复按下'b'和'r'的运行输出如下:

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

执行'b'命令似乎成功添加了断点,但后续的read()调用出现阻塞。


问题根源与修复方案

核心问题

  1. FIFO重复打开/关闭:父进程每次循环都重新打开、关闭FIFO,这会破坏FIFO的双向连接状态——FIFO需要读写两端同时保持打开才能正常传输数据,反复开关会导致read调用阻塞。
  2. 子进程stdin设置非阻塞:GDB默认需要阻塞读取标准输入,给stdin加上O_NONBLOCK会导致GDB输入处理逻辑异常,进而引发输出端的传输问题。
  3. read循环逻辑错误:未处理read()返回0的情况(表示写端已关闭,子进程退出),且每次循环都重新打开输出FIFO,导致读取状态混乱。

修改后的代码

#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;

    // 清理旧FIFO并创建新的
    unlink(DEBUGGER_IN_FIFO);
    unlink(DEBUGGER_OUT_FIFO);
    if (mkfifo(DEBUGGER_IN_FIFO, 0777) == -1 || mkfifo(DEBUGGER_OUT_FIFO, 0777) == -1) {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    switch (pid) {
        case -1:
            perror("fork");
            exit(EXIT_FAILURE);

        case 0:
            // 子进程:先打开输出FIFO(父进程会打开读端)
            debugger_output = open(DEBUGGER_OUT_FIFO, O_WRONLY);
            if (debugger_output == -1) {
                perror("open out fifo");
                exit(EXIT_FAILURE);
            }
            dup2(debugger_output, STDOUT_FILENO);
            close(debugger_output);

            // 子进程:打开输入FIFO,去掉O_NONBLOCK,GDB需要阻塞读输入
            debugger_input = open(DEBUGGER_IN_FIFO, O_RDONLY);
            if (debugger_input == -1) {
                perror("open in fifo");
                exit(EXIT_FAILURE);
            }
            dup2(debugger_input, STDIN_FILENO);
            close(debugger_input);

            execvp(cmd_dbg[0], cmd_dbg);
            perror("execvp");
            exit(EXIT_FAILURE);
            
        default:
            // 父进程:提前打开FIFO,保持连接,不要每次循环开关
            debugger_output = open(DEBUGGER_OUT_FIFO, O_RDONLY);
            if (debugger_output == -1) {
                perror("open out fifo parent");
                exit(EXIT_FAILURE);
            }

            debugger_input = open(DEBUGGER_IN_FIFO, O_WRONLY);
            if (debugger_input == -1) {
                perror("open in fifo parent");
                exit(EXIT_FAILURE);
            }

            for (;;) {
                printf("READ START\n");
                int got_prompt = 0;

                // 持续读取直到拿到(gdb)提示符
                while (!got_prompt) {
                    num_read = read(debugger_output, buffer, BUFFER_SIZE - 1);
                    if (num_read == -1) {
                        perror("read");
                        break;
                    }
                    if (num_read == 0) {
                        // 子进程已退出,结束循环
                        printf("Debugger exited\n");
                        goto cleanup;
                    }
                    buffer[num_read] = '\0';
                    printf("%s", buffer);

                    // 检查是否出现提示符
                    if (strstr(buffer, "(gdb)")) {
                        got_prompt = 1;
                    }
                }
                printf("READ END\n");

                // 读取用户输入
                ch = getchar();
                while ((c = getchar()) != '\n' && c != EOF); // 清空输入缓冲区
                printf("WRITE START %c\n", ch);

                // 发送命令到GDB
                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));
                        goto cleanup; // 发送退出命令后清理
                    default:
                        break;
                }
                printf("WRITE END\n");
            }

cleanup:
            close(debugger_output);
            close(debugger_input);
            // 清理FIFO
            unlink(DEBUGGER_IN_FIFO);
            unlink(DEBUGGER_OUT_FIFO);
            waitpid(pid, NULL, 0); // 等待子进程退出
    }

    return 0;
}

关键修改点

  • 子进程移除O_NONBLOCK:GDB需要阻塞读取标准输入,恢复默认的阻塞模式才能正常处理命令输入。
  • 父进程提前打开FIFO:在循环外一次性打开两个FIFO并保持连接,避免反复开关导致的连接异常。
  • 完善错误处理:添加FIFO打开、fork的错误检查,处理read()返回0的情况(子进程退出)。
  • 添加资源清理:程序退出时关闭FIFO、清理临时文件,并等待子进程结束。

内容的提问来源于stack exchange,提问作者Jeff_V

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:44:51