移植GDB(4) gdbserver v0.0
teawater<teawater@gmail.com>
修改记录:
v0.0
2006-03-03,v0.0版本编写完成。
struct target_ops没作详细介绍。
struct linux_target_ops的breakpoint_reinsert_addr介绍不是很清楚。
2006-03-01,文档创建。
目录
1.写在前面
2.整体结构描述
3.GDBSERVER/configure.srv
4.GDBSERVER/Makefile.in
5.GDB/gdb/regformats/reg-ARCH.dat
6.GDBSERVER/linux-ARCH-low.c
6.1.struct linux_target_ops the_low_target
6.2.struct regset_info target_regsets[]
1.写在前面
本文针对GDB-6.3进行编写。
gdbserver的移植是以完成gdbarch的移植为基础的,gdbarch的移植可以参见“移植GDB(1)”。
下面是本文将使用的缩写:
GDBINT GDB Internals Manual的缩写。
GDB 指GDB源文件目录。
GDBSERVER 指GDBSERVER源文件目录GDB/gdb/gdbserver。
ARCH 体系结构名称。
TARGET 体系结构下的调试目标,一般是一种操作系统,比如Linux。
2.整体结构描述
gdbserver从程序结构的角度讲是独立于GDB本身的,在编译的时候也是需要单独编译的,但是因为其利用到了GDB目录中的一些文件,所以不能单独发布。
GDBSERVER/server.c:main函数是gdbserver的核心部分,这个函数调用函数初始化gdbserver,跟远程的GDB建立连接,对接到的GDBRSP控制包进行分析并调用相应的调试函数或者调试宏进行各种调试操作,然后把调试信息返回。
每个调试函数和调试宏会根据需要调用注册在全局target_ops结构(这个结构跟GDB中的target_ops名称一样,功能类似,定义在GDBSERVER/target.h中)GDBSERVER/target.c:the_target中的函数,这个全局变量the_target是向下层不同的被调试目标提供的调试接口,其的初始化一般是在前面介绍的main函数调用的函数initialize_low完成。
这个initialize_low就定义在下层接口文件中,在编译的时候选择合适的下层接口文件就可以实现相应的支持。现有的gdbserver代码中只有一个用来支持Linux下ptrace方式调试的下层调试接口GDBSERVER/linux-low.c。当然如果按照其结构编写,应该可以象GDB一样支持多种下层调试接口。
在函数GDBSERVER/linux-low.c:initialize_low中,比较主要的操作有:
调用GDBSERVER/target.c:set_target_ops将GDBSERVER/linux-low.c:linux_target_ops注册到the_target上。linux_target_ops中的函数都会根据情况访问the_low_target结构中的函数和变量,这个the_low_target是向每个ARCH提供的扩展接口,具体在下面介绍。
调用GDBSERVER/mem-break.c:set_breakpoint_data将the_low_target中声明的断点和断点长度设置到全局变量中去,其将在gdbserver本身需要设置断点的时候使用。
调用init_registers函数初始化寄存器信息,这个函数是在编译的时候生成的,将在后面介绍其的生成过程。
the_low_target是在linux-low.c中为了方便其支持多个ARCH而定义的linux_target_ops结构的接口,linux_target_ops声明在GDBSERVER/linux-low.h文件中。在GDBSERVER目录中,有几个linux-ARCH-low.c文件,每个文件中都有一个the_low_target结构。在编译的时候只要选择合适的linux-ARCH-low.c文件一起编译,就可以支持相应的ARCH。
3.GDBSERVER/configure.srv
这个文件将被GDBSERVER/configure调用,其的作用是根据前面程序分析得到的${target}也就是当前的TARGET,设置一些编译的选项。文件的结构很简单,就不作详细介绍了,就介绍一下需要设置的选项:
srv_regobj=
这是一个必须设置的选项,其标明了当前需要连接到gdbserver中的寄存器库文件也就是包含init_registers函数的库文件,后面将介绍这个库文件如何生成。
srv_tgtobj=
这是一个必须设置的选项,其标明了当前需要连接到gdbserver中的跟要编译TARGET相关的库文件,一般来说是linux-low.o(由前面介绍过的linux-low.c生成)和linux-ARCH-low.o(由前面介绍过的linux-ARCH-low.c生成),注意多个文件用分号包起来。
srv_linux_usrregs=yes
这是一个选择设置的选项,当设置了这个选项的时候,configure将检查当前TARGET的ptrace系统调用是否支持PTRACE_POKEUSER和PTRACE_PEEKUSER,如果支持编译的时候将设置宏HAVE_LINUX_USRREGS。
srv_linux_regsets=yes
这是一个选择设置的选项,当设置了这个选项的时候,configure将检查当前TARGET的ptrace系统调用是否支持regset相关的几个参数,如果支持编译的时候将设置宏HAVE_LINUX_REGSETS。
注意srv_linux_usrregs选项和srv_linux_regsets选项至少要设置一个,否则linux-low.c中的读取被调试程序目标的函数将无法正常编译。
srv_linux_thread_db=yes
这是一个选择设置的选项,当设置了这个选项的时候,configure将检查当前TARGET是否支持libthread_db这个用来调试多线程程序的库,如果支持编译的时候将设置宏HAVE_THREAD_DB_H。
4.GDBSERVER/Makefile.in
这是configure的时候用来生成Makefile的模板,如果需要加入新的TARGET也需要对这个文件进行修改。
SFILES=
在这个项目最后加入新的TARGET增加的.c文件,不过这个项目更编译gdbserver并没什么关系,是给etags使用的。
linux-ARCH-low.o: linux-ARCH-low.c $(linux_low_h) $(server_h)
这里是生成前面srv_tgtobj=标出的.o文件的Makefile语句,后面会介绍这个.c文件如何编写。
reg-ARCH.o : reg-ARCH.c $(regdef_h)
reg-ARCH.c : $(srcdir)/../regformats/reg-ARCH.dat $(regdat_sh)
sh $(regdat_sh) $(srcdir)/../regformats/reg-ARCH.dat reg-ARCH.c
这里是生成前面srv_regobj=标出的.o文件的Makefile语句,用户要编写的文件是reg-ARCH.dat,后面会介绍。
5.GDB/gdb/regformats/reg-ARCH.dat
这个文件是用来标明某个ARCH的寄存器信息。
name:ARCH
第一行写ARCH的名称。
expedite:name1,name2,name3
这里表明的几个寄存器名称name1,name2,name3是在其后面声明的,在gdbserver向GDB用T停止包(具体信息见Debugging with GDB: Remote Protocol D.3 Stop Reply Packets)返回信息的时候,包中将包括这里指出的几个寄存器。当gdbserver向GDB发送停止包的时候,GDB通常需要取得当前pc,sp等几个寄存器的值来确定被调试程序所在位置等信息,如果返回包没包含这些信息其就需要再发包请求,所以通常在T停止包中会包含几个寄存器的值,这里就是用来指出哪些寄存器将出现在T停止包中的,一般来说是pc,sp,bp等几个寄存器。
bit_num:name
这里指出每个寄存器的位数和名称,前面的bit_num是寄存器的位数,32位就是32,64位就是64。后面的name写成寄存器的名称。有多少寄存器就写多少行。
6.GDBSERVER/linux-ARCH-low.c
6.1.struct linux_target_ops the_low_target
这个文件最主要的部分就是声明struct linux_target_ops the_low_target,并初始化其中内容,这个变量在前面介绍过,其中的函数和变量会被linux-low.c中的函数调用,现在来介绍一下这个结构中每个元素:
int num_regs;
这个变量用来标明寄存器的数量。
int *regmap;
一般会让这个指针指向一个int类型的数组,这个数组的第n个元素的值是第n个寄存器的值在user结构(这个结构的介绍见“移植GDB(2)”)中的偏移,如果值设置为-1则表示这个寄存器的值在user结构中不存在。
int (*cannot_fetch_register) (int);
注册在这个指针上的函数会判断参数指定序号的寄存器是否可以读取,如果不可以则返回真。
int (*cannot_store_register) (int);
注册在这个指针上的函数会判断参数指定序号的寄存器是否可以设置,如果可设置但是如果设置失败就报错返回0,如果不可设置返回1,如果可设置且设置失败不用报错返回2。
CORE_ADDR (*get_pc) (void);
注册在这个指针上的函数用来取得被调试程序的PC。
void (*set_pc) (CORE_ADDR newpc);
注册在这个指针上的函数用来设置被调试程序的PC为newpc。
const char *breakpoint;
这个指针指向当前TARGET支持的断点内容。
int breakpoint_len;
这个变量用来标明断点的长度。
CORE_ADDR (*breakpoint_reinsert_addr) (void);
如果当前的TARGET的ptrace支持PTRACE_SINGLESTEP也就是硬单步,则将这个函数指针设置为NULL就可以;如果不支持,设置在这个指针上的函数将当前被调试程序所在的函数的返回值返回,在RISC CPU中通常都有一个返回地址寄存器,直接取出这个寄存器的值返回就可以了。
int decr_pc_after_break;
因为某些ARCH被断点中断后取得的PC值并不是被中断的值,所以需要减去这个值取得实际的被中断时候的地址。
int (*breakpoint_at) (CORE_ADDR pc);
注册在这个指针上的函数先取得指定地址pc上的内存,然后判断是否是从GDB设置过来的断点,如果是则返回真。
6.2.struct regset_info target_regsets[]
如果在前面介绍过的GDBSERVER/configure.srv中设置了srv_linux_regsets=yes选项,也就是说有可能设置了HAVE_LINUX_REGSETS的时候,需要在linux-ARCH-low.c中包含target_regsets结构数组。
这个数组中的变量和函数指针将被GDBSERVER/linux-low.c文件中的regsets_fetch_inferior_registers函数和regsets_store_inferior_registers函数使用,这两个函数将使用ptrace系统调用regset直接取得寄存器的信息。
这个数组一般来说是有几个类型的寄存器就需要设置几个元素,一般的TARGET都是定义2个元素普通寄存器和浮点寄存器,i386在有扩展浮点寄存器的时候会定义3个比上面多一个扩展浮点寄存器。
struct regset_info定义在GDBSERVER/linux-low.h中,现在来介绍一下regset_info结构中的每个元素:
int get_request, set_request;
这两个参数是给ptrace用的读寄存器信息和写寄存器信息用的参数,一般来说普通寄存器就是PTRACE_GETREGS和PTRACE_SETREGS,浮点寄存器就是PTRACE_GETFPREGS和PTRACE_SETFPREGS,扩展浮点寄存器(i386常见)就是PTRACE_GETFPXREGS和PTRACE_SETFPXREGS。
int size;
取得和设置寄存器信息的长度,一般来说普通寄存器是sizeof (elf_gregset_t),浮点寄存器是sizeof (elf_fpregset_t),扩展浮点寄存器(i386常见)就是sizeof (elf_fpxregset_t)。
enum regset_type type;
分为三种类型:GENERAL_REGS,普通寄存器;FP_REGS,浮点寄存器;EXTENDED_REGS,扩展浮点寄存器。
regset_fill_func fill_function;
注册在这个指针上的函数的作用是依次用GDBSERVER/regcache.c:collect_register函数读出每个当前对应类型寄存器的值转化为相应的regset结构并将其存到参数BUF相应位置中。
regset_store_func store_function;
注册在这个指针上的函数的作用是依次用GDBSERVER/regcache.c:supply_register函数将参数BUF中的regset结构的寄存器信息读出并存到相应的寄存器缓冲中。
注意在使用target_regsets的同时文件要包含GDB_GREGSET_T和GDB_FPREGSET_T的定义,一般编写为:
#define GDB_GREGSET_T elf_gregset_t
#define GDB_FPREGSET_T elf_fpregset_t