通过C代码Hook setenv()/getenv()实现线程安全(优先Android平台)
Android Bionic平台Hook setenv/getenv实现线程安全方案
核心逻辑
我们自身代码完全不修改环境变量,崩溃100%来自第三方闭源库滥用setenv(),导致日志模块调用strftime()时因getenv()的竞态条件偶发崩溃。通过Hook这两个函数,在调用原实现前后加全局互斥锁,强制环境变量的读写操作原子化,就能解决这个问题。
具体实现步骤
1. 全局锁定义
用pthread互斥锁实现跨线程同步,Bionic原生支持pthread:
#include <pthread.h> #include <stdlib.h> #include <dlfcn.h> // 全局互斥锁,静态初始化 static pthread_mutex_t env_access_lock = PTHREAD_MUTEX_INITIALIZER;
2. 提前获取原函数指针
不能用LD_PRELOAD,所以必须在程序启动早期(比如JNI_OnLoad、main函数第一行)初始化原函数的指针,用dlsym(RTLD_NEXT)从动态链接器中拿到Bionic原生的实现:
// 定义原函数的类型别名 typedef int (*orig_setenv_func)(const char*, const char*, int); typedef char* (*orig_getenv_func)(const char*); static orig_setenv_func real_setenv = NULL; static orig_getenv_func real_getenv = NULL; // 初始化Hook的入口函数,务必早调用 void setup_env_hooks() { // 获取原生setenv/getenv的地址 real_setenv = (orig_setenv_func)dlsym(RTLD_NEXT, "setenv"); real_getenv = (orig_getenv_func)dlsym(RTLD_NEXT, "getenv"); }
3. 实现Hook函数
重写setenv和getenv,加锁后调用原生实现,确保操作原子性:
// Hook后的setenv int setenv(const char *name, const char *value, int overwrite) { int result = -1; pthread_mutex_lock(&env_access_lock); // 优先用原生实现, fallback到Bionic内部函数防止初始化失败 if (real_setenv) { result = real_setenv(name, value, overwrite); } else { result = __libc_setenv(name, value, overwrite); } pthread_mutex_unlock(&env_access_lock); return result; } // Hook后的getenv char *getenv(const char *name) { char *result = NULL; pthread_mutex_lock(&env_access_lock); if (real_getenv) { result = real_getenv(name); } else { result = __libc_getenv(name); } pthread_mutex_unlock(&env_access_lock); return result; }
4. 编译与部署注意事项
- 编译时必须链接dl库:添加
-ldl到编译选项 - 加载顺序:如果是JNI项目,把Hook代码打包进主程序的SO,保证主SO先于第三方库加载;如果是原生程序,直接把Hook代码编译进主程序,在main最开始调用
setup_env_hooks()
额外说明
- 无汇编依赖:全程用标准C和Bionic公开API实现,符合前瞻性要求
- 规避LD_PRELOAD限制:通过符号覆盖机制,当前模块的函数定义会被动态链接器优先选用
- 已知风险:无法防范直接访问全局
environ变量的情况,但根据第三方库的行为判断,这种情况不会发生 - 性能影响:互斥锁的开销极低,不会影响应用运行效率
内容的提问来源于stack exchange,提问作者The Storm
相关产品推荐
相关产品推荐

