5

Itanium C++ ABI中的“GP/函数地址对”是什么意思?GP代表什么?

4

2 回答 2

7

简短的解释:gp对于所有实际方法,它是符合 Itanium ABI 的所有功能的隐藏参数。它是一种this指向函数使用的全局变量的指针。据我所知,没有主流操作系统可以做到这一点。

GP 代表“全局指针”。它是由可执行文件静态分配的数据的基地址,Itanium 体系结构有一个专门用于它的寄存器。

例如,如果你的程序中有这些全局变量和这个函数:

int foo;
int bar;
int baz;

int func()
{
    foo++;
    bar += foo;
    baz *= bar / foo;
    return foo + bar + baz;
}

gp/function 对在概念上是&foo, &func. 生成的代码func将参考gp查找全局变量的位置。编译器知道foo可以在gpbar可以在gp + 4baz可以在gp + 8.

假设func 在外部库中定义,如果您从程序中调用它,编译器将使用如下指令序列:

  • 将当前 gp 值保存到堆栈中;
  • 将代码地址从该对加载func到某个寄存器中;
  • 将同一对中的 gp 值加载到 GP 中;
  • 对我们存储代码地址的寄存器执行间接调用;
  • 恢复我们之前保存在堆栈中的旧 gp 值,恢复调用函数。

这使得可执行文件完全与位置无关,因为它们从不将绝对地址存储到数据符号,因此无论有多少进程使用它,都可以在内存中只维护任何可执行文件的一个实例(您甚至可以加载在单个进程中多次执行相同的可执行文件,并且在系统范围内仍然只有一份可执行代码副本),代价是使函数指针有点奇怪。使用 Itanium ABI,函数指针不是代码地址(就像“常规”x86 ABI 一样):它是 gp 值和代码地址的地址,因为如果代码地址可以的话,它可能就不值钱了。 t 访问它的全局变量,就像一个方法如果没有this指针可能无法做很多事情一样。

我知道的唯一使用此概念的其他 ABI 是 Mac OS Classic PowerPC ABI。他们称这些对为“过渡向量”。

由于 x86_64 支持 RIP 相对寻址(x86 没有等效的 EIP 相对寻址),因此现在很容易创建与位置无关的代码,而无需使用额外的寄存器或不必使用“增强”函数指针。代码和数据只需要保持不变的偏移量。因此,Itanium ABI 的这一部分可能在 Intel 平台上已经消失了。

来自安腾寄存器约定

8.2 gp 寄存器

每个引用静态分配的数据或调用另一个过程的过程都需要一个指向其 gp 寄存器中的数据段的指针,以便它可以访问其静态数据及其链接表。每个加载模块都有自己的数据段,在调用加载模块中的任何入口点之前,必须正确设置 gp 寄存器。

链接约定要求每个加载模块准确定义一个 gp 值来引用其短数据段中的位置。预计将选择此位置以最大化短位移立即指令用于寻址标量和链接表条目的有用性。DLL 加载器在将其数据段加载到内存后,将为每个加载模块确定 gp 寄存器的绝对值。

对于加载模块中的调用,gp 寄存器将保持不变,因此可以对已知为本地的调用进行相应的优化。

对于加载模块之间的调用,gp寄存器必须为新的加载模块初始化正确的gp值,调用函数必须保证自己的gp值被保存和恢复。

于 2013-09-20T02:42:02.760 回答
0

只是从另一个答案中对此引用的评论:

It is expected that this location will be chosen to maximize the usefulness of short-displacement immediate instructions for addressing scalars and linkage table entries.

这是在说什么:Itanium 有三种不同的方式将值放入寄存器(这里的“立即”表示“从基数偏移”)。您可以从任何地方支持完整的 64 位偏移,但这需要两条指令:

// r34 has base address
movl r33 = <my immediate>
;;
add r35 = r34, r35
;;

这不仅需要 2 个独立的时钟,而且需要跨 2 个捆绑包的 3 个指令槽才能实现。

有两个较短的版本:add14(also adds) 和add22(also addl)。不同之处在于每个人可以处理的直接大小。每个都占用一个“A”插槽iirc,并在一个时钟内完成。

add14可以使用任何寄存器作为源和目标,但最多只能处理 14 位立即数。

add22可以使用任何寄存器作为目标,但对于源,只分配了两个位。所以你只能使用r0, r1, r2,r3作为源代码。r0不是一个真正的寄存器 - 它被硬连线为 0。但是使用其他 3 个中的一个作为本地堆栈寄存器,这意味着与使用本地堆栈寄存器相比,您可以使用简单的偏移量来寻址 256 倍的内存。因此,如果您将全局基地址放入r1(约定)中,您可以在必须为下一段代码执行单独的 movl 和/或修改 gp 之前访问更多的本地偏移量。

于 2014-03-04T20:36:17.663 回答