c - 如何在 Linux 上远程控制 GDB

标签 c linux process gdb

我必须实现这样的东西:

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

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

我被困在这一点上,任何想法将不胜感激。先谢谢了。

最佳答案

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

一些有趣的注意事项:

  1. 当 GDB 运行调试目标时,它不响应 SIGINT .您必须发送 SIGINT到调试目标。这就是为什么我 fork 两次而不是启动 gdb --args <target> 的原因.我需要它正在调试的进程的 PID,这样我就可以发送 SIGINT .
  2. 当您将管道附加到进程时' stdoutstderr , 你必须阅读它们 否则目标进程最终会阻塞(当它们填满管道的缓冲区时)。我的实现在这里很愚蠢,因为我不想花时间使用线程或做适当的 select电话。
  3. 您必须注意 API 何时会被阻止。请注意,我使用的是 read/write而不是 fread , fwrite由于他们无法阅读我要求的金额时的行为。

“追踪”程序是:

#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 的输出是“Continuing”。直到后来我读到它的标准输出管道时才出现。

关于c - 如何在 Linux 上远程控制 GDB,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47426472/

相关文章:

c - 复制名称中包含双引号的文件时,Mac OS 10.7.5 BSD copyfile() EINVAL

c++ - 反正有没有用更短的代码来填充结构?

c - 通过 termios 对伪终端通信进行编程。奇偶校验位选项不起作用

linux - 在linux中的多个目录中的文件上运行shell脚本

c# - "Win32Exception: The directory name is invalid"模拟和UAC

mysql - 端口 3306 繁忙但没有进程使用它

c - 如何为 Linux 编写 Hello world 驱动程序

linux - 有什么好的工具可以解决 linux 上的整数程序问题?

mysql - Mysql集群安装完成后启动Mysql

c# - 委托(delegate)无需创建实例即可工作