本文遵从 GNU Free Documentation License (see http://www.gnu.org/copyleft/fdl.html ),并特别对 冒充另类(TeaWater) 的《移植GDB》一文表示感谢 (see http://teawater.spaces.live.com )。由于大体和细节的东西在《GDB Internal》和《移植 GDB》两文中已经有了很多描述,所以本文的目的在于 Howto,step by step 地描述如何为 GDB 添加新的目标机,并对某些以上两文中阐释不清的地方做出自己的解释。
3. taget dependent 文件补充说明
其实关于 solrex-tdep.h, 和 solrex-tdep.c 文件中所涉及的函数, TeaWater 的《移植 GDB》一文讲得非常清晰,我也没必要重复,我只对一些他没提及的东西做一下解释。
(1) solrex-tdep.h 中应该放些什么
其实这个主要是根据需要,如果一些东西会被别的文件使用,那就放在这里,如果没有,这个文件甚至不需要。当然,一些应该放在头文件中的东西还是放这里比较好。
(2)关于 struct gdbarch_tdep
这个结构体是用户定义的,最重要的使用也是在这两个文件中。可以根据需要增减,一般的话需要几个:registers map,重要的 register number,然后就是 abi 的东西。只要用户觉得某个东西有用,就可以放在这个结构体里面。
(3)关于 pseudo registers
这个东西主要看编译器的实现,如果编译器就根本没有这些东西,没有必要处理这些东西。
(4)关于 unwind_pc
不要被它的名字迷惑,unwind_pc 所起到的作用原理很简单,返回一个位于(参数 next_frame 的上一层 frame)中的地址,一般情况下就是这个函数的返回地址。因为函数的返回地址一般就是对这个函数调用的汇编指令的下一个指令,GDB 内部会把这个返回地址再减去一个指令长度,这样就回到了函数调用那句话,函数调用肯定位于被调用的函数的上一个 frame 中。但是有两种情况你需要考虑,如果 next_frame 是一个 sentiniel frame, 只需要返回当前 pc 的位置,如果是一个 normal frame,就需要返回这个 frame 的函数返回地址。这个具体怎么做需要看特定的 target,比如 mips 很简单,只是把 pc 给它,但不是所有的 target 都是这样的。只需要看一下汇编代码就知道该怎么做了,一般情况下进入一个函数,首先是把移动 sp, 给这个函数留出一段栈地址,然后是把 ra 压栈。但是有时候也不完全这样,假如 ra 不是 GPR,不能直接存入栈中,只能先放到一个 GPR 中然后再把那个 GPR 压栈。
之所以要讲到上面的例子就是因为,GDB 的逻辑是这样的:告诉他要 unwind 哪个 register,它会先调用注册的 frame_prev_register 函数去找这个 register,很多情况下 target 都是先注册 dwarf2 的 dwarf2_frame_prev_register。这个函数会根据 debugging information 中的 FDE 和 CIE 去找这个 register 是放到哪里了,然后去那里找到这个 register 的值。但是这个函数有个缺陷就是,假如把一个 register 放入另一个 register 中,它只会到下一层的 frame 中去找那个 register,这样在上段说的最后一个例子,就会出现错误,因为被存入的 register 只是做一个中间值的用处,它会把自己压到栈中,而 dwarf2_frame_prev_register 到下一层的 frame 中去找它的值,显然不可能得到正确的结果。
(5)关于注册特定的 frame_unwind_append_sniffer 有没有用
在很多情况下,尤其是在 linux 的编译器都会为 ELF 文件加入 DWARF2 格式的 debugging information,所以很多情况下用户注册的 frame_sniffer 都是没有太大用处的,但是如果不注册也会发生问题。因为 GDB 中 DWALF2 的处理是先找 symbol 里对应于这个 pc, 有没有 FDE,如果有 FDE 就采用 dwarf2 的 frame sniffer,如果没有就采用用户另外注册的 frame sniffer。如果用户没有另外注册 frame sniffer 的时候,GDB 会出现 internal error,而当注册了以后,GDB 内部会在二进制文件中找这个 pc,如果找不到,会返回用户不知道现在位于什么位置。所以,当二进制文件中有 debugging information 时候,再注册特别的 frame sniffer 只是为了不出现 internal error。因为当 pc 正确时候,自然有 dwarf2 处理,当 pc 不正确的时候,GDB 也不会使用用户定义的 frame sniffer。用户定义的 frame sniffer 唯一有用的时候就是二进制文件中没有 debugging information,这样 GDB 会调用用户定义的 frame sniffer 来找 frame 的开始位置,栈分布和 register 的处理。
(6) gdbarch 中那么多东西,应该注册多少
我觉得,至少应该告诉 GDB 的是: info.byte_order, tdep, num_regs, sp_regnum, pc_regnum, stab_reg_to_regnum, dwarf2_reg_to_regnum, register_name, register_type, skip_prologue, inner_than, unwind_pc, call_dummy_location, unwind_dummy_id, push_dummy_call, print_registers_info, print_insn,然后再加上注册一下 frame_unwind_append_sniffer 和 frame_base_append_sniffer 就行了,剩下的就要看各个 target 的特性,需要不需要添加了。
唉,写了以后才发现自己没多少可写的,因为每个函数的用处 TeaWater 的《移植 GDB》中都讲到了,而要是真 step by step 地写,工作量也太大了点,算了,就这样吧,其实很多东西是只有到用的时候才知道哪里会出现问题的。不过用 GDB debug GDB 也是一件很有趣的事情。