robotSquirrel
robotSquirrel

Reputation: 27

Why does gdb mi give me &"\n" as return to my -gdb-exit command?

I am writing some code that has to communicate with gdb mi. So I forked my programm, put two pipes in place and started gdb mi in the child, so that I can communicate with gdb mi from the parent. gdb always returns "(gdb) \n" when it is finished, so I look for that and write my next command. This is a minimum example of my code:

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

If I call the same commands by hand, thats the output I get (-> means input from me)

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

But if I run my code, which should be essentialy the same, I get this output:

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

According to the gdb mi manual, an & preceeds a log entry, but this log entry is empty, exept for the newline. Also, I don't know why there should be a log entry for exiting, or why it fails to exit, but doesen't produce an error. Also, if you know any better sources for gdb mi than this: https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI , please let me know.

Upvotes: 0

Views: 177

Answers (1)

John Bollinger
John Bollinger

Reputation: 180306

There are several issues with your example code, many of which have already been summarized in comments:

  • read() and write() transfer only the bytes specified, or a leading subsequence of those. They do not append null bytes to the transfer.
  • read() and write() are not guaranteed to transfer the full number of bytes requested on any given call.
  • your buffer might not be long enough to accommodate some responses from the child all in a single read().

According to the gdb mi manual, an & preceeds a log entry, but this log entry is empty, exept for the newline. Also, I don't know why there should be a log entry for exiting, or why it fails to exit, but doesen't produce an error.

None of the above explain the log entry or other behavior differences, but this probably does:

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

Supposing that the full number of bytes requested is transferred, you are writing not only the command, but also a string terminator. The terminator is not part of the MI protocol, so your program is not behaving the same as your interactive session. Moreover, the particular error -- an extra null byte -- is one that is especially likely to produce mysterious output. I can only speculate about the specifics, but I could believe that gdb is treating the extra null byte immediately following a newline as if it terminated a zero-byte command. If so, then you are effectively issuing three commands to gdb, not two, and the gdb log output is about the null byte / empty command.

You would probably find stream I/O to be more convenient for your purposes than raw I/O. That would insulate you from many of the idiosyncrasies of raw I/O, and it would make fgets() available to you for input, which would be to your advantage. The fdopen() function can wrap streams around your file descriptors:

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

Then use fputs(), fgets(), fprintf(), etc. to interact with the child.

Also, if you know any better sources for gdb mi than this: https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI , please let me know.

Requests for external resources are off-topic here.

In any event, you are referencing the appropriate manual, and short of analyzing the source code, that is the ultimate source of knowledge on the matter.

Upvotes: 1

Related Questions