【问题标题】:Why does gdb mi give me &"\n" as return to my -gdb-exit command?为什么 gdb mi 给我 &"\n" 作为我的 -gdb-exit 命令的返回?
【发布时间】:2021-07-19 13:21:29
【问题描述】:

我正在编写一些必须与 gdb mi 通信的代码。所以我分叉了我的程序,放置了两个管道并在孩子中启动了 gdb mi,这样我就可以与父母的 gdb mi 进行通信。 gdb 完成后总是返回 "(gdb) \n",所以我寻找它并编写我的下一个命令。这是我的代码的最小示例:

int main(){
    printf("Starting Test\n");

    int fromGDB[2], toGDB[2], nbytes;
    pid_t childpid;
    char readbuffer[80] = "";

    pipe(fromGDB);
    pipe(toGDB);

    if((childpid = fork())==-1)
    {
        perror("fork");
        exit(1);
    }

    if(childpid == 0){
        
        close(toGDB[1]);
        close(fromGDB[0]);
        
        int backup = dup(1);    //store stdout
        if (dup2(fromGDB[1],1) < 0){puts("hat nicht geklappt");}
        
        int backupStdin = dup(0); //store stdin
        if (dup2(toGDB[0],0) < 0){puts("hat nicht geklappt");}

        system("gdb -q --interpreter=mi2"); // start gdb mi

        dup2(backup,1); // restore stdout
        
        puts("child fertig");
        exit(0);
        
    }else{
        close(toGDB[0]);
        close(fromGDB[1]);

        char* writeCommand = "";
        int commandCounter = 0;

        while (commandCounter <3){
            nbytes = read(fromGDB[0],readbuffer,sizeof(readbuffer));
            printf("parent recived: %s", readbuffer);
            if (strncmp(readbuffer+strlen(readbuffer)-strlen("(gdb) \n"),"(gdb) \n", strlen("(gdb) \n")) == 0){
                switch (commandCounter){
                    case 0: writeCommand = "-file-exec-and-symbols /home/dev/spielwiese/build/main\n"; break;
                    case 1: writeCommand = "-gdb-exit\n"; break;
                    default: writeCommand = "you should never reach here\n"; break;
                }
                write(toGDB[1],writeCommand,strlen(writeCommand)+1);
                printf("wrote: %s", writeCommand);
                commandCounter++;
            }else if(strncmp(readbuffer,"^exit", sizeof("^exit")-1) == 0){
                break;
            }
            memset(readbuffer,'\0',strlen(readbuffer)); //clear the readbuffer
        }

        puts("parent fertig");
        sleep(5);
    }
    
    return 0;
}

如果我手动调用相同的命令,这就是我得到的输出(-> 表示来自我的输入)

-> gdb -q --interpreter=mi2
=thread-group-added,id="i1"
(gdb) 
-> -file-exec-and-symbols /home/dev/spielwiese/build/main
^done
(gdb) 
-> -gdb-exit
^exit

但是如果我运行我的代码,这应该是基本相同的,我会得到这个输出:

Starting Test
parent recived: =thread-group-added,id="i1"
parent recived: (gdb) 
wrote: -file-exec-and-symbols /home/dev/spielwiese/build/main
parent recived: ^done
(gdb) 
wrote: -gdb-exit
parent recived: &"\n"
parent recived: ^done
(gdb) 
wrote: you should never reach here
parent fertig

根据 gdb mi 手册,& 前面有一个日志条目,但这个日志条目是空的,除了换行符。另外,我不知道为什么应该有一个退出的日志条目,或者为什么它无法退出,但没有产生错误。 另外,如果您知道比这更好的 gdb mi 来源:https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI,请告诉我。

【问题讨论】:

  • read() 不会用'\0' 终止缓冲区,因此之后使用printf( "%s" )strcmp() 之类的东西会导致UB。由于协议是面向行的,你可以考虑使用fgets()
  • 我并不清楚为什么 gdb-mi 可能会在一种情况下输出日志记录,而在另一种情况下不会。但是,由于您无法完全预测何时会发出日志消息,因此我倾向于说您的程序应该简单地准备好处理它们。
  • 还要注意,除了read()s 不会自动以空值结尾之外,它们可能很短。此外,它们可能很容易超过您的 80 字节缓冲区可以容纳的长度(或任何更长的缓冲区可以容纳的长度)。这两个因素都可能导致子级输出的消息跨越多个父级的read()。这同样适用于另一个方向,尽管您可以更好地控制缓冲区大小是否足够。
  • 另外,在分叉的孩子中使用system() 有点奇怪。只要您为自己费心fork(),使用其中一个 exec 函数会更传统。唯一花费您的是输出“child fertig”的能力(因为 exec 函数不返回,除非出现错误)。但是父母还有其他几种方法可以知道孩子何时终止以及如何终止。

标签: c gdb pipe


【解决方案1】:

您的示例代码存在几个问题,其中许多问题已经在 cmets 中进行了总结:

  • read()write() 仅传输指定的字节,或这些字节的前导子序列。它们不会将空字节附加到传输中。
  • read()write() 不能保证传输任何给定调用请求的全部字节数。
  • 您的缓冲区可能不够长,无法在单个read() 中容纳来自孩子的一些响应。

根据 gdb mi 手册,& 前面有一个日志条目,但这个日志条目是空的,除了换行符。另外,我不知道为什么应该有一个退出的日志条目,或者为什么它无法退出,但没有产生错误。

以上都没有解释日志条目或其他行为差异,但这可能是:

                write(toGDB[1],writeCommand,strlen(writeCommand)+1);

假设传输了请求的全部字节数,您不仅要编写命令,还要编写字符串终止符。终止符不是 MI 协议的一部分,因此您的程序与交互式会话的行为不同。此外,特定的错误——一个额外的空字节——是一个特别有可能产生神秘输出的错误。我只能推测细节,但我可以相信gdb 正在处理紧跟在换行符之后的额外空字节,就好像它终止了一个零字节命令一样。如果是这样,那么您实际上是在向 gdb 发出三个命令,而不是两个,并且 gdb 日志输出是关于空字节/空命令的。

您可能会发现流 I/O 比原始 I/O 更方便。这将使您免受原始 I/O 的许多特性的影响,并且它将使 fgets() 可供您输入,这将是您的优势。 fdopen() 函数可以将流包装在文件描述符周围:

    #define BUFFER_SIZE 1024

    // Parent
    FILE *fFromGDB = fdopen(fromGDB[0], "r");
    FILE *fToGDB = fdopen(fToGDB[1], "w");

    if (!fFromGDB || ! fToGDB) // ...

    // You probably want fToGDB to be line-buffered, not block buffered:
    setvbuf(fToGDB, NULL, _IOLBF, BUFFER_SIZE);

然后使用fputs()fgets()fprintf()。与孩子互动。

另外,如果您知道比这更好的 gdb mi 来源:https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI,请告诉我。

对外部资源的请求在这里是题外话。

在任何情况下,您都在参考相应的手册,并且没有分析源代码,这是有关此事的最终知识来源。

【讨论】:

  • 谢谢,正如您所怀疑的那样,它是书面命令中的空终止符。也感谢关于 I/O 的建议!
猜你喜欢
  • 2011-06-17
  • 1970-01-01
  • 2012-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多