ZHCADC4A September 2011 – March 2014
这是最通用的 TLS 访问模型。使用该访问模型的对象可用于构建任何 Linux 模块:可执行文件、初始加载模块和 dlopened 模块。在静态链接期间,为该模型生成的代码不能假设模块 id 或偏移是已知的。
采用这种访问模型,可在运行时加载动态模块。为实现这种可能性,线程库的线程管理架构必须提供一种方法,以便在运行时加载和卸载动态模块时,添加和移除 TLS 块。
编译器生成对 __tls_get_addr() 的调用来获取线程局部变量的地址。模块 TLS 块中的模块 id 和线程局部变量的偏移量都是作为形参来传递的。该代码从全局偏移量表 (GOT) 条目中获得模块 id 和偏移量,以确保位置独立性 (PIC) 和符号占先。
__tls_get_addr() 函数传递模块 id 和偏移量的最简单方法如下所示:
void * __tls_get_addr(unsigned int module_id, ptrdiff_t offset);
请注意,两者都是 32 位实参,GOT 条目也是 32 位条目。作为优化方案,如果 ISA 能够支持,我们可将这两个 GOT 条目加载为 64 位双字。这两个 GOT 条目必须连续分配并与 64 位边界对齐。可将该 GOT 实体视为如下结构:
struct TLS_descriptor
{
unsigned int module_id;
ptrditt_t offset;
} __attribute__ ((aligned (8)));
那么,__tls_get_addr() 接口变为:
void * __tls_get_addr(struct TLS_descriptor);
在该 EABI 中,大小为 64 位或更小的结构由值传递,从而在 A5:A4 寄存器对中传递 TLS 描述符。在小端字节序模式下,模块 id 在 A4 中传递,偏移量在 A5 中传递。在大端字节序模式下,按照 C6x EABI 调用约定交换寄存器。本节中的示例使用小端字节序模式。
使用该接口,线程局部访问变为以下内容(适用于 C64 及更高版本):
LDDW *+DP($GOT_TLS(X)), A5:A4 ;reloc R_C6000_SBR_GOT_U15_D_TLS
|| CALLP __tls_get_addr,B3 ; A4 has the address of X at return
LDW *A4, A4 ; A4 has the value of X
重定位 R_C6000_SBR_GOT_U15_D_TLS 使得链接器为 x 的模块 id 和偏移量创建 GOT 条目,如下所示:
64-bit aligned address:
GOT[n] ;reloc R_C6000_TLSMOD (symbol X)
GOT[n+1] ;reloc R_C6000_TBR_U32 (symbol X)
然后,链接器使用 GOT 实体的 DP 相对偏移量来解析 R_C6000_SBR_GOT_U15_D_TLS 重定位。动态加载器将 R_C6000_TLSMOD 解析为在其中定义 x 的模块的模块 id。它将 R_C6000_TBR_U32 解析为模块 TLS 块中 x 的偏移量。
C6x ISA 当前无直接加载 64 位 TLS 描述符的指令。但是,我们使用 64 位描述符来定义 __tls_get_addr() 接口,以期未来 ISA 具有此类支持功能。
void * __tls_get_addr(struct TLS_descriptor);
链接器须连续分配线程局部变量的模块 id 和偏移量的 GOT 条目,并在发现 R_C6000_SBR_GOT_U15_D_TLS 重定位时将第一个条目与 64 位边界对齐。
由于不支持 DP 相对 64 位加载,因此可在当前 ISA 上使用以下序列:
LDW *+DP($GOT_TLSMOD(X)), A5 ;reloc R_C6000_SBR_GOT_U15_W_TLSMOD
LDW *+DP($GOT_TBR(X)), A4 ;reloc R_C6000_SBR_GOT_U15_W_TBR
|| CALLP __tls_get_addr,B3 ; A4 has the address of X at return
LDW *A4, A4 ; A4 has the value of X
重定位 R_C6000_SBR_GOT_U15_W_TLSMOD 和 R_C6000_SBR_GOT_U15_W_TBR 使得链接器为 x 的模块 id 和偏移量分别创建 GOT 条目。该访问模式不要求这些 GOT 条目是连续且 64 位对齐的。如果链接器也未发现同一符号的 DW_TLS 重定位,则可自由分别定义模块 id 和偏移量 GOT 条目,无需 64 位对齐。但是,如果除同一符号的 TLSMOD/TBR 重定位之外还发现了 DW_TLS,则必须定义 64 位对齐的连续 GOT 条目,并将其重新用于 TLSMOD/TBR 重定位。
如果必须使用 far DP 寻址来寻址 GOT,则通用动态寻址变为:
MVKL $DPR_GOT_TLSMOD(X), A5 ;reloc R_C6000_SBR_GOT_L16_W_TLSMOD
MVKH $DPR_GOT_TLSMOD(X), A5 ;reloc R_C6000_SBR_GOT_H16_W_TLSMOD
ADD DP, A5, A5
LDW *A5, A5
MVKL $DPR_GOT_TPR(X), A4 ;reloc R_C6000_SBR_GOT_L16_W_TBR
MVKH $DPR_GOT_TPR(X), A4 ;reloc R_C6000_SBR_GOT_H16_W_TBR
ADD DP, A4, A4
LDW *A4, A4
|| CALLP __tls_get_addr,B3 ; A4 has the address of X at return
LDW *A4, A4 ; A4 has the value of X
__tls_get_addr() 可计算线程局部地址,如下所示:
void * __tls_get_addr(struct TLS_descriptor desc)
{
void *TP = __c6xabi_get_tp();
int *dtv = (int*)(((int*) TP)[0]);
char *tls = (char *)dtv[desc.module_id];
return tls + desc.offset;
}