...
Initialization of bionic_tcb and tpidr_el0: The function libc_init_main_thread_early is called to initialize bionic_tcb and tpidr_el0. This involves setting up the initial values for bionic_tcb and setting the tpidr_el0 register.
Initialization of TLS_SLOT_DTV in bionic_tcb: The function init_tcb_dtv is called to initialize the TLS_SLOT_DTV field in the bionic_tcb structure. The update flag for this field is set to 0.
Calling linker_setup_exe_static_tls: This function is called assuming that the main program contains TLS program segments. The following actions are taken:
a. Reserve space and determine the position of the bionic_tcb object, TLS space, and their locations in a variable of type StaticTlsLayout.
b. Call register_tls_module to obtain the module ID and add it to a variable of type TlsModules.
c. Reserve space and determine the position of the bionic_tls object in the variable.Loading of TLS program segments in the main program dependencies: If there are TLS program segments in the main program's dependencies, the function soinfo::register_soinfo_tls is called. This reserves space for TLS segments and their positions in the StaticTlsLayout variable, and adds them to the TlsModules variable.
Calling linker_finalize_static_tls: This function calculates the total size of the reserved space in the StaticTlsLayout variable, which represents the size of the static TLS block.
Calling __allocate_thread_mapping: This function allocates memory space on the main program's stack for the static TLS block. The static TLS block includes bionic_tcb, bionic_tls, and TLS program segments.
Calling __init_static_tls: This function copies the contents of the TLS segments from the dynamic libraries specified in the TlsModules variable into the reserved space of the static TLS block.
Calling bionic_tcb::copy_from_bootstrap: This function synchronizes the contents of bionic_tcb with the initial values set during the first step of initialization.
Calling __init_tcb: This function updates bionic_tcb with additional information.
Calling __init_bionic_tls_ptrs: This function updates the addresses of bionic_tls.
Relocation Initialization of TLS Variable's GOT Table Entries
Relocation Initialization of TLS Variable's GOT Table Entries
重定位初始化TLS变量的GOT表项
...
GD (Global Dynamic): The relocation types for GD include R_AARCH64_TLS_
...
DTPMOD64 and R_AARCH64_TLS_
...
DTPREL64. These types initialize the adjacent GOT table entries with the module ID and the offset of the variable within its TLS segment, respectively.
LD (Local Dynamic): The implementation of LD is the same as GD for AArch64. However, for x86_64, the relocation type is R_X86_64_
...
IE:重定位类型为R_AARCH64_TLS_TPREL64,将其GOT表项值初始为static STL block上的偏移
...
LE:无重定位项和GOT表项,不需要初始化
...
TLSDESC:重定位类型为R_AARCH64_TLSDESC,将其相邻的GOT表项初始化为tlsdesc_resolver_static函数地址和静态TLS块上的偏移量
从上初始化流程中,可看出dtv数组为空,dtv数组只在访问TLS变量时才会创建,这种延时分配的好处有:
对于TLS变量的IE和LE访问方式来说,可直接通过段寄存器和偏移量来获得TLS变量的地址,不需要通过dtv数组查找。
对于TLS变量的GD和LD访问方式来说,通过调用__tls_get_addr函数获取TLS变量地址,在其检查dtv数组为空时,根据TLS动态库的数量重新分配dtv数组空间
对于TLS变量的TLSDESC访问方式来说,其tlsdesc_resolver_static函数直接返回TLS变量在static STL block上的偏移量,也无需分配dtv数组
...
DTPMOD64, and the adjacent GOT table entries are initialized with the module ID and an offset of 0.
IE (Initial Executable): The relocation type for IE is R_AARCH64_TLS_TPREL64. It initializes the value of the corresponding GOT table entry with the offset on the static TLS block.
LE (Local Executable): There are no relocation entries or GOT table entries to initialize, so no initialization is needed.
TLSDESC: The relocation type for TLSDESC is R_AARCH64_TLSDESC. It initializes the adjacent GOT table entries with the address of the
tlsdesc_resolver_static
function and the offset on the static TLS block.
From the initialization process described above, it can be seen that the dtv
(Dynamic Thread Vector) array is initially empty. The dtv
array is only created when accessing TLS variables. This delayed allocation has the following benefits:
For IE and LE access to TLS variables, the address of the TLS variable can be obtained directly using the segment register and the offset, without the need to look up the
dtv
array.For GD and LD access to TLS variables, the
__tls_get_addr
function is called to obtain the TLS variable's address. When checking that thedtv
array is empty, thedtv
array is reallocated based on the number of TLS dynamic libraries.For TLSDESC access to TLS variables, the
tlsdesc_resolver_static
function directly returns the offset of the TLS variable on the static TLS block, without the need to allocate thedtv
array.
From the examples mentioned above, it is evident that the TLSDESC access mode provides significant optimization for accessing TLS variables on the static TLS block compared to GD. The TLSDESC mode directly returns the offset of the static TLS block in the GOT table entry, while GD requires accessing the dtv
array to calculate the address.
动态加载库初始化TLS
bionic通过dlopen动态加载库,初始TLS的步骤如下:
...