ZHCADC4A September 2011 – March 2014
每个共享同一库代码的可执行文件必须分配自己的库数据专用拷贝。此外,每个静态链接单元的自有数据(包括 GOT)使用 DP 相对寻址进行寻址,同时使用静态链接时固定的偏移量。(在具有 MMU 的系统上,为完成这一点,通常会使用 PC 相对寻址来实现位置无关虚拟偏移,并使用地址转换在同一(虚拟)地址,将数据段的多个物理拷贝实例化。)对于没有 MMU 的系统,如 C6000,通常依赖某种静态基址指针 (DP) 和偏移寻址。
给定静态链接单元的所有寻址均与它的数据段相对,因此与任何其他静态链接单元无关。结果会得到一个模型,其中由可执行文件和一个或多个(可能是共享的)库组成的给定程序具有多个数据段,每个数据段具有不同的地址,而 DP 相对偏移量便基于这些地址。当控制从一个模块转移到另一个模块时,DP 必须更改为新模块数据段的基础地址。
此模型的一般问题,同时也是大多数静态基址寻址方案所常见的问题是:
其他架构采用了各种解决方案,如 FDPIC、XFLAT 和 DSBT。我们选择了采用 DSBT 模型,作为效率、兼容性和灵活性之间的最佳折衷。
当调用导入函数时,被调用者负责将 DP 设置为指向其数据段(更准确地说,是包含被调用者的模块的底层可执行文件的专用数据段拷贝),并负责在返回后立即将其恢复。
在我们解释被调用者如何实现这一点之前,请考虑两个观察结果。首先,每个模块都有自己的数据段,以及自己的基地址,如果在多个可执行文件之间共享,则每个模块还有该段的专用拷贝,以及不同的基地址。此外,这些地址是动态确定的。显然,与存储在 GOT 中的地址非常相似的是,基地址不能是绝对地址,因此必须存储在数据段中。
其次,虽然被调用者 负责更改 DP,但进入被调用者 后,DP 仍然指向调用者的数据段。因此,被调用者只具有调用者的上下文,进而以某种方式设置自己的 DP。
解决方案是,每个数据段的前几个字包含一个表拷贝,称为数据段基表 (DSBT),并列出构成程序的其他模块中所有其他段的基地址。每个共享库都会分配到一个唯一索引,该索引从 1 开始。索引 0 为可执行文件而保留。被调用者使用其分配到的索引,在调用者的表拷贝中查找自己的基地址,然后将该值分配给 DP。在给定可执行文件及其共享库的专用数据段内,每个 DSBT 拷贝都相同,从而使任何被调用者都可以使用任何调用者的表来查找自己的基址。
DSBT 方法具有所需的特性,即动态链接的惩罚仅局限于导出函数。对于不使用动态链接的裸机程序或不使用导出函数的可执行文件,ABI 不受影响。通过明智地使用工具链特定声明结构,明确标识外部可访问的函数(请参阅节 6.7.2),程序员可以尽可能减少开销。在确实需要调整 DP 的函数中,开销通常仅为 3 条指令。
DSBT 模型的缺点是,需要协调库索引的分配并在最大模块数量上强制达成一致,这将决定每个数据段中的表大小。
DSBT 在 .dsbt 段中由静态链接器分配,并且必须位于每个模块第一个 DP 相对段的基地址,以便 DP 指向它。加载模块时,动态链接器会将表条目初始化。
可执行文件始终使用索引 0 来访问表;库索引从 1、2 或为特定平台指定的某个其他索引开始。可以通过以下两种方式之一分配库的索引:
关于每个对象的 DSBT,必须至少与分配给作为程序一部分动态加载的任何模块的最大索引一样大。动态链接器负责确保所有模块都具有足够大的 DSBT;如果不是这样,必定无法加载程序。DSBT 的大小在静态链接时(或静态链接后工具)通过命令行选项或环境变量指定。嵌入式系统通常需要少量动态库;因此 DSBT 的典型大小为 5 或更小。
模块的动态段包含 C6000 特定标记,这些标记指定其 DSBT 表的大小及其索引(如果已分配)。节 14.3.2中详细介绍了这些信息。