【问题标题】:How to wrap ioctl(int d, unsigned long request, ...) using LD_PRELOAD?如何使用 LD_PRELOAD 包装 ioctl(int d, unsigned long request, ...)?
【发布时间】:2024-01-02 00:03:01
【问题描述】:

这是我用于使用 LD_PRELOAD 包装函数的模板:

int gettimeofday(struct timeval *tv, struct timezone *tz) {
  static int (*gettimeofday_real)(struct timeval *tv, struct timezone *tz)=NULL;
  if (!gettimeofday_real) gettimeofday_real=dlsym(RTLD_NEXT,"gettimeofday");
  return gettimeofday_real(tv, tz);
}

我意识到虽然 ioctl 似乎具有以下签名:

   int ioctl(int d, unsigned long request, ...);

鉴于签名中的...,我如何以类似的方式包装它?

【问题讨论】:

    标签: c linux system-calls ld-preload


    【解决方案1】:

    一个可变参数函数没有可移植的方式将其可变参数转发给另一个可变参数函数。部分原因是编译器必须在编译时知道实际参数的数量和大小才能构造可变参数调用,但它们在被调用函数内部不是静态已知的(即使是参数方式)。

    【讨论】:

      【解决方案2】:

      虽然 John Bollinger 是正确的,根据 ioctl.h 中的原型,ioctl() 是一个可变参数函数,但实际上并非如此。

      参见本书Linux Device Drivers中的this quote

      原型在 Unix 系统调用列表中脱颖而出,因为 点,通常将函数标记为具有可变数字 的论点。然而,在实际系统中,系统调用实际上不能 有可变数量的参数。系统调用必须有一个 定义良好的原型,因为用户程序只能访问它们 通过硬件“门”。因此,原型中的点 表示不是可变数量的参数,而是一个可选的 参数,传统上标识为 char *argp。点很简单 以防止在编译期间进行类型检查。

      所以你可以写你的替代品ioctl(),如下:

      int ioctl(int d, unsigned long request, char *argp)
      {
        /* follow the same recipe as for your example gettimeofday() */
        return ioctl_real(d, request, argp);
      }
      

      如果您只想构建一个用于LD_PRELOAD 的包装器库,那么您的ioctl 签名与sys/ioctl.h 中的签名相矛盾这一事实是无关紧要的:链接器不检查类型,并且您的包装器库是假的@ 987654330@ 将使用与没有LD_PRELOAD 的真实参数完全相同的参数调用

      【讨论】:

      • 我认为告诉 char*p 将适用于所有 ABI 有点勇敢……例如,在 ARM 上,寄存器已用尽其可用性……
      • @Valeri Atamaniouk:char * 的使用只是传统。重要的是,它始终是一个机器字,它将被处理ioctl 的内核代码解释为int,或者说struct termios *,这取决于request 的值(参见eg@ 987654338@) 这个字可以在寄存器中传递,也可以在栈上传递,与argp的类型无关
      • 是的,你是对的。一个额外的长类型参数(如果存在)。