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

相关推荐
刘广睿1 天前
素材库智能集合怎么设计?把多维筛选存成动态收藏的功能复盘
客户端·架构设计·产品设计·素材管理
skr爱码士6 天前
15_国际化和本地化:tr()、ts 文件、QM 文件、多语言切换
c++·qt·ui·客户端
明月_清风10 天前
🖥️ Electron 三进程 Host 实战:从架构设计到生产落地的完整指南
前端·electron·客户端
skr爱码士11 天前
10_Qt Designer 与 UI 文件(.ui 原理、uic 编译、动态加载)
c++·qt·ui·客户端
程序员老刘14 天前
Flutter版本选择指南:3.47压哨发布,9月大限只剩一周 | 2026年8月
flutter·ai编程·客户端
skr爱码士16 天前
05_Qt 核心模块概览——Qt Core、Gui、Widgets、Quick 的职责划分
c++·qt·系统架构·客户端
skr爱码士19 天前
03_第一个Qt项目:Hello World 的完整拆解
c++·qt·客户端
nice先生的狂想曲20 天前
uniapp、uniappx、flutter热更新、全量更新
app·客户端
程序员老刘21 天前
跨平台开发地图 | 2026年8月
flutter·ai编程·客户端
程序员老刘23 天前
Flutter 3.47 更新要点:Material 正式分家,老项目先别急着更新
flutter·ai编程·客户端