覆盖libc open()函数导致Python os.popen().read()调用挂死问题问询
问题根因
- 初始化循环依赖:全局对象
g_sys_funcs的构造函数在共享库加载阶段执行,你在构造函数中调用dlopen加载libc.so.6时,dlopen内部需要打开动态库文件,会触发你已经预加载的自定义open函数。但此时g_sys_funcs还没完成构造,sys_open函数指针是空值,自定义open的逻辑执行异常,会返回错误的文件描述符。 - 标准IO锁继承问题:你在自定义open中调用了
printf,标准IO函数内部持有全局互斥锁保证线程安全。当Python的os.popen调用fork创建子进程时,如果父进程刚好持有printf的锁,子进程会继承这个锁的持有状态,子进程后续调用任何标准IO函数都会陷入无限等锁的状态。
你碰到的按回车恢复的现象,是因为异常逻辑返回的文件描述符刚好指向了标准输入,popen的读操作直接从标准输入读取数据,输入回车后read拿到数据就会继续执行。
解决方案
可以通过以下几个修改彻底解决问题:
- 移除全局对象初始化逻辑,改为第一次调用open时的延迟初始化,完全避开共享库加载阶段的循环依赖。
- 不要用
dlsym获取libc的open函数指针,直接调用系统调用SYS_open/SYS_openat来执行实际的打开操作,从根源上避免循环调用。 - 自定义open中不要使用任何标准IO函数(printf/puts等),如果需要打印日志直接调用
write系统调用输出,避免标准IO锁的继承问题。 - 同时hook
open64函数,64位系统下绝大多数程序(包括你用的Python 3.6)会调用open64来支持大文件,仅hook open会漏掉大量调用。
修改后的代码示例如下:
#define _GNU_SOURCE #include <dlfcn.h> #include <stdarg.h> #include <fcntl.h> #include <unistd.h> #include <sys/syscall.h> #include <string.h> // 延迟初始化标记 static volatile int g_init_done = 0; static void init_sys_funcs() { if (__sync_bool_compare_and_swap(&g_init_done, 0, 1)) { // 直接使用系统调用不需要额外初始化libc函数指针 } } static int realopen(const char *path, int flags, va_list args) { if (flags & (O_CREAT | O_TMPFILE)) { mode_t mode = va_arg(args, mode_t); return syscall(SYS_open, path, flags, mode); } else { return syscall(SYS_open, path, flags); } } extern "C" __attribute__((visibility ("default"))) int open(const char *pathname, int flags, ...) { if (!g_init_done) init_sys_funcs(); // 用write系统调用打日志,规避标准IO锁问题 const char log_prefix[] = "---- custom open "; write(STDOUT_FILENO, log_prefix, sizeof(log_prefix)-1); write(STDOUT_FILENO, pathname, strlen(pathname)); write(STDOUT_FILENO, "\n", 1); va_list args; va_start(args, flags); int fd = realopen(pathname, flags, args); va_end(args); return fd; } // 同时hook open64兼容大文件调用 extern "C" __attribute__((visibility ("default"))) int open64(const char *pathname, int flags, ...) { if (!g_init_done) init_sys_funcs(); const char log_prefix[] = "---- custom open64 "; write(STDOUT_FILENO, log_prefix, sizeof(log_prefix)-1); write(STDOUT_FILENO, pathname, strlen(pathname)); write(STDOUT_FILENO, "\n", 1); va_list args; va_start(args, flags); int fd; if (flags & (O_CREAT | O_TMPFILE)) { mode_t mode = va_arg(args, mode_t); fd = syscall(SYS_open, pathname, flags | O_LARGEFILE, mode); } else { fd = syscall(SYS_open, pathname, flags | O_LARGEFILE); } va_end(args); return fd; }
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

