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()调用出现阻塞。
问题根源与修复方案
核心问题
- FIFO重复打开/关闭:父进程每次循环都重新打开、关闭FIFO,这会破坏FIFO的双向连接状态——FIFO需要读写两端同时保持打开才能正常传输数据,反复开关会导致read调用阻塞。
- 子进程stdin设置非阻塞:GDB默认需要阻塞读取标准输入,给stdin加上
O_NONBLOCK会导致GDB输入处理逻辑异常,进而引发输出端的传输问题。 - 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
相关产品推荐
相关产品推荐

