如何在Linux上远程控制GDB

时间:2017-11-22 03:50:11

标签: c linux process gdb

我必须实现这样的事情:

  1. GDB
  2. 下启动一个程序(例如a.out
  3. 设置一些断点
  4. 定期向 GDB 发送 CTRL - C 信号以暂停执行a.out。
  5. 在停止点或断点处执行“bt,info threads”等命令
  6. 继续执行a.out
  7. 直到a.out
  8. 结束

    这些步骤可以在shell下以交互方式执行,但我需要在程序中自动执行。我正在考虑使用fork为 GDB 创建子进程并使用popen执行初始 GDB 启动命令,但是我如何定期发送 > GDB 子命令(bt,continue)到该子进程并让它执行它们?

    我陷入了困境,任何想法都会受到高度赞赏。谢谢你。

1 个答案:

答案 0 :(得分:1)

这是一个非常简单的实现。它在没有管道的情况下分配目标进程,我们只需要学习它pid。然后它使用-p <PID>选项分叉gdb以附加到我们的目标。在执行之前,GDB的fork为stdin / stdout / stderr设置管道,以便我们可以远程控制GDB。

一些有趣的说明:

  1. 当GDB运行调试目标时,它不响应SIGINT。您必须将SIGINT发送到调试目标。这就是我分叉两次而不是启动gdb --args <target>的原因。我需要调试进程的PID,以便发送SIGINT
  2. 当您将流程附加到流程“stdoutstderr时,必须读取它们,否则目标流程最终会阻止(当它们填充管道的缓冲区时)。我的实现在这里很愚蠢,因为我不想花时间使用线程或进行适当的select调用。
  3. 你必须要小心API何时会阻止。请注意,我使用的是read / write而不是freadfwrite,因为当他们无法读取我请求的金额时,他们的行为就会出现。
  4. '追踪'计划是:

    #include <stdio.h>
    #include <string.h>
    
    #include <unistd.h>
    #include <signal.h>
    #include <fcntl.h>
    #include <sys/select.h>
    
    char gdb_pid_buf[20];
    
    char *gdb_argv[] =
    {
      "gdb",
      "-p",
      gdb_pid_buf,
      NULL
    };
    
    char *child_argv[] =
    {
      "./looper",
      NULL
    };
    
    const char GDB_PROMPT[] = "(gdb)";
    
    int wait_for_prompt(const char *prefix, int fd)
    {
      char readbuf[4096];
      size_t used = 0;
      while(1)
      {
        ssize_t amt;
        char *prompt;
        char *end;
    
        amt = read(fd, readbuf+used, sizeof(readbuf)-used-1);
        if(amt == -1)
        {
          return 1;
        }
        else if(amt == 0)
        {  }
        else
        {
          used += amt;
    
          readbuf[used] = '\0';
          for(end = strstr(readbuf, "\n"); end; end= strstr(readbuf, "\n"))
          {
            size_t consumed;
            size_t remaining;
    
            *end = '\0';
            printf("%s: %s\n", prefix, readbuf);
    
            consumed = (end-readbuf) + strlen("\n");
            remaining = used - consumed;
            memmove(readbuf, readbuf+consumed, remaining);
            used -= consumed;
          }
    
          prompt = strstr(readbuf, GDB_PROMPT);
          if(prompt)
          {
            *prompt = '\0';
            printf("%s: %s", prefix, readbuf);
            printf("[PROMPT]\n");
            fflush(stdout);
            break;
          }
        }
      }
      return 0;
    }
    
    int main(int argc, char *argv)
    {
      int i;
    
      int stdin_pipe[2];
      int stdout_pipe[2];
      int stderr_pipe[2];
    
      pipe(stdin_pipe);
      pipe(stdout_pipe);
      pipe(stderr_pipe);
    
      int gdb_pid;
      int child_pid;
    
      //Launch child
      child_pid = fork();
      if(child_pid == 0)
      {
        close(stdin_pipe[0]);
        close(stdout_pipe[0]);
        close(stderr_pipe[0]);
        close(stdin_pipe[1]);
        close(stdout_pipe[1]);
        close(stderr_pipe[1]);
    
        execvp(child_argv[0], child_argv);
        return 0;
      }
    
      sprintf(gdb_pid_buf, "%d", child_pid);
    
      //Launch gdb with command-line args to attach to child.
      gdb_pid = fork();
      if(gdb_pid == 0)
      {
        close(stdin_pipe[1]);
        close(stdout_pipe[0]);
        close(stderr_pipe[0]);
    
        dup2(stdin_pipe[0],0);
        dup2(stdout_pipe[1],1);
        dup2(stderr_pipe[1],2);
    
        execvp(gdb_argv[0], gdb_argv);
        return 0;
      }
    
      close(stdin_pipe[0]);
      close(stdout_pipe[1]);
      close(stderr_pipe[1]);
    
      //Wait for GDB to reach its prompt
      if(wait_for_prompt("GDB", stdout_pipe[0]))
        {fprintf(stderr,"child died\n");return 1;}
    
      printf("[SENDING \"continue\\n\"]\n");
      fflush(stdout);
      write(stdin_pipe[1], "continue\n", strlen("continue\n"));
    
      sleep(4);
    
      printf("[SENDING \"CTRL+C\"]\n");
      fflush(stdout);
      kill(child_pid, SIGINT);
    
      //Then read through all the output until we reach a prompt.
      if(wait_for_prompt("POST SIGINT", stdout_pipe[0]))
        {fprintf(stderr,"child died\n");return 1;}
    
      //Ask for the stack trace
      printf("[SENDING \"where\\n\"]\n");
      fflush(stdout);
      write(stdin_pipe[1], "where\n", strlen("where\n"));
    
      //read through the stack trace output until the next prompt
      if(wait_for_prompt("TRACE", stdout_pipe[0]))
        {fprintf(stderr,"child died\n");return 1;}
    
      kill(child_pid, SIGKILL);
      kill(gdb_pid, SIGKILL);
    }
    

    目标计划looper只是:

    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[])
    {
      while(1)
      {
        printf(".");
        fflush(stdout);
        sleep(1);
      }
    }
    

    示例输出是:

    $ ./a.out
    .GDB: GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7
    GDB: Copyright (C) 2013 Free Software Foundation, Inc.
    GDB: License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
    GDB: This is free software: you are free to change and redistribute it.
    GDB: There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
    GDB: and "show warranty" for details.
    GDB: This GDB was configured as "x86_64-redhat-linux-gnu".
    GDB: For bug reporting instructions, please see:
    GDB: <http://www.gnu.org/software/gdb/bugs/>.
    GDB: Attaching to process 8057
    GDB: Reading symbols from /home/<nope>/temp/remotecontrol/looper...(no debugging symbols found)...done.
    GDB: Reading symbols from /lib64/libc.so.6...(no debugging symbols     found)...done.
    GDB: Loaded symbols for /lib64/libc.so.6
    GDB: Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
    GDB: Loaded symbols for /lib64/ld-linux-x86-64.so.2
    GDB: 0x00007f681b4f9480 in __nanosleep_nocancel () from /lib64/libc.so.6
    GDB: Missing separate debuginfos, use: debuginfo-install glibc-2.17-    106.el7_2.4.x86_64
    GDB: [PROMPT]
    [SENDING "continue\n"]
    ....[SENDING "CTRL+C"]
    POST SIGINT: Continuing.
    POST SIGINT:
    POST SIGINT: Program received signal SIGINT, Interrupt.
    POST SIGINT: 0x00007f681b4f9480 in __nanosleep_nocancel () from /lib64/libc.so.6
    POST SIGINT: [PROMPT]
    [SENDING "where\n"]
    TRACE: #0  0x00007f681b4f9480 in __nanosleep_nocancel () from /lib64/libc.so.6
    TRACE: #1  0x00007f681b4f9334 in sleep () from /lib64/libc.so.6
    TRACE: #2  0x0000000000400642 in main ()
    TRACE: [PROMPT]
    

    你可以从....看到目标确实继续运行,即使GDB的输出是“继续”。直到后来当我读到它的stdout管道时才出现。