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

相关推荐
程序员老刘35 分钟前
Flutter版本选择指南:3.35稳定,3.38发布 | 2025年11月
flutter·客户端
fouryears_234172 天前
Android 与 Flutter 通信最佳实践 - 以分享功能为例
android·flutter·客户端·dart
程序员老刘6 天前
跨平台开发地图:客户端技术选型指南 | 2025年11月 |(Valdi 加入战场)
flutter·react native·客户端
程序员老刘7 天前
Flutter 3.38 版本更新:客户端开发者需要关注这三点?
flutter·客户端
想不明白的过度思考者7 天前
Cookie与Session深度解析:原理、区别
网络·http·客户端·cookie·session·服务端
程序员老刘10 天前
4:2:1!老刘的三季度项目报告
flutter·harmonyos·客户端
Jony_14 天前
Android 类加载机制
前端·客户端
猛喝威士忌14 天前
Tauri 和 enigo 你们不许再崩溃啦!
rust·客户端
JulyYu16 天前
【Android】针对非SDK接口的限制解决方案
android·客户端