大概分析 gdbserver 原理

這次來研究一下 gdbserver https://github.com/bet4it/gdbserver.git https://github.com/trixirt/deebe 簡單來說 gdbserver 工作原理就是透過ptrace 去控制 process 的動作 在main 可以看到 這邊會透過 get_request 來持續查看是否有封包傳入,也就是連接後等待gdb送指令過來

c
void get_request()
{
  while (true)
  {
    read_packet();
    process_packet();
    write_flush();
  }
}

要解碼封包可以看這邊 https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#Packets 也就是假設要在一個gdb 加上自己想要的功能的話,可能就要去異動這個部分,以這邊的例子是有os的情況下所以有ptrace可以用,在嵌入式可能要透過jtag 去讀寫register

c
void process_packet()
{
  uint8_t *inbuf = inbuf_get();
  int inbuf_size = inbuf_end();
  uint8_t *packetend_ptr = (uint8_t *)memchr(inbuf, '#', inbuf_size);
  int packetend = packetend_ptr - inbuf;
  assert('$' == inbuf[0]);
  char request = inbuf[1];
  char *payload = (char *)&inbuf[2];
  inbuf[packetend] = '\0';

  uint8_t checksum = 0;
  uint8_t checksum_str[3];
  for (int i = 1; i < packetend; i++)
    checksum += inbuf[i];
  assert(checksum == (hex(inbuf[packetend + 1]) << 4 | hex(inbuf[packetend + 2])));

  switch (request)
  {
  case 'D':
    for (int i = 0, n = 0; i < THREAD_NUMBER && n < threads.len; i++)
      if (threads.t[i].tid)
        if (ptrace(PTRACE_DETACH, threads.t[i].tid, NULL, NULL) < 0)
          perror("ptrace()");
    exit(0);
  case 'g':
  {
    regs_struct regs;
    uint8_t regbuf[20];
    tmpbuf[0] = '\0';
    ptrace(PTRACE_GETREGS, threads.curr->tid, NULL, &regs);
    for (int i = 0; i < ARCH_REG_NUM; i++)
    {
      mem2hex((void *)(((size_t *)&regs) + regs_map[i].idx), regbuf, regs_map[i].size);
      regbuf[regs_map[i].size * 2] = '\0';
      strcat(tmpbuf, regbuf);
    }
    write_packet(tmpbuf);
    break;
  }
  case 'H':
    if ('g' == *payload++)
    {
      pid_t tid;
      char *dot = strchr(payload, '.');
      assert(dot);
      tid = strtol(dot, NULL, 16);
      if (tid > 0)
        set_curr_thread(tid);
    }
    write_packet("OK");
    break;
  case 'm':
  {
    size_t maddr, mlen, mdata;
    assert(sscanf(payload, "%zx,%zx", &maddr, &mlen) == 2);
    if (mlen * SZ * 2 > 0x20000)
    {
      puts("Buffer overflow!");
      exit(-1);
    }
    for (int i = 0; i < mlen; i += SZ)
    {
      errno = 0;
      mdata = ptrace(PTRACE_PEEKDATA, threads.curr->tid, maddr + i, NULL);
      if (errno)
      {
        sprintf(tmpbuf, "E%02x", errno);
        break;
      }
      mdata = restore_breakpoint(maddr, sizeof(size_t), mdata);
      mem2hex((void *)&mdata, tmpbuf + i * 2, (mlen - i >= SZ ? SZ : mlen - i));
    }
    tmpbuf[mlen * 2] = '\0';
    write_packet(tmpbuf);
    break;
  }
  case 'M':
  {
    size_t maddr, mlen, mdata;
    assert(sscanf(payload, "%zx,%zx", &maddr, &mlen) == 2);
    for (int i = 0; i < mlen; i += SZ)
    {
      if (mlen - i >= SZ)
        hex2mem(payload + i * 2, (void *)&mdata, SZ);
      else
      {
        mdata = ptrace(PTRACE_PEEKDATA, threads.curr->tid, maddr + i, NULL);
        hex2mem(payload + i * 2, (void *)&mdata, mlen - i);
      }
      ptrace(PTRACE_POKEDATA, threads.curr->tid, maddr + i, mdata);
    }
    write_packet("OK");
    break;
  }
  case 'p':
  {
    int i = strtol(payload, NULL, 16);
    if (i >= ARCH_REG_NUM && i != EXTRA_NUM)
    {
      write_packet("E01");
      break;
    }
    size_t regdata;
    if (i == EXTRA_NUM)
    {
      regdata = ptrace(PTRACE_PEEKUSER, threads.curr->tid, SZ * EXTRA_REG, NULL);
      mem2hex((void *)&regdata, tmpbuf, EXTRA_SIZE);
      tmpbuf[EXTRA_SIZE * 2] = '\0';
    }
    else
    {
      regdata = ptrace(PTRACE_PEEKUSER, threads.curr->tid, SZ * regs_map[i].idx, NULL);
      mem2hex((void *)&regdata, tmpbuf, regs_map[i].size);
      tmpbuf[regs_map[i].size * 2] = '\0';
    }
    write_packet(tmpbuf);
    break;
  }
  case 'P':
  {
    int i = strtol(payload, &payload, 16);
    assert('=' == *payload++);
    if (i >= ARCH_REG_NUM && i != EXTRA_NUM)
    {
      write_packet("E01");
      break;
    }
    size_t regdata = 0;
    hex2mem(payload, (void *)&regdata, SZ * 2);
    if (i == EXTRA_NUM)
      ptrace(PTRACE_POKEUSER, threads.curr->tid, SZ * EXTRA_REG, regdata);
    else
      ptrace(PTRACE_POKEUSER, threads.curr->tid, SZ * regs_map[i].idx, regdata);
    write_packet("OK");
    break;
  }
  case 'q':
    process_query(payload);
    break;
  case 'v':
    process_vpacket(payload);
    break;
  case 'X':
  {
    size_t maddr, mlen, mdata;
    int offset, new_len;
    assert(sscanf(payload, "%zx,%zx:%n", &maddr, &mlen, &offset) == 2);
    payload += offset;
    new_len = unescape(payload, (char *)packetend_ptr - payload);
    assert(new_len == mlen);
    for (int i = 0; i < mlen; i += SZ)
    {
      if (mlen - i >= SZ)
        memcpy((void *)&mdata, payload + i, SZ);
      else
      {
        mdata = ptrace(PTRACE_PEEKDATA, threads.curr->tid, maddr + i, NULL);
        memcpy((void *)&mdata, payload + i, mlen - i);
      }
      ptrace(PTRACE_POKEDATA, threads.curr->tid, maddr + i, mdata);
    }
    write_packet("OK");
    break;
  }
  case 'Z':
  {
    size_t type, addr, length;
    assert(sscanf(payload, "%zx,%zx,%zx", &type, &addr, &length) == 3);
    if (type == 0 && sizeof(break_instr))
    {
      bool ret = set_breakpoint(threads.curr->tid, addr, length);
      if (ret)
        write_packet("OK");
      else
        write_packet("E01");
    }
    else
      write_packet("");
    break;
  }
  case 'z':
  {
    size_t type, addr, length;
    assert(sscanf(payload, "%zx,%zx,%zx", &type, &addr, &length) == 3);
    if (type == 0)
    {
      bool ret = remove_breakpoint(threads.curr->tid, addr, length);
      if (ret)
        write_packet("OK");
      else
        write_packet("E01");
    }
    else
      write_packet("");
    break;
  }
  case '?':
    write_packet("S05");
    break;
  default:
    write_packet("");
  }

  inbuf_erase_head(packetend + 3);
}
c
int main(int argc, char *argv[])
{
  pid_t pid;
  char **next_arg = &argv[1];
  char *arg_end, *target = NULL;
  int stat;

  if (*next_arg != NULL && strcmp(*next_arg, "--attach") == 0)
  {
    attach = true;
    next_arg++;
  }

  target = *next_arg;
  next_arg++;

  if (target == NULL || *next_arg == NULL)
  {
    printf("Usage : gdbserver 127.0.0.1:1234 a.out or gdbserver --attach 127.0.0.1:1234 2468\n");
    exit(-1);
  }

  if (attach)
  {
    pid = atoi(*next_arg);
    init_tids(pid);
    for (int i = 0, n = 0; i < THREAD_NUMBER && n < threads.len; i++)
      if (threads.t[i].tid)
      {
        if (ptrace(PTRACE_ATTACH, threads.t[i].tid, NULL, NULL) < 0)
        {
          perror("ptrace()");
          return -1;
        }
        if (waitpid(threads.t[i].tid, &threads.t[i].stat, __WALL) < 0)
        {
          perror("waitpid");
          return -1;
        }
        ptrace(PTRACE_SETOPTIONS, threads.t[i].tid, NULL, PTRACE_O_TRACECLONE);
        n++;
      }
  }
  else
  {
    pid = fork();
    if (pid == 0)
    {
      char **args = next_arg;
      setpgrp();
      ptrace(PTRACE_TRACEME, 0, NULL, NULL);
      execvp(args[0], args);
      perror(args[0]);
      _exit(1);
    }
    if (waitpid(pid, &stat, __WALL) < 0)
    {
      perror("waitpid");
      return -1;
    }
    threads.t[0].pid = threads.t[0].tid = pid;
    threads.t[0].stat = stat;
    threads.len = 1;
    int options = PTRACE_O_TRACECLONE;
#ifdef PTRACE_O_EXITKILL
    options |= PTRACE_O_EXITKILL;
#endif
    ptrace(PTRACE_SETOPTIONS, pid, NULL, options);
  }
  threads.curr = &threads.t[0];
  initialize_async_io(sigint_pid);
  remote_prepare(target);
  get_request();
  return 0;
}

在面對不同的指令集架構的情況下,可能從arch.h 描述register 的,不同版本的gdb 可能對應到的 headfile不太相同,版號大改之類的

c
#ifndef ARCH_H
#define ARCH_H

#include <stdint.h>

struct reg_struct
{
    int idx;
    int size;
};

#define ARCH_REG_NUM (sizeof(regs_map) / sizeof(struct reg_struct))

#ifdef __i386__

#include <sys/reg.h>

#define SZ 4
#define FEATURE_STR "l<target version=\"1.0\"><architecture>i386</architecture></target>"
static uint8_t break_instr[] = {0xcc};

#define PC EIP
#define EXTRA_NUM 41
#define EXTRA_REG ORIG_EAX
#define EXTRA_SIZE 4

typedef struct user_regs_struct regs_struct;

// gdb/features/i386/32bit-core.c
struct reg_struct regs_map[] = {
    {EAX, 4},
    {ECX, 4},
    {EDX, 4},
    {EBX, 4},
    {UESP, 4},
    {EBP, 4},
    {ESI, 4},
    {EDI, 4},
    {EIP, 4},
    {EFL, 4},
    {CS, 4},
    {SS, 4},
    {DS, 4},
    {ES, 4},
    {FS, 4},
    {GS, 4},
};

#endif /* __i386__ */

#ifdef __x86_64__

#include <sys/reg.h>

#define SZ 8
#define FEATURE_STR "l<target version=\"1.0\"><architecture>i386:x86-64</architecture></target>"
static uint8_t break_instr[] = {0xcc};

#define PC RIP
#define EXTRA_NUM 57
#define EXTRA_REG ORIG_RAX
#define EXTRA_SIZE 8

typedef struct user_regs_struct regs_struct;

// gdb/features/i386/64bit-core.c
struct reg_struct regs_map[] = {
    {RAX, 8},
    {RBX, 8},
    {RCX, 8},
    {RDX, 8},
    {RSI, 8},
    {RDI, 8},
    {RBP, 8},
    {RSP, 8},
    {R8, 8},
    {R9, 8},
    {R10, 8},
    {R11, 8},
    {R12, 8},
    {R13, 8},
    {R14, 8},
    {R15, 8},
    {RIP, 8},
    {EFLAGS, 4},
    {CS, 4},
    {SS, 4},
    {DS, 4},
    {ES, 4},
    {FS, 4},
    {GS, 4},
};

#endif /* __x86_64__ */

#ifdef __arm__

#define SZ 4
#define FEATURE_STR "l<target version=\"1.0\"><architecture>arm</architecture></target>"

static uint8_t break_instr[] = {0xf0, 0x01, 0xf0, 0xe7};

#define PC 15
#define EXTRA_NUM 25
#define EXTRA_REG 16
#define EXTRA_SIZE 4

typedef struct user_regs regs_struct;

struct reg_struct regs_map[] = {
    {0, 4},
    {1, 4},
    {2, 4},
    {3, 4},
    {4, 4},
    {5, 4},
    {6, 4},
    {7, 4},
    {8, 4},
    {9, 4},
    {10, 4},
    {11, 4},
    {12, 4},
    {13, 4},
    {14, 4},
    {15, 4},
};

#endif /* __arm__ */

#ifdef __powerpc__

#define SZ 4
#define FEATURE_STR "l<target version=\"1.0\">\
  <architecture>powerpc:common</architecture>\
  <feature name=\"org.gnu.gdb.power.core\">\
    <reg name=\"r0\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r1\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r2\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r3\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r4\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r5\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r6\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r7\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r8\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r9\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r10\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r11\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r12\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r13\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r14\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r15\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r16\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r17\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r18\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r19\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r20\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r21\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r22\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r23\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r24\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r25\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r26\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r27\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r28\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r29\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r30\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"r31\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>\
    <reg name=\"msr\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"orig_r3\" bitsize=\"32\" type=\"int\"/>\
    <reg name=\"ctr\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"lr\" bitsize=\"32\" type=\"code_ptr\"/>\
    <reg name=\"xer\" bitsize=\"32\" type=\"uint32\"/>\
    <reg name=\"cr\" bitsize=\"32\" type=\"uint32\"/>\
</feature>\
</target>"

static uint8_t break_instr[] = {};

#define PC 32
#define EXTRA_NUM -1
#define EXTRA_REG -1
#define EXTRA_SIZE -1

typedef struct pt_regs regs_struct;

struct reg_struct regs_map[] = {
    {0, 4},
    {1, 4},
    {2, 4},
    {3, 4},
    {4, 4},
    {5, 4},
    {6, 4},
    {7, 4},
    {8, 4},
    {9, 4},
    {10, 4},
    {11, 4},
    {12, 4},
    {13, 4},
    {14, 4},
    {15, 4},
    {16, 4},
    {17, 4},
    {18, 4},
    {19, 4},
    {20, 4},
    {21, 4},
    {22, 4},
    {23, 4},
    {24, 4},
    {25, 4},
    {26, 4},
    {27, 4},
    {28, 4},
    {29, 4},
    {30, 4},
    {31, 4},
    {32, 4},
    {33, 4},
    {34, 4},
    {35, 4},
    {36, 4},
    {37, 4},
    {38, 4},
};

#endif /* __powerpc__ */

#endif /* ARCH_H */