当前位置: 首页 > news >正文

广州的网站建设公司哪家好dw网页设计实验报告

广州的网站建设公司哪家好,dw网页设计实验报告,安徽居建标准,纯静态网站模板Howto:Porting the GUN Debugger ✍【作者】#xff1a;电子科大不知名程序员 #x1f4e3;【说明】#xff1a;本文是自己在搭建mcore架构GDB时的参考的手册#xff0c;具有很强的学习指导性#xff0c;因原文档#xff08;链接#xff1a;https://www.embecosm.com/a…Howto:Porting the GUN Debugger ✍【作者】电子科大不知名程序员 【说明】本文是自己在搭建mcore架构GDB时的参考的手册具有很强的学习指导性因原文档链接https://www.embecosm.com/appnotes/ean3/embecosm-howto-gdb-porting-ean3-issue-2.html为纯英文因此自行将它翻译用于个人学习现将它分享出来供各位参考学习。 目录 Howto:Porting the GUN Debugger第一章介绍1.1. 缘由1.2. 目标受众 第二章GDB内部概述**2.1. GDB术语****2.2. 主要功能区域和数据结构****2.2.1. 二进制文件描述BFD****2.2.2. 架构描述****2.2.3. 目标操作****2.2.4. 将命令添加到GDB** **2.3. GDB体系结构规范****2.3.1.1. struct gdbarch_info****2.3.2.1. struct gdbarch_tdep** **2.3.3. 指定硬件数据表示****2.3.4. 指定硬件架构和ABI****2.3.5. 指定寄存器架构****2.3.5.1. struct gdbarch函数指定寄存器架构****2.3.5.2. struct gdbarch函数提供寄存器信息****2.3.5.3. 寄存器缓存** **2.3.6. 指定帧处理**2.3.6.1. 栈帧处理术语2.3.6.2. 前言缓存 2.3.6.3. struct gdbarch用于分析帧并根据需要进行调整的函数2.3.6.4. struct gdbarch用于访问帧数据的函数 2.4. 目标操作2.4.1. 目标层次2.4.2. 指定新目标2.4.2.1. 本机目标2.4.2.2. 远程目标 2.4.3. struct target_ops函数和变量提供信息2.4.4. struct target_ops函数控制目标连接**2.4.5. struct target_ops 访问内存和寄存器的函数****2.4.6. struct target_ops 处理断点和监视点的函数****2.4.7. struct target_ops 控制执行的函数** **2.5. 向GDB添加命令****2.6. 模拟器****2.7. 远程串行协议RSP****2.7.1. RSP客户端实现****2.7.2. RSP服务器实现** **2.8. GDB文件组织****2.9. 测试GDB****2.10. 文档****2.11.GDB中的示例流程**2.11.3. GDB的load命令2.11.4. GDB的break命令2.11.5. GDB的run命令2.11.6. GDB的backtrace命令2.11.7. GDB的continue命令在断点后 2.12. 总结 GDB移植摘要 第3章OpenRISC 1000 架构3.1.OpenRISC 1000 JTAG 接口**3.2.OpenRISC 1000 远程 JTAG 协议**3.3. 应用二进制接口ABI3.4. Or1ksimOpenRISC 1000体系结构模拟器 第4章将OpenRISC 1000架构移植至GDB4.1. BFD规范4.2. OpenRISC 1000体系结构规范**4.2.1. 创建struct gdbarch****4.2.2. OpenRISC 1000硬件数据表示****4.2.3. OpenRISC 1000架构的信息函数**4.2.4. OpenRISC 1000寄存器体系结构4.2.5. OpenRISC 1000帧处理**4.2.5.1. OpenRISC 1000分析帧的功能****4.2.5.2. 用于访问帧数据的OpenRISC 1000函数****4.2.5.3. 用于创建虚构堆栈帧的OpenRISC 1000函数****4.2.5.4. OpenRISC 1000帧嗅探器** **4.2.5.5. OpenRISC 1000基础帧嗅探器****4.2.5.6. OpenRISC 1000低级别帧嗅探器** 4.3. OpenRISC 1000 JTAG 远程目标规范4.3.1. 为 OpenRISC 1000 创建 struct target_ops4.3.2. OpenRISC 1000 目标函数和变量提供信息4.3.3. 控制连接的 OpenRISC 1000 目标函数**4.3.4. OpenRISC 1000 目标函数以访问内存和寄存器**4.3.5. OpenRISC 1000 处理断点和监视点的目标函数4.3.6. OpenRISC 1000 目标函数以控制执行4.3.7. OpenRISC 1000目标执行命令的函数4.3.8. 低级JTAG接口 4.4. OpenRISC 1000反汇编器4.5. OpenRISC 1000专用GDB命令4.5.1. info spr命令4.5.2. spr命令 第5章总结术语表 第一章介绍 本文档是对GDB[3][4][5]现有文档的补充。其目的是帮助首次将GDB移植到新体系结构的软件工程师。 本应用注释基于作者迄今为止的经验将在未来版本中进行更新。欢迎提出改进建议。 1.1. 缘由 尽管GDB项目包括一个关于其内部的100页指南但该文档主要面向那些希望开发GDB本身的人。该文档还存在三个局限性。它倾向于在详细级别上进行文档编写。单个函数描述得很好但很难获得整体情况。它是不完整的。许多最有用的部分例如关于帧解释的部分尚未编写。它往往过时。例如与UI独立输出相关的文档描述了一些不再存在的函数。因此面对首次将GDB移植到新体系结构的工程师必须通过阅读源代码并查看其他体系结构的移植方式来了解GDB的工作原理。本应用注释的作者在将OpenRISC 1000体系结构移植到GDB时经历了这个过程。本文档捕捉了这一学习经验旨在帮助其他人。 1.2. 目标受众 如果您即将开始将GDB移植到新体系结构本文档适用于您。如果在您的努力结束时您有更多了解请通过添加到本文档来提供帮助。如果您已经经历了移植过程请通过添加到本文档来帮助其他人。 第二章GDB内部概述 GDB主要涉及三个主要领域 用户界面GDB与用户的通信方式。符号方面对目标文件的分析以及将其中包含的信息映射到相应源文件。目标方面执行程序并分析其数据。 GDB对处理器有一个非常简单的视图。它有一个内存块和一个寄存器块。执行代码将其状态保存在寄存器和内存中。GDB将该信息映射到正在调试的源级程序。 将新体系结构移植到GDB意味着提供一种读取可执行文件的方法ABI的描述物理体系结构的描述以及访问正在调试的目标的操作。 GDB最常见的用途可能是调试其实际运行的体系结构。这是本地调试其中主机和目标的体系结构相同。 对于OpenRISC 1000通常在与目标分离的主机上运行GDB通常是工作站通过JTAG连接到OpenRISC 1000目标使用OpenRISC 1000远程JTAG协议。以这种方式进行远程调试是嵌入式系统中最常见的工作方法。 2.1. GDB术语 一个完整的词汇表将在本文档末尾提供。然而值得在前期解释一些关键概念。 Exec或Program执行程序或程序 一个可执行程序即可以独立运行的二进制文件。通常用户文档中使用术语“程序”而在注释和GDB内部文档中使用术语“exec”。Inferior下位 代表已运行、正在运行或将来会运行的程序或exec的GDB实体。一个下位对应一个进程或核心转储文件。地址空间 一个GDB实体可以解释地址即CORE_ADDR类型的值。下位必须至少有一个地址空间而下位可以共享一个地址空间。Thread线程 下位内的单个控制线程。 对于GDB的OpenRISC 1000移植是为了进行“裸金属”调试因此将仅具有单个地址空间和具有单个线程的下位。 2.2. 主要功能区域和数据结构 2.2.1. 二进制文件描述BFD BFD是一个允许应用程序使用相同的例程在不同的目标文件格式上操作的包。通过创建一个新的BFD后端并将其添加到库中可以支持新的目标文件格式。 BFD库后端创建了多个描述特定类型目标文件中的数据的数据结构。最终为每个个体体系结构定义了一个唯一的枚举常量类型为enum bfd_architecture。然后此常量用于访问与特定体系结构的BFD相关联的各种数据结构。 对于OpenRISC 1000的32位实现可能是COFF或ELF二进制枚举常量为bfd_arch_or32。 BFD是binutils软件包的一部分。任何打算支持GNU工具链的体系结构都必须提供binutils实现。 OpenRISC 1000得到了GNU工具链的支持。已经存在的BFD后端适用于在RTEMS或Linux操作系统中使用的32位OpenRISC 1000映像的ELF或COFF格式。 2.2.2. 架构描述 GDB描述要由其调试的任何体系结构都在struct gdbarch中。当要调试对象文件时GDB将使用其BFD中捕获的关于对象文件的信息来选择正确的struct gdbarch。 struct gdbarch中的数据既有助于符号方面的处理它还使用BFD信息又有助于目标方面的处理与帧和目标操作信息结合使用。 struct gdbarch是数据值例如整数中的字节数和执行标准操作的函数例如打印寄存器的混合体。主要的功能组包括 描述硬件体系结构详细信息的数据值。例如字节顺序、地址中的位数以及字中的位数。其中一些数据包含在BFD中struct gdbarch中引用了BFD。还有一个结构struct gdbarch_tdep用于捕获超出标准struct gdbarch范围的附加特定目标的数据。描述所有标准高级标量数据结构char、int、double等的数据值。访问和显示寄存器的函数。GDB包括“伪寄存器”概念即在体系结构内不存在但在体系结构中具有意义的寄存器。例如在OpenRISC 1000中浮点寄存器实际上与通用寄存器相同。但是可以定义一组浮点伪寄存器以便以浮点格式显示GPR。访问堆栈帧信息的函数。这包括设置“虚拟”帧以允许GDB评估函数例如使用call命令。 体系结构将需要指定struct gdbarch的大多数内容为此提供了一组函数全部以set_gdbarch_开头。对所有条目提供了默认值在少数情况下这些默认值将是合适的。 分析执行程序的堆栈帧在不同情况下需要不同的方法与每个struct gdbarch相关联的一组函数用于识别堆栈帧并分析其内容。 提供了一组实用函数用于访问struct gdbarch的成员。可以使用gdbarch_xyzg…访问由g指向的struct gdbarch的元素xyz。这将使用gdb_assert检查g是否已定义并在函数的情况下检查g-x是否不为NULL并返回g-xyz的值对于值或调用g-xyz…的结果对于函数。这样可以使用户在每次函数调用之前测试存在性确保任何错误都得到干净处理。 2.2.3. 目标操作 为了实现目标端功能需要一组操作来访问使用struct gdbarch描述的目标体系结构的程序。对于任何给定的体系结构可以使用GDB target命令指定与目标连接的多种方式。例如在OpenRISC 1000体系结构中连接可以直接到通过主机计算机的并行端口连接的JTAG接口或通过TCP/IP上的OpenRISC 1000远程JTAG协议。 这些目标操作在struct target_ops中描述。与struct gdbarch一样这包括数据和函数的混合。主要功能组包括 用于建立和关闭与目标的连接的函数。用于访问目标上的寄存器和内存的函数。用于在目标上插入和远程断点以及监视点的函数。用于启动和停止在目标上运行的程序的函数。一组描述目标特性的数据因此可以应用哪些操作。例如在检查核心转储时可以检查数据但无法执行程序。 与struct gdbarch一样为struct target_ops的值提供了默认值。在许多情况下这些默认值已经足够因此不需要提供。 2.2.4. 将命令添加到GDB GDB的命令处理旨在具有可扩展性。一组函数在cli-decode.h中定义提供了这种可扩展性。 GDB将其命令分组为多个命令列表struct cmd_list_element的列表由多个全局变量指向在cli-cmds.h中定义。其中cmdlist是所有定义命令的列表。单独的列表定义各种顶级命令的子命令。例如infolist是所有info子命令的列表。 命令还根据它们处理的领域进行分类例如提供支持的命令、检查数据的命令、文件处理等命令。这些类别由在command.h中定义的enum command_class指定。这些类别提供了在其中给出帮助的顶级类别。 2.3. GDB体系结构规范 为了为新体系结构创建GDB描述通常在源文件arch-tdep.c中按照约定定义了一个名为_initialize_arch_tdep的全局函数。在OpenRISC 1000的情况下此函数称为_initialize_or1k_tdep位于文件or1k-tdep.c中。 包含实现_initialize_arch_tdep函数的对象文件的规范在GDB configure.tgt文件中指定该文件包括一个大型的case语句与configure命令的–target选项进行模式匹配。 在_initialize_arch_tdep函数内部通过调用gdbarch_register创建新的struct gdbarch void gdbarch_register (enum bfd_architecture architecture,gdbarch_init_ftype *init_func,gdbarch_dump_tdep_ftype *tdep_dump_func);例如_initialize_or1k_tdep通过调用以下方式为32位OpenRISC 1000架构创建其架构 gdbarch_register (bfd_arch_or32, or1k_gdbarch_init, or1k_dump_tdep);架构枚举将标识此架构的唯一BFD参见第2.2.1节。init_func被调用以创建并返回新的struct gdbarch参见第2.3节。tdep_dump_func是一个将转储与该体系结构相关的目标特定详细信息的函数也在第2.3节中描述。 gdbarch_register调用参见第2.2节指定了一个函数该函数将为特定的BFD体系结构定义struct gdbarch。 struct gdbarch gdbarch_init_func (struct gdbarch_info info,struct gdbarch_list *arches);例如在OpenRISC 1000体系结构的情况下初始化函数是or1k_gdbarch_init。 [提示] 按照惯例GDB中所有特定目标的函数和全局变量都以唯一于该体系结构的字符串开头。这有助于在使用C时避免命名空间污染。因此所有特定于MIPS的函数以mips_开头特定于ARM的函数以arm_开头依此类推。对于OpenRISC 1000所有特定目标的函数和全局变量都以or1k_开头。 2.3.1. 查找现有体系结构 架构初始化函数的第一个参数是一个包含有关此体系结构的所有已知信息的struct gdbarch_info从提供给gdbarch_register的BFD枚举中推断出。第二个参数是GDB中当前定义的体系结构列表。 查找是使用gdbarch_list_lookup_by_info完成的。它接受现有体系结构的列表和struct gdbarch_info可能已更新作为参数并返回找到的第一个匹配体系结构如果没有找到则返回NULL。如果找到体系结构初始化函数可以完成将找到的体系结构作为结果返回。 2.3.1.1. struct gdbarch_info struct gdbarch_info具有以下组件 struct gdbarch_info {const struct bfd_arch_info *bfd_arch_info;int byte_order;bfd *abfd;struct gdbarch_tdep_info *tdep_info;enum gdb_osabi osabi;const struct target_desc *target_desc; };bfd_arch_info包含有关体系结构的关键详细信息。byte_order是指示字节顺序的枚举。abfd是指向完整BFD的指针。tdep_info是额外的自定义目标特定信息。gdb_osabi是一个枚举标识该体系结构使用的操作系统特定ABI如果有。target_desc是一组名称-值对提供有关在此目标中使用的寄存器的信息。 在调用struct gdbarch初始化函数时并非所有字段都提供了——仅提供了可以从BFD推断出的字段。struct gdbarch_info用作具有现有体系结构列表的查找键初始化函数的第二个参数以查看是否已存在合适的体系结构。在此查找之前可能会添加tdep_info、osabi和target_desc字段以细化搜索。 2.3.2. 创建新体系结构 如果未找到体系结构则必须通过使用提供的struct gdbarch_info和struct gdbarch_tdep中的任何附加自定义目标特定信息来调用gdbarch_alloc来创建新体系结构。 然后新创建的struct gdbarch必须进行填充。虽然有默认值但在大多数情况下它们不是所需的。对于每个元素X都有一个相应的访问函数来设置该元素的值即set_gdbarch_X。 以下各节标识了以这种方式应设置的主要元素。这不是完整列表但代表了通常必须为新体系结构指定的函数和元素。许多函数在头文件gdbarch.h中进行描述许多可以在GDB Internals文档[4]中找到。 2.3.2.1. struct gdbarch_tdep struct gdbarch *gdbarch_alloc (const struct gdbarch_info *info,struct gdbarch_tdep *tdep);struct gdbarch_tdep在GDB中未定义——如果需要保存标准struct gdbarch不包含的自定义目标信息则用户必须定义此struct。例如在OpenRISC 1000架构中它用于保存目标中可用的匹配点数量以及其他信息。如果没有其他目标特定信息可以将其设置为NULL。 2.3.3. 指定硬件数据表示 struct gdbarch中的一组值定义了在体系结构内部如何表示不同的数据类型。 short_bitC/C short变量中的位数。默认值是2*TARGET_CHAR_BIT。TARGET_CHAR_BIT是一个已定义的常量如果未显式设置则默认为8。int_bit、long_bit、long_long_bit、float_bit、double_bit、long_double_bit这些与short类似分别表示对应类型的C/C变量中的位数。对于int、long和float默认值是4TARGET_CHAR_BIT对于long long、double和long double默认值也是4TARGET_CHAR_BIT。ptr_bitC/C指针中的位数。默认值是4*TARGET_CHAR_BIT。addr_bitC/C地址中的位数。几乎总是与指针中的位数相同但有一小部分体系结构的指针无法到达所有地址。默认值是4*TARGET_CHAR_BIT。float_format、double_format和long_double_format这些指向一个C结构数组每个都有一个字节序定义了每个浮点类型的格式。预定义了多个这样的数组。它们又基于由库libiberty定义的一组标准类型。char_signed如果char要被视为有符号则为1如果char要被视为无符号则为0。默认值为-1未定义因此应始终设置它。 2.3.4. 指定硬件架构和ABI struct gdbarch的一组函数成员定义了体系结构及其ABI的各个方面。对于其中的一些函数提供了默认值适用于大多数体系结构。 return_value此函数确定给定数据类型的返回约定。例如在OpenRISC 1000上结构/联合和大于32位的标量作为引用返回而小标量则在GPR 11中返回。此函数应始终定义。 breakpoint_from_pc在内存中的特定位置时返回用于断点的指令。对于具有可变长度指令的体系结构断点指令的选择可能取决于程序计数器处指令的长度。返回指令序列及其长度。 默认值为NULL未定义。如果GDB要支持此体系结构的断点则此函数应始终定义。 adjust_breakpoint_address某些体系结构根本不允许在所有点放置断点。给定一个程序计数器此函数返回可以放置断点的地址。默认值为NULL未定义。只有对于无法在所有程序计数器位置接受断点的体系结构才需要定义此函数。 memory_insert_breakpoint和memory_remove_breakpoint这些函数插入或删除基于内存的也称为软件断点。默认值default_memory_insert_breakpoint和default_memory_remove_breakpoint适用于大多数体系结构因此在大多数情况下无需定义这些函数。 decr_pc_after_break某些体系结构要求在断点后将程序计数器减小以允许在恢复时执行断点的指令。此函数返回要减小地址的字节数。默认值为NULL未定义这意味着程序计数器保持不变。只有在需要此功能时才需要定义此函数。 实际上此函数仅对最简单的体系结构有用。它仅适用于软件断点不适用于监视点或硬件断点。通常会在目标的to_wait和to_resume函数中根据需要调整程序计数器参见第2.4节。 single_step_through_delay如果目标正在执行延迟槽并且需要在指令完成之前进行进一步的单步操作则返回1。默认值为NULL未定义。如果目标具有延迟槽则应实现此函数。 print_insn反汇编一条指令并打印。默认值为NULL未定义。如果要支持代码的反汇编则应定义此函数。 反汇编是binutils库所需的功能。此函数在opcodes子目录中定义。如果binutils已经进行了移植则可能已经存在一个合适的实现。 2.3.5. 指定寄存器架构 GDB将寄存器视为一组成员编号从0递增的集合。该集合的第一部分对应于真实的物理寄存器第二部分对应于任何“伪寄存器”。伪寄存器没有独立的物理存在但对体系结构内的信息表示很有用。例如OpenRISC 1000体系结构最多有32个通用寄存器通常表示为32位或64位整数。但是定义一组伪寄存器可能是方便的以显示将GPR表示为浮点寄存器的情况。 对于任何体系结构实施者将决定从硬件到GDB寄存器编号的映射。与真实硬件对应的寄存器称为原始寄存器其余寄存器称为伪寄存器。总的寄存器集原始和伪称为烹饪寄存器集。 2.3.5.1. struct gdbarch函数指定寄存器架构 这些函数指定体系结构中寄存器的数量和类型。 read_pc和write_pc读取程序计数器的函数。默认值为NULL没有可用的函数。但是如果程序计数器只是一个普通寄存器则可以在struct gdbarch中指定它见下文的pc_regnum并将使用标准例程来读取或写入它。因此只有在程序计数器不是普通寄存器时才需要指定此函数。pseudo_register_read和pseudo_register_write如果有伪寄存器则应定义这些函数参见第2.2.2节和第2.3.5.3节了解有关伪寄存器的更多信息。默认值为NULL。num_regs和num_pseudo_regs定义真实和伪寄存器的数量。它们默认为-1未定义应始终显式定义。sp_regnum、pc_regnum、ps_regnum和fp0_regnum指定保存堆栈指针、程序计数器、处理器状态和第一个浮点寄存器的寄存器。除了第一个浮点寄存器默认为0外其余寄存器默认为-1未定义。它们可以是真实或伪寄存器。必须始终定义sp_regnum。如果未定义pc_regnum则必须定义函数read_pc和write_pc见上文。如果未定义ps_regnum则GDB用户将无法使用$ps变量。如果目标支持浮点运算则无需fp0_regnum。 2.3.5.2. struct gdbarch函数提供寄存器信息 这些函数返回有关寄存器的信息。 register_name此函数应将寄存器编号原始或伪转换为寄存器名称作为C char *。这既用于确定寄存器的输出名称也用于解析输入时使用的任何寄存器名称的含义。例如在OpenRISC 1000上GDB寄存器0-31是通用寄存器寄存器32是程序计数器寄存器33是监视寄存器它们映射到字符串gpr00到gpr31、“pc和sr”。这意味着GDB命令print $gpr5应打印OR1K通用寄存器5的值。此函数的默认值为NULL。应始终定义它。 从历史上看GDB总是有一个帧指针寄存器的概念可以通过GDB变量 f p 访问。该概念现已弃用认识到并非所有体系结构都有帧指针。但是如果体系结构确实具有帧指针寄存器并且定义了一个名为 f p 的寄存器或伪寄存器则该寄存器将用作 fp访问。该概念现已弃用认识到并非所有体系结构都有帧指针。但是如果体系结构确实具有帧指针寄存器并且定义了一个名为fp的寄存器或伪寄存器则该寄存器将用作 fp访问。该概念现已弃用认识到并非所有体系结构都有帧指针。但是如果体系结构确实具有帧指针寄存器并且定义了一个名为fp的寄存器或伪寄存器则该寄存器将用作fp变量的值。 register_type给定寄存器编号此函数标识其可能包含的数据类型指定为struct type。GDB允许创建任意类型但还提供了一些内置类型builtin_type_void、builtin_type_int32等以及从这些类型派生类型的函数。通常程序计数器将具有“指向函数”的类型指向代码帧指针和堆栈指针将具有“指向void”的类型它们指向堆栈上的数据而所有其他整数寄存器将具有32位或64位整数类型。这些信息在显示寄存器信息时引导格式化。默认值为NULL表示在显示寄存器时没有可用于指导格式化的信息。 print_registers_info、print_float_info和print_vector_info分别定义这些函数以为GDB info registers、info float和info vector命令提供输出。默认值为NULL未定义表示不提供信息。如果目标分别支持浮点或矢量操作请定义每个函数。 register_reggroup_pGDB将寄存器分组到不同的类别通用、矢量、浮点等。给定一个寄存器和组如果寄存器在该组中则返回1true否则返回0。默认值为函数default_register_reggroup_p该函数将根据寄存器的类型参见上文的register_type函数执行合理的工作为通用寄存器、浮点寄存器、矢量寄存器和原始即非伪寄存器提供不同的组。 2.3.5.3. 寄存器缓存 寄存器的缓存用于在寄存器值不可能已更改的情况下多次访问并重新分析目标。 GDB提供了struct regcache与特定的struct gdbarch关联以保存原始寄存器的缓存值。提供了一组函数来访问原始寄存器其名称中带有raw和完整的烹饪寄存器其名称中带有cooked。提供了函数来确保寄存器缓存与目标中实际寄存器的值保持同步。 通过struct regcache例程访问寄存器将确保在需要时调用适当的struct gdbarch函数来访问底层的目标体系结构。通常用户应该使用“cooked”函数因为这些函数将根据需要自动映射到“raw”函数。 两个关键函数是regcache_cooked_read和regcache_cooked_write它们读取或将寄存器的值写入字节缓冲区类型为gdb_byte *。为方便起见还提供了包装函数regcache_cooked_read_signed、regcache_cooked_read_unsigned、regcache_cooked_write_signed和regcache_cooked_write_unsigned它们读取或写入值并根据需要进行值的转换。 2.3.6. 指定帧处理 GDB需要了解本地自动变量存储在哪个堆栈上。包含函数调用的所有本地变量的堆栈区域称为该函数的堆栈帧或俗称为“帧”。反过来调用该函数的函数将具有其堆栈帧依此类推一直返回到已调用的函数链。 几乎所有体系结构都有一个专用于指向堆栈末尾堆栈指针的寄存器。许多体系结构还有第二个寄存器指向当前活动堆栈帧的起始位置帧指针。体系结构的具体安排是ABI的关键部分。 以下是一个说明的图表。这里是一个计算阶乘的简单程序 #include stdio.hint fact(int n) {if (0 n){return 1;}else{return n * fact(n - 1);} }int main() {int i;for (i 0; i 10; i){int f fact(i);printf(%d! %d\n, i, f);} }考虑代码到达第6行时的堆栈状态此时主程序已调用fact(3)。函数调用链将是main、fact(3)、fact(2)、fact(1)和fact(0)。在此示例中堆栈是降序的与OpenRISC 1000 ABI使用的方式相同。堆栈指针SP位于堆栈末尾最低地址帧指针FP位于当前堆栈帧中的最高地址。图2.1显示了堆栈的外观。 图2.1 一个示例堆栈帧 在每个堆栈帧中相对于堆栈指针的偏移为0的位置是上一帧的帧指针而相对于堆栈指针的偏移为4这是一个32位架构的示例的位置是返回地址。局部变量从帧指针开始索引使用负索引。在函数fact中相对于帧指针的偏移为-4的位置是参数n。在主函数中相对于帧指针的偏移为-4的位置是局部变量i而相对于帧指针的偏移为-8的位置是局部变量f。 [注意] 注意这只是一个用于说明的简化示例。对于这样简单的函数优化良好的编译器可能不会将任何内容放在堆栈上。实际上它们可能会消除递归和对堆栈的使用 在检查堆栈时很容易混淆。GDB在整个过程中都使用严格的术语。当前执行的函数的堆栈帧编号为零。在这个例子中帧0是对fact0的调用的堆栈帧。其调用函数的堆栈帧在这种情况下是fact1)的编号为1依此类推一直返回到调用链中。 描述帧的主要GDB数据结构是struct frame_info。它不是直接使用的而是通过其访问器函数使用。struct frame_info包括有关帧中寄存器的信息以及指向与该帧关联的函数代码的指针。整个堆栈被表示为struct frame_info的链表。 2.3.6.1. 栈帧处理术语 在引用栈帧时很容易感到困惑。GDB使用一些精确的术语。 此栈帧THIS frame是当前正在考虑的栈帧。下一个栈帧NEXT frame有时也称为内部或较新的栈帧是由此栈帧调用的函数的栈帧。前一个栈帧PREVIOUS frame有时也称为外部或较旧的栈帧是调用此栈帧的函数的栈帧。 因此在图2.1的示例中如果此栈帧是#3对fact3的调用则下一个栈帧是栈帧#2对fact2的调用前一个栈帧是栈帧#4对main的调用。 最内部的栈帧是当前执行函数的栈帧或者在程序停止时例如在对fact0的调用中间。它始终编号为栈帧0。 栈帧的基址是下一个栈帧的开始之前的地址。对于下降堆栈这将是最低地址对于上升堆栈这将是栈帧中的最高地址。 通常GDB用于分析堆栈的函数通常会提供一个指向下一个栈帧的指针以确定有关此栈帧的信息。有关此栈帧的信息包括有关前一个栈帧的寄存器存储在此栈帧中的数据。在此示例中前一个栈帧的帧指针存储在此栈帧的堆栈指针的偏移0处。 通过将一个函数指针传递给下一个栈帧来确定有关此栈帧的信息的过程称为展开。在此过程中涉及的GDB函数通常在其名称中包含“unwind”。 确定应在struct frame_info中放置的信息的目标分析过程称为嗅探。执行此操作的函数通常称为嗅探器并且通常在其名称中包含“sniffer”。为了提取特定帧的所有信息可能需要超过一个嗅探器。 由于许多函数使用下一个栈帧存在一个关于寻址最内部栈帧的问题——它没有下一个栈帧。为解决这个问题GDB创建了一个虚拟的栈帧“#-1”称为哨兵栈帧。 2.3.6.2. 前言缓存 所有栈帧嗅探函数通常会检查相应函数开头的代码以确定寄存器的状态。ABI将在每个函数的开始处保存旧值并设置关键寄存器的新值这称为函数前言。 对于特定的栈帧此数据不会更改因此所有标准展开函数除了将下一个栈帧的指针作为其第一个参数之外还会将前言缓存的指针作为其第二个参数。这可用于存储与特定栈帧相关的值以便在涉及相同栈帧的后续调用中重复使用。 用户需要定义所使用的结构它是void *指针并安排存储的分配和释放。但是对于一般用途GDB提供了struct trad_frame_cache其中包含一组访问例程。此结构保存了此栈帧的堆栈和代码地址栈帧的基址指向下一个栈帧的struct frame_info的指针以及前一个栈帧的寄存器在此栈帧中的位置的详细信息。 通常第一次使用下一个栈帧调用任何嗅探器函数时此栈帧的前言嗅探器将为NULL。嗅探器将分析栈帧分配前言缓存结构并将其填充。随后使用相同下一个栈帧调用的调用将传递此前言缓存以便可以在无需额外分析的情况下返回数据。 2.3.6.3. struct gdbarch用于分析帧并根据需要进行调整的函数 skip_prologue函数的序言是函数开头的代码用于设置堆栈帧、保存返回地址等。该函数在函数的程序计数器在函数的序言中时跳过该序言。对于现代优化编译器这可能是一个相当棘手的任务。但是如果所需的信息在二进制文件中作为DWARF2调试信息则该任务可能更加容易。默认值为NULL未定义。此函数应始终提供但如果可用DWARF2调试信息可以利用它。 inner_than给定两个帧或堆栈指针如果第一个表示“内部”堆栈帧则返回1true否则返回0false。这用于确定目标堆栈帧是上升还是下降。参见第2.3.6节中关于“内部”帧的解释。该函数的默认值为NULL应始终定义。但是对于几乎所有体系结构都可以使用内置函数之一core_addr_lessthan用于下降堆栈或core_addr_greaterthan用于上升堆栈。 frame_align体系结构可能对其帧的对齐方式有约束。给定堆栈指针的建议地址此函数返回适当对齐的地址通过扩展堆栈帧。默认值为NULL未定义。对于可能发生堆栈失调的任何体系结构应定义此函数。辅助函数align_down用于下降堆栈和align_up用于上升堆栈将有助于实现此函数。 frame_red_zone_size某些ABI在堆栈末尾保留空间供无序言或结尾或异常处理程序使用OpenRISC 1000属于此类。这称为红区AMD术语。默认值为0。如果体系结构具有这样的红区则设置此字段。 2.3.6.4. struct gdbarch用于访问帧数据的函数 这些函数提供对堆栈帧中关键寄存器和参数的访问。 unwind_pc 和 unwind_sp给定指向“THIS”堆栈帧的指针请参阅第2.3.6节以了解帧的表示方式分别返回“PREVIOUS”帧中的程序计数器和堆栈指针的值即调用此函数的函数的帧。 frame_num_args给定指向“THIS”堆栈帧的指针请参阅第2.3.6节以了解帧的表示方式返回正在传递的参数数量如果不知道则返回-1。默认值为NULL未定义在这种情况下任何堆栈帧上传递的参数数量始终是未知的。对于许多体系结构这将是一个合适的默认值。 2.3.6.5. struct gdbarch Functions Creating Dummy Frames GDB可以调用目标代码中的函数例如使用call或print命令。这些函数可能会被断点中断如果函数碰到断点像backtrace这样的命令仍然能够正确工作是至关重要的。 这通过使堆栈看起来好像函数是从GDB之前停止的地方调用一样来实现。这要求GDB能够设置适用于这种函数调用的堆栈帧。 以下函数提供了设置这种“虚拟”堆栈帧的功能。 push_dummy_call: 此函数为即将被调用的函数设置一个虚拟堆栈帧。push_dummy_call会接收要传递的参数并必须根据ABI将它们复制到寄存器或适当地推送到堆栈上。然后GDB将控制传递给目标代码中函数的地址并且它将找到堆栈和寄存器的设置就像预期的那样。 此函数的默认值是NULL未定义。如果未定义此函数那么GDB将不允许用户在被调试的目标中调用函数。 unwind_dummy_id: 这是push_dummy_call的反函数它在使用虚拟堆栈帧评估函数调用后恢复堆栈和帧指针。默认值为NULL未定义。如果定义了push_dummy_call则还应定义此函数。 push_dummy_code: 如果未定义此函数其默认值为NULL虚拟调用将使用目标的入口点作为其返回地址。在那里将设置一个临时断点因此该位置必须是可写的并且有空间容纳断点。 可能这个默认值不太适用。它可能不可写可能在ROM中或者ABI可能要求在调用返回之前执行代码以展开堆栈然后才遇到断点。 如果有这样的情况那么应该定义push_dummy_code以在堆栈末尾推送指令序列该虚拟调用应该返回到该序列。 注意 这要求堆栈中的代码可以执行。某些哈佛架构可能不允许这样做。 2.3.6.6. 分析堆栈帧嗅探器 当程序停止时GDB需要使用适当的嗅探器构建表示堆栈状态的struct frame_info链。 每个架构都需要适当的嗅探器但它们不会形成struct gdbarch的条目因为可能需要多个嗅探器而且一个嗅探器可能适用于多个struct gdbarch。相反使用以下函数将嗅探器与架构关联起来。 frame_unwind_append_sniffer: 用于在给定指向NEXT堆栈帧的指针时添加新的嗅探器以分析此堆栈帧。frame_base_append_sniffer: 用于添加新的嗅探器该嗅探器可以确定堆栈帧的基址的信息。frame_base_set_default: 用于指定默认的基址嗅探器。 这些函数都需要struct gdbarch的引用因此它们与特定架构相关联。通常在struct gdbarch初始化函数中调用它们此时struct gdbarch已经设置好。除非设置了默认值否则将首先尝试最近添加的嗅探器。 主要的帧展开嗅探器由frame_unwind_append_sniffer设置返回一个结构指定一组嗅探函数 struct frame_unwind {enum frame_type type;frame_this_id_ftype *this_id;frame_prev_register_ftype *prev_register;const struct frame_data *unwind_data;frame_sniffer_ftype *sniffer;frame_prev_pc_ftype *prev_pc;frame_dealloc_cache_ftype *dealloc_cache; };type字段指示此嗅探器可以处理的堆栈帧的类型正常、虚拟参见第2.3节中的push_dummy_call、信号处理程序或哨兵。信号处理程序有时会为了效率而有自己的简化堆栈结构因此可能需要它们自己的处理程序。 unwind_data保存可能与特定类型的堆栈帧相关的附加信息。例如它可能保存信号处理程序帧的附加信息。 其余字段定义了在给定指向NEXT堆栈帧的指针时产生不同类型信息的函数。不需要提供所有函数。如果一个条目为NULL则将尝试下一个嗅探器。 this_id: 确定THIS堆栈帧的堆栈指针和函数代码入口点。prev_register: 确定在THIS堆栈帧中的哪个位置存储了PREVIOUS堆栈帧的寄存器的值。sniffer: 查看THIS帧的寄存器以确定这是否是适当的展开器。prev_pc: 确定THIS堆栈帧的程序计数器。只在程序计数器不是普通寄存器时需要参见第2.3节中的prev_pc。dealloc_cache: 释放与此帧的prologue缓存关联的任何附加内存参见第2.3.6.2。 通常只有对于自定义嗅探器需要定义this_id和prev_register函数。 基础嗅探器要简单得多。它是一个struct frame_base它引用相应的struct frame_unwind并提供在帧内生成各种地址的函数。 struct frame_base {const struct frame_unwind *unwind;frame_this_base_ftype *this_base;frame_this_locals_ftype *this_locals;frame_this_args_ftype *this_args; };所有这些函数都接受指向NEXT帧的指针作为参数。this_base返回THIS帧的基址this_locals返回THIS帧中本地变量的基址this_args返回此帧中函数参数的基址。 值得注意的是如果无法以其他方式确定例如存在一个名为“fp”的寄存器那么this_base函数的结果将用作GDB中帧指针变量$fp的值。 2.4. 目标操作 与目标的通信归结为一组目标操作。这些操作存储在struct target_ops中与描述目标行为的标志一起。struct target_ops元素在target.h中定义和记录。以下部分描述了其中最重要的一些函数。 2.4.1. 目标层次 GDB有几种不同类型的目标可执行文件、核心转储、正在执行的进程等。在任何时候GDB可能有多组目标操作正在使用。例如用于执行进程的目标操作可以运行代码的操作可能与检查核心转储时使用的操作不同。 GDB知道的所有目标都保存在一个堆栈中。GDB通过堆栈向下遍历以找到适用的目标操作集。堆栈被组织为一系列递减重要性的层次结构线程的目标操作然后是适用于进程的目标操作用于下载远程目标的目标操作用于核心转储的目标操作用于可执行文件的目标操作最后是虚拟目标的目标操作。因此当GDB调试正在运行的进程时它将始终选择来自process_stratum的目标操作如果可用的话而不是来自file_stratum的目标操作即使file_stratum的目标操作是最近推入堆栈的。 在任何特定时间都有一个当前目标保存在全局变量current_target中。这永远不会为NULL - 如果没有其他目标可用它将指向虚拟目标。 target.h定义了一组方便的宏用于访问current_target中的函数和值。因此current_target-to_xyz可以被访问为target_xyz。 2.4.2. 指定新目标 一些目标struct target_ops中的目标操作集是由GDB自动设置的 - 这些包括驱动模拟器的操作请参见第2.6节和驱动GDB远程串行协议RSP的操作请参见第2.7节。 其他目标必须由实现者显式设置使用add_target函数。其中最常见的是用于本机调试的本机目标。设置非本机目标的情况较少例如用于OpenRISC 1000的JTAG目标[1]。 2.4.2.1. 本机目标 通过在arch-os-nat.c源文件中为体系结构arch和操作系统os定义_initialize_arch_os_nat函数可以创建新的本机目标。在config/arch/os.mh文件中创建用于从源文件生成二进制文件的代码片段其中包含在config/arch/nm-os.h中的头部该头部在构建时将链接到nm.h。_initialize_函数应创建一个新的struct target_ops并调用add_target将此目标添加到可用目标的列表中。 对于新的本机目标有可重用的标准实现只需进行一两个更改。例如函数linux_trad_target返回适用于大多数Linux本机目标的struct target_ops。通常只需要更改描述字段和用于获取和存储寄存器的函数。 2.4.2.2. 远程目标 对于新的远程目标流程稍微简单。应将源文件添加到configure.tgt中就像对体系结构描述一样请参见第2.3节。在源文件内定义一个新的函数_initialize_remote_arch以实现新的远程目标arch。 对于新的远程目标remote.c中用于实现RSP的定义提供了一个良好的起点。 2.4.3. struct target_ops函数和变量提供信息 这些函数和变量提供关于目标的信息。第一组标识目标的名称并为用户提供帮助信息。 to_shortname。这个字符串是目标的名称用于GDB的target命令。将to_shortname设置为foo意味着目标foo将连接到该目标调用to_open为此目标见下文。to_longname。给出目标类型的简要描述的字符串。这将与info target命令一起打印另请参见下文的to_files_info。to_doc。这是此目标的帮助文本。如果目标的简称是foo那么help target命令将打印target foo后面是这个帮助文本的第一句话。命令help target foo将打印出完整的文本。to_files_info。此函数为info target命令提供附加信息。 第二组变量提供有关目标当前状态的信息。 to_stratum。一个枚举常量指示struct target_ops属于哪个stratum。to_has_all_memory。布尔值指示目标是否包含整个内存还是只包含其中的一部分。如果只有部分内存则可以通过堆栈中的其他目标满足失败的内存请求。to_has_memory。布尔值指示目标是否具有内存虚拟目标没有。to_has_stack。布尔值指示目标是否有堆栈。目标文件没有核心转储和可执行线程/进程有。to_has_registers。布尔值指示目标是否有寄存器。目标文件没有核心转储和可执行线程/进程有。to_has_execution。布尔值指示目标当前是否正在执行。对于某些目标这与它们是否能够执行是相同的。但是一些远程目标可能处于未执行状态直到调用create_inferior或attach。 2.4.4. struct target_ops函数控制目标连接 这些函数控制与目标的连接。对于远程目标这可能意味着使用TCP/IP等协议建立和拆除连接。对于本机目标这些函数将更关心设置描述状态的标志。 to_open。这个函数由GDB的target命令调用。除了调用的目标名称之外还将任何其他参数传递给此函数。to_open应该与目标建立通信。它应该设置目标的状态例如它当前是否正在运行并适当地初始化数据结构。如果目标当前没有运行这个函数不应该启动目标运行 - 这是由GDB的run命令调用的函数to_create_inferior和to_resume的工作。to_xclose和to_close。这两个函数都应该关闭远程连接。to_close是遗留函数。新的实现应该使用to_xclose它还应该释放为此目标分配的任何内存。to_attach。对于可以在没有连接调试器的情况下运行的目标此函数将调试器连接到运行中的目标它应该首先被打开。to_detach。从目标分离的函数使其继续运行。to_disconnect。这类似于to_detach但不费力地通知目标调试器正在分离。它应该只是断开与目标的连接。to_terminal_inferior。此函数将目标的终端I/O连接到本地终端。此功能在远程目标中并非总是可用。to_rcmd。如果目标能够运行命令则此函数请求在目标上运行该命令。这对于远程目标最相关。 2.4.5. struct target_ops 访问内存和寄存器的函数 这些函数在目标寄存器和内存之间传输数据。 to_fetch_registers 和 to_store_registers。用于将目标的值传递给寄存器缓存并使用寄存器缓存中的值设置目标寄存器的函数。to_prepare_to_store。在存储寄存器之前调用此函数以设置所需的任何附加信息。在大多数情况下它将是一个空函数。to_load。将文件加载到目标中。对于大多数实现通用函数 generic_load 会重用用于内存访问的其他目标操作因此非常合适。to_xfer_partial。这个函数是一个通用函数用于在目标和目标之间传输数据。它的最重要的功能通常是唯一实际实现的功能是从目标内存中加载和存储数据。 2.4.6. struct target_ops 处理断点和监视点的函数 对于所有目标GDB都可以通过在目标中插入代码来在软件中实现断点和写入访问监视点。但是许多目标为这些功能提供了更为高效的硬件支持而且还可能实现读取访问监视点。 struct target_ops 中的这些函数提供了一种访问此类功能如果可用的机制。 to_insert_breakpoint 和 to_remove_breakpoint。这些函数在目标上插入和删除断点。它们可以选择使用硬件或软件断点。然而如果插入函数允许使用硬件断点则GDB命令 set breakpoint auto-hw off 将不起作用。to_can_use_hw_breakpoint。如果目标可以设置硬件断点或监视点则此函数应返回1true否则返回0。该函数被传递一个枚举值指示正在查询监视点还是断点并应使用有关当前正在使用的硬件断点/监视点数量的信息来确定是否可以设置断点/监视点。to_insert_hw_breakpoint 和 to_remove_hw_breakpoint。插入和删除硬件断点的函数。如果没有硬件断点可用则返回失败的结果。to_insert_watchpoint 和 to_remove_watchpoint。插入和删除监视点的函数。to_stopped_by_watchpoint。如果最后一次停止是由监视点引起的则此函数返回1true。to_stopped_data_address。如果最后一次停止是由监视点引起的则此函数返回触发监视点的数据的地址。 2.4.7. struct target_ops 控制执行的函数 对于能够执行的目标这些函数提供了启动和停止执行的机制。 to_resume。告诉目标再次开始运行或第一次运行的函数。to_wait。等待目标将控制返回给调试器的函数。通常情况下当目标完成执行或命中断点时控制将返回。如果连接中断例如通过 ctrl-C它也可能发生。to_stop。停止目标的函数——每当需要中断目标时使用例如通过 ctrl-C。to_kill。终止与目标的连接。即使与目标的连接中断这也应该有效。to_create_inferior。对于可以执行的目标这将初始化一个准备运行的程序。它由GDB的 run 命令调用随后将调用 to_resume 来开始执行。to_mourn_inferior。在目标执行结束后整理工作例如在退出或被终止后。大多数实现调用通用函数 generic_mourn_inferior但可能会进行一些额外的整理工作。 [1] 对于任何新的远程目标建议的方法是使用标准的GDB远程串行协议RSP并使目标实现此接口的服务器端。仅剩下的远程目标是历史遗留接口例如OpenRISC 1000远程JTAG协议。 2.5. 向GDB添加命令 正如在第2.2节中所述GDB的命令处理是可扩展的。命令被分组到多个命令列表struct cmd_list_element 类型中由多个全局变量在 cli-cmds.h 中定义指向。其中cmdlist 是所有定义的命令的列表还为各种顶层命令的子命令定义了单独的列表。例如infolist 是所有 info 子命令的列表。 每个命令或子命令与一个回调函数相关联该函数实现了命令的行为。对于在GDB中设置或显示值的函数还有额外的要求。每个函数还接受一个文档字符串由 help 命令使用。添加命令的函数都会返回一个指向所添加命令的 struct cmd_list_element 的指针这未必是其命令列表的头。最有用的函数包括 add_cmd。将函数添加到命令列表。add_com。将函数添加到主命令列表 cmdlist。这是 add_cmd 的便捷包装。add_prefix_cmd。添加新的前缀命令。此命令应该有自己的函数以便在其自己上调用以及专用于前缀命令的全局命令列表指针所有其子命令都将添加到其中。如果使用未知的子命令调用前缀命令它可以要么报错要么调用前缀命令自身的函数。在调用 add_prefix_cmd 时通过调用标志来指定使用这两者中的哪一个。add_alias_cmd。为已定义的命令添加别名。add_info。将子命令添加到 info。这是 add_cmd 的便捷包装。 通常在定义 struct gdbarch 后新命令会在 _initialize_arch 函数中添加。 2.6. 模拟器 GDB允许实现者将GDB链接到一个内置的模拟器以便通过使用 target sim 命令执行模拟目标。 模拟器应该构建为一个库 libsim.a实现标准的GDB模拟器接口。库的位置由在 configure.tgt 中设置的 gdb_sim 参数指定。 接口由一组应该被实现的函数组成。详细的规范可以在 include 目录中的头文件 remote-sim.h 中找到。 sim_open。初始化模拟器。sim_close。销毁模拟器实例包括释放任何内存。sim_load。将程序加载到模拟器的内存中。sim_create_inferior。准备运行模拟程序。在调用 sim_resume见下文之前不要实际运行它。sim_read 和 sim_write。从模拟器的内存中读取和写入字节。sim_fetch_register 和 sim_store_register。读取和写入模拟器的寄存器。sim_info。为 info sim 命令打印信息。sim_resume。恢复或开始模拟程序的执行。sim_stop。停止模拟程序的执行。sim_stop_reason。返回程序停止的原因。sim_do_command。执行模拟器支持的任意命令。 2.7. 远程串行协议RSP GDB远程串行协议是一种用于连接到远程目标的通用协议。它通过 target remote 和 target extended-remote 命令调用。 该协议是一种简单的文本命令-响应协议。GDB会话充当协议的客户端。它向在目标上实现的服务器发出命令该服务器必须由目标实现。通过实现RSP的服务器端任何远程目标都可以与GDB通信。为各种体系结构提供了一些存根实现可用作新实现的基础。该协议作为主GDB用户指南附录的完整文档 [3]。 强烈建议任何新的远程目标都应使用RSP来实现而不是创建新的远程目标协议。 2.7.1. RSP客户端实现 客户端实现可以在 gdb 子目录中的源文件 remote.h 和 remote.c 中找到。这些实现一组目标操作如第2.4节所述。标准操作的每个操作都映射到与目标上的服务器进行的RSP交互的序列。 2.7.2. RSP服务器实现 RSP服务器实现是一个庞大的主题不构成GDB实现的直接部分因为它是目标的一部分而不是调试器的一部分。 Embecosm编写了一份全面的“Howto”描述了使用OpenRISC 1000体系结构模拟器Or1ksim作为RSP目标的示例以及描述了RSP服务器实现技术 [2]。 2.8. GDB文件组织 GDB源代码的大部分内容位于少数几个目录中。GDB的某些组件是在其他地方使用的库例如BFD在GNU binutils中使用这些库有它们自己的目录。主要目录包括 include。包含横跨主要组件的信息的头文件。例如主模拟器接口头文件在这里remote-sim.h因为它将GDB在gdb目录中与模拟器在sim目录中链接起来。其他头文件特定于特定组件的驻留在该组件的目录中。bfd。二进制文件描述符库。如果必须识别新的目标文件类型则应在此处添加。gdb。主GDB目录。所有源文件应首先包含 defs.h然后包含它们引用的任何其他头文件。头文件也应包含它们引用的任何头文件但可以假定已经包含了 defs.h。configure.tgt 文件包含一个大型的开关语句用于匹配指定给主 configure 命令的目标。通过在此文件中包含其模式匹配来添加新目标。config 子目录包含本地目标的特定配置信息。libiberty。在POSIX和glibc之前这是一个GNU项目旨在提供一组标准函数。它在GDB中继续存在。最有价值的是它的自由存储管理和参数解析函数。opcodes。包含用于GDBdisassemble 命令的反汇编器。由于这段代码也用于binutils中因此它在自己的目录中。sim。各种目标的模拟器。每个目标架构模拟器都构建在其自己的子目录中。 2.9. 测试GDB 运行GDB测试套件需要安装DejaGNU软件包。然后可以使用以下命令运行测试 make check测试运行完成后摘要结果将在 gdb/testsuite 目录中的 gdb.sum 文件中详细日志在 gdb.log 文件中。对于在主机和目标不同的环境中进行最全面的测试DejaGNU需要一些额外的配置。这可以通过将 DEJAGNU 环境变量设置为引用适当的配置文件并在 ~/boards 目录中定义自定义板配置文件来实现。这些配置文件可用于指定适当的模拟器以及在运行测试时如何连接它。 2.10. 文档 GDB的一些子目录又包含doc子目录。文档是用texinfo [9]编写的可以生成PDF、PostScript、HTML或info文件。文档不会在 make all 或 make doc 中自动构建。 要创建文档请切换到各个文档目录并使用 make html、make pdf、make ps 或 make info根据需要选择。 主要感兴趣的文档有 bfd/doc/bfd.texinfo。这是BFD手册。gdb/doc/gdb.texinfo。这是主要的GDB用户指南 [3]。gdb/doc/gdbint.texinfo。这是内部用户指南 [4]。对于任何正在进行代码移植的开发人员来说这是必读的。 唯一例外的是使用 make install。这将为 gdb/doc 目录中的任何文档构建info文件并将其安装在安装目录的info子目录中。 2.11.GDB中的示例流程 了解体系结构规范功能和目标操作如何响应各种GDB命令是有益的。以下各节通过序列图说明了几个过程流。这些图显示了过程的调用链。仅显示了关键函数 - 实际调用通常涉及多个中间函数调用。 2.11.1. 初始启动 图2.2显示了GDB启动的序列图。 图 2.2 GDB 启动时的序列图 target 命令直接映射到当前目标的 to_open。一个典型的实现会建立与目标的物理连接例如通过打开到远程目标的 TCP/IP 连接。对于远程目标通常会调用 start_remote该函数等待目标停止使用当前目标的 to_wait 函数确定停止的原因handle_inferior_event然后标记为正常停止normal_stop。 handle_inferior_event 是 GDB 中的一个核心函数。每当通过目标的 to_wait 函数将控制返回给 GDB 时它必须确定发生了什么以及应该如何处理。图 2.4 显示了在响应 target 命令时 handle_inferior_event 的行为。 图 2.4. 对 GDB target 命令响应中的 handle_inferior_event 的时序图 handle_inferior_event 需要确定执行停止的程序计数器位置因此调用 read_pc_pid。由于程序计数器是一个寄存器这会触发寄存器缓存的创建其中必须使用 gdbarch_register_type 确定每个寄存器的类型这是一次性的操作因为它永远不会改变。在确定了寄存器类型后寄存器缓存通过调用当前目标的 to_fetch_registers 函数为相关寄存器的程序计数器值进行填充。 然后handle_inferior_event 确定停止是否由断点或监视点引起。watchpoints_triggered 函数使用目标的 to_stopped_by_watchpoint 来确定是否是监视点触发了停止。 对 normal_stop 的调用还会调用 struct gdbarch 函数包括调用 gdbarch_unwind_pc 来确定当前程序计数器和帧探测器函数以建立帧探测器栈。 2.11.3. GDB的load命令 图2.5显示了GDB响应load命令的高级顺序图。这映射到当前目标的to_load函数在大多数情况下该函数最终会调用当前目标的to_xfer_partial函数为加载图像的每个部分调用一次将其加载到内存中。 load函数将从加载的文件中获取数据最重要的是其执行的起始地址。 2.11.4. GDB的break命令 图2.6显示了GDB响应break命令的高级顺序图。此示例是针对break目标是目标可执行文件中的符号即函数名的情况。 大多数与断点相关的操作发生在程序开始运行时任何活动断点都会被安装。但是对于任何break命令必须在断点数据结构中设置断点的地址。 对于符号地址可以从符号表中用于调试目的的行号信息中获取函数的开始地址称为符号和行信息或SAL。对于函数这将产生代码的起始地址。但是断点必须设置在函数序言之后。使用gdbarch_skip_prolog来在代码中找到该地址。 2.11.5. GDB的run命令 图2.7显示了GDB响应run命令的高级顺序图。 run命令必须创建inferior插入任何活动的断点和监视点然后开始inferior的执行。直到目标报告停止控制权才会返回到GDB。 实现run命令的顶级函数是run_command。这将通过调用当前目标的to_create_inferior函数创建inferior。 GDB支持能够动态描述其体系结构的目标例如可用寄存器的数量。这是通过当前目标的to_find_description函数默认情况下为空函数实现的。 执行是通过proceed开始的。这首先必须确定代码是否正在重新启动需要经过延迟槽的指令以便代码永远不会停在延迟槽上。如果需要此功能则由gdbarch_single_sep_through_delay函数实现。 使用当前目标的to_insert_breakpoint函数插入活动断点。然后使用当前目标的to_resume函数运行代码。 然后GDB调用wait_for_inferior该函数将等待目标停止然后确定停止的原因。最后normal_stop将从目标代码中删除断点并根据需要向用户报告目标的当前状态。 很多详细的处理都发生在wait_for_inferior和normal_stop函数中请参阅它们在第2.11.2节中的使用。这些是重要的函数详细查看它们的行为会很有用。 图2.8显示了处理GDB run命令时wait_for_inferior的顺序图。 再次关键工作在handle_inferior_event中。该代码使用当前目标的to_stopped_by_watchpoint函数检查监视点。该函数还检查断点但由于它已经知道当前程序计数器由target_wait返回控制时设置因此不需要对目标操作进行进一步调用。target_wait将报告是否由于断点引起而停止。handle_inferior_event然后可以查找活动断点列表中的程序计数器以确定遇到了哪个断点。 图2.9显示了处理GDB run命令时normal_stop的顺序图。在这个例子中停止是由目标遇到断点引起的。 首先要执行的操作是删除断点。这确保目标可执行文件返回到其正常状态而没有插入任何陷阱或类似的代码。 为了识别目标的帧嗅探器使用体系结构的帧嗅探器arch_frame_sniffer。然后为用户打印当前堆栈帧。这需要使用帧嗅探器从NEXT帧这里是arch_frame_this_id中识别THIS帧的ID从而识别所有其他数据。print_stack_frame将从哨兵帧开始向内工作直到找到包含当前堆栈指针和程序计数器的堆栈帧。 2.11.6. GDB的backtrace命令 图2.10显示了GDB响应backtrace命令的高级顺序图。此顺序图显示了在控制返回到GDB后第一次调用backtrace时的行为。 主要的命令函数是backtrace_command它使用print_frame_info打印堆栈上每个函数的名称及其参数。 第一个帧已知来自停止目标的程序计数器和堆栈指针因此由print_frame打印出来。这将最终使用当前目标的to_xfer_partial函数获取局部参数值。 由于这是程序停止后的第一次回溯因此通过get_func_type从哨兵帧获取堆栈指针和程序计数器。然后对于每个帧依次调用print_frame直到没有更多的堆栈帧。每个帧中的信息是使用体系结构的帧嗅探器构建的。 详细查看print_frame是很有用的。图2.11显示了处理GDB backtrace命令时print_frame函数的第二系列调用的顺序图用于打印堆栈帧。 可以从与堆栈帧相关联的程序计数器和堆栈指针中获取关于堆栈帧上函数的信息。这是通过对gdbarch_unwind_pc和gdbarch_unwind_sp函数的调用实现的。 然后对于每个参数必须打印其值。符号表调试数据将标识参数并为GDB提供足够的信息以判断值是在堆栈上还是在寄存器中。帧嗅探器函数在此示例中是arch_frame_prev_register用于根据需要获取寄存器的值。 具体的调用顺序取决于堆栈帧中的函数、它们具有的参数以及这些参数是在寄存器中还是在堆栈上。 2.11.7. GDB的continue命令在断点后 最后的顺序图显示了在断点后使用continue命令恢复执行时的行为。图2.12显示了GDB响应continue命令的高级顺序图。此顺序图显示了在由于断点而停止后的第一次调用continue时的行为。 命令功能由continue_command提供该命令在很大程度上调用proceed函数。 proceed调用当前目标的to_resume函数以恢复执行。对于这第一次调用执行完成后未替换执行run命令后删除的断点目标的恢复仅为单步执行。这允许在触发异常的情况下将目标步过断点。 然后proceed使用wait_for_inferior等待控制在单步之后返回并诊断下一步操作。等待使用当前目标的to_wait函数然后调用handle_inferior_event来分析结果。在这种情况下handle_inferior_event确定目标刚刚越过断点。它重新插入断点并再次调用目标的to_resume函数这次是为了连续运行。 wait_for_inferior将再次使用当前目标的to_wait函数等待目标停止执行然后再次调用handle_inferior_event来处理结果。这一次控制权应该返回到GDB因此断点被移除handle_inferior_event和wait_for_inferior返回。proceed调用normal_stop来整理并打印关于执行停止的当前堆栈帧位置的消息请参阅第2.11.5节。 查看handle_inferior_event的第一次调用的行为是有用的以查看完成单步操作并继续连续执行的顺序。图2.13显示了handle_inferior_event的第一次调用的顺序图。 handle_inferior_event首先确定是否触发了监视点。如果不是这种情况它会检查处理器是否现在处于指令的延迟槽中需要立即进行另一个单步操作。在确定连续执行是适当的情况下它调用keep_going函数重新插入活动断点使用当前目标的to_insert_breakpoint函数。最后它调用当前目标的to_resume函数不设置单步标志以恢复连续执行。 2.12. 总结 GDB移植摘要 将新架构移植到 GDB 中可以分为多个步骤。 确保 bfd 目录中存在目标架构可执行文件的 BFD。如果不存在通过修改现有的类似文件创建一个。在 opcodes 目录中为目标架构实现一个反汇编器。在 gdb 目录中定义目标架构。在 configure.tgt 中添加新目标的模式包括包含该架构代码的文件的名称。按照惯例对于架构 arch 的目标架构定义将放置在 arch-tdep.c 中。在 arch-tdep.c 中定义 _initialize_arch_tdep 函数该函数调用 gdbarch_register 来创建新的 struct gdbarch 用于该架构。如果需要一个新的远程目标请考虑通过定义 _initialize_remote_arch 函数添加新的远程目标。但如果可能的话使用远程串行协议Remote Serial Protocol并独立实现目标的服务器端协议。如果需要的话在 sim 目录中实现一个模拟器。这应该创建一个名为 libsim.a 的库实现 remote-sim.h 中的接口该接口在 include 目录中找到。构建和测试。如果需要的话请求 GDB 主管小组将新的移植包含在主分发版中在主 GDB 用户指南gdb/doc/gdb.texinfo [3]的“配置特定信息”部分中添加对新架构的描述。 本文档的其余部分展示了如何使用这个过程将 GDB 移植到 OpenRISC 1000 架构。 第3章OpenRISC 1000 架构 OpenRISC 1000 架构定义了一系列自由、开源的 RISC 处理器核。它是一种32或64位的载荷和存储 RISC 架构注重性能、简单性、低功耗、可扩展性和通用性。 OpenRISC 1000 的完整文档可在其《架构手册》[8]中找到。 从调试的角度来看由指令集操作的有三个数据区域。 主内存一个统一的地址空间具有32或64位寻址。支持单独或统一的指令和数据以及指令缓存。支持单独或统一的、1级或2级数据和指令内存管理单元。通用寄存器GPRs最多32个寄存器长度为32或64位。特殊寄存器SPRs最多32个组每个组最多有2048个寄存器长度为32或64位。这些寄存器提供了处理器的所有管理功能程序计数器、处理器状态、保存的异常寄存器、调试接口、内存管理单元和缓存接口等。 特殊目的寄存器SPRs对于 GDB 的实现具有挑战性因为它们既不表示可寻址的内存也不具有寄存器集的特性通常数量不多。 对于 GDB 实现而言一些 SPR 对于 GDB 的实现尤为重要。 配置寄存器单元存在寄存器SPR 1, UPR、CPU 配置寄存器SPR 2, CPUCFGR和调试配置寄存器SPR 7, DCFGR标识特定 OpenRISC 1000 实现中可用的功能。这包括所使用的指令集、通用寄存器的数量以及硬件调试接口的配置。程序计数器上一个程序计数器SPR 0x12, PPC是刚刚执行的指令的地址。下一个程序计数器SPR 0x10, NPC是即将执行的下一条指令的地址。NPC 是 GDB 的 $pc 变量报告的值。监督寄存器监督寄存器SPR 0x11, SR表示处理器的当前状态。这是 GDB 的状态寄存器变量 $ps 报告的值。 特别重要的是控制调试单元如果存在的第6组 SPR。调试单元可以响应高达10个观察点中的任何一个触发陷阱异常。观察点是由组合匹配点matchpoints构建的逻辑表达式这些匹配点是对特定行为例如是否已访问指定地址的简单点测试。 调试值和控制寄存器有多达8对调试值SPR 0x3000–0x3007, DVR0 到 DVR7和调试控制寄存器SPR 0x3008–0x300f, DCR0 到 DCR7寄存器。每对寄存器与一个硬件匹配点相关联。每对中的调试值寄存器给出要与之比较的值。调试控制寄存器指示匹配点是否已启用要与之比较的值的类型指令获取地址、数据加载和/或存储地址、数据加载和/或存储值以及要进行比较的方式相等、不相等、小于、小于或等于、大于、大于或等于包括有符号和无符号。如果启用了匹配点并且测试满足则触发相应的匹配点。调试观察点计数器有两个16位的调试观察点计数器寄存器SPR 0x3012–0x3013, DWCR0 和 DWCR1与另外两个匹配点相关联。上16位是要匹配的值下16位是计数器。在触发指定的匹配点时计数器递增参见调试模式寄存器1。当计数达到匹配值时触发相应的匹配点。 【注意】注意存在潜在的歧义因为计数器在响应匹配点时递增并且还生成自己的匹配点。将计数器设置为递增自己的匹配点是不好的做法 调试模式寄存器有两个调试模式寄存器用于控制调试单元的行为SPR 0x3010–0x3011, DMR1 和 DMR2。DMR1为每个10个匹配点与DVR/DCR对相关联的8个与计数器相关联的2个提供一对比特。这些指定观察点是否由相关匹配点触发由与前一个观察点进行 AND 或 OR 运算的匹配点触发。通过构建观察点链可以建立对硬件行为的复杂逻辑测试。 另外DMR1中的两个位启用单步行为每个指令完成后触发一个陷阱异常和分支步进行为每个分支指令完成后触发一个陷阱异常。 DMR2 包含每个计数器的一个启用位10个指示哪些观察点分配给哪个计数器的位以及10个指示哪些观察点生成陷阱异常的位。它还包含10位输出指示哪些观察点生成了陷阱异常。 调试停止和原因寄存器在正常操作中所有 OpenRISC 1000 异常都通过位于地址0x100至0xf00的异常向量处理。调试停止寄存器SPR 0x3014, DSR用于将特定异常分配给 JTAG 接口。这些异常会使处理器停止允许通过 JTAG 接口分析机器状态。通常调试器将为用于断点的陷阱异常启用此功能。 在异常被重定向到开发接口的情况下调试原因寄存器SPR 0x3021, DRR指示导致重定向的异常。请注意尽管单步和分支步进引发陷阱但如果它们被分配到 JTAG 接口它们不会在 DRR 中设置 TE 位。这允许外部调试器区分断点陷阱和单步/分支步进陷阱。 3.1.OpenRISC 1000 JTAG 接口 OpenRISC 1000 具有两个用于 JTAG 接口的变体。 原始 JTAG 接口是作为 OpenRISC SoC 项目 ORPSoC [10] 的一部分创建的。它提供三个扫描链一个用于访问所有 SPR一个用于访问外部存储器一个用于控制 CPU。控制扫描链可以重置、停滞或跟踪处理器。新的 JTAG 接口是由 Igor Mohor 在 2004 年提供的 [11]。它提供了对 SPR 和外部存储器的相同访问权限但提供了一个更简单的控制接口只提供了停滞或重置处理器的功能。 目前OpenRISC 架构模拟器 Or1ksim见第 3.4 节支持第一种接口。 这两种接口都提供三个扫描链 RISC_DEBUG扫描链 1提供对 SPR 的读/写访问。REGISTER扫描链 4提供对 CPU 的控制。在 ORPSoC 接口中它提供多个寄存器用于读写以控制 CPU。其中寄存器 0MODER控制硬件跟踪寄存器 4RISC_OP控制重置和停滞最为重要。通过设置 MODER 中的位 1 启用跟踪通过清除禁用。通过设置 RISC_OP 中的位 1 和位 0 触发和清除重置和处理器停滞。通过读取 RISC_OP 中的停滞位可以确定停滞状态。 在 Mohor 接口中有一个单一的控制寄存器其行为与原始调试接口中的 RISC_OP 相同。WISHBONE扫描链 5提供对主内存的读/写访问。 由于通用寄存器GPRs映射到 SPR 组 0这个机制也允许对 GPR 进行读写。 3.2.OpenRISC 1000 远程 JTAG 协议 重要说明 OpenRISC 1000 的最新版本 GDB 实现了 GDB 远程串行协议这是连接到远程目标的首选机制 [2]。 但是这里描述的协议保留为向后兼容性。它在这里被用作教程的载体以说明如何在 GDB 中使用自定义调试协议。 为了方便 GDB 进行远程调试OpenRISC 定义了一种软件协议描述了适用于通过套接字接口经由 TCP/IP 传输的 JTAG 访问。 注意 这个协议在 GDB 远程串行协议见第 2.7 节之前。在将来的某个日期OpenRISC 1000 远程 JTAG 协议将被 RSP 替代。 OpenRISC 1000 远程 JTAG 协议是一种简单的消息发送/确认协议。JTAG 请求被封装为 32 位命令、32 位长度和一系列 32 位数据字的形式。JTAG 响应被封装为 32 位状态和可选数量的 32 位数据字。可用的命令有 OR1K_JTAG_COMMAND_READ1。读取单个 JTAG 寄存器。请求中提供了一个 32 位地址。响应包括 64 位读取数据。OR1K_JTAG_COMMAND_WRITE2。写入单个 JTAG 寄存器。请求中提供了一个 32 位地址和要写入的 64 位数据。OR1K_JTAG_COMMAND_READ_BLOCK3。读取多个 32 位 JTAG 寄存器。请求中提供了第一个寄存器的 32 位地址和要读取的寄存器数。响应包括读取的寄存器数和每个读取的寄存器的 32 位数据。OR1K_JTAG_COMMAND_WRITE_BLOCK4。写入多个 32 位 JTAG 寄存器。请求中提供了第一个寄存器的 32 位地址和要写入的寄存器数然后是每个寄存器要写入的 32 位数据。OR1K_JTAG_COMMAND_CHAIN5。选择扫描链。请求中提供了一个 32 位扫描链编号。 如果使用 Mohor 版本的 JTAG 接口将被忽略对 REGISTER 扫描链的读/写访问的地址因为只有一个控制寄存器。 注意 这个协议中似乎存在一个矛盾点。为了通信效率提供了读/写单个寄存器的 64 位而块读/写只有 32 位。 图 3.1 显示了所有五个请求及其相应成功响应的结构。请注意如果请求失败响应将只包含状态字。 OpenRISC 1000远程JTAG协议的客户端部分发出请求的部分由专为OpenRISC 1000设计的GDB端口实现。 另一方面服务器端应用程序可以实现此协议以与物理硬件进行交互通常通过其JTAG端口或者与包含JTAG功能的模拟进行交互。前者的例子包括ORSoC™ AB生产的USB JTAG连接器。后者的示例是OpenRISC 1000体系结构模拟器Or1ksim有关详细信息请参见第3.4节。 3.3. 应用二进制接口ABI OpenRISC 1000的ABI描述在《体系结构手册》的第16章[8]中。然而实际的GCC编译器实现与文档中描述的ABI略有不同。由于对ABI的准确理解对于GDB至关重要这里记录了这些差异。 寄存器使用R12被用作另一个被调用保存的寄存器。在32位体系结构上它从不用于返回64位结果的高32位。大于32位的所有值都通过指针返回。 尽管规范要求堆栈帧应为双字对齐但当前的GCC编译器实现了单字对齐。 大于32位在64位体系结构上为64位、结构和联合的整数值以指向结果位置的指针的形式返回。调用函数提供该位置并将其作为GPR 3中的第一个参数传递。换句话说当函数返回这种类型的结果时函数的第一个真实参数将出现在R4中或者在32位体系结构上是R5/R6如果它是64位参数。 3.4. Or1ksimOpenRISC 1000体系结构模拟器 Or1ksim是OpenRISC 1000体系结构的指令集模拟器ISS。目前只模拟32位体系结构。除了对核心处理器的建模外Or1ksim还可以模拟多个外设以提供完整的片上系统SoC功能。 Or1ksim模拟OpenRISC 1000 JTAG接口并在服务器端实现OpenRISC 1000远程JTAG协议。它被用作对GDB进行的此端口的测试平台。 JTAG接口模拟了旧的ORPSoC的行为支持多个控制寄存器和硬件跟踪。未来的版本将提供支持Igor Mohor JTAG接口的选项。 [注意] 注意 GDB的移植揭示了Or1ksim中的一些错误。该实现现在相当陈旧早于当前的OpenRISC 1000规范。有一个修复这些错误的补丁可以从www.embecosm.com/download/index.html下载。 第4章将OpenRISC 1000架构移植至GDB 本章描述了将OpenRISC 1000架构移植至GDB的步骤。它使用了第2章中描述的信息和数据结构。 OpenRISC 1000版本的GDB在GDB用户指南[3]中有简要文档。更全面的教程[6]位于gdb/doc子目录中的or1k.texinfo文件中。 严格来说这不是一个新的移植。在GDB 5.3中存在一个旧的移植。然而由于那时GDB发生了重大变化因此几乎需要进行完整的重新实现。 [提示] 在处理任何大型代码库时TAGS文件非常有价值。这允许在整个代码库中立即查找任何过程或变量。通常对于任何GNU项目可以使用make tags命令来实现这一点。但是这对于GDB不起作用——在opcodes目录中的tags目标存在问题。 然而在gdb目录中构建tags是有效的因此可以通过以下方式在该目录中构建TAGS文件 cd gdb make tags cd ..4.1. BFD规范 OpenRISC 1000的BFD规范已经存在它是binutils的一部分因此无需实现这个。现有的代码被重复使用。 4.2. OpenRISC 1000体系结构规范 代码位于gdb子目录。主要的架构规范位于or1k-tdep.c中带有OpenRISC 1000全局标头的or1k-tdep.h。对OpenRISC 1000远程JTAG接口的支持位于remote-or1k.c中具有在or1k-jtag.c中的详细协议和在or1k-jtag.h中的协议标头。 有几个目标可以使用OpenRISC 1000体系结构。这些都以or16、or32或or32开头。configure.tgt被编辑以添加这些目标以便从这些源文件生成的二进制文件。 or16* | or32* | or64*)# 目标OpenCores OpenRISC 1000体系结构gdb_target_obsor1k-tdep.o remote-or1k.o or1k-jtag.o;;注意: configure.tgt仅指定二进制文件因此无法显示对标头的依赖关系。为了纠正这一点可以编辑Makefile.in以便automake和configure生成带有正确依赖关系的Makefile。 架构定义是通过通过调用gdbarch_register从_initialize_or1k_tdep创建的。该函数还初始化了反汇编器build_automata并添加了两个新命令info命令的子命令以读取SPR和新的顶级支持命令spr以设置SPR的值。 4.2.1. 创建struct gdbarch 使用初始化函数or1k_gdbarch_init和目标特定的dump函数or1k_dump_tdep调用gdbarch_register以BFD类型bfd_arch_or32注册。将来的实现可能会通过使用相同的函数来创建64位版本的架构。 gdbarch_init接收从BFD条目创建的struct gdbarch_info以及现有架构的列表。首先使用gdbarch_list_lookup_by_info检查该列表看看是否已经有了适合给定struct gdbarch_info的架构如果是则返回该架构。 否则将创建一个新的struct gdbarch。为此将目标依赖项保存在一个OpenRISC 1000特定的struct gdbarch_tdep中在or1k-tdep.h中定义。 struct gdbarch_tdep {unsigned int num_matchpoints;unsigned int num_gpr_regs;int bytes_per_word;int bytes_per_address; };这是除了struct gdbarch中保存的信息之外的信息。通过使用这个结构可以使OpenRISC 1000的GDB实现足够灵活以处理32和64位的实现以及可变数量的寄存器和匹配点。 注意: 尽管这种灵活性内置在代码中但当前的实现仅已经在32位OpenRISC 32寄存器上进行了测试。 然后通过gdbarch_alloc创建新的架构传入struct gdbarch_info和struct gdbarch_tdep。使用各种set_gdbarch_函数填充struct gdbarch并将OpenRISC 1000 Frame sniffer与架构关联起来。 在创建新的struct gdbarch时必须提供一个函数以将struct gdbarch_tdep中的目标特定定义转储到文件中。这是在or1k_dump_tdep中提供的。它接收一个指向struct gdbarch的指针和一个文件句柄并简单地写出struct gdbarch_tdep中的字段带有适当的解释性文本。 4.2.2. OpenRISC 1000硬件数据表示 struct gdbarch的第一个条目初始化所有标准数据类型的大小和格式。 set_gdbarch_short_bit (gdbarch, 16); set_gdbarch_int_bit (gdbarch, 32); set_gdbarch_long_bit (gdbarch, 32); set_gdbarch_long_long_bit (gdbarch, 64); set_gdbarch_float_bit (gdbarch, 32); set_gdbarch_float_format (gdbarch, floatformats_ieee_single); set_gdbarch_double_bit (gdbarch, 64); set_gdbarch_double_format (gdbarch, floatformats_ieee_double); set_gdbarch_long_double_bit (gdbarch, 64); set_gdbarch_long_double_format (gdbarch, floatformats_ieee_double); set_gdbarch_ptr_bit (gdbarch, binfo-bits_per_address); set_gdbarch_addr_bit (gdbarch, binfo-bits_per_address); set_gdbarch_char_signed (gdbarch, 1);4.2.3. OpenRISC 1000架构的信息函数 这些struct gdbarch函数提供有关架构的信息。 set_gdbarch_return_value (gdbarch, or1k_return_value); set_gdbarch_breakpoint_from_pc (gdbarch, or1k_breakpoint_from_pc); set_gdbarch_single_step_through_delay(gdbarch, or1k_single_step_through_delay); set_gdbarch_have_nonsteppable_watchpoint(gdbarch, 1); switch (gdbarch_byte_order (gdbarch)){case BFD_ENDIAN_BIG:set_gdbarch_print_insn (gdbarch, print_insn_big_or32);break;case BFD_ENDIAN_LITTLE:set_gdbarch_print_insn (gdbarch, print_insn_little_or32);break;case BFD_ENDIAN_UNKNOWN:error (or1k_gdbarch_init: Unknown endianism);break;}or1k_return_value: 该函数告诉GDB如何按ABI返回特定类型的值。结构/联合和大标量 4字节放在内存中并通过引用返回RETURN_VALUE_ABI_RETURNS_ADDRESS。较小的标量在GPR 11RETURN_VALUE_REGISTER_CONVENTION中返回。or1k_breakpoint_from_pc: 返回在给定程序计数器地址处使用的断点函数。由于所有OpenRISC 1000指令的大小相同因此该函数始终返回相同的值即l.trap指令的指令序列。or1k_single_step_through_delay: 此函数用于确定单步执行的指令是否实际上是执行延迟槽。如果先前执行的指令是分支或跳转则是这种情况。print_insn_big_or32和print_insn_little_or32: 有两个反汇编器的变体具体取决于字节顺序。有关更详细的信息请参见第4.4节。 4.2.4. OpenRISC 1000寄存器体系结构 寄存器体系结构由两组struct gdbarch函数和字段定义。第一组指定了寄存器原始和伪造的数量以及一些“特殊”寄存器的寄存器编号。 set_gdbarch_pseudo_register_read (gdbarch, or1k_pseudo_register_read); set_gdbarch_pseudo_register_write (gdbarch, or1k_pseudo_register_write); set_gdbarch_num_regs (gdbarch, OR1K_NUM_REGS); set_gdbarch_num_pseudo_regs (gdbarch, OR1K_NUM_PSEUDO_REGS); set_gdbarch_sp_regnum (gdbarch, OR1K_SP_REGNUM); set_gdbarch_pc_regnum (gdbarch, OR1K_PC_REGNUM); set_gdbarch_ps_regnum (gdbarch, OR1K_SR_REGNUM); set_gdbarch_deprecated_fp_regnum (gdbarch, OR1K_FP_REGNUM);第二组函数提供有关寄存器的信息。 set_gdbarch_register_name (gdbarch, or1k_register_name); set_gdbarch_register_type (gdbarch, or1k_register_type); set_gdbarch_print_registers_info (gdbarch, or1k_registers_info); set_gdbarch_register_reggroup_p (gdbarch, or1k_register_reggroup_p);原始寄存器的表示参见第2.3.5.3节如下寄存器0-31是相应的GPR寄存器32是前一个程序计数器33是下一个程序计数器通常称为程序计数器寄存器34是监控寄存器。为了方便起见头文件or1k_tdep.h中定义了所有特殊寄存器的常量。 #define OR1K_SP_REGNUM 1 #define OR1K_FP_REGNUM 2 #define OR1K_FIRST_ARG_REGNUM 3 #define OR1K_LAST_ARG_REGNUM 8 #define OR1K_LR_REGNUM 9 #define OR1K_RV_REGNUM 11 #define OR1K_PC_REGNUM (OR1K_MAX_GPR_REGS 0) #define OR1K_SR_REGNUM (OR1K_MAX_GPR_REGS 1)在此实现中没有伪寄存器。可以提供一个集合来表示以浮点格式表示的GPR用于浮点指令但尚未实现这一点。为各种总数定义了常量。 #define OR1K_MAX_GPR_REGS 32 #define OR1K_NUM_PSEUDO_REGS 0 #define OR1K_NUM_REGS (OR1K_MAX_GPR_REGS 3) #define OR1K_TOTAL_NUM_REGS (OR1K_NUM_REGS OR1K_NUM_PSEUDO_REGS)注意 这些总数目前是硬编码的常数。它们实际上应该依赖于struct gdbarch_tdep中的数据为具有少于32个寄存器的架构提供支持。这种功能将在将来的实现中提供。 不提供伪寄存器的一个结果是GDB中的帧指针变量$fp将不具有其正确的值。将此寄存器作为GDB的固有部分提供的做法已不再受支持。如果需要应将其定义为寄存器或伪寄存器。 但是如果没有具有此名称的寄存器GDB将使用struct gdbarch中deprecated_fp_regnum值的值或由帧基础嗅探器报告的当前帧基础。 目前deprecated_fp_regnum已设置。但长期计划是将帧指针表示为伪寄存器其取值为GPR 2。 寄存器体系结构在大多数情况下只涉及在struct gdbarch中设置所需的值。然而定义了两个函数or1k_pseudo_register_read和or1k_pseudo_register_write以提供对任何伪寄存器的访问。这些函数被定义为为将来提供挂钩但在没有伪寄存器的情况下它们什么都不做。 有一组函数提供有关寄存器名称和类型的信息并为GDB info registers命令提供输出。 or1k_register_name: 这是一个简单的表查找从其编号中获取寄存器名称。 or1k_register_type: 此函数必须将类型作为struct type返回。这个GDB数据结构包含有关每种类型及其与其他类型的关系的详细信息。 为此函数预定义了许多标准类型其中包括一些实用函数用于从中构造其他类型。对于大多数寄存器预定义的builtin_type_int32是合适的。堆栈指针和帧指针是指向任意数据的指针因此需要void *的等效类型。通过将预定义的builtin_type_void应用于lookup_pointer_type函数来构造它。程序计数器是指向代码的指针因此适用于指向void函数的指针的等效类型。通过将lookup_pointer_type和lookup_function_type应用于builtin_type构造它。 or1k_register_info: 此函数由info registers命令使用以显示有关一个或多个寄存器的信息。 实际上这个函数并不是真正需要的。它只是default_print_registers_info的一个包装器对于这个函数来说这是默认设置。 or1k_register_reggroup_p: 这个谓词函数如果给定寄存器在特定组中则返回1true。在请求特定类别的寄存器时info registers命令使用此函数。 实际上这个函数与默认函数default_register_reggroup_p几乎没有区别因为无论如何对于任何未知情况都会调用它。然而它确实利用了目标相关的数据struct gdbarch_tdep从而为不同的OpenRISC 1000架构提供了灵活性。 4.2.5. OpenRISC 1000帧处理 OpenRISC 1000帧结构在其ABI [8]中有描述。一些细节在当前OpenRISC实现中略有不同这在第3.3节中有描述。 帧处理的关键是理解每个函数中负责初始化堆栈帧的前奏和可能的尾声。对于OpenRISC 1000GPR 1用作堆栈指针GPR 2用作帧指针GPR 9用作返回地址。前奏序列如下 l.addi r1,r1,-frame_size l.sw save_loc(r1),r2 l.addi r2,r1,frame_size l.sw save_loc-4(r1),r9 l.sw x(r1),ryOpenRISC 1000堆栈帧容纳任何局部自动变量和临时值然后是返回地址接着是旧的帧指针最后是由该函数调用的函数的基于堆栈的参数。这最后一条规则意味着返回地址和旧帧指针不一定在堆栈帧的末尾 - 将留出足够的空间来构建必须放在堆栈上的任何被调用函数的参数。图4.1显示了前奏结束时堆栈的外观。 并非所有字段都始终存在。函数不需要将其返回地址保存到堆栈中可能没有需要保存的被调用者保存的寄存器即GPR 12, 14, 16, 18, 20, 22, 24, 26, 28和30。叶函数不需要设置新的堆栈帧。 尾声是前奏的反向。被调用者保存的寄存器被恢复返回地址放在GPR 9中并在跳转到GPR 9中的地址之前恢复堆栈和帧指针。 l.lwz ry,x(r1) l.lwz r9,save_loc-4(r1) l.lwz r2,save_loc(r1) l.jr r9 l.addi r1,r1,frame_size只有与前奏相对应的尾声的那些部分实际上需要出现。OpenRISC 1000在分支指令后有一个延迟槽因此为了效率堆栈恢复可以放在l.jr指令之后。 4.2.5.1. OpenRISC 1000分析帧的功能 一组struct gdbarch函数和一个值提供有关当前堆栈及其如何由目标程序处理的信息。 set_gdbarch_skip_prologue (gdbarch, or1k_skip_prologue); set_gdbarch_inner_than (gdbarch, core_addr_lessthan); set_gdbarch_frame_align (gdbarch, or1k_frame_align); set_gdbarch_frame_red_zone_size (gdbarch, OR1K_FRAME_RED_ZONE_SIZE);or1k_skip_prologue: 如果程序计数器当前位于函数前奏中则此函数返回函数前奏的末尾。 最初的方法是使用DWARF2符号和行SAL信息来标识函数的开始find_pc_partial_function因此在前奏结束时skip_prologue_using_sal标识。 如果此信息不可用or1k_skip_prologue将重用帧嗅探器函数or1k_frame_unwind_cache见第4.2.5.5节的辅助函数以遍历似乎是函数前奏的代码。 core_addr_lessthan: 此标准函数如果其第一个参数的地址低于第二个参数的地址则返回1true。它提供了struct gdbarch inner_than函数所需的功能适用于像OpenRISC 1000这样具有降低堆栈帧的体系结构。 or1k_frame_align: 此函数接受一个堆栈指针并返回一个值扩展帧以满足ABI的堆栈对齐要求。由于OpenRISC 1000 ABI使用降低的堆栈因此使用内置函数align_down。对齐度在or1k-tdep.h中定义的常量OR1K_STACK_ALIGN中指定。 OR1K_FRAME_RED_ZONE_SIZE: OpenRISC 1000为堆栈指针下方的2,560字节保留供异常处理程序和无帧函数使用的空间。这被称为红区AMD术语。此常量记录在struct gdbarch frame_red_zone_size字段中。任何虚构的堆栈帧见第4.2.5.3节将放置在此点之后。 4.2.5.2. 用于访问帧数据的OpenRISC 1000函数 set_gdbarch_unwind_pc (gdbarch, or1k_unwind_pc); set_gdbarch_unwind_sp (gdbarch, or1k_unwind_sp);只有两个在这里需要的函数or1k_unwind_pc和or1k_unwind_sp。给定指向NEXT帧的指针这些函数分别返回THIS帧中程序计数器和堆栈指针的值。 由于OpenRISC架构定义了标准的帧嗅探器并且这两个寄存器都是原始寄存器因此可以通过调用frame_unwind_register_unsigned函数来实现这些函数。 4.2.5.3. 用于创建虚构堆栈帧的OpenRISC 1000函数 set_gdbarch_push_dummy_call (gdbarch, or1k_push_dummy_call); set_gdbarch_unwind_dummy_id (gdbarch, or1k_unwind_dummy_id);or1k_push_dummy_call: 此函数创建一个虚构的堆栈帧以便GDB可以在目标代码中评估函数例如在调用命令中。输入参数包括调用的所有参数包括返回地址和应该返回结构的地址。 该函数的函数返回地址始终被断点设置以便GDB可以捕获返回。使用regcache_cooked_write_unsigned将此返回地址写入链接寄存器在寄存器缓存中。 如果函数要返回结构则结构应该返回的地址作为第一个参数传递位于GPR 3中。 接下来的参数传递给剩余的参数寄存器最多GPR 8。结构按引用传递到它们在内存中的位置。对于传递64位参数的32位体系结构将使用一对寄存器3和4、5和6或7和8。 任何剩余的参数必须推送到堆栈的末尾。这里有一个困难因为推送每个参数可能会导致堆栈不对齐OpenRISC 1000指定双字对齐。因此代码首先计算所需的空间然后调整结果堆栈指针以正确对齐。然后可以在正确的位置将参数写入堆栈。 or1k_unwind_dummy_id: 这是or1k_push_dummy_call的反向操作。给定指向NEXT堆栈帧的指针将是虚构调用的帧它返回此帧的帧ID即堆栈指针和函数入口点地址。 这并不完全琐碎。对于虚构帧有关此帧的NEXT帧信息未必完整因此简单调用frame_unwind_id会无限递归回到此函数。相反通过取消堆栈指针和程序计数器并尝试使用DWARF2符号和行SAL信息查找从PC到函数开头的开始的方式来构建帧信息。如果该信息不可用则程序计数器将用作函数开始地址的代理。 4.2.5.4. OpenRISC 1000帧嗅探器 前面的函数都与struct gdbarch有一个1:1的关系。但是对于堆栈分析或“嗅探”可能有多种方法是合适的因此维护了一个函数列表。 使用frame_unwind_append_sniffer设置低级别堆栈分析函数。OpenRISC 1000具有用于查找帧的ID并获取由or1k_frame_sniffer见第4.2.5.5节指定的帧上寄存器值的自己的嗅探器。对于所有其他嗅探功能都使用默认的DWARF2帧嗅探器dwarf2_frame_sniffer。 高级别的嗅探器找到了堆栈帧的基地址。OpenRISC定义了自己的基础嗅探器or1k_frame_base作为默认值。它提供了所需的所有功能因此可以用作默认的基础嗅探器使用frame_base_set_default设置。帧基是一个结构其中的条目指向相应的帧嗅探器和提供基地址的函数帧上的参数和帧上的局部变量。由于这三者对于OpenRISC 1000都是相同的因此相同的函数or1k_frame_base_address用于所有三者。 4.2.5.5. OpenRISC 1000基础帧嗅探器 相同的函数or1k_frame_base_address用于提供三个基础函数对于帧本身局部变量和任何参数。在OpenRISC 1000中这三者的值都相同。 cCopy codeor1k_frame_base.unwind or1k_frame_sniffer (NULL); or1k_frame_base.this_base or1k_frame_base_address; or1k_frame_base.this_locals or1k_frame_base_address; or1k_frame_base.this_args or1k_frame_base_address; frame_base_set_default (gdbarch, or1k_frame_base);此函数的规范需要堆栈的末尾即堆栈指针。令人困惑的是如果未设置deprecated_fp_regnum并且没有名为“fp”的寄存器则此函数还用于确定$fp变量的值。然而正如前面提到的GDB正在摆脱对帧指针的固有理解。对于OpenRISC 1000deprecated_fp_regnum目前已定义尽管以后将定义一个伪寄存器其名称为fp映射到GPR 2。 与所有帧嗅探器一样此函数通过使用通用寄存器解缠器frame_unwind_register_unsigned从堆栈中解缠NEXT帧的地址来传递。 4.2.5.6. OpenRISC 1000低级别帧嗅探器 函数or1k_frame_sniffer返回一个指向struct frame_unwind的指针其中包含此嗅探器定义的函数的条目。对于OpenRISC 1000这定义了一个自定义函数来构造给定指向NEXT帧的指针的THIS帧的帧ID的函数or1k_frame_this_id和一个自定义函数给定指向NEXT帧的指针给定指向NEXT帧的指针此帧的帧ID的值的寄存器的函数or1k_frame_prev_register。 or1k_frame_this_id: 此函数的输入是指向NEXT帧的指针和此帧的前奏缓存如果存在。如果前奏缓存不存在则它使用主要的OpenRISC 1000帧分析器or1k_frame_unwind_cache生成前奏缓存见下文。 从缓存的数据中函数返回帧ID。这包括两个值此帧的堆栈指针和使用此堆栈帧的函数的代码的地址通常是入口点 or1k_frame_prev_register: 此函数的输入是指向NEXT帧的指针此帧的前奏缓存如果存在和寄存器号。如果前奏缓存不存在则它使用主要的OpenRISC 1000帧分析器or1k_frame_unwind_cache生成前奏缓存见下文。 从缓存的数据中返回一个标志指示是否已优化掉寄存器永远不会是这种情况寄存器表示的l值的类型是什么寄存器内存还是非l值保存在内存中的寄存器的地址如果在内存中保存的话保存此寄存器值的不同寄存器的编号如果是这种情况如果提供了缓冲区则是从内存或寄存器缓存中获取的实际值。 由于实现使用内置的struct trad_frame_cache作为其寄存器缓存因此代码可以使用trad_frame_get_register函数从缓存中解码所有这些信息。 OpenRISC 1000低级嗅探器依赖于or1k_frame_unwind_cache。这是嗅探器的核心。它必须确定给定指向NEXT帧的指针的THIS帧的帧ID然后确定在THIS帧中关于PREVIOUS帧中寄存器值的信息。 所有这些数据都在前奏缓存见第2.3.6节中返回将其引用作为一个参数传递。如果此缓存已经存在于此帧中则它可以立即作为结果返回。 如果缓存尚不存在则它将被分配使用trad_frame_cache_zalloc。第一步是从NEXT帧中解缠此函数的开始地址。可以使用目标文件中的DWARF2信息找到前奏的末尾使用skip_prologue_using_sal。 然后代码通过每个前奏的指令来查找所需的数据。 谨慎: 分析只能考虑实际执行的前奏指令。很可能程序计数器位于前奏代码中只能分析实际执行的指令。 通过简单地解缠NEXT帧来找到堆栈指针和程序计数器。堆栈指针是THIS帧的基础并使用trad_frame_set_this_base将其添加到缓存数据中。 end_iaddr标记应该分析的代码的结束。只有地址小于此的指令将被考虑。 l.addi指令应该首先出现其立即常数字段是堆栈的大小。如果缺少它那么这是对函数的无帧调用。如果程序计数器恰好位于函数开始之前在设置堆栈和帧指针之前则它也将看起来像是无帧函数。 除非后来发现它已在堆栈上保存否则PREVIOUS帧的程序计数器是THIS帧的链接寄存器并且可以在寄存器缓存中记录。 提示: 保存寄存器数据时必须使用正确的函数。 当从THIS帧中的寄存器中获取PREVIOUS帧中的寄存器时请使用trad_frame_set_reg_realreg。 当从THIS帧中的地址中获取PREVIOUS帧中的寄存器时请使用trad_frame_set_reg_addr。 当在THIS帧中的特定值中获取PREVIOUS帧中的寄存器时请使用trad_frame_set_reg_value。 每个寄存器的默认条目是PREVIOUS帧中的值是从THIS帧中的相同寄存器中获取的。 对于无帧调用将不再找到更多的信息因此代码分析的其余部分仅在帧大小为非零时适用。 前奏中的第二条指令是保存PREVIOUS帧的帧指针的地方。如果缺少此项则是一个错误。保存它的地址THIS帧的堆栈指针加上l.sw指令中的偏移被保存在缓存中使用trad_frame_set_reg_addr。 第三条指令应该是设置帧指针的l.addi指令。此指令中设置的帧大小应该与第一条指令中设置的帧大小相匹配。设置了此帧后就可以使用帧指针来获取前一帧的堆栈指针。此信息记录在寄存器缓存中。 第四条指令应该是保存PREVIOUS帧的链接寄存器的l.sw指令。只有当PREVIOUS帧在堆栈上保存链接寄存器时才出现。 最后对于实际的前奏所有PREVIOUS帧的GPR1到31的保存都应该在其一开始完成。可以在缓存中记录哪些GPR已被保存使用trad_frame_set_reg_realreg。 最后PREVIOUS帧的实际帧大小包括任何以字节为单位的局部变量和非l值参数可以在缓存中记录使用trad_frame_set_frame_size。 最终这将返回指向缓存的指针以供OpenRISC 1000的所有嗅探器使用。 4.3. OpenRISC 1000 JTAG 远程目标规范 OpenRISC 远程 JTAG 协议的远程目标规范代码位于 gdb 子目录中。remote-or1k.c 包含了目标定义。低级接口位于 or1k-jtag.c 中共享头文件位于 or1k-jtagh 中。 低级接口被抽象成一组与目标行为相关的 OpenRISC 1000 特定函数。提供两种实现在 or1k-jtag.c 中一种用于直接连接到主机的并行端口的目标另一种用于通过 OpenRISC 1000 远程 JTAG 协议通过 TCP/IP 连接的目标。 void or1k_jtag_init (char *args); void or1k_jtag_close (); ULONGEST or1k_jtag_read_spr (unsigned int sprnum); void or1k_jtag_write_spr (unsigned int sprnum, ULONGEST data); int or1k_jtag_read_mem (CORE_ADDR addr, gdb_byte *bdata, int len); int or1k_jtag_write_mem (CORE_ADDR addr, const gdb_byte *bdata, int len); void or1k_jtag_stall (); void or1k_jtag_unstall (); void or1k_jtag_wait (int fast);选择使用哪种实现由传递给 or1k_jtag_init 的参数确定。 or1k_jtag_init 和 or1k_jtag_close。初始化和关闭与目标的连接。or1k_jtag_init 接收一个包含目标地址可以是本地设备或远程 TCP/IP 端口地址的参数字符串。可提供可选的第二个参数 reset以指示连接后应重置目标。 or1k_jtag_read_spr 和 or1k_jtag_write_spr。读取或写入特殊目的寄存器。 or1k_jtag_read_mem 和 or1k_jtag_write_mem。读取或写入一块内存。 or1k_jtag_stall 和 or1k_jtag_unstall。暂停或取消暂停目标。 or1k_jtag_wait。等待目标暂停。 远程目标接口的二进制文件remote-or1k.o 和 or1k-jtag.o被添加到 OpenRISC 目标的 configure.tgt 文件中。如第4.2节所述这只指定二进制文件因此无法捕获对头文件的依赖关系。为此需要编辑 Makefile.in。 [提示] 提示 对于简单的端口可以省略编辑 Makefile.in。相反在调用 make 之前触摸目标特定的 C 源文件以确保它们被重新构建。 4.3.1. 为 OpenRISC 1000 创建 struct target_ops 通过定义函数 _initialize_remote_or1k 创建远程目标。创建并填充一个新的 struct target_opsor1k_jtag_target并通过调用 add_target 将其添加为目标。 大部分目标操作对于 OpenRISC 1000 是通用的独立于实际的低级接口。通过在第4.3节中描述的接口函数中抽象低级接口来实现这一点。 在建立了所有目标函数之后通过调用 add_target 添加目标。 当选择了目标使用 GDB 目标 jtag 命令将使用在 or1k-tdep.c 中定义的全局变量 or1k_target 来引用用于 OpenRISC 1000 架构的目标操作集。 [注意] GDB 有自己的全局变量 current_target它指的是当前的目标操作集。然而这是不够的因为即使通过 OpenRISC 远程接口连接了目标它可能不是当前目标。GDB 使用的层次结构意味着在同一时间可能有另一个处于活动状态的目标。 目标接口的操作大部分涉及操作调试 SPRs。为了避免不断地将它们写入目标它们的值的缓存保存在 or1k_dbgcache 中在任何将取消目标暂停导致其执行的操作之前被清除。 4.3.2. OpenRISC 1000 目标函数和变量提供信息 一组变量和函数提供接口的名称和不同类型的信息。 to_shortname。这是在 GDB 中连接时用于目标的名称。对于 OpenRISC 1000它是 “jtag”因此在 GDB 中通过命令 target jtag … 建立连接。 to_longname。用于 GDB info target 命令的命令的简要描述。 to_doc。此目标的帮助文本。第一句用于关于所有目标的一般帮助关于此目标的详细帮助的完整文本。文本解释了如何直接连接和通过 TCP/IP 连接。 or1k_files_info。此函数为 info target 提供初始信息。对于 OpenRISC 1000它提供在目标上运行的程序的名称如果已知。 OpenRISC 远程目标始终是可执行的一旦连接建立就可以完全访问内存、堆栈、寄存器等。OpenRISC 1000 的 struct target_ops 中的一组变量记录了这一点。 to_stratum。由于 OpenRISC 1000 目标可以执行代码因此将此字段设置为 process_stratum。 to_has_all_memory、to_has_memory、to_has_stack 和 to_has_registers。一旦连接到 OpenRISC 1000 目标它就可以访问其所有内存、堆栈和寄存器因此将所有这些字段设置为 1true。 to_has_execution。当连接初始建立时OpenRISC 1000 处理器将处于暂停状态因此实际上不执行。因此此字段初始化为 0false。 to_have_steppable_watchpoint 和 to_have_continuable_watchpoint。这些标志指示目标是否可以在执行完监视点后立即步进或者监视点是否可以在没有任何效果的情况下立即继续。 如果 OpenRISC 1000 触发硬件监视点受影响的指令尚未执行完成因此必须重新执行它代码无法继续。此外在重新执行时监视点必须暂时禁用否则它将再次触发无法步进。因此这两个标志都设置为 0false。 4.3.3. 控制连接的 OpenRISC 1000 目标函数 这些函数控制与目标的连接。对于远程目标这涉及建立和关闭到驱动硬件的服务器的 TCP/IP 套接字链接。对于本地目标这涉及打开和关闭设备。 or1k_open。通过调用 target_preopen 清理现有的连接并通过 unpush_target 从目标堆栈中移除此目标的任何实例来建立到目标的连接。然后通过低级接口例程 or1k_jtag_init如果请求则重置目标建立连接。 连接建立后将检查目标的 Unit Present SPR 以验证它是否有可用的调试单元。从 CPU Configuration SPR 中读取关于 GPR 数量和匹配点数量的数据并用于更新 struct gdbarch_tdep。 然后目标处理器被暂停以防止进一步执行并等待 1000 微秒以完成暂停。 调试缓存被清除将 Debug Stop SPR 设置为在陷阱异常上触发 JTAG 接口用于调试断点、监视点和单步执行。在执行重新开始之前缓存将被写入 SPRs。 建立连接后目标被推送到堆栈上。它被标记为运行中这将设置与运行中进程相关联的所有标志并更新当前目标的选择根据层次可能是此目标。然而OpenRISC 连接是在目标处理器暂停的情况下建立的因此通过将宏 target_has_execution 设置为 0 来清除 to_has_execution 标志。在 or1k_resume 恢复目标暂停时它将被设置。 作为良好管理的一部分使用 no_shared_libraries 清除所有共享库符号。 GDB 根据其进程和线程 ID 标识所有下级可执行文件。OpenRISC 1000 的此端口用于裸金属调试因此没有可能执行的不同进程的概念。因此null_ptid 被用作目标的进程/线程 ID。这在 GDB 全局变量 inferior_pid 中设置。 [注意] 确保在这个早期阶段建立下级进程/线程 ID以便始终能够唯一标识目标是很重要的。 最后调用通用的 start_remote 来设置准备好执行的新目标。这可能失败因此调用被包装在一个函数 or1k_start_remote 中该函数具有使用 catch_exception 运行的正确原型。如果发生失败则在异常被抛到顶层之前可以将目标弹出。 or1k_close。这通过调用低级接口函数 or1k_jtag_close 来关闭连接。目标已经被弹出并且下级已经被删除参见第4.3.6节因此不需要这些操作。 or1k_detach。这只是从正在调试的目标分离通过调用 or1k_close 来实现。 没有明确的重新连接到目标的函数但通过在 GDB 中使用 target jtag 命令调用 or1k_open 可以实现相同的效果。 4.3.4. OpenRISC 1000 目标函数以访问内存和寄存器 这是一组用于访问目标寄存器和内存的函数。 or1k_fetch_registers。此函数从实际目标寄存器中填充寄存器缓存。与 OpenRISC 1000 的接口仅允许读取内存或 SPRs。然而通用寄存器GPRs被映射到 SPR 空间因此可以通过这种方式读取。 or1k_store_registers。这是 or1k_fetch_registers 的反向操作。它将寄存器缓存的内容写回目标上的物理寄存器。 or1k_prepare_to_store。GDB 允许需要在存储之前进行一些准备工作的目标因此提供了此函数。对于 OpenRISC 1000这不是必需的因此只是返回。 or1k_xfer_partial。这是从目标读取和写入对象的通用函数。然而只需要支持从内存读取和写入的对象类别。通过低级接口例程 or1k_jtag_read_mem 和 or1k_jtag_write_mem 来实现这一点。 generic_load。此通用函数用作目标操作的 to_load 函数。加载 OpenRISC 1000 映像没有什么特别之处。此函数将调用 or1k_xfer_partial 函数以传输映像的每个部分的字节。 4.3.5. OpenRISC 1000 处理断点和监视点的目标函数 OpenRISC 1000 可以支持硬件断点和监视点如果调试单元中有匹配点可用。 [注意] 要注意术语 “监视点” 可能会导致混淆。在 GDB 中它用于指代正在监视读取或写入活动的位置。这可能是通过硬件或软件实现。 如果以硬件形式实现它们可能使用 OpenRISC 1000 调试单元机制该机制也使用术语 “监视点”。本文档在存在混淆风险时使用术语 “GDB 监视点” 和 “OpenRISC 1000 监视点”。 or1k_set_breakpoint。这是设置硬件断点的底层 OpenRISC 1000 函数如果有可用的话。这由 Debug Value 寄存器和 Debug Control 寄存器控制。此函数由目标操作函数 or1k_insert_breakpoint 和 or1k_insert_hw_breakpoint 使用。 通过搜索 Debug Control 寄存器找到第一个没有设置其 DVR/DCR PresetDP标志的寄存器使用 or1k_first_free_matchpoint。 将 Debug Value 寄存器设置为断点的地址将 Debug Control 寄存器设置为在取指令的无符号有效地址等于 Debug Value 寄存器时触发。在 Debug Mode 寄存器 1 中将相应的 OpenRISC 1000 监视点标记为未链接并设置为在 Debug Mode 寄存器 2 中触发陷阱异常。 or1k_clear_breakpoint。这是 or1k_set_breakpoint 的对应部分。它由目标操作函数 remove_breakpoint 和 remove_hw_breakpoint 调用。 在 Debug Control 寄存器中搜索与给定地址匹配的条目使用 or1k_matchpoint_equal。如果找到寄存器则清除其 DVR/DCR Present 标志并在 Debug Mode 寄存器 2 中标记匹配点未使用。 or1k_insert_breakpoint。此函数插入断点。它尝试使用 or1k_set_breakpoint 插入硬件断点。如果失败则使用通用的 memory_insert_breakpoint 设置软件断点。 or1k_remove_breakpoint。这是 or1k_insert_breakpoint 的对应部分。它尝试清除硬件断点如果失败则尝试使用通用的 memory_remove_breakpoint 清除软件断点。 or1k_insert_hw_breakpoint 和 or1k_remove_hw_breakpoint。这些函数类似于 or1k_insert_breakpoint 和 or1k_remove_breakpoint。然而如果硬件断点不可用则它们不会尝试使用软件内存断点。 or1k_insert_watchpoint。此函数尝试插入 GDB 硬件监视点。为此它需要一对链接在一起的 OpenRISC 1000 监视点。第一个将检查大于或等于感兴趣的起始地址的内存访问。第二个将检查小于或等于感兴趣的结束地址的内存访问。如果两个条件都满足则访问类型可以是加载有效地址对于 GDB rwatch 监视点、存储有效地址对于 GDB watch 监视点或两者兼而有之对于 GDB awatch 监视点。 OpenRISC 1000 监视点对必须是相邻的以便可以使用 Debug Mode 寄存器 1 将它们链接在一起但是可能连续的断点已经分散使用了 OpenRISC 1000 监视点。or1k_watchpoint_gc 用于整理可以移动的所有现有 OpenRISC 1000 监视点以找到一对如果可能的话。 or1k_remove_watchpoint。这是 or1k_insert_watchpoint 的对应部分。它搜索匹配的相邻一对 OpenRISC 1000 监视点使用 or1k_matchpoint_equal。如果找到两者都将在其 Debug Control 寄存器中标记为未使用并在 Debug Mode 寄存器 2 中清除。 or1k_stopped_by_watchpoint 和 or1k_stopped_data_address。这些函数用于了解可能已触发的 GDB 监视点。两者都使用实用程序函数 or1k_stopped_watchpoint_info该函数确定是否触发了 GDB 监视点如果触发了则是哪个监视点以及对于什么地址。or1k_stopped_watchpoint 仅返回一个布尔值表示是否触发了监视点。or1k_stopped_data_address 每次触发监视点时调用一次。它返回触发监视点的地址并且必须还清除监视点在 Debug Mode 寄存器 2 中。 4.3.6. OpenRISC 1000 目标函数以控制执行 当使用 GDB 的 run 命令启动执行时它需要在下级上建立可执行文件然后开始执行。这是通过目标的 to_create_inferior 和 to_resume 函数完成的。 一旦开始执行GDB 将等待目标的 to_wait 函数返回控制。 此外目标提供了停止执行的操作。 or1k_resume。这是导致目标程序运行的函数。它是响应于 run、step、stepi、next 和 nexti 指令调用的。 该函数清除 Debug Reason 寄存器在 Debug Mode 寄存器 2 中清除任何监视点状态位然后提交调试寄存器。 如果调用方请求单步执行将使用 Debug Mode 寄存器 1 设置这一点否则将清除此寄存器。 最后目标可以标记为正在执行第一次调用 or1k_resume 时不会标记为正在执行调试寄存器写出并且处理器取消暂停。 or1k_wait。此函数等待目标暂停并分析原因。关于目标暂停的原因的信息通过状态参数返回给调用方。函数返回暂停的进程/线程 ID尽管对于 OpenRISC 1000这将始终是相同的值。 在等待目标暂停使用 or1k_jtag_wait的同时将安装一个信号处理程序以便用户可以使用 ctrl-C 中断执行。 等待返回后所有寄存器和帧缓存都是无效的。这通过调用 registers_changed其反过来清除了帧缓存来实现。 当处理器暂停时Debug Reason 寄存器一个 SPR显示暂停的原因。这可能是由于 Debug Stop 寄存器中设置的任何异常当前仅为 trap由于单步执行由于复位调试器在复位时使处理器停滞或者当目标是体系结构模拟器 Or1ksim 时由于执行退出 l.nop 1。 在所有情况下先前的程序计数器 SPR 指向刚刚执行的指令下一个程序计数器 SPR 指向即将执行的指令。然而对于生成陷阱的监视点和断点前一个程序计数器处的指令将不会执行完成。因此在程序恢复执行时应重新执行此指令而不使断点/监视点生效。 GDB 理解这一点。将程序计数器设置为前一个程序计数器就足够了。GDB 将意识到该指令对应于刚刚遇到的断点/监视点取消断点单步执行此指令然后重新启用断点。通过调用 write_pc 以前的程序计数器值实现。 在此OpenRISC 1000 产生了一个小问题。标准 GDB 方法很好但是如果断点在分支或跳转指令的延迟槽中则重新执行不仅必须是前一个指令而且必须是前一个的前一个指令如果它是跳转并且是跳转-并链接指令则还要恢复链接寄存器。此外这仅在分支确实是前面的指令的情况下才是正确的重启而不是延迟槽是不同分支指令的目标的情况。 在没有“前一个前一个”程序计数器的情况下在所有情况下都无法正确重启。暂时来说不希望在延迟槽上设置断点。但是在实际情况下几乎不可能源代码级别的调试器在延迟槽中设置断点。 将来的更完整的解决方案将使用 struct gdbarch adjust_breakpoint_address 来移动对延迟槽的任何断点的请求以坚持断点放置在前导的跳转或分支上。这对于除最不寻常的代码之外的所有情况都适用该代码将延迟槽用作分支目标。 解决了程序计数器的调整问题后将任何单步操作标记为陷阱即使单步操作不设置陷阱异常也无需重新执行但通过在这里设置标志异常将正确映射到 TARGET_SIGNAL_TRAP 以返回给 GDB。 响应被标记为停止的处理器TARGET_WAITKIND_STOPPED。所有异常都映射到相应的 GDB 信号。如果没有引发异常则将信号设置为默认值除非刚刚执行的指令是 l.nop 1这由体系结构模拟器用于指示终止。在这种情况下响应被标记为 TARGET_WAITKIND_EXITED并且关联值设置为退出返回代码。 现在 Debug Reason 寄存器它是粘性的可以清除并且可以返回进程/线程 ID。 or1k_stop。这将停止执行处理器。为了干净地实现这一点将处理器暂停设置为单步模式然后取消暂停因此执行将在指令结束时停止。 or1k_kill。这是更激烈的终止当 or1k_stop 未能令人满意时使用。假定与目标的通信已中断因此将哀悼目标这将关闭连接。 or1k_create_inferior。这在下级上设置一个要在目标上运行的程序但实际上并未开始运行。这是响应于 GDB run 命令的调用并传递给该命令的任何参数。然而OpenRISC 1000 JTAG 协议无法将参数发送到目标因此这些参数被忽略。 如果已使用 file 命令加载了可执行文件的本地符号表调试会更加容易。这进行检查并发出警告。但是如果不存在可以在没有符号数据的情况下调试 OpenRISC 1000 目标上的代码。 然后通过调用 init_wait_for_inferior 在 GDB 中清除所有静态数据结构断点列表等。 [提示] 如果在 ddd 中使用 OpenRISC 1000 的 GDB则经常会触发有关传递参数的警告。当 ddd 被要求在单独的执行窗口中运行程序时它会尝试通过创建 xterm 并通过伪终端将 I/O 重定向到该 xterm 来实现这一点。重定向是传递给 GDB run 命令的参数。 OpenRISC 1000 的 GDB 不支持这一点。应该使用 ddd 禁用在单独窗口中运行的选项。 or1k_mourn_inferior。这是 or1k_create_inferior 的对应部分在执行完成后调用。它通过调用通用函数 generic_mourn_inferior 进行整理。如果目标仍然显示为正在执行则将其标记为已退出这将导致选择新的当前目标。 4.3.7. OpenRISC 1000目标执行命令的函数 OpenRISC 1000目标实际上没有执行命令的直接方式。然而将SPR访问实现为远程命令提供了一种独立于目标访问协议的访问机制。特别是GDB架构无需了解实际远程协议的细节。 SPR访问被实现好像目标能够运行两个命令readspr用于从SPR获取值writespr用于在SPR中设置值。每个命令都有一个十六进制指定的第一个参数表示SPR编号。writespr有一个第二个参数用十六进制指定表示要写入的值。readspr返回读取的值以十六进制表示为一个数字。 当需要访问SPR时通过将这些命令之一作为参数传递给目标的to_rcmd函数来实现。 or1k_rcmd。要执行的命令readspr或writespr作为第一个参数传递。命令的结果通过第二个参数即独立于UI的文件句柄写回。 readspr被映射到相应的or1k_jtag_read_spr调用。writespr被映射到相应的or1k_jtag_write_spr调用。在发生错误的情况下例如命令格式错误返回结果E01。如果writespr成功返回结果为OK。如果readspr成功返回值为十六进制形式的结果。在所有情况下结果都被写入到指定为函数的第二个参数的UI独立文件句柄中。 4.3.8. 低级JTAG接口 OpenRISC JTAG系统的接口位于gdb/or1k-jtag.c和gdb/or1k-jtag.h中。这些细节对于移植GDB并不直接相关因此这里仅提供概述。完整的细节可在源代码的注释中找到。 接口是分层的以最大程度地提高使用效率。特别是在目标通过TCP/IP远程连接还是直接通过连接到并行端口的JP1头连接时很多功能是相同的。 最高层是公共函数接口这些接口根据在GDB中可见的实体操作打开和关闭连接读写SPR读写内存暂停取消暂停和等待处理器。这些函数始终成功并具有or1k_jtag_的函数前缀。 接下来的层是由OR1K JTAG协议提供的抽象读/写JTAG寄存器读/写一块JTAG寄存器并选择扫描链。这些函数可能会遇到错误但否则不会返回错误结果。这些是静态函数即本地文件中的函数前缀是or1k_jtag_。 接下来的层分为两组一组用于本地连接的JTAGJP1另一组用于通过TCP/IP进行远程连接与上一层中的函数相对应。这些函数检测到错误并返回一个错误代码指示发生了错误。这些是静态函数前缀分别是jp1_和jtr_。 最后一层有不同的版本用于本地连接的JTAG用于驱动JP1接口的低级例程和远程使用用于构建和发送/接收TCP/IP数据包。这些函数检测错误并返回错误代码指示发生了错误。这是带有前缀jp1_ll_和jtr_ll_的静态函数。 错误可以通过静默处理或者如果是致命错误通过GDB的错误函数来处理。 [注意] 少数人现在使用JP1直接连接并且无法确保该代码是否有效 4.4. OpenRISC 1000反汇编器 OpenRISC 1000反汇编器是更广泛的binutils实用工具集的一部分位于opcodes子目录中。它提供两个版本的反汇编函数print_insn_big_or32和print_insn_little_or32用于大端和小端实现的or32-dis.c架构。 指令解码使用or32-opc.c中的有限状态自动机FSA。这是通过从描述指令集的表中构建的build_automata函数在启动时构建的。此函数是在在OpenRISC 1000体系结构被定义后即在_initialize_or1k_tdep函数中立即调用的。 反汇编器利用符号表信息以尽可能替换分支和跳转目标为符号名称。 4.5. OpenRISC 1000专用GDB命令 第2.5节描述了如何扩展GDB命令集。对于OpenRISC 1000架构扩展了info命令以显示SPR值info spr并添加了一个新命令spr以设置SPR的值。 这两个命令是在架构被创建并反汇编器自动机被初始化之后在_initialize_or1k_tdep中添加的。 4.5.1. info spr命令 使用add_info添加了新的子命令 add_info (spr, or1k_info_spr_command,Show the value of a special purpose register);功能由or1k_info_spr_command提供。用户可以通过名称或编号指定组显示该组中所有寄存器的值或者通过寄存器名称显示该寄存器的值或者通过组名称/编号和寄存器名称/编号显示该组中寄存器的值。 参数是使用or1k_parse_params从命令文本中提取的它还处理语法或语义错误。如果成功解析参数则使用UI独立函数ui_out_field_fmt打印结果。 SPR是使用方便函数or1k_read_spr读取的。这将访问转换为调用readspr命令该命令可以通过其to_rcmd目标操作传递给目标请参阅第4.3.7节。这将允许以最适合当前目标访问方法的方式访问SPR。 4.5.2. spr命令 使用add_com添加了新的顶级命令被分类为支持命令class_support“add_com(spr, class_support, or1k_spr_command);”。 功能由or1k_spr_command提供。它还使用or1k_parse_spr_params解析参数尽管现在多了一个参数要设置的值。新值被写入到相关的SPR中并使用ui_out_field_fmt记录更改。 SPR是使用方便函数or1k_write_spr写入的。这将访问转换为调用writespr命令该命令可以通过其to_rcmd目标操作传递给目标请参阅第4.3.7节。这将允许以最适合当前目标访问方法的方式访问SPR。 【2】强烈建议将此作为集合的新子命令。但是spr命令是在GDB 5.0中引入的现在没有替代它的必要。 第5章总结 本应用注释详细描述了将GDB移植到新体系结构所需的步骤。该过程以OpenRISC 1000架构的端口为例进行了说明。 欢迎提出更正或改进的建议。请通过jeremy.bennettembecosm.com与作者联系。 术语表 ABIApplication Binary Interface 应用程序和操作系统之间的低级接口确保程序之间的二进制兼容性。 big endian 计算机体系结构中字节和字寻址之间关系的描述。在big endian架构中数据字中的最低有效字节位于内存中最高的字节地址字节中的字节。 Binary File Descriptor (BFD) 允许应用程序使用相同的例程操作对象文件的软件包。通过创建新的BFD后端并将其添加到库中可以支持新的对象文件格式。 COFFCommon Object File Format 用于Unix系统上的可执行文件、目标代码和共享库计算机文件的格式规范。现在在很大程度上被ELF替代。 ELFExecutable and Linkable Format 一种用于可执行文件、目标代码、共享库和核心转储的通用标准文件格式。这是Unix和类Unix系统上x86的标准二进制文件格式现在在很大程度上替代了COFF。曾被称为Extensible Linking Format。 frame pointer 在基于堆栈的语言中堆栈指针通常指的是本地框架的末尾。帧指针是第二个寄存器它指向本地框架的开头。并非所有基于堆栈的体系结构都使用帧指针。 GPRGeneral Purpose Register 在OpenRISC 1000架构中介于16到32个通用整数寄存器之间的一个。 JTAGJoint Test Action Group IEEE 1149.1标准的通常名称该标准为用于测试印刷电路板和使用边界扫描测试芯片的测试访问端口定义了标准测试访问端口和边界扫描架构。该标准允许在板或芯片内部读取状态因此是调试器连接到嵌入式系统的一种自然机制。 little endian 计算机体系结构中字节和字寻址之间关系的描述。在little endian架构中数据字中的最低有效字节位于内存中最低的字节地址字节中的字节。 MMUMemory Management Unit 一种硬件组件通过页面查找表将虚拟地址引用映射到物理内存地址。当访问时可能需要异常处理程序将不存在的内存页面从后备存储器中带入物理内存。 RTEMSReal Time Executive for Multiprocessor Systems 用于实时嵌入式系统的操作系统提供POSIX接口。它没有进程或内存管理的概念。 SPRSpecial Purpose Register 在OpenRISC 1000架构中控制处理器所有方面的最多65536个寄存器之一。这些寄存器按照2048个寄存器的组进行排列。当前的架构总共定义了12个组。 stack frame 在过程化语言中用于在执行的特定点保存过程中的本地变量值的动态数据结构。 SoCSystem on Chip 包括一个或多个处理器核心的硅芯片。
http://www.zqtcl.cn/news/857203/

相关文章:

  • 网站自适应手机代码网络服务机构的网站
  • 系统网站重庆智能建站模板
  • wordpress适合优化吗宝塔 wordpress优化
  • 怎么利用网站做外链接怎样做公司网站介绍
  • 广州网站优化渠道木门网站模板
  • 手机网站菜单设计wordpress加联系方式
  • 网站管理助手怎么使用多种郑州网站建设
  • 汉中网站建设费用外贸网站服务商
  • 苏宿工业园区网站建设成功案例色流网站如何做
  • 北沙滩网站建设公司电子商务网站建设管理论文
  • 公司备案证查询网站查询系统网页设计html代码大全及含义
  • 成都开发网站建设做网站一般会出现的问题
  • 企业网站设计布局方式如何在社交网站上做视频推广方案
  • 惠城网站建设服务做1688网站需要懂英语吗
  • 请人做网站要多少钱搜索引擎优化概述
  • 郑州中森网站建设免费网站app生成软件
  • 做诚信通网站seo新手快速入门
  • 做网站怎么去找客户带会员中心WordPress免费主题
  • 网站建设资费安平县护栏网站建设
  • 做视频网站侵权吗个体户网站备案
  • 苏州姑苏区建设局网站智慧团建登录官网手机版
  • 如何搭建一个视频网站广告制作方案
  • 网站策划ps苏州建站公司速找苏州聚尚网络
  • 网站备案 关闭客户制作网站时的问题
  • 项目网站分析推荐做ppt照片的网站
  • wordpress注明网站网站建设需要什么手续
  • 厦门过路费网站福建省建设执业资格注册中心网站
  • c 网站开发案例详解手机网站返回顶部代码
  • 济南微网站建设图书馆评估定级关于网站建设标准
  • flash型网站宝塔面板做网站绑定域名