如何将父线程ID传递给子线程中的LLVM函数钩子
可行实现方案
下面提供3种不同适用场景的落地思路,覆盖大部分业务需求:
方案1:线程入口函数包装(通用无侵入方案,适配所有线程库)
这是最常用的实现方案,不需要修改原有业务逻辑,仅需要对线程创建接口做一层封装:
- 定义参数包装结构体,将原始线程执行函数、原始执行函数入参、父线程ID打包在一起
- 实现通用包装入口函数,子线程启动后优先执行你的钩子函数,再调用原始业务执行函数
- 进一步封装线程创建接口,上层业务仅需要替换调用的创建函数即可,无其他修改成本
pthread场景示例代码如下:
#include <pthread.h> #include <stdlib.h> // 你定义的钩子函数 void newThreadCreated(void *ptid) {} // 线程参数包装结构体 typedef struct { void *(*real_routine)(void *); // 原始的子线程业务执行函数 void *real_arg; // 原始执行函数的入参 void *parent_tid; // 要传递的父线程ID } ThreadWrapperArg; // 通用包装入口函数 static void *thread_routine_wrapper(void *arg) { ThreadWrapperArg *wrapper_arg = (ThreadWrapperArg *)arg; // 先执行钩子,传入父线程ID newThreadCreated(wrapper_arg->parent_tid); // 执行原始业务逻辑 void *ret = wrapper_arg->real_routine(wrapper_arg->real_arg); // 释放堆申请的包装结构体内存 free(wrapper_arg); return ret; } // 封装后的线程创建函数,上层业务直接替换原有的pthread_create调用即可 int my_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) { ThreadWrapperArg *wrapper_arg = malloc(sizeof(ThreadWrapperArg)); if (!wrapper_arg) return -1; wrapper_arg->real_routine = start_routine; wrapper_arg->real_arg = arg; // 父线程获取自身ID填入包装参数,不同平台获取线程ID的接口不同,pthread下为pthread_self() wrapper_arg->parent_tid = (void *)pthread_self(); int ret = pthread_create(thread, attr, thread_routine_wrapper, wrapper_arg); if (ret != 0) { free(wrapper_arg); // 线程创建失败要释放申请的内存 } return ret; }
方案2:线程本地存储(TLS)传递(适配不想修改入参的场景)
如果你的场景不希望修改线程创建的入参结构,可以通过TLS传递父线程ID:
- 定义一个全局TLS变量用于存储待传递的父线程ID
- 父线程创建子线程前,把自身的ID写入这个TLS变量
- 子线程执行入口最开头读取TLS内存储的父线程ID,调用钩子函数后清空对应TLS值即可
注意:需要确认你使用的线程库支持子线程创建时继承父线程的TLS值,主流系统的pthread、Windows线程都默认支持该特性
方案3:动态链接劫持/编译器插桩(适配全局埋点的场景)
如果你需要整个进程内所有线程创建都自动触发钩子,不需要上层业务做任何代码修改,可以通过动态链接劫持系统的线程创建接口(比如Linux下劫持pthread_create),内部逻辑和方案1的my_pthread_create完全一致,所有调用系统线程创建接口的逻辑都会自动走钩子流程。
内容的提问来源于stack exchange,提问作者ruke
相关产品推荐
相关产品推荐

