# 大概分析 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, ®s);
for (int i = 0; i < ARCH_REG_NUM; i++)
{
mem2hex((void *)(((size_t *)®s) + 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 *)®data, tmpbuf, EXTRA_SIZE);
tmpbuf[EXTRA_SIZE * 2] = '\0';
}
else
{
regdata = ptrace(PTRACE_PEEKUSER, threads.curr->tid, SZ * regs_map[i].idx, NULL);
mem2hex((void *)®data, 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 *)®data, 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 */
```