我 已经写过两篇关于gdbserver调试共享库的BLOG了:第一篇解决了调试共享库的难题,让调试共享库成为可能,但是使用起来很麻烦。第二篇做了点改 进,通过一个脚本文件计算偏移量,使用起来稍微方便一点。几年过去了,gdbserver还是不支持调试共享库,我也受够了,最终决定去修改了gdb的代 码。其实我们要做的就是计算共享库加载符号表的地址,算法很简单:

共享库加载符号表的地址 = 共享库在内存中的加载地址+代码段的偏移量。

实现原理如下:
o 通过gdbserver读/proc/$pid/maps文件,以获取共享库在内存中的加载地址。
o 通过bfd查询共享库的代码段的偏移量。
o 计算共享库加载符号表的地址。
o 调用add-symbol-file加载符号表。

具体实现如下:

o gdbserver增加qMaps请求(gdb/gdbserver/server.c)

void handle_query (char *own_buf, int packet_len, int *new_packet_len_p) {
… if (strncmp (own_buf, "qMaps;", 5) == 0) {
handle_q_maps (own_buf); return; } … }

o gdbserver处理qMaps请求

handle_q_maps (char *own_buf) {
FILE* fp = NULL; char file_name[256] = {0}; snprintf(file_name, sizeof(file_name), "/proc/%ld/maps", signal_pid); if((fp = fopen(file_name, "rb")) != NULL) {
char line[512] = {0}; while(fgets(line, sizeof(line), fp) != NULL) {
putpkt(line); } putpkt(""); fclose(fp); }

包的大小有限制(2000),所以我们只传一行过去,最后再发”“表示传输结束。

o修改gdb/remote.c解析maps文件。

static Maps* maps_parse(Maps* maps, char* maps_str) {
char* line = maps_str; char* next_line = NULL; while(line != NULL) {
next_line = strchr(line, '\n'); if(next_line != NULL) {
*next_line = '\0'; next_line++; } if(strstr(line, "r-xp") != NULL && strstr(line, ".so") != NULL) {
int unused = 0; MapsItem item; memset(&item, 0x00, sizeof(item)); sscanf(line, "%08x-%08x r-xp %08x %02x:%02x %d %s", &(item.start), &(item.end), &unused, &unused, &unused, &unused, item.file_name); maps_add(maps, &item); } line = next_line; } return maps; }

o修改gdb/remote.c获取代码段的偏移量。

int remote_get_so_vma(const char* file_name, size_t* vma) {
char **matching = NULL; bfd* b = bfd_openr(file_name, NULL); if(b != NULL && bfd_check_format_matches (b, bfd_object, &matching)) {
asection* s = bfd_get_section_by_name(b, ".text"); if(s != NULL) {
*vma = s->vma; return 1; } } return 0; }

o 修改gdb/symfile.c实现add_shared_symbol_files_command,这个函数在linux下没有实现。

static void add_shared_symbol_files_command (char *args, int from_tty) {
#ifdef ADD_SHARED_SYMBOL_FILES ADD_SHARED_SYMBOL_FILES (args, from_tty); #else /*support sharelib: {*/ extern void remote_list_so(void); extern int remote_get_so_nr(void); extern int remote_get_so_vma(const char* file_name, size_t* vma); extern int remote_get_so_text_start_addr(const char* file_name, size_t* addr); extern int remote_get_so_name_text_start_addr(int index, const char** file_name, size_ t* addr); if (current_target.to_shortname && (strcmp (current_target.to_shortname, "remote") == 0 || strcmp (current_target.to_shortname, "extended-remote") == 0)) {
size_t addr = 0; char cmd[2048] = {0}; if(args == NULL || args[0] == '\0') {
printf_unfiltered("available so:\n"); remote_list_so(); printf_unfiltered("usage: add-shared-symbol-files file\n"); printf_unfiltered("usage: add-shared-symbol-files all remote_path:local_path\n"); return; } else if(strncmp(args, "all ", 4) == 0) {
size_t i = 0; size_t n = 0; int rlen = 0; char rpath[260] = {0}; char lpath[260] = {0}; char* p = strchr(args, ':'); const char* file_name = NULL; if(p == NULL) return; *p = '\0'; strncpy(lpath, p + 1, sizeof(lpath)); strncpy(rpath, args + 4, sizeof(rpath)); rlen = strlen(rpath); n = remote_get_so_nr(); for(i = 0; i < n; i++) {
remote_get_so_name_text_start_addr(i, &file_name, &addr); if(file_name != NULL && strncmp(file_name, rpath, rlen) == 0) {
size_t vma = 0; char lfile_name[512] = {0}; snprintf(lfile_name, sizeof(lfile_name),"%s/%s", lpath, file_name+rlen); if(remote_get_so_vma(lfile_name, &vma)) {
addr += vma; } snprintf(cmd, sizeof(cmd),"%s/%s %p", lpath, file_name+rlen, (void*)addr); add_symbol_file_command(cmd, 0); } } } else {
remote_get_so_text_start_addr(args, &addr); if(addr > 0) {
size_t vma = 0; if(remote_get_so_vma(args, &vma)) {
addr += vma; } snprintf(cmd, sizeof(cmd), "%s %p", args, (void*)addr); add_symbol_file_command(cmd, from_tty); } else {
printf_unfiltered("%s is not found.\n", args); } } return; } /*support sharelib: }*/ error (_("This command is not available in this configuration of GDB.")); #endif }

使用方法:
o 加载单个共享库的符号表。add-shared-symbol-files 共享库文件名(本地绝对路径).
o 加载多个共享库的符号表。add-shared-symbol-files all 远程路径:本地路径

因为加载符号表时是加载本地文件,所以加载多个文件时,要把远程路径替换成本地路径。