iOS/Macos C++ thread_local 具体实现分析

示例如下:

直接断点运行查看汇编实现

由于我们对 thread_local tls_variable 变量进行了 ++ 操作,因此在汇编中大概率会有一个 add x?, x?, #1 的指令,因此通过观察下图划线的三条指令,可以得知 x8 寄存器中存储的地址就是获取 tls_variable 变量的 dyld 函数 tlv_get_addr

tlv_get_addr 进行符号断点分析发现:

  1. TPIDRRO_EL0 寄存器对应内存中存在 pthread_key_t key 对应的值,则直接返回内存地址 ( 函数 instantiateTLVs_thunk 的第一个参数的签名为 pthread_key_t )
  2. 如果不符合 1,则执行 dyld instantiateTLVs_thunk 以及 RuntimeState::_instantiateTLVs

tlv_get_addr 函数的源码也可通过 dyld 的 threadLocalHelpers.s 文件查看

instantiateTLVs_thunk 的实现主要是对 RuntimeState::_instantiateTLVs 的包装

RuntimeState::_instantiateTLVs 实现如下: 针对单个 pthread_key_t 的 lazy 实现,使用 libsystem 的 malloc 开辟相关的内存,再保存到 pthread 的 tsd 数组中

libpthread 中 _pthread_setspecific 的实现如下:

基本流程了解后,目前未解决的问题有如下:

  1. 变量 thread_local int tls_variable 是如何访问到的?
  2. tlv_get_addr 函数是如何被设置到 x8 寄存器对应内存?其中偏移值为 #0x8 #0x10 的内存具体有什么含义?
  3. TPIDRRO_EL0 寄存器是何时被赋值的?

问题一:

tls_variable 变量是如何访问到的?

注意这里的 adrp x0, 5 指令,代表 ( 当前 pc 寄存器值 & page_size ) + 5 * page_size 的结果赋值到 x0 寄存器。由于在 Macos 下 page_size 是 4K,因此这里的计算方式为 x0 = (0x1000030a4 & 0x1000) + 5 * 0x1000 = 0x100008000

同时该内存在进程中所在的 section 为 __DATA,__thread_vars,我们的进程中有两个 thread_local 变量,此 section 的大小却为 0x30,因此推断每个变量在 Section 中占用 0x18 字节,同时也能和汇编中的 #0x8, #0x10 的偏移量访问对应。同时 thread_local 变量的初始值是通过 __DATA,__thread_data__DATA,__thread_bss 两个 Section 来初始化的(相关代码可以在 ld64 和 dyld 中找到)

问题二:

tlv_get_addr 函数是如何被设置到 x8 寄存器对应内存?其中偏移值为 #0x8 #0x10 的内存具体有什么含义?

arm64 dyld 在进程启动时,forEachThreadLocalVariable函数会以单次 0x18 (struct TLV_Info) 字节大小遍历 __DATA,__thread_vars,同时在 #0x0 设置 tlv_get_addr 函数指针,#0x8 设置 pthread_key_t,#0x10 代表 offset。TLV_Info 结构体如下:

C 复制代码
struct TLV_Thunk
{
    void*   (*thunk)(TLV_Thunk*);
    size_t	  key;
    size_t	  offset;
};

因此 #0x0 指的是此处的 thunk, #0x8 是 pthread_key,#0x16 是 offset 变量

问题三: TPIDRRO_EL0 寄存器是何时被赋值的?

明确一个结论:用户态下 TPIDRRO_EL0 是无法被设置的,只有在内核态才能。

默认情况下, libpthread 在初始化线程时将会使用 struct phthread_s 成员变量 tsd 的起始地址作为 TPIDRRO_EL0 寄存器的值

最终在内核态的 xnu/osfmk/arm/machdep_call.c 设置 TPIDRRO_EL0 寄存器

因此,如果我们能使用用户态 API 直接设置 TPIDRRO_EL0 寄存器,即可伪造指定线程的 TLS

相关推荐
程序员老刘2 天前
Flutter官方拒绝适配鸿蒙的真相:不是技术问题,而是...
flutter·harmonyos·客户端
鹏多多3 天前
解锁flutter弹窗新姿势:dialog-flutter_smart_dialog插件解读+案例
前端·flutter·客户端
前端拿破轮4 天前
ReactNative从入门到性能优化(一)
前端·react native·客户端
huangyuchi.7 天前
【Linux网络】Socket编程实战,基于UDP协议的Echo Server
linux·运维·服务器·udp·socket·客户端·网络通信
yuec10 天前
iOS 26 你的 property 崩了吗?
ios·客户端
我有与与症10 天前
从0使用Kuikly框架写一个小红书Demo-Day6
客户端
程序员老刘11 天前
Flutter版本选择指南:避坑3.27,3.35基本稳定 | 2025年10月
flutter·客户端
超低空12 天前
Android MediaSession深度解析:车载音乐播放器完整案例
android·架构·客户端
程序员老刘14 天前
别再抱怨Flutter方案太多了,这个就叫生态!
flutter·客户端