如何正确拦截并重定向libc的fcntl函数调用?
解决fcntl可变参数拦截器的参数传递问题
问题背景
fcntl是个可变参数函数:int fcntl(int fd, int action, ...),其第三个参数类型随action变化——有时是int,有时是void*。用dlsym(RTLD_NEXT, "fcntl")做日志拦截时,直接用va_arg传递参数会因类型大小不匹配出问题,需要针对性处理。
解决方案
核心逻辑是根据action的值明确第三个参数的类型(fcntl的每个action对应的参数类型是固定的,可查man手册),分支处理后调用原始函数,避免泛型可变参数的传递问题。
完整代码示例
#define _GNU_SOURCE #include <dlfcn.h> #include <fcntl.h> #include <stdio.h> #include <stdarg.h> // 定义原始fcntl的函数指针类型 typedef int (*orig_fcntl_t)(int fd, int action, ...); static orig_fcntl_t orig_fcntl = NULL; int fcntl(int fd, int action, ...) { // 初始化原始函数指针(仅执行一次) if (!orig_fcntl) { orig_fcntl = (orig_fcntl_t)dlsym(RTLD_NEXT, "fcntl"); if (!orig_fcntl) { fprintf(stderr, "[fcntl-log] dlsym error: %s\n", dlerror()); return -1; } } // 记录基础调用日志 fprintf(stderr, "[fcntl-log] fd=%d, action=%d\n", fd, action); va_list args; va_start(args, action); int result; switch (action) { // 分支1:参数为int类型的action case F_DUPFD: case F_GETFD: case F_SETFD: case F_GETFL: case F_SETFL: { int arg = va_arg(args, int); fprintf(stderr, "[fcntl-log] arg(int)=%d\n", arg); result = orig_fcntl(fd, action, arg); break; } // 分支2:参数为void*类型的action case F_GETLK: case F_SETLK: case F_SETLKW: case F_SETOWN: case F_GETOWN: { void* arg = va_arg(args, void*); fprintf(stderr, "[fcntl-log] arg(void*)=%p\n", arg); result = orig_fcntl(fd, action, arg); break; } // 分支3:处理未知/未覆盖的action default: { // 用long兼容int和void*的大小(32位4字节,64位8字节) long arg = va_arg(args, long); fprintf(stderr, "[fcntl-log] arg(unknown)=%ld\n", arg); result = orig_fcntl(fd, action, arg); break; } } va_end(args); // 可选:记录返回值 fprintf(stderr, "[fcntl-log] return=%d\n", result); return result; }
编译与使用
将代码保存为fcntl_log.c,编译成共享库:
gcc -shared -fPIC -o libfcntl_log.so fcntl_log.c -ldl
通过LD_PRELOAD加载拦截器运行目标程序:
LD_PRELOAD=./libfcntl_log.so ./your_target_program
关键说明
- 按action分支处理:fcntl的每个
action对应的参数类型是标准定义的,无需泛型处理,直接分支匹配最可靠。 - 类型兼容性:未知action用
long作为参数载体,因为long的大小在32位和64位系统中分别与int/指针一致,避免参数传递时的栈对齐错误。 - 初始化逻辑:原始函数指针只初始化一次,避免重复调用
dlsym影响性能。
内容的提问来源于stack exchange,提问作者Vadim Kantorov
相关产品推荐
相关产品推荐

