URN Logo
UNIX Resources » Linux » China Linux Forum » C/C++编程版 » 21 » [精华] 移植GDB [原创](更新时间 2006.04.01)
announcement 声明: 本页内容为中国Linux论坛的内容镜像,文章的版权以及其他所有的相关权利属于中国Linux论坛和相应文章的作者,如果转载,请注明文章来源及相关版权信息。
Resources
China Linux Forum(finished)
Linux Forum(finished)
FreeBSD China(finished)
linuxforum.net
  业界新闻与评论
  自由软件杂谈
  IT 人生
  Linux软件快递
  翻译作坊
  Linux图书与评论
  GNU Emacs/XEmacs
  Linux 中文环境和中文化
  Linux桌面与办公软件
  Linux 多媒体与娱乐版
  自由之窗Mozilla
  笔记本电脑上的Linux
  Gentoo
  Debian 一族
  网络管理技术
  Linux 安装与入门
  WEB服务器和FTP服务器
  域名服务器和邮件服务器
  Linux防火墙和代理服务器应用
  文件及打印服务器
  技术培训与认证
  Linux内核技术
  Linux 嵌入技术
  Linux设备驱动程序
  Linux 集群技术
  LINUX平台数据库
  系统和网络安全
  CPU 与 编译器
  系统计算研究所专栏
  Linux下的GUI软件开发
  C/C++编程版
  PHP 技 术
  Java&jsp技术
  Shell编程技术
  Perl 编 程
  Python 编 程
  XML/Web Service 技术
  永远的Unix
  FreeBSD世界
   
[精华] 移植GDB [原创](更新时间 2006.04.01)
 
 
[精华] 移植GDB(1) arch和frame v0.3 - teawater [2006-04-01 23:39 | 55,677 byte(s)]
 
 
 
[精华] 移植GDB(4) gdbserver v0.0 - teawater [2006-03-03 22:03 | 10,302 byte(s)]
 
[精华] 移植GDB(3) init.c和target_ops v0.0 - teawater [2006-02-27 20:28 | 4,308 byte(s)]
 
 
 
[精华] 移植GDB(1) arch和frame v0.2 - teawater [2006-02-21 20:48 | 48,429 byte(s)]
 
[精华] 移植GDB(2) native v0.0 - teawater [2006-02-21 20:43 | 22,756 byte(s)]
 
[精华] 移植GDB(1) arch和frame v0.1[原创] - teawater [2006-01-23 20:55 | 44,293 byte(s)]
 
 
 
[精华] 下一步工作的展望 - teawater [2006-01-23 21:24 | 187 byte(s)]
 
 
 
 
Subject: [精华] 移植GDB [原创](更新时间 2006.04.01)
Author: teawater    Posted: 2006-01-09 22:20    Length: 731 byte(s)
[Original] [Print] [Top]
移植GDB(1) arch和frame v0.3 更新时间:2006-04-01
http://www.linuxforum.net/forum/showthreaded.php?Cat=&Board=program&Number=603645

移植GDB(2) native v0.0 更新时间:2006-02-21
http://www.linuxforum.net/forum/showthreaded.php?Cat=&Board=program&Number=595833

移植GDB(3) init.c和target_ops v0.0 更新时间:2006-02-27
http://www.linuxforum.net/forum/showthreaded.php?Cat=&Board=program&Number=596941

移植GDB(4) gdbserver v0.0 更新时间:2006-03-03
http://www.linuxforum.net/forum/showthreaded.php?Cat=&Board=program&Number=597734

附件中有txt版本 需要登陆后才能看见
----
读了这么多年的书 还是觉得幼儿园好混
[Original] [Print] [Top]
Subject: [精华] Re: 移植GDB(1) arch和frame v0.0[原创]
Author: teawater    Posted: 2006-01-09 22:25    Length: 386 byte(s)
[Original] [Print] [Top]
猛然发现自己最近2年从看GDB/ARMulator到参与开发skyeye等都跟GDB相关的东西打交道 干脆写点东西 这样即能够整理思路 防止以后忘记 也能跟大家分享经验 这个部分只是一部分 希望懒惰的我能再坚持写出别的部分

本来想写完dummy frame的部分再发布 但是今天看到论坛恢复实在是激动啊 干脆发出来庆祝一下吧 呵呵

本人水平有限 难免会有很多错误 希望大家多多指点~~~ 多谢多谢
----
读了这么多年的书 还是觉得幼儿园好混
[Original] [Print] [Top]
Subject: [精华] Re: 移植GDB(1) arch和frame v0.0[原创]
Author: shy828301    Posted: 2006-01-10 09:50    Length: 8 byte(s)
[Original] [Print] [Top]
不错不错
[Original] [Print] [Top]
Subject: [精华] Re: 移植GDB(1) arch和frame v0.0[原创]
Author: lx_cyh    Posted: 2006-01-16 23:51    Length: 12 byte(s)
[Original] [Print] [Top]
加油,请继续
----
古今陵谷茫茫市朝往往耕桑此地居然形胜似曾小小兴亡
[Original] [Print] [Top]
Subject: [精华] 移植GDB(1) arch和frame v0.1[原创]
Author: teawater    Posted: 2006-01-23 20:55    Length: 44,293 byte(s)
[Original] [Print] [Top]
附件中有txt版本 需要登陆后才能看见

移植GDB(1) arch和frame v0.1
teawater<teawater@gmail.com>
转载请标明来自 http://www.linuxforum.net




修改记录:
v0.1
2006-01-23,增加第7章frame的介绍。增加第8章dummy frame。
v0.0
2006-01-09,v0.0版本编写完成,dummy frame相关部分没有进行介绍。
2005-11-19,文档创建。




1.写在前面
本文针对GDB-6.3进行编写。

LIBBFD是一套用来分析二进制文件的库,GDB用其来对二进制文件进行分析,具体关于LIBBFD可以参见http://www.gnu.org/software/binutils/manual/bfd-2.9.1/bfd.html。

在GDB中,用来表示一个arch最核心部分就是gdbarch结构,其中包含着很多函数指针和变量。
在GDB初始化以及读入新的可执行文件的时候,将调用相应代码(个人认为编写这部分代码以及用来给gdbarch结构中函数指针赋值是arch移植的主要工作),产生gdbarch结构的变量,将相应arch相关的函数地址和变量初始化在其中,并将其地址存在current_gdbarch这个指针中。
在GDB进行调试的时候,可以通过访问current_gdbarch中的函数指针和变量来达到进行arch相关调试的目的。

还有一部分跟ARCH有关的设置,在GDB/gdb/config/ARCH/tm-TARGET.h进行一些宏定义来进行设置GDB的运行行为。
有一部分宏定义会在GDB/gdb/gdbarch.h定义成current_gdbarch定义在一起,GDB调用这部分宏实际上就调用了current_gdbarch中的内容,所以这部分的宏既可以通过设置gdbarch结构来实现也可以通过设置宏的方式来实现。

frame是stack frame,也就是栈结构,个人觉得跟frame相关的部分是gdbarch结构中最重要的部分,其在调试进行中起比较重要的作用,但是在GDB Internals中相关章节是空着的,因此我决定介绍一下frame,这样也对编写初始化gdbarch相关代码有帮助。

在以下章节,将先对移植中需要增加和修改的文件依次进行详细介绍,然后对frame进行介绍。下面是文件名中使用的缩写和GDB代码中的定义。
GDBINT GDB Internals Manual的缩写。
GDB 指GDB源文件目录。
ARCH 体系结构名称。
TARGET 体系结构下的调试目标,一般是一种操作系统,比如Linux。
bfd_architecture 定义在LIBBFD中用来描述ARCH的枚举类型。
gdb_osabi 定义在GDB中用来描述当前操作系统的ABI的枚举类型,其具体分类可以见GDBINT 9.1节。




2.GDB/config.sub
这是一个shell文件,其作用是在运行configure的时候将用户指定的target和host转化成标准的字符串格式。
这个文件可以以后面跟一个要转化的字符串的形式被调用,输出转化后的字符串,可以很方便的进行测试。

这个文件主要分为以下几个部分:
第一部分,将用户输入的结构参数分成前$basic_machine和后$os两部分。
第二部分,对$os也就是后面的操作系统相关的字段进行分析和处理。
第三部分,对$basic_machine进行分析和处理,如果是要增加一个芯片的支持,就在这里增加分析和处理,有时候也对$os进行处理。一般来说增加一个如果在ARCH名称后面没跟任何文件类型的则给$basic_machine增加一个"-unknown"。
第四部分,对$os进行进一步的分析和处理。
第五部分,根据$basic_machine对$os进行设置。
第六部分,如果$basic_machine是*-unknown的形式,则根据$os得到其$vendor,也就是制造商,然后将$basic_machine中的unknown替换成$vendor。
第七部分,打印$basic_machine$os。




3.GDB/gdb/configure.tgt
这个文件跟运行configure时设置的target有关。这个文件主要作用是根据上面文件得到的字符串得到编译需要的一些信息取得编译需要的信息。
其是在GDB/gdb/configure中被调用,在GDB/gdb/configure中先将从GDB/config.sub得到的信息存在$target中,然后将其中三部分存放在$target_cpu、$target_vendor、$target_os中。然后在其包含的文件GDB/configure.tgt进行分析。

这个文件首先根据$target_cpu来取得$gdb_target_cpu的值。如果你的CPU不象mips那样含有mipsel类的值,就不用对这段进行修改。这个值将作为ARCH的值。
后面一段根据$target来取得$gdb_target的值,其就是TARGET的值。一般来说这个值使用操作系统的名称。
最后一段根据$target来取得$gdb_osabi的值,这个值是系统ABI的值,这个值最终将被设置为GDB_OSABI_DEFAULT,也就是系统ABI的默认值。




4.GDB/gdb/config/ARCH/TARGET.mt
这个文件是设置一些编译GDB需要的跟当前TARGET有关的文件。

TDEPFILES=
这个是指定要编译的.o文件,后面介绍的跟TARGET有关的.c文件编译成的.o文件需要在这里指出,而那个.c文件使用的一些函数相关的.o文件也需要在这里指定。
DEPRECATED_TM_FILE=
这个是指定描述TARGET的头文件,一般来说这个头文件的写法为tm-TARGET.h存放在GDB/gdb/config/ARCH/目录中,具体内容将在后面介绍。注意这里跟GDBINT写的不同,这里的写法是对的,用GDBINT中介绍的方法无法正常编译。




5.GDB/gdb/ARCH-tdep.c GDB/gdb/ARCH-TARGET-tdep.c
5.1.综述
这两个文件的主要作用就是初始化gdbarch结构,第一个是跟ARCH相关的,第二个是跟这个TARGET相关的。他们的.o文件都将在TARGET.mt的TDEPFILES=中被指定。
将这两个文件分开的好处是,一个ARCH可能需要支持若干个TARGET,也就是一个芯片支持。而这些设置中有一部分是跟ARCH有关,另一部分则跟ARCH和T
在不需要针对TARGET作ARCH以外的设置的时候,可以不包含GDB/gdb/ARCH-TARGET-tdep.c文件。例如arm和mips的embed.mt都是这种情况。
在GDB中很多ARCH的这两个文件都对应了GDB/gdb/ARCH-tdep.h和GDB/gdb/ARCH-TARGET-tdep.h形式的头文件,不过这两个头文件主要是保存一些跟这个ARCH相关的私用数据,所以对其的使用可以灵活掌握。


5.2.GDB/gdb/ARCH-tdep.c
GDB/gdb/ARCH-tdep.c文件的初始化函数是void _initialize_ARCH_tdep (void),这个函数在GDB启动的时候将被调用,这里最主要的就是对gdbarch_register函数的调用,当然如果需要也可以在这个函数中增加一些命令。
void gdbarch_register (enum bfd_architecture bfd_architecture, gdbarch_init_ftype *init, gdbarch_dump_tdep_ftype *dump_tdep)
这个函数的作用就是将init函数指针和dump_tdep函数指针标记为bfd_architecture注册到全局链表gdbarch_registry上。这样当GDB读入类型为bfd_architecture的可执行文件的时候,init和dump_tdep就将被调用。

注册的init函数的作用就是初始化并设置一个gdbarch结构,并将其地址返回。编写这个函数基本上可以参考GDB/gdb/目录中其他ARCH的编写,下面我来基本介绍一下其的编写方法。
基本上头一步就是给gdbarch结构分配空间,使用gdbarch_alloc函数,其第一个参数是init函数的参数info,第二个参数tdep是一个gdbarch_tdep结构指针,其主要是存储了一些跟这个ARCH相关的私有数据,这个gdbarch_tdep结构要定义在ARCH相关的几个文件里,可以根据需要随意增加内容。在gdbarch_alloc函数中,其先分配空间,然后将tdep以及其他默认的gdbarch参数初始化到gdbarch中,然后返回。
然后就使用一些函数将这个ARCH相关的函数和数据设置到这个gdbarch上,这个将在本节最后进行详细的介绍。
最后调用“gdbarch_init_osabi (info, gdbarch);”,这个函数的作用是调用后面介绍的GDB/gdb/ARCH-TARGET-tdep.c通过gdbarch_register_osabi注册跟TARGET相关的初始化函数。

注册的dump_tdep函数主要是用来显示这个TARGET中一些私有的例如tdep的信息,其被GDB/gdb/gdbarch.c:gdbarch_dump函数调用,这个函数当执行GDB命令“maintenance print architecture”的时候被调用到,主要就是用来显示TARGET的信息。


5.3.GDB/gdb/ARCH-TARGET-tdep.c
GDB/gdb/ARCH-TARGET-tdep.c文件初始化函数是void _initialize_ARCH_TARGET_tdep (void),这个函数在GDB启动的时候将被调用,这里最主要的就是对gdbarch_register函数的调用,当然如果需要也可以在这个函数中增加一些命令。
void gdbarch_register_osabi (enum bfd_architecture arch, unsigned long machine, enum gdb_osabi osabi, void (*init_osabi)(struct gdbarch_info, struct gdbarch *))
这个函数的作用是将init_osabi函数指针标记为arch和osabi注册到全局链表gdb_osabi_handler_list上。这个函数将被前面提到过的gdbarch_init_osabi函数调用。
注册的init_osabi函数的作用就是根据操作系统的特性对前面init中分配和设置的gdbarch进行进一步的初始化。


5.4.gdbarch_register_osabi_sniffer
void gdbarch_register_osabi_sniffer (enum bfd_architecture arch, enum bfd_flavour flavour, enum gdb_osabi (*sniffer)(bfd *abfd))
这个函数一般在_initialize_ARCH_tdep或者_initialize_ARCH_TARGET_tdep中调用,其主要目的是因为二进制可执行文件的格式中对OS ABI的描述并不一定能跟gdb_osabi对应的上,所以这里就是注册一个函数来帮助GDB判断当前文件使用的gdb_osabi类型。
在这个函数中arch表示ARCH,flavour是定义在LIBBFD中的对可执行文件的描述,sniffer是要注册的函数指针。
注册的init_osabi函数要根据传递来的LIBBFD的文件指针对当前读入的二进制可执行文件进行分析,并将得到的gdb_osabi返回。


5.5.gdbarch设置函数
因为其的初始化函数和init中使用的函数属于同一系列,所以将一起在下面进行介绍,这些函数的大部分都在GDB/gdb/gdbarch.c中。有一部分函数是跟frame有关的,将在后面关于frame的章节再进行介绍。
在GDBINT 9.10介绍了一些对TARGET进行设置的宏定义,而在GDB/gdb/gdbarch.h中可以看到,下面这些函数设置在gdbarch结构中的函数和变量最终将被定义在相关的宏中来实现调用,所以GDBINT 9.10中关于宏的介绍可以作为相关gdbarch初始化函数的介绍。

void set_gdbarch_num_regs (struct gdbarch *gdbarch, int num_regs)
设置ARCH的寄存器数量num_regs到gdbarch中,调用其的宏是NUM_REGS。

void set_gdbarch_register_type (struct gdbarch *gdbarch, gdbarch_register_type_ftype *register_type)
设置一个gdbarch_register_type_ftype类型的函数指针到gdbarch中。
注册的这个函数的作用是根据其参数reg_nr指定的寄存器号码确定寄存器,将这个寄存器对应的类型返回,这些类型定义在GDB/gdb/gdbtypes.c中。

void set_gdbarch_register_name (struct gdbarch *gdbarch, gdbarch_register_name_ftype *register_name)
设置一个gdbarch_register_name_ftype类型的函数指针到gdbarch中,调用其的宏是REGISTER_NAME。
注册的这个函数的作用是根据其参数regnr指出的寄存器序号返回相应的寄存器名称。

void set_gdbarch_pc_regnum (struct gdbarch *gdbarch, int pc_regnum)
设置ARCH的PC寄存器序号pc_regnum到gdbarch中,调用其的宏是PC_REGNUM。

void set_gdbarch_sp_regnum (struct gdbarch *gdbarch, int sp_regnum)
设置ARCH的SP寄存器序号sp_regnum到gdbarch中,调用其的宏是SP_REGNUM。

void set_gdbarch_fp0_regnum (struct gdbarch *gdbarch, int fp0_regnum)
设置ARCH的FP0寄存器序号fp0_regnum到gdbarch中,调用其的宏是FP0_REGNUM。

void set_gdbarch_read_pc (struct gdbarch *gdbarch, gdbarch_read_pc_ftype *read_pc)
设置一个gdbarch_read_pc_ftype类型的函数指针到gdbarch中,调用其的宏是TARGET_READ_PC和TARGET_READ_PC_P。
注册的这个函数的作用是通过用户自定义的方式来对调试进程进行PC寄存器的读取,如果已经用set_gdbarch_pc_regnum设置了PC寄存器的序号,而且PC寄存器可以通过普通方式读取到,则不需要注册这个函数。

void set_gdbarch_write_pc (struct gdbarch *gdbarch, gdbarch_write_pc_ftype *write_pc)
设置一个gdbarch_write_pc_ftype类型的函数指针到gdbarch中,调用其的宏是TARGET_WRITE_PC。
注册的这个函数的作用是通过用户自定义的方式来对调试进程进行PC寄存器的设置,如果已经用set_gdbarch_pc_regnum设置了PC寄存器的序号,而且PC寄存器可以通过普通方式设置,则不需要注册这个函数。

void set_gdbarch_read_sp (struct gdbarch *gdbarch, gdbarch_read_sp_ftype *read_sp)
设置一个gdbarch_read_sp_ftype类型的函数指针到gdbarch中,调用其的宏是TARGET_READ_SP和TARGET_READ_SP_P。
注册的这个函数的作用是通过用户自定义的方式来对调试进程进行SP寄存器的读取,如果已经用set_gdbarch_sp_regnum设置了SP寄存器的序号,而且SP寄存器可以通过普通方式读取到,则不需要注册这个函数。

void set_gdbarch_breakpoint_from_pc (struct gdbarch *gdbarch, gdbarch_breakpoint_from_pc_ftype *breakpoint_from_pc)
设置一个gdbarch_breakpoint_from_pc_ftype类型的函数指针到gdbarch中,调用其的宏是BREAKPOINT_FROM_PC。
注册的这个函数的作用是根据参数中的要插入的断点的地址pcptr,选择合适的断点指令,并且设置这个断点指令的长度在lenptr中,如果需要的话地址也需要进行修改。例如ARM就需要判断插入一个ARM断点指令还是插入一个THUMB断点指令。

void set_gdbarch_inner_than (struct gdbarch *gdbarch, gdbarch_inner_than_ftype *inner_than)
设置一个gdbarch_inner_than_ftype类型的函数指针到gdbarch中,调用其的宏是INNER_THAN。
注册的这个函数是用来比较栈地址。如果ARCH的栈是向下增长(向内存低地址)的,则注册core_addr_lessthan(lhs小于rhs返回真)。如果ARCH的栈是向上增长(向内存高地址)的,则注册core_addr_greaterthan(lhs大于rhs返回真)。

void set_gdbarch_skip_prologue (struct gdbarch *gdbarch, gdbarch_skip_prologue_ftype *skip_prologue)
设置一个gdbarch_skip_prologue_ftype类型的函数指针到gdbarch中,调用其的宏是SKIP_PROLOGUE。
注册的这个函数的参数pc是一个函数的起始地址,函数将返回这个地址对应的函数的prologue的结束地址。prologue指函数开始分配栈空间、保存寄存器等那部分代码。一般来说这个函数的编写都是先扫描符号表,一般可以通过符号表找到结束地址,如果符号表查找失败就需要反汇编来分析代码取得结束地址(个人觉得靠反汇编来来分析这部分可有可无)。具体的过程可以参见GDB/gdb目录中ARCH-tedp.c类型文件中的相关代码。

void set_gdbarch_print_insn (struct gdbarch *gdbarch, gdbarch_print_insn_ftype *print_insn)
设置一个gdbarch_print_insn_ftype类型的函数指针到gdbarch中,调用其的宏是TARGET_PRINT_INSN。
注册的这个函数将在运行GDB的disassemble命令的时候被调用,用来对指定的地址进行反汇编。整个这个函数的结构与binutils中的一个ARCH的结构完全一样,可以直接将其目录中文件拷贝到GDB目录中使用。

void set_gdbarch_software_single_step (struct gdbarch *gdbarch, gdbarch_software_single_step_ftype *software_single_step)
设置一个gdbarch_software_single_step_ftype类型的函数指针到gdbarch中,调用其的宏是SOFTWARE_SINGLE_STEP和SOFTWARE_SINGLE_STEP_P。
注册的这个函数的作用在当前指令执行的下一条指令插入(参数非0)和删除(参数为0)断点,注意如果是跳转类的指令则还需要先对指令进行反汇编,判断出下一条指令的地址。设置这个函数就表明当前TARGET不支持单步执行,需要通过这个函数提供的软单步功能来实现单步。

void set_gdbarch_return_value (struct gdbarch *gdbarch, gdbarch_return_value_ftype *return_value)
void set_gdbarch_extract_return_value (struct gdbarch *gdbarch, gdbarch_extract_return_value_ftype *extract_return_value)
void set_gdbarch_store_return_value (struct gdbarch *gdbarch, gdbarch_store_return_value_ftype *store_return_value)
这三个函数分别设置gdbarch_return_value_ftype、gdbarch_extract_return_value_ftype和gdbarch_store_return_value_ftype类型的函数指针到gdbarch中,调用后面两个注册的函数指针的宏分别是EXTRACT_RETURN_VALUE和STORE_RETURN_VALUE。
注册的第一个函数用来取得函数的返回值以及这个返回值的传递方式。这个函数指针在gdbarch初始化的时候设置的默认值是函数legacy_return_value,这个函数将调用宏EXTRACT_RETURN_VALUE和STORE_RETURN_VALUE,也就是将调用后两个注册的函数。
取得返回值的传递方式的方法为通过gdbarch_return_value_ftype类型函数参数valtype用宏TYPE_CODE取得函数返回值类型,这些类型是定义在GDB/gdb/gdbtypes.h的枚举类型type_code中,例如TYPE_CODE_INT代表返回值是int。根据这个类型就可以确定返回值的传递方式,这些方式定义在GDB/defs.h中的枚举类型return_value_convention中。这些类型是:
RETURN_VALUE_REGISTER_CONVENTION表示返回值通过一个或者多个寄存器返回。
RETURN_VALUE_STRUCT_CONVENTION表示调用函数会传递一个隐含的参数传递一个用来传递返回值的指针。
RETURN_VALUE_ABI_RETURNS_ADDRESS类似RETURN_VALUE_STRUCT_CONVENTION,ABI保证被调用函数将返回值放到一个定义明确的位置。
RETURN_VALUE_ABI_PRESERVES_ADDRESS也类似RETURN_VALUE_STRUCT_CONVENTION,ABI保证返回值的地址放到一个定义明确的位置。
一般情况下TYPE_CODE_STRUCT、TYPE_CODE_UNION和TYPE_CODE_ARRAY使用RETURN_VALUE_STRUCT_CONVENTION,其他用RETURN_VALUE_REGISTER_CONVENTION,当然具体情况要由ABI决定。
在要取得函数返回值的时候gdbarch_return_value_ftype类型函数的参数readbuf为非空,通过将regcache中的值存入这个参数就可以得到返回值。要设置函数的返回值,则参数writebuf为非空,通过将writebuf中的值存入regcache中设置返回值。
如果编写一个gdbarch_return_value_ftype函数完成了所有的功能,则不需要设置gdbarch_extract_return_value_ftype和gdbarch_store_return_value_ftype函数。如果gdbarch_return_value_ftype函数使用的默认值,则需要设置gdbarch_extract_return_value_ftype和gdbarch_store_return_value_ftype函数。这几个函数的编写可以参照GDB/gdb/arch-utils.c中的第一个参数的默认值legacy_return_value,以及GDB/gdb/目录中ARCH-tdep.c类似文件中同样类型的函数。

从这里开始的介绍的函数一般来说是TARGET相关的,所以一般情况下他们在GDB/gdb/ARCH-TARGET-tdep.c中进行注册,当然要具体情况具体分析。

void set_gdbarch_get_longjmp_target (struct gdbarch *gdbarch, gdbarch_get_longjmp_target_ftype *get_longjmp_target)
设置一个gdbarch_get_longjmp_target_ftype类型的函数指针到gdbarch中,调用其的宏是GET_LONGJMP_TARGET和GET_LONGJMP_TARGET_P。
注册的这个函数的作用是计算出longjmp要跳转到的地址并存在函数的参数pc中,如果成功返回0,失败返回非0。一般来说这个函数的编写方法是分析存放longjump跳转目标的缓存,将目标地址取出就可以。具体的过程可以参见GDB/gdb目录中ARCH-tedp.c类型文件中的相关代码。

void set_gdbarch_skip_solib_resolver (struct gdbarch *gdbarch, gdbarch_skip_solib_resolver_ftype *skip_solib_resolver)
设置一个gdbarch_skip_solib_resolver_ftype类型的函数指针到gdbarch中,调用其的宏是SKIP_SOLIB_RESOLVER,不过这个宏好像已经被从GDB的代码中去掉了,现在都是通过gdbarch_skip_solib_resolver进行调用。
注册的这个函数是用来跳过参数pc后面的动态连接器符号定义函数,如果这个函数的参数pc在动态连接器符号定义函数中的时候,返回动态连接器符号定义函数后面的地址,如果不是则返回0。一般来说这个工作需要对符号表进行分析来实现。
如果TARGET使用的是GLIBC则可以直接注册glibc_skip_solib_resolver函数,如果使用这个函数的话需要在文件中包含glibc-tdep.h头文件,同时在前面提到的GDB/gdb/config/ARCH/TARGET.mt中的TDEPFILES=项目中包含glibc-tdep.o。

void set_gdbarch_skip_trampoline_code (struct gdbarch *gdbarch, gdbarch_skip_trampoline_code_ftype *skip_trampoline_code)
设置一个gdbarch_skip_trampoline_code_ftype类型的函数指针到gdbarch中,调用其的宏是SKIP_TRAMPOLINE_CODE。
如果TARGET在调用函数和被调用函数之间有trampolines代码(跳板代码,对其介绍可以见http://www.science.uva.nl/~mes/jargon/t/trampoline.html),则可以注册这个函数用来跳过trampolines代码。这个函数的参数pc就是当前的PC寄存器的值,如果这个函数返回非0则就是trampolines代码后面普通函数的地址,如果返回0则表示执行不成功。
如果trampolines代码是跟动态链接库有关的,可以注册标准函数GDB/gdb/minsyms.c:find_solib_trampoline_target来实现需要的功能。

void set_gdbarch_in_solib_return_trampoline (struct gdbarch *gdbarch, gdbarch_in_solib_return_trampoline_ftype *in_solib_return_trampoline)
设置一个gdbarch_in_solib_return_trampoline_ftype类型的函数指针到gdbarch中,调用其的宏是IN_SOLIB_RETURN_TRAMPOLINE。其实还有一个IN_SOLIB_CALL_TRAMPOLINE(设置函数是set_gdbarch_in_solib_call_trampoline),但是其实际已经没有再在GDB中被调用,所以不进行介绍,也不建议使用那个选项。
注册的这个函数是用来确定参数pc指定的地址是否在trampolines代码中,如果是就返回非0,不是就返回0,其参数name表示当前所在的函数名称。

void set_solib_svr4_fetch_link_map_offsets (struct gdbarch *gdbarch, struct link_map_offsets *(*func) (void))
设置一个struct link_map_offsets *(*func) (void)类型的函数到gdbarch中。调用其的宏是SVR4_FETCH_LINK_MAP_OFFSETS。如果要使用设置函数set_solib_svr4_fetch_link_map_offsets则要在文件中solib-svr4.h头文件,同时在前面提到的GDB/gdb/config/ARCH/TARGET.mt中的TDEPFILES=项目中包含solib-svr4.o。
当你的TARGET支持SVR4共享库的时候,需要注册的这个函数用来初始化SVR4共享库的link_map_offsets结构,如果你的TARGET是标准的ILP32结构(代表integer/long/pointer是 32位)可以直接注册GDB/gdb/solib-svr4.c:svr4_ilp32_fetch_link_map_offsets函数,如果是标准的LP64结构(代表long/pointer是64位)可以直接注册GDB/gdb/solib-svr4.c:svr4_lp64_fetch_link_map_offsets函数。




6.GDB/gdb/config/ARCH/tm-ARCH.h GDB/gdb/config/ARCH/tm-TARGET.h
6.1.综述
这两个文件后面的GDB/gdb/config/ARCH/tm-TARGET.h文件中是TARGET相关的信息,一般其将在TARGET.mt的DEPRECATED_TM_FILE=中被指定。定义在DEPRECATED_TM_FILE=中的文件将被软连接到GDB/gdb/tm.h文件,这个文件将被GDB/gdb/defs.h文件包含,而这个文件将被很多文件包含,所以在在这个文件中设置的宏以及其他的定义最终将被相关的文件包含,达到了设置这些文件的目的。
GDB/gdb/config/ARCH/tm-ARCH.h文件中是ARCH相关的信息,一般其是被ARCH的所有TARGET的tm-TARGET.h文件包含的,不过如果TARGET除了ARCH相关的定义不需要自己再进行什么定义的时候,也可以直接被在TARGET.mt的DEPRECATED_TM_FILE=中指定tm-ARCH.h,这个是可以灵活掌握的。


6.2.宏定义
在5.5节介绍gdbarch设置函数的时候,同时就介绍了调用注册上的变量或者函数的宏,这些宏跟下面将要介绍的宏一样,都可以在tm-ARCH.h和tm-TARGET.h中进行设置。要注意的是很多宏都是跟“宏_P”名称的宏一起使用的,这个“宏_P”形式的宏是用来判断名称中的那个宏是否使用,所以使用设置这个宏一定要同时设置“宏_P”形式的宏为1。
这些宏在GDBINT 9.10进行了一些介绍,可以作为参考。

STEP_SKIPS_DELAY (pc)和STEP_SKIPS_DELAY_P
这个宏用来确定pc地址上的指令是否是一条缓冲槽指令,如果是则返回真。
GDB用这个宏来判断当前指令是不是缓冲槽指令,然后就会去判断下一条指令也就是缓冲槽中的指令是不是断点,如果是则表明当前指令是一条断点指令,具体原因对MIPS缓冲槽实现有了解的人应该理解。
在现在GDB源码中只有MIPS用了这个宏,没有别的ARCH有缓冲槽指令?难道缓冲槽是被MIPS申请了的专利?

IN_SOLIB_DYNSYM_RESOLVE_CODE (pc)
这个宏用来确定pc地址是否在动态连接器符号定义函数中,如果是则返回真。
如果TARGET使用的动态链接库已经被GDB支持(GDB/gdb/solib-库名称.c),则可以不设置这个宏,而是在tm-TARGET.h中包含solib.h头文件,同时在TARGET.mt的TDEPFILES=中包含solib.o和solib-库名称.o来对动态库进行支持。




7.frame
7.1.概述
frame是stack frame,也就是栈结构,个人认为其主要的功能就是用来确定一条指令所在的函数位置,一个函数中的所有指令的frame结构都是一样的(用来帮助取得frame结构的-1级结构是不同的,后面会详细介绍)。
比如GDB使用指令next和nexti的时候,在执行前先会取得当前函数的frame信息,然后在每执行一条指令后取得跟这条指令对应的函数frame信息的上一层函数的frame信息,如果这两个frame信息相同则表明执行进入了一个函数,将在前一个函数现在所在函数返回位置设置断点,然后使用连续执行,达到快速通过单步代码执行中函数调用的目的。其中比较以及后面功能的代码在GDB/gdb/infrun.c:handle_inferior_event:2285行开始。
类似的还有finish指令通过frame信息取得当前函数调用函数位置等。
frame分为四种类型,分别是普通frame结构的NORMAL_FRAME,在通过GDB调用程序中某个函数的时候使用的DUMMY_FRAME,用在信号处理代码中的SIGTRAMP_FRAME,用来记录当前指令所在位置(普通frame记录的是当前指令所在函数的信息)信息的SENTINEL_FRAME,他们定义在GDB/gdb/frame.h的枚举类型frame_type中,后面会在对frame介绍的时候介绍到他们。


7.2.struct frame_info
这个结构中保存了一个frame的各种信息。其被定义在GDB/gdb/frame.c中,在这个文件之外对这个结构的调用只限于指针,所以这个结构定义在这里就可以。

其主要的元素有:
int level
这个变量代表frame的等级,当前函数的frame的等级是0,这个函数的调用函数的等级是1,依此类推。下面都将按照这个方式,按照level值的高低来区别frame,称呼他们为高一级和低一级。
有一个特殊的level是-1,是通过当前指令的信息取得,每条指令一个。因为GDB总是需要一个frame,所以就需要有这个特殊的frame表示当前指令的frame,一般来说这个frame就是SENTINEL_FRAME类型的。
const struct frame_unwind *unwind;
这个结构指针是对frame_info进行控制的重要元素,每个TARGET都是通过将函数指针和变量设置到其上来达到控制frame的目的,具体的这个结构和设置过程将在后面详细介绍。
注意其中的const是为了保证这个变量只在初始化设置一次,其他时候设置将报错。
void *prologue_cache;
这个指针是给unwind函数互相传递数据使用,具体使用方式将在后面详细介绍。
struct {
int p;
CORE_ADDR value;
} prev_pc;
这个结构中的value存储了这个frame相关的函数的返回地址,因为这个地址就是比其高一级的frame的函数的地址,所以称为prev_pc。p代表这个值是否有效。
struct
{
CORE_ADDR addr;
int p;
} prev_func;
这个结构中的addr存储了比当前frame高一级的frame相关的函数的起始地址。p代表这个值是否有效。
struct
{
int p;
struct frame_id value;
} this_id;
这个结构是当前frame的ID,这是用来分辨frame的核心数据,在2个frame进行比较的时候,将用这个结构中的数据进行比较。这个结构中的p代表整个这个结构是否有效,frame_id结构将在下面介绍。
struct frame_info *next;
这个指针指向比这个frame低一级的frame的结构。
struct frame_info *prev;
这个指针指向比这个frame高一级的frame的结构。
int prev_p;
这个变量用来标明prev指针是否被赋值,在访问prev以前需要先检查这个变量,如果为1可直接访问,如果为0则需要先取得prev。
更多的信息可以看相关代码的注释,写的比较详细。


7.3.struct frame_id
这个结构是frame的ID,用来区分frame,定义在GDB/gdb/frame.h中。

其主要的元素有:
CORE_ADDR stack_addr;
这个frame对应函数的栈地址,一般来说就是调用函数后,函数进行栈空间分配之前的栈地址。
unsigned int stack_addr_p : 1;
stack_addr只有在stack_addr_p为真的时候有效。
CORE_ADDR code_addr;
这个frame对应函数的的起始地址。
unsigned int code_addr_p : 1;
code_addr只有在code_addr_p为真的时候有效。
CORE_ADDR special_addr;
有一些ARCH的某部分frame的stack_addr不频繁变化,但是有一个其他地址的变化可以作为标识的补充,则将这个地址设置到special_addr中。
unsigned int special_addr_p : 1;
special_addr只有在special_addr_p为真的时候有效。


7.4.取得当前指令对应函数frame_info信息
使用GDB/gdb/frame.c:get_current_frame函数。

其的执行过程主要是:
第一,检查当前target(跟前面提到的TARGET不是一个东西,其是被调试目标,介绍可以见GDBINT 10)的寄存器、栈和内存进行检查,没有任何一个都将出错。
第一,判断全局变量GDB/gdb/frame.c:current_frame是否为空,如果不为空则返回。
第二,调用函数GDB/gdb/frame.c:create_sentinel_frame取得sentinel_frame,sentinel_frame就是7.2提到level为-1类型是SENTINEL_FRAME的frame。
在函数create_sentinel_frame中,首先给frame_info结构分配空间,然后将level设置为-1。接着是初始化prologue_cache和unwind,其中prologue_cache将当前寄存器信息存储其中,而unwind初始化为GDB/gdb/sentinel-frame.c:sentinel_frame_unwind。设置next为当前初始化的这个frame_info结构,保证当需要访问比这个frame还低一级的frame的时候可以访问到这个frame自身。设置frame_id为GDB/gdb/frame.c:null_frame_id,设置这个值保证每次frame_id的比较都相等。最后将frame返回。
第三,通过GDB/gdb/top.c:catch_exceptions函数调用GDB/gdb/frame.c:unwind_to_current_frame函数,这个函数会调用函数GDB/gdb/frame.c:get_prev_frame。
在函数get_prev_frame中,先作一些检查,然后调用函数GDB/gdb/frame.c:get_prev_frame_1进行实际的工作。
在函数get_prev_frame_1中,这里先检查参数this_frame->prev_p是否为真,如果为真表明this_frame->prev已经存在,则直接返回this_frame->prev。设置this_frame->prev_p为1,因为现在要开始设置this_frame->prev了。对this_frame进行一些检查,如果检查没问题则给prev_frame分配空间,设置prev_frame->level比this_frame->level大1,表明比this_frame高一个级别。最后互相注册到prev和next上建立连接,返回prev_frame。
这样就取得了比sentinel_frame高一级的frame,也就是对应当前函数的frame。将取得的这个frame设置到current_frame上。可以注意到这个新取得的current_frame上很多元素都是空的,这是因为当在使用这个frame_info中的一部分元素的时候,会先自动检查其是否存在,如果不存在则调用相应函数取得其。
第四,整个get_current_frame函数返回current_frame。


7.5.取得指定frame_info的frame_id
使用GDB/gdb/frame.c:get_frame_id函数。

其的执行过程主要是:
第一,检查frame_info结构的this_id.p,如果为真表明this_id.value存在,直接返回。
第二,检查frame_info结构中的unwind是否为空,如果为空则调用GDB/gdb/frame-unwind.c:frame_unwind_find_by_frame函数根据当前frame低一级的next取得unwind。
在frame_unwind_find_by_frame中,首先通过gdbarch取得frame_unwind结构的列表,然后依次先调用其注册的时候注册的sniffer函数,然后调用frame_unwind结构上自带的sniffer函数,如果成功则返回frame_unwind结构,失败则报错。关于将frame_unwind结构注册到gdbarch中的方式将在介绍跟frame相关的gdbarch初始化函数的时候,再进行介绍。
第三,调用frame_unwind结构中的this_id函数指针,取得this_id.value,设置this_id.p为1,然后返回。

取得指定frame_info的寄存器信息的函数GDB/gdb/frame.c:frame_register_unwind跟这个函数结构类似,只是将调用this_id函数指针换成prev_register函数指针,所以不作详细的介绍。


7.6.取得指定frame_info对应的函数返回地址
使用函数GDB/gdb/frame.c:frame_pc_unwind,因为取得frame_info对应的函数返回地址是取得比其高一级的frame的frame_info信息的关键,因为取得了这个地址就意味着取得了高一级的frame的地址。

这个函数的执行过程主要是:
第一,检查这个frame的prev_pc.p是否为真,如果为真则表明prev_pc.value存在,直接将其返回。
第二,判断gdbarch是否设置了unwind_pc函数(这个函数的设置和编写将在跟frame相关的gdbarch设置函数章节进行介绍),如果设置了则运行其来取得相关值,然后设置到prev_pc.value返回。
第三,判断当前frame的级别是否小于0,如果小于0就表明是前面提到的sentinel_frame,则直接通过read_pc取得当前pc值设置到prev_pc.value并返回。
第四,如果都不存在则报错。

个人认为frame_pc_unwind以及gdbarch中的unwind_pc实在是函数起名的错误典型,明明是取得这个frame高一级的frame的pc,却偏偏叫frame_pc_unwind,很容易让人误以为是取得当前frame相关函数起始之用。确实很多TARGET之中注册到unwind_pc上的函数是靠取得frame寄存器中pc值来实现的,但是这是因为这个TARGET的这个值正好是函数返回地址。以这样的函数名称,再加上很多TARGET中的相关代码,很容易让人错误理解这个函数的作用。所以个人认为其应该叫frame_prev_pc_unwind和unwind_prev_pc。


7.7.取得指定frame的高一级frame的函数地址
使用函数GDB/gdb/frame.c:frame_func_unwind。

这个函数的执行过程主要是:
第一,检查这个frame的prev_func.p是否为真,如果为真则表明prev_func.addr存在,直接将其返回。
第二,调用GDB/gdb/frame.c:frame_unwind_address_in_block函数,这个函数主要就是调用frame_pc_unwind取得指定frame的函数返回地址,也就是比这个frame高一级的frame函数中的一个地址。
第三,调用函数GDB/gdb/blockframe.c:get_pc_function_start通过分析符号信息,用比这个frame高一级的frame函数中的一个地址取得这个函数的起始地址,设置到prev_func.addr中,然后返回。

个人觉得这个函数起名跟frame_pc_unwind有类似的问题。


7.8.跟frame相关的gdbarch设置函数
void frame_unwind_append_sniffer (struct gdbarch *gdbarch, frame_unwind_sniffer_ftype *sniffer)
设置一个frame_unwind_sniffer_ftype类型的函数指针到gdbarch中。注意这个函数可以在初始化的代码中多次调用来注册多个函数。
每个注册的函数都跟一个frame_unwind结构指针相关,其主要作用是在前面介绍过的frame_unwind_find_by_frame函数中依照注册的先后被调用。一般这个函数会通过GDB/gdb/frame.c:frame_pc_unwind函数取得next_frame也就是比其低一级的frame的函数返回地址,也就是当前函数的一个地址,通过这个地址进行分析跟注册的这个函数相关的frame_unwind结构是否符合这个地址相关的frame(一般来说是执行文件调试信息有这个地址相关的调试信息),如果符合则返回这个frame_unwind结构指针,如果不符合则返回NULL。
如果可执行文件支持DWARF2调试格式,可以直接注册GDB/gdb/dwarf2-frame.c:dwarf2_frame_sniffer函数,使用GDB中的DWARF2格式分析结构GDB/gdb/dwarf2-frame.c:dwarf2_frame_unwind对相关信息进行分析。
如果最后一个sniffer函数失败,frame_unwind_find_by_frame函数的执行将出错,GDB的执行也会退出,所以建议最后用frame_unwind_append_sniffer注册一个不会进行任何检查而直接返回frame_unwind结构指针sniffer函数。而跟这个函数相关的frame_unwind结构指针中的函数相对也是最基础的frame分析方式,主要是通过对代码反汇编取得函数行为来确定,将在下面介绍frame_unwind结构的时候进行详细的介绍。

void set_gdbarch_unwind_pc (struct gdbarch *gdbarch, gdbarch_unwind_pc_ftype *unwind_pc)
设置一个gdbarch_unwind_pc_ftype类型的函数指针到gdbarch中。
注册的这个函数将被7.6介绍过的函数GDB/gdb/frame.c:frame_pc_unwind调用,其将返回参数next_frame相关的函数的返回地址。一般在这个函数中调用GDB/gdb/frame.c:frame_unwind_register_unsigned 、get_frame_register_signed等函数(这些函数将调用7.5介绍过的函数GDB/gdb/frame.c:frame_register_unwind)来取得相应序号寄存器的信息,具体取哪个寄存器的数据则需要根据frame_register_unwind调用的frame_unwind结构中的函数的情况来决定。比如有一些ARCH中是取PC寄存器的值,因为存储在栈中PC寄存器位置的值就是这个函数的返回值。


7.9.struct frame_unwind
这个结构定义在GDB/gdb/frame-unwind.h中,其在前面已经被多次提到,其中的指针就是对frame信息进行分析的核心函数。下面将介绍一下这个结构中主要函数指针的作用。

enum frame_type type;
这是本章开头提到的frame的类型,一般用前面介绍的frame_unwind_append_sniffer函数注册的frame_unwind结构都使用NORMAL_FRAME类型。

因为下面要介绍的两个函数的编写方法一般来说都是从栈中取得寄存器信息,然后进行处理,所以一般都是先调用一个独立的函数,这个函数是对栈进行分析,然后将结果保存在参数this_cache中。
这个this_cache指针就是frame_info结构中的prologue_cache,所以这个指针可以用来保存分析出来的寄存器信息。因为每个ARCH的结构差异很大,所以一般使用例如struct mips_frame_cache(定义在GDB/gdb/mips-tdep.c中)这样的结构来定义*this_cache指向的内存,一般来说这个结构包含一个CORE_ADDR类型的值存储当前frame对应函数的栈地址,一个GDB/gdb/trad-frame.h:trad_frame_saved_reg结构的指针用来保存其他分析出的寄存器的信息。
当*this_cache为NULL的时候,表明这个frame还没被分析过。首先调用GDB/gdb/frame.h:FRAME_OBSTACK_ZALLOC分配内存,参数为这个ARCH的frame结构类型的长度。如果当前的结构中定义了trad_frame_saved_reg类型结构指针,则需要调用GDB/gdb/trad-frame.c:trad_frame_alloc_saved_regs以next_frame为参数对这个结构指针进行初始化。分析信息首先要分析出的就是当前frame对应的函数栈地址,就是函数进行栈空间分配之前的栈地址。如果当前的TARGET中有BP寄存器,因为BP寄存器存储函数栈地址,在函数调用的时候BP寄存器将被存入下一个函数的栈中,所以可以通过取得参数中next_frame(也就是比这个frame低一级的frame)的BP值来取得函数栈地址。如果没有BP寄存器,则取得参数next_frame相关函数的栈地址信息(一般来说是SP寄存器信息),然后通过对当前函数的分析得到这个函数分配的栈空间长度(这里是栈分析的最主要工作,使用编译中产生的调试信息,如果没有则只有反汇编代码,在取得长度的同时一般也会分析出哪些寄存器被存在了栈中以及他们的位置),将这两个值相加(如果栈是递减形式分配)就可以得到当前函数栈地址信息。这个信息以及对栈中信息分析得到的寄存器信息都要存到前面提到的*this_cache中。如果使用了trad_frame_saved_reg类型结构指针存储寄存器信息,则将每个分析到的寄存器用GDB/gdb/trad-frame.c:trad_frame_set_value来设置其中寄存器的值。这里要注意如果你在前面介绍过的unwind_pc函数中设置的取用某个寄存器的值作用函数的返回值,则在这里一定要将这个寄存器的值设置为存在栈中的函数返回值。
当*this_cache不为NULL的时候表明已经被分析过,不需要再进行分析,可以世界使用其中的值。

frame_this_id_ftype *this_id;
取得参数next_frame的高一级的frame的frame_id。
函数的编写方式是首先调用前面提到的栈分析函数得到栈信息,如果你的栈结构比较普通,可以调用GDB/gdb/frame.c:frame_id_build函数取得frame_id,其的第一个参数使用前面分析得到的栈地址,第二个参数用前面介绍过的frame_func_unwind函数取得frame相关函数的起始地址就可以。

frame_prev_register_ftype *prev_register;
分解(unwind)出当前这个frame的寄存器信息(被函数存入栈的寄存器信息)。
函数的编写方式是首先调用前面提到的栈分析函数得到栈信息,如果使用了trad_frame_saved_reg类型结构指针存储寄存器信息,则将每个分析到的寄存器用GDB/gdb/trad-frame.c:trad_frame_get_prev_register来设置寄存器的值。




8.dummy frame
8.1.概述
在GDB中可以用命令call或者命令printf调用被调试的程序中的函数,调用的方式是先创建一个dummy frame(就是前面介绍过的DUMMY_FRAME),然后以这个dummy frame为基础(跟前面提到过的一样,GDB任何时候都需要一个frame)调用函数。跟dummy frame相关的一些函数等将在下面进行介绍。


8.2.struct value *call_function_by_hand (struct value *function, int nargs, struct value **args)
这个函数的定义在GDB/gdb/infcall.c中,当用户用命令调用某个函数的时候,先会调用GDB/gdb/printcmd.c文件中的函数,这个函数调用GDB/gdb/eval.c中的函数,最终就会调用call_function_by_hand函数。这个函数上完成了前面提到的创建dummy frame以及调用函数的工作,理解这个函数对理解dummy frame非常有帮助,所以要对这个函数进行介绍。
这个函数的第一个参数function是要调用函数的描述结构,nargs是函数参数的数量,args是函数参数的值。

下面来介绍一下这个函数的主要执行过程:
第一步,调用target_has_execution检查current_target是否处于执行中状态,没有则报错返回,因为只有处于执行的中的状态才能调用函数。
第二步,保存一些当前程序的寄存器等信息以及给一些后面需要的指针分配空间。
第三步,调用GDB/gdb/regcache.c:read_sp取得当前栈指针(一般来说是SP寄存器的信息)。
调用gdbarch_frame_align_p函数判断TARGET是否设置了frame_align函数指针(8.3介绍),如果TARGET没有设置这个函数指针则跳过下面下栈指针进行处理的代码,因为这个函数指针将在下面的处理中保证栈仍然按照ARCH要求对齐。
处理栈指针首先调用INNER_THAN(前面介绍过)用来判断栈是递减分配还是递增分配,然后根据情况从栈中分配gdbarch_frame_red_zone_size(8.3介绍)长度的值,red zone是一部分的ARCH的ABI令函数使用栈空间底部以外的空间的称呼,这部分空间有一个特点是如果在当前函数中再进行函数调用,则这部分空间讲被分配为被被调用函数的空间,则其中数据将被破坏,解决办法是调用函数以前先将red zone的空间分配到当前函数空间中,这也就是我们当前所作的工作,没有red zone的ARCH的gdbarch_frame_red_zone_size默认被设置为0,也就不进行分配工作。然后检查空间是否仍然按照要求对齐,如果不对齐则报错退出。
现在检查新取得的栈指针跟原来的栈指针是否相同,如果相同则需要再对栈指针进行先分配1字节然后对齐的操作,这样作的目的在那段代码上面的注释上有比较详细的介绍,我按照我的理解来介绍一下:在很多RISC ARCH中,没有参数没有返回值的函数往往形成没有栈变化的函数(因为RISC ARCH好像倾向于把函数返回地址存到一个特定寄存器,相比较CISC比如X86是把函数返回值放到栈中则不会出现这种问题),前面我们介绍过栈指针是产生用来比较栈的frame_id的重要部值,栈指针相同就表明可能产生相同的frame_id,这样GDB/gdb/dummy-frame.c:dummy_frame_sniffer在搜索dummy_frame_stack列表的时候,将只能返回相同frame_id的第一个dummy frame,这样将产生错误。所以这里进行分配空间的操作防止产生相同的frame_id的dummy frame。
如果TARGET没有设置frame_align函数指针,后面将直接使用前面取得的当前栈指针,这里有一些注释说没有对齐的栈指针是个很严重的问题,其实也没那么严重,没有设置frame_align的TARGET可以在下面调用的push_dummy_call函数指针中来完成对栈指针进行设置的工作,当然设置frame_align我觉得是相对来说更清晰一点。
第四步,进行一些分析取得将被调用的地址funaddr以及这个函数的返回值是否是struct的标志struct_return。
第五步,调用CALL_DUMMY_LOCATION,这个宏一般定义为调用TARGET中设置的变量call_dummy_location,这个变量用来定义调用函数将返回的位置,在这个位置将设置断点。有三个位置可以选择,ON_STACK,AT_ENTRY_POINT,AT_SYMBOL。
ON_STACK,将返回地址设置在栈上,这里要调用GDB/gdb/infcall.c:push_dummy_code这个函数的作用是在栈中分配给返回地址上的断点需要的空间。
AT_ENTRY_POINT,这是TARGET中使用最多的一种形式,将返回地址设置在symfile_objfile->ei.entry_point的地址上。
AT_SYMBOL,这个位置好像只有MIPS使用,将返回地址设置在符号__CALL_DUMMY_ADDRESS的地址上。
第六步,对参数和返回地址进行处理。
第七步,调用gdbarch_push_dummy_call将当前的栈和寄存器的状态设置到执行完调用函数指令后的状态。如果TARGET没有设置push_dummy_call函数指针,则提示用户当前的TARGET不支持相关功能,然后退出。
第八步,调用frame_id_build取得dummy_id,这是比将被运行的函数的frame高一级的frame的ID。在前面指定好的位置设置断点,这样要被调用的函数在返回后将可以被中断,这样GDB就能返回正常状态。调用GDB/gdb/dummy-frame.c:dummy_frame_push将dummy_id添加到列表dummy_frame_stack中(这么作的目的在后面介绍)。
第九步,作一些初始化的工作,最后调用GDB/gdb/infrun.c:proceed函数,其的作用是在GDB中执行被调试程序,其中real_pc执行的地址就是其开始执行的地址。当这个函数返回的时候就表明对参数中指定的要调用的函数执行完毕。然后调用函数返回最开始存储的当前被调试程序的状态。最后函数返回。


8.3.跟dummy frame相关的gdbarch设置函数
void set_gdbarch_unwind_dummy_id (struct gdbarch *gdbarch, gdbarch_unwind_dummy_id_ftype *unwind_dummy_id)
设置一个gdbarch_unwind_dummy_id_ftype类型的函数指针到gdbarch中。
注册函数的编写方法一般是用frame_id_build函数(后面将详细介绍)取得当前frame的信息,一般来说调用其使用的的第一个参数是next_frame存储的SP寄存器,第二个参数是next_frame存储的PC寄存器,这两个值需要用frame_unwind_register_unsigned或者frame_pc_unwind等来取得。
在前面提到过的函数dummy_frame_sniffer中,将调用注册的这个函数。包含dummy_frame_sniffer的frame_unwind结构dummy_frame_unwinder已经被注册,在GDB每次调用前面提到过的函数frame_unwind_find_by_frame来取得当前的frame_unwind结构的时候,dummy_frame_unwinder结构都被第一个被比较,则函数dummy_frame_sniffer第一个被调用,检查当前要取得的frame是不是dummy frame。在dummy_frame_sniffer函数中,通过调用注册的unwind_dummy_id函数指针来取得当前的frame_id,然后跟用dummy_frame_push存到列表dummy_frame_stack中的每个dummy_id进行比较,如果相同则frame_unwind_find_by_frame将返回dummy_frame_unwinder。这么作的目的是因为dummy frame需要自己特殊的frame_unwind结构才能正常使用。
由上面对调用unwind_dummy_id函数指针的过程也可以注意到,不管当前TARGET是否需要在GDB中支持dummy frame也就是call命令,都必须用set_gdbarch_unwind_dummy_id注册unwind_dummy_id函数,否则GDB将不能运行。

void set_gdbarch_frame_align (struct gdbarch *gdbarch, gdbarch_frame_align_ftype *frame_align)
设置一个gdbarch_frame_align_ftype类型的函数指针到gdbarch中。
注册的这个函数的作用在前面介绍过,其作用对参数指定的地址进行对齐操作。
如果当前TARGET不支持dummy frame或者栈指针通过set_gdbarch_push_dummy_call注册的函数来保证对齐,而且这个ARCH在调用函数的时候不会出现相同的栈指针,还不支持red zone,则可以不设置这个函数。

void set_gdbarch_frame_red_zone_size (struct gdbarch *gdbarch, int frame_red_zone_size)
设置变量frame_red_zone_size到gdbarch中,调用其的宏是FRAME_RED_ZONE_SIZE。
如果当前ARCH支持red zone,则需要设置这个变量。不支持可以设置为0。

void set_gdbarch_call_dummy_location (struct gdbarch *gdbarch, int call_dummy_location)
设置变量call_dummy_location到gdbarch中,调用其的宏是CALL_DUMMY_LOCATION。
当TARGET支持dummy frame的时候,需要设置这个变量来确定调用函数将返回的位置,具体设置什么值前面已经介绍过了,一般情况设置为AT_SYMBOL。

void set_gdbarch_push_dummy_code (struct gdbarch *gdbarch, gdbarch_push_dummy_code_ftype *push_dummy_code)
设置一个gdbarch_push_dummy_code_ftype类型的函数指针到gdbarch中。
当上面的call_dummy_location设置为ON_STACK的时候,需要调用push_dummy_code这个函数在栈中分配给返回地址上的断点需要的空间,如果设置了push_dummy_code则会调用这个函数,如果没设置则会调用GDB/gdb/infcall.c:generic_push_dummy_code。
从上面的分析可以看出这个函数只有在call_dummy_location设置为ON_STACK会被调用,而且如果需要进行的处理比较普通,可以不设置,直接让GDB调用generic_push_dummy_code函数。

void set_gdbarch_push_dummy_call (struct gdbarch *gdbarch, gdbarch_push_dummy_call_ftype *push_dummy_call)
设置一个gdbarch_push_dummy_call_ftype类型的函数指针到gdbarch中。
注册的这个函数的作用是将当前的栈和寄存器的状态设置到执行完调用函数指令后的状态。
参数function是要调用的函数。参数regcache是当前的寄存器,可以通过regcache_cooked_write_unsigned等函数设置寄存器。参数bp_addr是被调用函数返回的地址。参数nargs和args是函数的参数数量和值。参数struct_return如果为真则表明返回值是struct类型,则参数struct_addr就是用来返回返回值的地址。
一般情况下会将返回地址bp_addr设置在RA寄存器(risc寄存器常见)或者压入栈中(例如x86);然后检查参数struct_return是否为真,如果为真则将参数struct_addr存入函数的一个位置中,有时候是作为函数的第一个参数;最后依次存储参数nargs和args中指定的每个参数到寄存器或者栈中。总体来说这个函数的编写跟TARGET相关的ABI的具体实现关系密切。
如果不设置这个函数指针,则TARGET不能够支持dummy frame。
----
读了这么多年的书 还是觉得幼儿园好混
--
Attached file: 592197-pgdb1_v0.1.txt
[Original] [Print] [Top]
Subject: [精华] 下一步工作的展望
Author: teawater    Posted: 2006-01-23 21:24    Length: 187 byte(s)
[Original] [Print] [Top]
有几个目标 SIGTRAMP_FRAME的介绍 native移植介绍 GDBRSP协议介绍以及服务器端的实现的介绍 GDBSERVER移植介绍

其中我对native和GDBRSP熟悉一点 估计会先作 希望懒惰的俺能坚持下来
----
读了这么多年的书 还是觉得幼儿园好混
[Original] [Print] [Top]
Subject: [精华] Re: 移植GDB(1) arch和frame v0.1[原创]
Author: tigerwood    Posted: 2006-02-05 18:43    Length: 1,084 byte(s)
[Original] [Print] [Top]
不错不错,看的出来的确是做过东西的。:-)
不过我还是有几个建议供你参考 :-)

1 结合一个具体的例子来讲是不是好一些?个人觉得更有针对性一些。
另外如果真的做过某种ARCH/TARGET的移植,不妨把PATCH发到社区让大家评估以下
这样有利于提高代码质量,以后的维护也方便;如果能够被接受,不是更好。取之社区,回馈社区嘛。 :-)

我知道有一个叫ZHANGJIE的,移植GDB到BLACKFIN上,PATCH好象已经被社区接受了

2 关于ARCH/TARGET的部分有五六个SECTION,其实这些SECTION之间也是有关联的。
如果把它们放在一个章节里;再把与FRAME相关的两个章节放在一起,
整体结构也许会更清晰一些

3 正如你所说,GDB INTERNAL中关于FRAME的内容很少,也许你可以把自己的内容以英文的形式回馈给
GDB社区?

4 另外一个是关于提高GDB的,GDB INTERNAL中提到当前的FRAME实现其实不是很好,有点缝缝补补又三年的意思。
既然你对这一块很了解,何不改进改进? 我想GDB社区应该是很欢迎这种CONTRIBUTION的。
而且这种工作也许更有意义一些。你说呢?

几点拙见,不到之处请谅解。 :-)
[Original] [Print] [Top]
Subject: [精华] Re: 移植GDB(1) arch和frame v0.1[原创]
Author: teawater    Posted: 2006-02-05 20:13    Length: 703 byte(s)
[Original] [Print] [Top]
多谢你的回复 这个估计是我发的东东里得到的最多的回复了 非常感谢 呵呵

1.我确实作过这方面工作 不过现在还不方便拿出来 不过等以后应该是可以拿出来让大家批判一下的 呵呵
我没把一个具体的ARCH当成例子主要是觉得那样可能会忽略一些这个ARCH不支持的东西

2.都放一起弄的相应的章节是不是太大,那样会不会有点乱?
frame确实应该是放一起,不过最后的dummy frame是我后加的 本身也分了好几块 俺觉得又贴在上面一部分上乱了点 所以就单分了一块

3.确实也有过这样想法,不过只停留在想想了,现在俺还是先得把GDB其他俺了解的部分写出来,不然俺这个记性估计很快就全都忘记了。

4.看也看了一些相关的代码,说实话也没看出什么可插手的地方,惭愧惭愧~~~
----
其实俺是一个泼皮无赖
[Original] [Print] [Top]
Subject: [精华] 移植GDB(2) native v0.0
Author: teawater    Posted: 2006-02-21 20:43    Length: 22,756 byte(s)
[Original] [Print] [Top]
附件中有txt版本 需要登陆后才能看见

移植GDB(2) native v0.0
teawater<teawater@gmail.com>
转载请标明来自 http://www.linuxforum.net




修改记录:
v0.0
2006-02-21,v0.0版本编写完成。
没有详细介绍GDB/gdb/solib.c上动态链接库支持的扩展。
2006-02-02,文档创建。




目录
1.写在前面
2.long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data)
3.struct user
4.GDB/gdb/config/ARCH/TARGET.mh
5.GDB/gdb/config/ARCH/nm-TARGET.h
6.GDB/gdb/ARCH-NAT.c 和 GDB/gdb/ARCH-TARGET-NAT.c
6.1.概述
6.2.跟寄存器读取有关的函数
6.3.gregset相关函数
7.本地core文件支持
7.1.概述
7.2.struct core_fns
7.3.GDB分析core文件的过程
7.4.让TARGET支持core文件分析的方法
8.动态连接库支持




1.写在前面
本文针对GDB-6.3进行编写。

native表示对某个ARCH在某个OS上(称为一个TARGET)调试的支持,当然要让ARCH支持本地调试首先要让GDB支持这个TARGET,这个可以参见“移植GDB(1)”。
GDB对被调试程序的调试主要是通过调用当前TARGET提供的调试接口(ptrace、procfs等,后面将详细介绍)对被调试程序进行控制,同时取得这个程序的寄存器和内存的信息,所以这个调试接口相关的代码就是native代码中比较重要的部分。一般来说不同ARCH在同一个OS会使用同一个调试接口,只是其中略有不同,所以移植一个ARCH到GDB已经支持的一个OS中的时候,只需要根据这个ARCH的情况进行一些编码就可以。而关于调试接口的编写将在“移植GDB(3)”中进行介绍。
在“GDBINT 11. Native Debugging”中,是对native的介绍,本文的部分内容也将取自其中。

在以下章节,将先对移植中需要增加和修改的文件依次进行详细介绍,然后对frame进行介绍。下面是文件名中使用的缩写和GDB代码中的定义。
GDBINT GDB Internals Manual的缩写。
GDB 指GDB源文件目录。
ARCH 体系结构名称。
TARGET 体系结构下的调试目标,一般是一种操作系统,比如Linux。




2.long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data)
ptrace是一个系统调用,其作用是让一个进程观测和控制另一个进程的执行,因为后面有不少跟其相关的内容,所以先在这里对其进行介绍。注意不同的OS下ptrace的参数等可能略有不同,这里只是简单介绍,具体情况要以实际OS为准。
不同的参数request值表示不同的控制功能,参数pid是被控制进程的进程ID。
ptrace的请求和功能说明如下:
PTRACE_TRACEME,被控制进程向控制进程发出跟踪请求。
PTRACE_PEEKTEXT,读取被控制进程代码段的一个字,addr给出地址。
PTRACE_PEEKDATA,读取被控制进程数据段的一个字,addr给出地址。
PTRACE_PEEKUSR,读取被控制进程user结构中的一个字,addr给出地址。
PTRACE_POKETEXT,向被控制进程代码段写入一个字,addr给出地址,data给出数据。
PTRACE_POKEDATA,向被控制进程数据段写入一个字,addr给出地址,data给出数据。
PTRACE_POKEUSR,向被控制进程user结构写入一个字,addr给出地址,data给出数据。
PTRACE_CONT,如果data参数为non-zero并且不是SIGSTOP时,它被解释成一个信号传递给被控制进程,否则,无信号被传递。
PTRACE_KILL,向被控制进程发送SIGKILL信号来终止其。
PTRACE_SINGLESTEP,使被控制进程单步执行。




3.struct user
这个结构在Linux中被定义在Linux Kernel目录include/asm-ARCH/user.h中,因为其中记录了寄存器等跟ARCH和OS相关的信息,所以不同的ARCH和OS是不同的。

在某些OS比如Linux的很多ARCH的ptrace实现中,PTRACE_PEEKUSR和PTRACE_POKEUSR都只对寄存器相关的参数读写起作用,其他部分根本不进行实际的操作,所以实际对user结构的使用要视实际情况来做。




4.GDB/gdb/config/ARCH/TARGET.mh
这个文件是设置一些编译GDB需要的跟当前TARGET有关的文件。下面介绍一下其中可设置的信息。


NAT_FILE=
这个是指定描述TARGET的头文件,一般来说这个头文件的写法为nm-TARGET.h存放在GDB/gdb/config/ARCH/目录中,具体内容将在后面介绍。

NATDEPFILES=
这个是指定要编译的.o文件,后面将介绍的跟TARGET有关的.c文件编译成的.o文件需要在这里指出。这个TARGET需要的ARCH-TARGET-NAT.o文件也需要在这里指定,有些ARCH会有一个通用的ARCH-NAT.o文件也在这里指定。
下面介绍一下常见的.o文件:
inftarg.o
这个文件中包含了通过ptrace对被调试程序进行控制的调试接口。
procfs.o
这个文件中包含了通过/proc对被调试程序进行控制的调试接口。
sol-thread.o
这个文件中包含了Solaris线程调试接口。
corelow.o
这个文件包含了core调试接口,给GDB增加了对core文件进行分析的功能。
core-aout.o
这个文件向上面的corelow.o提供了对未知格式的core文件的支持。
core-regset.o
这个文件向上面的corelow.o提供了对elf格式同时支持regsets结构的OS的core文件的支持。
infptrace.o
这个文件中包含了一些使用ptrace的接口,如果NATDEPFILES=包含inftarg.o则最好包含这个文件。
fork-child.o
这个文件中包含了fork和exec启动被调试程序的接口,一般的NATDEPFILES=都需要包含这个文件。
gcore.o
给GDB增加一个gcore的命令,用来给当前被调试程序产生core文件。
thread-db.o
这个.o文件包含了对target multi-thread的支持。
proc-service.o
用来向不支持proc_service的编译环境提供proc-service函数,thread-db.o文件需要proc-service函数。
linux-nat.o
这个.o文件包含了很多Linux相关的命令等,建议当TARGET所在的OS是Linux的时候包含这个.o文件。
solib.o
这个.o文件中包含了通用的动态链接库处理函数,具体可见下面对SOLIB_ADD等宏的介绍。
solib-TYPE.o
有一系列.o文件,比如solib-svr4.o solib-aix.o,他们是向solib.o提供对某个具体动态库支持的.o文件。
这其中跟Linux比较密切的有solib-svr4.o和solib-legacy.o。

LOADLIBES=
指定连接时候需要的库文件,如果TARGET的OS是Linux,因为需要在GDB启动的时候转载动态链接库libthread_db,所以通常使用“-ldl -rdynamic”。

具体这个文件的编写还可以参照其他TARGET的.mh文件。




5.GDB/gdb/config/ARCH/nm-TARGET.h
这个文件是TARGET的头文件,在native的代码这个文件是比较重要的。
一般来说这个文件都包含一个“config/nm-TARGET.h”名称的头文件,因为这个TARGET的OS在GDB中已经支持,比如config/nm-linux.h(注意:TARGET和OS名称一样),则可以包含这个头文件来设置一些跟这个OS相关的选项。

在GDBINT 11.6中介绍了头文件中可以设置的宏,下面将对其中一部分进行介绍:
FETCH_INFERIOR_REGISTERS
定义这个宏表明当前的TARGET将定义自己的fetch_inferior_registers函数和store_inferior_registers函数。具体这两个函数将在后面进行介绍。

KERNEL_U_SIZE
定义user结构的长度,一般是定义一个函数,然后函数中返回sizeof(struct user)。比较独特的是MIPS的Nm-linux.h,直接写了个504,也是其的sizeof(struct user)的长度,注释说这么写的好处是对交叉编译的支持更好,不过我觉得要是头文件都没配置好,再友好后面也要出错吧。

KERNEL_U_ADDR
定义user结构在Kernel中的地址。这个宏的作用是这样的,因为user结构中有些指针是指向这个结构中另一个参数,通过取得指针的值然后减去KERNEL_U_ADDR,就可以取得指针指向参数相对于user结构的偏移,也就可以方便的取得其中的值。
很多系统不需要这个值,可以直接将其设置为0。

U_REGS_OFFSET
定义user结构中寄存器信息元素(一般命名为regs)在整个结构中的偏移,比如很多TARGET的user结构中的寄存器信息元素在最开头,这个宏就会定义为0。

REGISTER_U_ADDR (addr, blockend, regno)
这个宏的作用是根据从U_REGS_OFFSET得到的寄存器信息在user结构中的偏移blockend取得序号为regno的寄存器在user结构中的偏移并将其存入addr。
这个宏实际只有一个作用,就是如果定义这个宏则在GDB/gdb/core-aout.c中生成一个函数register_addr,所以也可以不在头文件中定义REGISTER_U_ADDR而直接在TARGET相关的.c文件中编写一个register_addr,但是不要REGISTER_U_ADDR宏和register_addr同时编写。具体register_addr将在后面进行详细介绍。
还要注意如果你的TARGET编译的.o文件中不包含core-aout.o也就是默认生成register_addr的库文件,则需要在你自己的项目库文件中包含register_addr,光在头文件中包含REGISTER_U_ADDR就不行了。同时因为register_addr函数的作用是在GDB自带的fetch_inferior_registers函数和store_inferior_registers函数中被调用,也就是没定义FETCH_INFERIOR_REGISTERS的时候,所以如果当前TARGET定义了FETCH_INFERIOR_REGISTERS,REGISTER_U_ADDR和register_addr都不需要被定义。

CHILD_PREPARE_TO_STORE
这个宏会在ptrace、procfs和gnuhurd调试接口的child_prepare_to_store函数使用,这个函数的作用是在对寄存器进行设置以前,有时候需要先进行一些操作,比如在某些TARGET下无法设置一个寄存器,只能设置全部寄存器,则需要将全部寄存器的值读出来,再进行设置,这个操作就将在child_prepare_to_store调用。
当TARGET需要这种设置寄存器以前的操作的时候,可以将函数定义为这个宏。
在GDB的现有代码中,只有GDB/gdb/config/nm-gnu.h中设置了这个宏。

I386_USE_GENERIC_WATCHPOINTS
顾名思义这个宏只有ARCH为I386的TARGET需要,定义这个宏表明使用GDB/gdb/i386-nat.c中的特定断点功能,我估计其中使用了i386的调试寄存器。

ONE_PROCESS_WRITETEXT
定义这个宏表明被调试进程的TEXT节只允许一个进程对其写,当设置断点的时候如果发生错误则会返回相应的错误信息。在现有的GDB代码中没有TARGET使用这个宏。

SHELL_COMMAND_CONCAT
当GDB运行被调试程序的时候,这个宏将被添加到程序名称的前面,一般来说这个宏不需要设置。

SHELL_FILE
这个宏用来指定GDB运行被调试程序的SHELL的目录名,一般不需要设置,使用GDB/gdb/fork-child.c中默认指定的"/bin/sh"就可以。

SOLIB_ADD (filename, from_tty, targ, readsyms)
增加动态链接库的符号信息到当前GDB的符号表中,一般情况可以使用GDB/gdb/solib.c中的代码,直接包含solib.h就可以,注意前面介绍过的config/nm-linux.h包含solib.h文件。这个GDB/gdb/solib.c中的代码是可以通过向struct target_so_ops *current_target_so_ops中增加结构来扩展支持很多类型的动态库,现在支持的有aix、sunos、svr4等,关于其扩展的方式将在以后的版本中进行介绍。

SOLIB_CREATE_INFERIOR_HOOK
这个宏将在运行被调试程序后调用,一般运行一些动态连接库重定位相关的代码。跟上面一个宏一样使用GDB/gdb/solib.c中的代码,直接包含solib.h就可以。

CLEAR_SOLIB
这个宏会在GDB清除所有符号的时候调用,用来清除动态链接库的信息。跟上面一个宏一样使用GDB/gdb/solib.c中的代码,直接包含solib.h就可以。

START_INFERIOR_TRAPS_EXPECTED
当启动被调试程序的时候,一般会有两次TRAP(指被调试程序被断点中断,进入KERNEL),第一次是SHELL的执行,第二次是被调试程序的执行。如果编写的TARGET是两次TRAP则不需要进行设置,如果不是可以用这个宏进行指定。
根据现有GDB的代码,建议定义这个宏的时候对其先undef一下。举例:
#undef START_INFERIOR_TRAPS_EXPECTED
#define START_INFERIOR_TRAPS_EXPECTED 4

DEBUG_PTRACE
在GDB/gdb/infptrace.c文件137行开始也就是判断是否定义了DEBUG_PTRACE开始,使用前面定义的call_ptrace函数来替换ptrace进行调试,这样可以在call_ptrace设置断点方便调试GDB。

PTRACE_TYPE_ARG5
判断的位置跟DEBUG_PTRACE一样,但是目的不同。这个宏用来确定当前TARGET的ptrace是否有第5个参数,如果是则使用call_ptrace函数替换ptrace进行调试,而在call_ptrace对是否定义了PTRACE_TYPE_ARG5进行了检查,保证可以正常编译。
这个宏应该是定义在configure的时候生成的GDB/gdb/config.h文件中的,不需要在文件中指定。

PTRACE_ARG3_TYPE
ptrace系统调用的第三个参数的类型。如果不设置则会按照GDB/gdb/inferior.h文件中的将其定义为PTRACE_TYPE_ARG3,而这个PTRACE_TYPE_ARG3定义在configure的时候生成的GDB/gdb/config.h文件中。

PTRACE_XFER_TYPE
这个宏用来标记传输ptrace数据的类型,主要是用来帮助计算数据的长度。
注意这个宏并不是一个通用的宏,只是在部分TARGET的.c代码中进行了使用,所以如果你没使用可以不进行设置。

USE_PROC_FS
如果定义这个宏则前面介绍过的跟当前TARGET相关的.o文件中的进行寄存器信息转换(GDB内部表示方式和/proc表示方式)函数进行编译,否则不进行编译。

HAVE_OPTIONAL_PROC_FS
只有少数的TARGET使用这个宏,这个宏的作用是表明当前TARGET同时支持ptrace和procfs两种调试接口,在当前系统有/proc文件系统的时候优先使用procfs调试接口。在GDB/gdb/inftarg.c的初始化函数_initialize_inftarg中,就可以看到先会检查系统中的/proc文件系统是否正常,如果正常就不初始化ptrace调试接口。

PROC_NAME_FMT
这个宏就是在上面这个宏中介绍的检查代码中使用的,其用来标明当前/proc文件系统中下进程文件的名称。




6.GDB/gdb/ARCH-NAT.c 和 GDB/gdb/ARCH-TARGET-NAT.c
6.1.概述
这两个文件是跟TARGET相关的.c文件,主要包含一些TARGET相关的函数。而这个两个函数的关系可以说第一个可以比较方便的应用于同一ARCH的多个TARGET,所以主要是一些ARCH相关的函数,当然实际使用的时候可以编写者自己灵活掌握。


6.2.跟寄存器读取有关的函数
CORE_ADDR register_addr (int regno, CORE_ADDR blockend)
前面介绍宏REGISTER_U_ADDR的时候已经介绍过这个函数,这个函数跟宏REGISTER_U_ADDR的功能一样,根据从U_REGS_OFFSET得到的寄存器信息在user结构中的偏移blockend取得序号为regno的寄存器在user结构中的偏移并返回。
其他关于这个函数信息可以看上面REGISTER_U_ADDR的介绍内容。

void fetch_inferior_registers (int regno)
这个函数只有在定义了FETCH_INFERIOR_REGISTERS宏后才能包含在TARGET自己的库函数里。这个函数的作用是将指定序号regno的寄存器的值通过调试接口比如ptrace从被调试程序中读出,然后调用GDB/gdb/regcache.c:regcache_raw_supply函数将这个寄存器的信息存储到GDB/gdb/regcache.c:current_regcache中。如果regno为-1则表示要将所有寄存器的信息存储到GDB/gdb/regcache.c:current_regcache中。

void store_inferior_registers (int regno)
这个函数只有在定义了FETCH_INFERIOR_REGISTERS宏后才能包含在TARGET自己的库函数里。这个函数的作用从GDB/gdb/regcache.c:current_regcache中通过GDB/gdb/regcache.c:regcache_raw_collect函数读出regno指定的寄存器信息,然后通过调试接口比如ptrace写如被调试程序中。如果regno为-1则表示要将所有GDB/gdb/regcache.c:current_regcache中的寄存器的信息存储到被调试程序中。


6.3.gregset相关函数
下面介绍的四个函数是用来将GDB内部使用的格式的寄存器信息和本地OS使用的gregset格式的寄存器信息(在Linux中被定义在Linux Kernel目录include/asm-ARCH/elf.h中)进行相互转化的函数。其中gdb_gregset_t存储普通寄存器和控制寄存器信息,gdb_fpregset_t存储浮点寄存器的信息。注意有这几个函数的时候别忘记包含头文件gregset.h。

void fill_gregset (gdb_gregset_t *gregsetp, int regno)
这个函数从GDB/gdb/regcache.c:current_regcache中通过GDB/gdb/regcache.c:regcache_raw_collect函数读出regno指定的寄存器信息,然后转化成gdb_gregset_t的格式,存入gregsetp中。如果regno为-1则表示要将所有GDB/gdb/regcache.c:current_regcache中的寄存器的信息转化存储到gregsetp中。

void supply_gregset (gdb_gregset_t *gregsetp)
将gregsetp中的信息先转化成GDB内部使用的寄存器格式,然后调用GDB/gdb/regcache.c:regcache_raw_supply函数这些信息存储到GDB/gdb/regcache.c:current_regcache的每个寄存器中。

void fill_fpregset (gdb_fpregset_t *fpregsetp, int regno)
这个函数从GDB/gdb/regcache.c:current_regcache中通过GDB/gdb/regcache.c:regcache_raw_collect函数读出regno指定的浮点寄存器信息,然后转化成gdb_fpregset_t的格式,存入fpregsetp中。如果regno为-1则表示要将所有GDB/gdb/regcache.c:current_regcache中的浮点寄存器的信息转化存储到fpregsetp中。

void supply_fpregset (gdb_fpregset_t *fpregsetp)
将fpregsetp中的信息先转化成GDB内部使用的浮点寄存器格式,然后调用GDB/gdb/regcache.c:regcache_raw_supply函数这些信息存储到GDB/gdb/regcache.c:current_regcache的每个浮点寄存器中。

还有两个函数supply_fpxregset和fill_fpxregset是用来对i386扩展浮点寄存器(SSE)进行格式转化用的,因为只有i386使用,而且结构比较清晰,所以就不进行介绍了。




7.本地core文件支持
7.1.概述
前面已经介绍过,core文件的支持是通过corelow.o也就是GDB/gdb/corelow.c文件来实现的,其中的调试接口在用户用GDB的-c选项或者使用target core命令的方式分析core文件的时候将会被调用,而在调用这些调试接口的时候,将先会进行一些处理,然后对core_file_fns列表中注册的每个core_fns结构的进行扫描,选择合适的core_fns结构,调用其中的函数。也有可能是调用“移植GDB(1)”中介绍过的用set_gdbarch_regset_from_core_section注册的函数。具体整个过程将在下面进行详细的介绍。


7.2.struct core_fns
这个结构定义在文件GDB/gdb/gdbcore.h中,因为core文件有不同的类型,所以需要可以根据情况进行扩展,而core_fns结构就是其扩展接口。
下面来介绍一下core_fns结构中的每个元素:
enum bfd_flavour core_flavour;
这个变量表示注册的这组函数关联的core文件的类型,在GDB中常见的就是bfd_target_unknown_flavour和bfd_target_elf_flavour,分别代表未知和elf格式。对core_flavour和core文件类型进行比较的只有一个函数GDB/gdb/corelow.c:default_core_sniffer,具体使用在介绍core_sniffer的时候进行介绍。
int (*check_format) (bfd *);
并不是每个被指定来的当作core文件分析的文件都是BFD支持的core文件,比如一个特殊的core_fns结构,分析的core文件并不符合BFD结构中bfd_core格式,为了也能对其进行分析,则需要注册check_format函数。函数如果确定当前core文件的格式可以用当前的core_fns结构分析就非0,否则返回0。如果支持的core文件就是BFD支持的bfd_core格式,就注册函数GDB/gdb/corelow.c:default_check_format,这个函数不会进行任何判断而直接返回0。
int (*core_sniffer) (struct core_fns *, bfd *);
注册在这个函数指针的函数会根据当前core文件的BFD格式和当前的core_fns结构支持的BFD格式,如果相同就返回非0,不同就返回0。一般这个指针注册前面提到的函数GDB/gdb/corelow.c:default_core_sniffer,这个函数结构很简单,取出当前core文件的BFD格式和当前core_fns结构的core_flavour结构进行比较,相同就返回真。
注意如果前面的check_format返回了真,则要保证在这里也返回真,否则将影响GDB的运行,后面7.3会作详细解释。
void (*core_read_registers) (char *core_reg_sect, unsigned core_reg_size, int which, CORE_ADDR reg_addr);
注册在这个指针上的函数用来从core文件内容中将寄存器信息读出然后进行转化用函数regcache_raw_supply存储到current_regcache中。其参数core_reg_sect是指向core文件中寄存器信息的指针;core_reg_size是这些信息的长度;which是信息的类型,0是普通寄存器,2是浮点寄存器,3是前面提过的扩展浮点寄存器;reg_addr,指从u.u_ar0到寄存器信息的偏移,用来确定老式的core文件的section中的寄存器信息的位置,具体看注释。
一般来说这个函数的编写方式是先根据which确定core_reg_sect中信息的格式是普通类型gdb_gregset_t或者浮点类型gdb_fpregset_t,然后进行转化然后调用regcache_raw_supply将每个寄存器的信息写入current_regcache或者直接调用前面介绍过的supply_gregset进行这个工作。
struct core_fns *next;
指向下一个core_fns结构。将一个core_fns结构加入列表core_file_fns使用函数GDB/gdb/corelow.c:deprecated_add_core_fns,这个函数会将每个结构依次用next连起来。


7.3.GDB分析core文件的过程
第一步,当用户用GDB的-c选项或者使用target core命令的方式分析core文件的时候,会调用GDB/gdb/corelow.c:core_open函数。经过一些打开等的操作,就会调用GDB/bfd/format.c:bfd_check_format和GDB/gdb/corelow.c:gdb_check_format,这里的作用就是检查core文件的格式是否正确。前面的bfd_check_format函数是检查当前打开的core文件格式是否是bfd_core,而后面的函数gdb_check_format会循环调用core_file_fns列表中每个core_fns结构的check_format函数指针,只要有一个返回真就返回真,这两个函数如果都返回假则报错退出。这里调用的目的就是判断当前的core文件格式是否能被GDB处理。
第二步,经过一部分的处理,然后调用函数GDB/gdb/corelow.c:sniff_core_bfd。这个函数会先用函数GDB/gdb/gdbarch.c:gdbarch_regset_from_core_section_p检查当前的core_gdbarch是否支持regset_from_core_section,如果是则直接返回,这么做因为支持regset_from_core_section就可以通过其取得寄存器信息,不需要使用core_fns结构中的core_read_registers(下面介绍到取得寄存器信息的部分中很清晰),所以不需要再执行后面的代码,关于regset_from_core_section的信息可以见“移植GDB(1)5.5”最后部分的介绍。
后面开始的代码就是调用core_file_fns列表中每个core_fns结构的core_sniffer函数指针,如果返回真就记录下这个core_fns结构,最后进行检查,如果没有记录到任何core_fns结构,就以core_file_fns列表中第一个core_fns结构设置进去。
最后sniff_core_bfd返回,返回值存储在本地全局变量core_vec中,后面需要使用core_fns结构的时候,都将直接使用core_vec。这里也说明了core_sniffer函数指针和设置core_vec的密切关系,所以在前面介绍core_sniffer函数指针的时候说前面的check_format返回了真,则要保证在这里也返回真。
第三步,最后经过一系列的处理,调用GDB/gdb/target.h:target_fetch_registers宏,取得全部寄存器的信息,这个宏会调用current_target.to_fetch_registers,最终会调用GDB/gdb/corelow.c:get_core_registers。
在get_core_registers函数中,会先检查core_gdbarch中的regset_from_core_section是否设置以及core_vec中的core_read_registers是否可用,如果他们都不可用则会报错返回。这里再次说明了这2个指针的存在作用,都是用来从core文件中取得寄存器信息,并且只设置其中一个就可以。
然后是三次调用GDB/gdb/corelow.c:get_core_register_section,这三次调用分别是对普通寄存器,浮点寄存器和扩展浮点寄存器的读取。在get_core_register_section函数中,先根据参数name也就是存储了寄存器信息的section的名称取得了section的长度size和内容contents。然后检查core_gdbarch中的regset_from_core_section是否设置,如果设置了则调用gdbarch_regset_from_core_section根据name和size取得一个regset结构,然后调用regset结构中的函数supply_regset设置寄存器的信息,在“移植GDB(1)5.5”中已经介绍过这些函数等,所以这里不作详细介绍。最后调用core_vec->core_read_registers读取寄存器信息。函数get_core_register_section返回。
get_core_registers最后调用GDB/gdb/regcache.c:deprecated_registers_fetched函数,标记全部寄存器已经都被从被调试程序取出值了。


7.4.让TARGET支持core文件分析的方法
通过前面的介绍,可以知道如果想在TARGET中支持core文件的分析,可以使用两种方式:第一种是给gdbarch用set_gdbarch_regset_from_core_section设置regset_from_core_section;第二种是用deprecated_add_core_fns向列表core_file_fns设置core_fns结构。
第一种方式需要在gdbarch的代码中进行设置,其不能方便的支持多种core文件结构,而且如果core文件不是标准的BFD的结构bfd_core也不能用这种方式支持。
第二种方式可以在TARGET的GDB/gdb/ARCH-TARGET-NAT.c文件中的_initialize_名称类型的初始化函数(这个函数的初始化原理将在“移植GDB(3)”中进行介绍)中用deprecated_add_core_fns向列表core_file_fns设置自己定义的core_fns结构;也可以在GDB/gdb/config/ARCH/TARGET.mh中定义前面介绍过的core-aout.o文件和core-regset.o文件,这两个文件关联的格式分别是bfd_target_unknown_flavour和bfd_target_elf_flavour。实际的使用中这两种设置core_fns结构的方式可以根据需要混合使用。




8.动态连接库支持
主要的支持方式可以见前面SOLIB_ADD、SOLIB_CREATE_INFERIOR_HOOK、CLEAR_SOLIB几个宏的介绍。





----
没有一种感觉比的上回家睡觉
--
Attached file: 595833-pgdb2_v0.0.txt
[Original] [Print] [Top]
Subject: [精华] 移植GDB(1) arch和frame v0.2
Author: teawater    Posted: 2006-02-21 20:48    Length: 48,429 byte(s)
[Original] [Print] [Top]
附件中有txt版本 需要登陆后才能看见

移植GDB(1) arch和frame v0.2
teawater<teawater@gmail.com>
转载请标明来自 http://www.linuxforum.net




修改记录:
v0.2
2006-02-20,增加了set_gdbarch_regset_from_core_section的介绍。
2006-02-12,增加了set_gdbarch_cannot_fetch_register和set_gdbarch_cannot_store_register的介绍。
sigtramp frame相关部分没有进行介绍。
v0.1
2006-01-23,增加第7章frame的介绍。增加第8章dummy frame。
v0.0
2006-01-09,v0.0版本编写完成,dummy frame相关部分没有进行介绍。
2005-11-19,文档创建。




目录
1.写在前面
2.GDB/config.sub
3.GDB/gdb/configure.tgt
4.GDB/gdb/config/ARCH/TARGET.mt
5.GDB/gdb/ARCH-tdep.c GDB/gdb/ARCH-TARGET-tdep.c
5.1.综述
5.2.GDB/gdb/ARCH-tdep.c
5.3.GDB/gdb/ARCH-TARGET-tdep.c
5.4.gdbarch_register_osabi_sniffer
5.5.gdbarch设置函数
6.GDB/gdb/config/ARCH/tm-ARCH.h GDB/gdb/config/ARCH/tm-TARGET.h
6.1.综述
6.2.宏定义
7.frame
7.1.概述
7.2.struct frame_info
7.3.struct frame_id
7.4.取得当前指令对应函数frame_info信息
7.5.取得指定frame_info的frame_id
7.6.取得指定frame_info对应的函数返回地址
7.7.取得指定frame的高一级frame的函数地址
7.8.跟frame相关的gdbarch设置函数
7.9.struct frame_unwind
8.dummy frame
8.1.概述
8.2.struct value *call_function_by_hand (struct value *function, int nargs, struct value **args)
8.3.跟dummy frame相关的gdbarch设置函数




1.写在前面
本文针对GDB-6.3进行编写。

LIBBFD是一套用来分析二进制文件的库,GDB用其来对二进制文件进行分析,具体关于LIBBFD可以参见http://www.gnu.org/software/binutils/manual/bfd-2.9.1/bfd.html。

在GDB中,用来表示一个arch最核心部分就是gdbarch结构,其中包含着很多函数指针和变量。
在GDB初始化以及读入新的可执行文件的时候,将调用相应代码(个人认为编写这部分代码以及用来给gdbarch结构中函数指针赋值是arch移植的主要工作),产生gdbarch结构的变量,将相应arch相关的函数地址和变量初始化在其中,并将其地址存在current_gdbarch这个指针中。
在GDB进行调试的时候,可以通过访问current_gdbarch中的函数指针和变量来达到进行arch相关调试的目的。

还有一部分跟ARCH有关的设置,在GDB/gdb/config/ARCH/tm-TARGET.h进行一些宏定义来进行设置GDB的运行行为。
有一部分宏定义会在GDB/gdb/gdbarch.h定义成current_gdbarch定义在一起,GDB调用这部分宏实际上就调用了current_gdbarch中的内容,所以这部分的宏既可以通过设置gdbarch结构来实现也可以通过设置宏的方式来实现。

frame是stack frame,也就是栈结构,个人觉得跟frame相关的部分是gdbarch结构中最重要的部分,其在调试进行中起比较重要的作用,但是在GDB Internals中相关章节是空着的,因此我决定介绍一下frame,这样也对编写初始化gdbarch相关代码有帮助。

在以下章节,将先对移植中需要增加和修改的文件依次进行详细介绍,然后对frame进行介绍。下面是文件名中使用的缩写和GDB代码中的定义。
GDBINT GDB Internals Manual的缩写。
GDB 指GDB源文件目录。
ARCH 体系结构名称。
TARGET 体系结构下的调试目标,一般是一种操作系统,比如Linux。
bfd_architecture 定义在LIBBFD中用来描述ARCH的枚举类型。
gdb_osabi 定义在GDB中用来描述当前操作系统的ABI的枚举类型,其具体分类可以见GDBINT 9.1节。




2.GDB/config.sub
这是一个shell文件,其作用是在运行configure的时候将用户指定的target和host转化成标准的字符串格式。
这个文件可以以后面跟一个要转化的字符串的形式被调用,输出转化后的字符串,可以很方便的进行测试。

这个文件主要分为以下几个部分:
第一部分,将用户输入的结构参数分成前$basic_machine和后$os两部分。
第二部分,对$os也就是后面的操作系统相关的字段进行分析和处理。
第三部分,对$basic_machine进行分析和处理,如果是要增加一个芯片的支持,就在这里增加分析和处理,有时候也对$os进行处理。一般来说增加一个如果在ARCH名称后面没跟任何文件类型的则给$basic_machine增加一个"-unknown"。
第四部分,对$os进行进一步的分析和处理。
第五部分,根据$basic_machine对$os进行设置。
第六部分,如果$basic_machine是*-unknown的形式,则根据$os得到其$vendor,也就是制造商,然后将$basic_machine中的unknown替换成$vendor。
第七部分,打印$basic_machine$os。




3.GDB/gdb/configure.tgt
这个文件跟运行configure时设置的target有关。这个文件主要作用是根据上面文件得到的字符串得到编译需要的一些信息取得编译需要的信息。
其是在GDB/gdb/configure中被调用,在GDB/gdb/configure中先将从GDB/config.sub得到的信息存在$target中,然后将其中三部分存放在$target_cpu、$target_vendor、$target_os中。然后在其包含的文件GDB/configure.tgt进行分析。

这个文件首先根据$target_cpu来取得$gdb_target_cpu的值。如果你的CPU不象mips那样含有mipsel类的值,就不用对这段进行修改。这个值将作为ARCH的值。
后面一段根据$target来取得$gdb_target的值,其就是TARGET的值。一般来说这个值使用操作系统的名称。
最后一段根据$target来取得$gdb_osabi的值,这个值是系统ABI的值,这个值最终将被设置为GDB_OSABI_DEFAULT,也就是系统ABI的默认值。




4.GDB/gdb/config/ARCH/TARGET.mt
这个文件是设置一些编译GDB需要的跟当前TARGET有关的文件。

TDEPFILES=
这个是指定要编译的.o文件,后面介绍的跟TARGET有关的.c文件编译成的.o文件需要在这里指出,而那个.c文件使用的一些函数相关的.o文件也需要在这里指定。
DEPRECATED_TM_FILE=
这个是指定描述TARGET的头文件,一般来说这个头文件的写法为tm-TARGET.h存放在GDB/gdb/config/ARCH/目录中,具体内容将在后面介绍。注意这里跟GDBINT写的不同,这里的写法是对的,用GDBINT中介绍的方法无法正常编译。




5.GDB/gdb/ARCH-tdep.c GDB/gdb/ARCH-TARGET-tdep.c
5.1.综述
这两个文件的主要作用就是初始化gdbarch结构,第一个是跟ARCH相关的,第二个是跟这个TARGET相关的。他们的.o文件都将在TARGET.mt的TDEPFILES=中被指定。
将这两个文件分开的好处是,一个ARCH可能需要支持若干个TARGET,也就是一个芯片支持。而这些设置中有一部分是跟ARCH有关,另一部分则跟ARCH和T
在不需要针对TARGET作ARCH以外的设置的时候,可以不包含GDB/gdb/ARCH-TARGET-tdep.c文件。例如arm和mips的embed.mt都是这种情况。
在GDB中很多ARCH的这两个文件都对应了GDB/gdb/ARCH-tdep.h和GDB/gdb/ARCH-TARGET-tdep.h形式的头文件,不过这两个头文件主要是保存一些跟这个ARCH相关的私用数据,所以对其的使用可以灵活掌握。


5.2.GDB/gdb/ARCH-tdep.c
GDB/gdb/ARCH-tdep.c文件的初始化函数是void _initialize_ARCH_tdep (void),这个函数在GDB启动的时候将被调用,这里最主要的就是对gdbarch_register函数的调用,当然如果需要也可以在这个函数中增加一些命令。
void gdbarch_register (enum bfd_architecture bfd_architecture, gdbarch_init_ftype *init, gdbarch_dump_tdep_ftype *dump_tdep)
这个函数的作用就是将init函数指针和dump_tdep函数指针标记为bfd_architecture注册到全局链表gdbarch_registry上。这样当GDB读入类型为bfd_architecture的可执行文件的时候,init和dump_tdep就将被调用。

注册的init函数的作用就是初始化并设置一个gdbarch结构,并将其地址返回。编写这个函数基本上可以参考GDB/gdb/目录中其他ARCH的编写,下面我来基本介绍一下其的编写方法。
基本上头一步就是给gdbarch结构分配空间,使用gdbarch_alloc函数,其第一个参数是init函数的参数info,第二个参数tdep是一个gdbarch_tdep结构指针,其主要是存储了一些跟这个ARCH相关的私有数据,这个gdbarch_tdep结构要定义在ARCH相关的几个文件里,可以根据需要随意增加内容。在gdbarch_alloc函数中,其先分配空间,然后将tdep以及其他默认的gdbarch参数初始化到gdbarch中,然后返回。
然后就使用一些函数将这个ARCH相关的函数和数据设置到这个gdbarch上,这个将在本节最后进行详细的介绍。
最后调用“gdbarch_init_osabi (info, gdbarch);”,这个函数的作用是调用后面介绍的GDB/gdb/ARCH-TARGET-tdep.c通过gdbarch_register_osabi注册跟TARGET相关的初始化函数。

注册的dump_tdep函数主要是用来显示这个TARGET中一些私有的例如tdep的信息,其被GDB/gdb/gdbarch.c:gdbarch_dump函数调用,这个函数当执行GDB命令“maintenance print architecture”的时候被调用到,主要就是用来显示TARGET的信息。


5.3.GDB/gdb/ARCH-TARGET-tdep.c
GDB/gdb/ARCH-TARGET-tdep.c文件初始化函数是void _initialize_ARCH_TARGET_tdep (void),这个函数在GDB启动的时候将被调用,这里最主要的就是对gdbarch_register函数的调用,当然如果需要也可以在这个函数中增加一些命令。
void gdbarch_register_osabi (enum bfd_architecture arch, unsigned long machine, enum gdb_osabi osabi, void (*init_osabi)(struct gdbarch_info, struct gdbarch *))
这个函数的作用是将init_osabi函数指针标记为arch和osabi注册到全局链表gdb_osabi_handler_list上。这个函数将被前面提到过的gdbarch_init_osabi函数调用。
注册的init_osabi函数的作用就是根据操作系统的特性对前面init中分配和设置的gdbarch进行进一步的初始化。


5.4.gdbarch_register_osabi_sniffer
void gdbarch_register_osabi_sniffer (enum bfd_architecture arch, enum bfd_flavour flavour, enum gdb_osabi (*sniffer)(bfd *abfd))
这个函数一般在_initialize_ARCH_tdep或者_initialize_ARCH_TARGET_tdep中调用,其主要目的是因为二进制可执行文件的格式中对OS ABI的描述并不一定能跟gdb_osabi对应的上,所以这里就是注册一个函数来帮助GDB判断当前文件使用的gdb_osabi类型。
在这个函数中arch表示ARCH,flavour是定义在LIBBFD中的对可执行文件的描述,sniffer是要注册的函数指针。
注册的init_osabi函数要根据传递来的LIBBFD的文件指针对当前读入的二进制可执行文件进行分析,并将得到的gdb_osabi返回。


5.5.gdbarch设置函数
因为其的初始化函数和init中使用的函数属于同一系列,所以将一起在下面进行介绍,这些函数的大部分都在GDB/gdb/gdbarch.c中。有一部分函数是跟frame有关的,将在后面关于frame的章节再进行介绍。
在GDBINT 9.10介绍了一些对TARGET进行设置的宏定义,而在GDB/gdb/gdbarch.h中可以看到,下面这些函数设置在gdbarch结构中的函数和变量最终将被定义在相关的宏中来实现调用,所以GDBINT 9.10中关于宏的介绍可以作为相关gdbarch初始化函数的介绍。

void set_gdbarch_num_regs (struct gdbarch *gdbarch, int num_regs)
设置ARCH的寄存器数量num_regs到gdbarch中,调用其的宏是NUM_REGS。

void set_gdbarch_register_type (struct gdbarch *gdbarch, gdbarch_register_type_ftype *register_type)
设置一个gdbarch_register_type_ftype类型的函数指针到gdbarch中。
注册的这个函数的作用是根据其参数reg_nr指定的寄存器号码确定寄存器,将这个寄存器对应的类型返回,这些类型定义在GDB/gdb/gdbtypes.c中。

void set_gdbarch_register_name (struct gdbarch *gdbarch, gdbarch_register_name_ftype *register_name)
设置一个gdbarch_register_name_ftype类型的函数指针到gdbarch中,调用其的宏是REGISTER_NAME。
注册的这个函数的作用是根据其参数regnr指出的寄存器序号返回相应的寄存器名称。

void set_gdbarch_pc_regnum (struct gdbarch *gdbarch, int pc_regnum)
设置ARCH的PC寄存器序号pc_regnum到gdbarch中,调用其的宏是PC_REGNUM。

void set_gdbarch_sp_regnum (struct gdbarch *gdbarch, int sp_regnum)
设置ARCH的SP寄存器序号sp_regnum到gdbarch中,调用其的宏是SP_REGNUM。

void set_gdbarch_cannot_fetch_register (struct gdbarch *gdbarch, gdbarch_cannot_fetch_register_ftype *cannot_fetch_register);
设置一个gdbarch_cannot_fetch_register_ftype类型的函数指针到gdbarch中,调用其的宏是CANNOT_FETCH_REGISTER。
注册的这个函数的作用是判断其参数指定的寄存器在本地调试ptrace(关于本地调试见移植GDB(2))的时候是否是不能读取的,如果是则返回真。
因为这个项目跟本地调试的关系更密切,所以也有将宏CANNOT_FETCH_REGISTER定义到本地调试目标头文件GDB/gdb/config/ARCH/nm-TARGET.h中的方式。

void set_gdbarch_cannot_store_register (struct gdbarch *gdbarch, gdbarch_cannot_store_register_ftype *cannot_store_register)
设置一个gdbarch_cannot_store_register_ftype类型的函数指针到gdbarch中,调用其的宏是CANNOT_STORE_REGISTER。
注册的这个函数的作用是判断其参数指定的寄存器在本地调试ptrace(关于本地调试见移植GDB(2))的时候是否是不能设置的,如果是则返回真。
因为这个项目跟本地调试的关系更密切,所以也有将宏CANNOT_FETCH_REGISTER定义到本地调试目标头文件GDB/gdb/config/ARCH/nm-TARGET.h中的方式。

void set_gdbarch_fp0_regnum (struct gdbarch *gdbarch, int fp0_regnum)
设置ARCH的FP0寄存器序号fp0_regnum到gdbarch中,调用其的宏是FP0_REGNUM。

void set_gdbarch_read_pc (struct gdbarch *gdbarch, gdbarch_read_pc_ftype *read_pc)
设置一个gdbarch_read_pc_ftype类型的函数指针到gdbarch中,调用其的宏是TARGET_READ_PC和TARGET_READ_PC_P。
注册的这个函数的作用是通过用户自定义的方式来对调试进程进行PC寄存器的读取,如果已经用set_gdbarch_pc_regnum设置了PC寄存器的序号,而且PC寄存器可以通过普通方式读取到,则不需要注册这个函数。

void set_gdbarch_write_pc (struct gdbarch *gdbarch, gdbarch_write_pc_ftype *write_pc)
设置一个gdbarch_write_pc_ftype类型的函数指针到gdbarch中,调用其的宏是TARGET_WRITE_PC。
注册的这个函数的作用是通过用户自定义的方式来对调试进程进行PC寄存器的设置,如果已经用set_gdbarch_pc_regnum设置了PC寄存器的序号,而且PC寄存器可以通过普通方式设置,则不需要注册这个函数。

void set_gdbarch_read_sp (struct gdbarch *gdbarch, gdbarch_read_sp_ftype *read_sp)
设置一个gdbarch_read_sp_ftype类型的函数指针到gdbarch中,调用其的宏是TARGET_READ_SP和TARGET_READ_SP_P。
注册的这个函数的作用是通过用户自定义的方式来对调试进程进行SP寄存器的读取,如果已经用set_gdbarch_sp_regnum设置了SP寄存器的序号,而且SP寄存器可以通过普通方式读取到,则不需要注册这个函数。

void set_gdbarch_breakpoint_from_pc (struct gdbarch *gdbarch, gdbarch_breakpoint_from_pc_ftype *breakpoint_from_pc)
设置一个gdbarch_breakpoint_from_pc_ftype类型的函数指针到gdbarch中,调用其的宏是BREAKPOINT_FROM_PC。
注册的这个函数的作用是根据参数中的要插入的断点的地址pcptr,选择合适的断点指令,并且设置这个断点指令的长度在lenptr中,如果需要的话地址也需要进行修改。例如ARM就需要判断插入一个ARM断点指令还是插入一个THUMB断点指令。

void set_gdbarch_inner_than (struct gdbarch *gdbarch, gdbarch_inner_than_ftype *inner_than)
设置一个gdbarch_inner_than_ftype类型的函数指针到gdbarch中,调用其的宏是INNER_THAN。
注册的这个函数是用来比较栈地址。如果ARCH的栈是向下增长(向内存低地址)的,则注册core_addr_lessthan(lhs小于rhs返回真)。如果ARCH的栈是向上增长(向内存高地址)的,则注册core_addr_greaterthan(lhs大于rhs返回真)。

void set_gdbarch_skip_prologue (struct gdbarch *gdbarch, gdbarch_skip_prologue_ftype *skip_prologue)
设置一个gdbarch_skip_prologue_ftype类型的函数指针到gdbarch中,调用其的宏是SKIP_PROLOGUE。
注册的这个函数的参数pc是一个函数的起始地址,函数将返回这个地址对应的函数的prologue的结束地址。prologue指函数开始分配栈空间、保存寄存器等那部分代码。一般来说这个函数的编写都是先扫描符号表,一般可以通过符号表找到结束地址,如果符号表查找失败就需要反汇编来分析代码取得结束地址(个人觉得靠反汇编来来分析这部分可有可无)。具体的过程可以参见GDB/gdb目录中ARCH-tedp.c类型文件中的相关代码。

void set_gdbarch_print_insn (struct gdbarch *gdbarch, gdbarch_print_insn_ftype *print_insn)
设置一个gdbarch_print_insn_ftype类型的函数指针到gdbarch中,调用其的宏是TARGET_PRINT_INSN。
注册的这个函数将在运行GDB的disassemble命令的时候被调用,用来对指定的地址进行反汇编。整个这个函数的结构与binutils中的一个ARCH的结构完全一样,可以直接将其目录中文件拷贝到GDB目录中使用。

void set_gdbarch_software_single_step (struct gdbarch *gdbarch, gdbarch_software_single_step_ftype *software_single_step)
设置一个gdbarch_software_single_step_ftype类型的函数指针到gdbarch中,调用其的宏是SOFTWARE_SINGLE_STEP和SOFTWARE_SINGLE_STEP_P。
注册的这个函数的作用在当前指令执行的下一条指令插入(参数非0)和删除(参数为0)断点,注意如果是跳转类的指令则还需要先对指令进行反汇编,判断出下一条指令的地址。设置这个函数就表明当前TARGET不支持单步执行,需要通过这个函数提供的软单步功能来实现单步。

void set_gdbarch_return_value (struct gdbarch *gdbarch, gdbarch_return_value_ftype *return_value)
void set_gdbarch_extract_return_value (struct gdbarch *gdbarch, gdbarch_extract_return_value_ftype *extract_return_value)
void set_gdbarch_store_return_value (struct gdbarch *gdbarch, gdbarch_store_return_value_ftype *store_return_value)
这三个函数分别设置gdbarch_return_value_ftype、gdbarch_extract_return_value_ftype和gdbarch_store_return_value_ftype类型的函数指针到gdbarch中,调用后面两个注册的函数指针的宏分别是EXTRACT_RETURN_VALUE和STORE_RETURN_VALUE。
注册的第一个函数用来取得函数的返回值以及这个返回值的传递方式。这个函数指针在gdbarch初始化的时候设置的默认值是函数legacy_return_value,这个函数将调用宏EXTRACT_RETURN_VALUE和STORE_RETURN_VALUE,也就是将调用后两个注册的函数。
取得返回值的传递方式的方法为通过gdbarch_return_value_ftype类型函数参数valtype用宏TYPE_CODE取得函数返回值类型,这些类型是定义在GDB/gdb/gdbtypes.h的枚举类型type_code中,例如TYPE_CODE_INT代表返回值是int。根据这个类型就可以确定返回值的传递方式,这些方式定义在GDB/defs.h中的枚举类型return_value_convention中。这些类型是:
RETURN_VALUE_REGISTER_CONVENTION表示返回值通过一个或者多个寄存器返回。
RETURN_VALUE_STRUCT_CONVENTION表示调用函数会传递一个隐含的参数传递一个用来传递返回值的指针。
RETURN_VALUE_ABI_RETURNS_ADDRESS类似RETURN_VALUE_STRUCT_CONVENTION,ABI保证被调用函数将返回值放到一个定义明确的位置。
RETURN_VALUE_ABI_PRESERVES_ADDRESS也类似RETURN_VALUE_STRUCT_CONVENTION,ABI保证返回值的地址放到一个定义明确的位置。
一般情况下TYPE_CODE_STRUCT、TYPE_CODE_UNION和TYPE_CODE_ARRAY使用RETURN_VALUE_STRUCT_CONVENTION,其他用RETURN_VALUE_REGISTER_CONVENTION,当然具体情况要由ABI决定。
在要取得函数返回值的时候gdbarch_return_value_ftype类型函数的参数readbuf为非空,通过将regcache中的值存入这个参数就可以得到返回值。要设置函数的返回值,则参数writebuf为非空,通过将writebuf中的值存入regcache中设置返回值。
如果编写一个gdbarch_return_value_ftype函数完成了所有的功能,则不需要设置gdbarch_extract_return_value_ftype和gdbarch_store_return_value_ftype函数。如果gdbarch_return_value_ftype函数使用的默认值,则需要设置gdbarch_extract_return_value_ftype和gdbarch_store_return_value_ftype函数。这几个函数的编写可以参照GDB/gdb/arch-utils.c中的第一个参数的默认值legacy_return_value,以及GDB/gdb/目录中ARCH-tdep.c类似文件中同样类型的函数。

从这里开始的介绍的函数一般来说是TARGET相关的,所以一般情况下他们在GDB/gdb/ARCH-TARGET-tdep.c中进行注册,当然要具体情况具体分析。

void set_gdbarch_get_longjmp_target (struct gdbarch *gdbarch, gdbarch_get_longjmp_target_ftype *get_longjmp_target)
设置一个gdbarch_get_longjmp_target_ftype类型的函数指针到gdbarch中,调用其的宏是GET_LONGJMP_TARGET和GET_LONGJMP_TARGET_P。
注册的这个函数的作用是计算出longjmp要跳转到的地址并存在函数的参数pc中,如果成功返回0,失败返回非0。一般来说这个函数的编写方法是分析存放longjump跳转目标的缓存,将目标地址取出就可以。具体的过程可以参见GDB/gdb目录中ARCH-tedp.c类型文件中的相关代码。

void set_gdbarch_skip_solib_resolver (struct gdbarch *gdbarch, gdbarch_skip_solib_resolver_ftype *skip_solib_resolver)
设置一个gdbarch_skip_solib_resolver_ftype类型的函数指针到gdbarch中,调用其的宏是SKIP_SOLIB_RESOLVER,不过这个宏好像已经被从GDB的代码中去掉了,现在都是通过gdbarch_skip_solib_resolver进行调用。
注册的这个函数是用来跳过参数pc后面的动态连接器符号定义函数,如果这个函数的参数pc在动态连接器符号定义函数中的时候,返回动态连接器符号定义函数后面的地址,如果不是则返回0。一般来说这个工作需要对符号表进行分析来实现。
如果TARGET使用的是GLIBC则可以直接注册glibc_skip_solib_resolver函数,如果使用这个函数的话需要在文件中包含glibc-tdep.h头文件,同时在前面提到的GDB/gdb/config/ARCH/TARGET.mt中的TDEPFILES=项目中包含glibc-tdep.o。

void set_gdbarch_skip_trampoline_code (stru