我刚刚阅读了一篇Phrack文章,该文章在HP-UX中进行了反汇编。我读到在HP-UX和SPARC下有两类功能可以使用;叶和非叶功能。以下是我从here获取的反汇编部分。
(gdb) disass leaf
Dump of assembler code for function foo:
0x3280 <leaf>: copy r3,r1
0x3284 <leaf+4>: copy sp,r3
0x3288 <leaf+8>: stw,ma r1,40(sr0,sp)
0x328c <leaf+12>: stw r26,-24(sr0,r3)
0x3290 <leaf+16>: stw r0,8(sr0,r3)
0x3294 <leaf+20>: ldi 1,r19
0x3298 <leaf+24>: stw r19,8(sr0,r3)
0x329c <leaf+28>: ldo 40(r3),sp
0x32a0 <leaf+32>: ldw,mb -40(sr0,sp),r3
0x32a4 <leaf+36>: bv,n r0(rp)
End of assembler dump.
(gdb)
通常在调用函数时,将返回地址压入堆栈,以便程序在函数执行完毕后知道将控件返回到何处。在这些叶子函数的情况下,它是如何工作的?
我没有任何HP-UX / SPARC机器的访问权限,所以无法自己尝试这个(由于同样的原因,我也不太了解这种情况下的程序集。)
有人可以解释在这种情况下控制如何返回到被调用函数?
答案 0 :(得分:4)
首先,您链接到的文档和您显示的代码不是Sparc,而是PA-RISC,它是一种独特的架构。据我所知,没有任何版本的HP / UX可以在基于Sparc的系统上运行。
尽管如此,关于叶子功能的观点在许多架构中都是类似的,包括Sparc,PA-RISC,PowerPC,ARM,MIPS ......实际上所有的RISC架构。对于所有这些,执行函数调用的操作码不会将返回地址存储在堆栈中;事实上,硬件并不存在“真正的”堆栈。相反,只有一个软件约定使用特定寄存器作为堆栈指针。调用操作码将返回的地址存储在特定的寄存器中,通常称为“链接寄存器”。从函数返回的操作码只读取该寄存器。
如果函数本身(让我们称之为A)调用另一个函数(B),那么该嵌套调用也将使用链接寄存器;但是,当它返回时,A将需要链接寄存器的内容。因此,A必须将链接寄存器保存在某处,通常是在内存中,更准确地说是在通常用作“堆栈”的存储区中。
叶子函数是一个不调用其他函数的函数;它只是做它的工作和回报。叶子函数不需要保存堆栈上的链接寄存器,因为没有任何东西会改变链接寄存器的内容。
另外的约束,也是常规的,是关于堆栈帧结构。某些体系结构上的某些操作系统坚持以非常特定的方式使用堆栈的功能(称为“堆栈帧”),调试器可以可靠地探索这些功能。然后,规范要求将返回地址保存在堆栈的精确插槽中,并且被调用的函数必须在执行的早期就这样做。然后,相同的操作系统可能会声明叶子函数不是绝对需要的(如果该函数不调用其他函数,调试器更容易处理无帧函数,即堆栈上没有框架被推入一个缺少的)。不设置堆栈帧比设置堆栈帧更有效(执行速度和代码大小),因此例外。