先说说几个概念:
(1) 什么是inferior
GDB表示一个程序执行的状态叫inferior。一个Inferior通常情况下是和一个进程相关,但它也应用于没有进程概念的target。每次运行一个可执行的程序就会创建一个新的inferiro。
(2) 什么叫inferior call
在调试状态下,当我们想要判断一个函数的输出结果是否和预期结果相同的时候。我们可以在GDB中使用p fun(arg) 或者call fun(arg)等方式来让函数在目标端执行,执行完毕之后输出程序执行结果,并保持目标系统和调用这个函数之前的状态一致。
本文将针对sparc v8平台对GDB中inferior call 的实现进行分析。
通过对GDB-7.3中inferior call实现的跟踪分析,其主要实现函数为call_function_by_hand,该函数主要做了如下几件事情:
(1) 将断点链表保存起来
(2) 保存调用者的寄存器和与inferior相关的其它状态,以便于inferior call返回之后能恢复到调用之前的状态。
(3) 变换堆栈指针以便为inferior call做准备。这部分涉及到具体目标平台和相关OS ABI约定。
1> 在一台RISC架构的机器上,调用一个没有参数和没有返回值的参数所形成的栈通常情况下是不会push任何东西的,这样使得其SP和FP的值相等。
2> 在SPARC中,叶子函数可能由于编译器的优化,将会使用调用者的堆栈,这样使得FP和SP没有变化。如何1> 和2>情况下所说的函数产生一系列调用将形成一连串相似的栈。这样调试器在进行栈展开的时候可能陷入无限循环
为了避免上述所说情况的发生,我们通常会增长一些栈空间,并让SP满足此时OS ABI要求的对齐要求。
(4) 根据符号表找到inferior call函数的前两条地址,其目的是为了将其分别赋值给pc 和npc
(5) 紧接着要做的事情就是按照OS ABI以及目标平台处理器的一些约定,调整SP的值,开辟栈空间保存调用一个函数要做的事情。下面根据SPARC处理器的函数调用约定,展示调用一个函数,它的栈的是如何布局的。
图1 用户栈布局
下面对上图做一些解释:
1. 上图适用于被优化的叶子函数调用之外的所有的函数调用。
2. SPARC V8平台中每个函数调用栈都要分配空间
1> 六个字的空间
为了给被调用者传递参数
2> 一个字的空间
这是用于当调用者期待被调用者返回一个聚合数据。
3> 16个字的空间
用于保存caller的 in 和 local 寄存器
3. 根据需要在编译的时候分配栈空间
1> Caller传递给callee的参数超过六个的时候需要的额外空间
2> Callee中所有自动数据、自动变量等
3> 编译器产生的临时值
4> 浮点寄存器保存的地方,加入在callee中用到了浮点指令
4. 通过c库里alloca动态分配的空间
(6) 在(5)调整的过程中改变寄存器的一些值,将计算出来的call-pc值写入寄存器o7
将参数的值写入O0,O1….等寄存器
存储参数的值到栈空间对应的地址中
调整pc和npc的值分别为要执行的函数的第一条和第二条地址
在栈中存放断点的地方写入ta 1
(7) 然后就是调试器执行恢复程序运行,遇到断点停下来之后,恢复(1) (2)中保存的值
总结:inferior call的实现原理还是比较简单的,关键要根据目标平台的一些约定,开辟好调用一个函数所需要的栈空间,并将相关值写入相关寄存器和指定内存处即可
转自 http://blog.chinaunix.net/uid-20912808-id-2951159.html