如何强制链接到较旧的libc`fcntl`而不是`fcntl64`?

Hos*_*ork 6 c glibc variadic-functions ld fcntl

似乎GLIBC 2.28(于2018年8月发布)对fcntl进行了相当激进的更改。定义已更改<fcntl.h>为不再是外部函数,而是#define更改为fcntl64

其结果是,如果你的系统上使用此glibc的编译代码-如果它使用的fcntl()在所有从2018年八月前--the生成的二进制文件将不能在系统上执行这会影响到相当多的各种应用.. .fcntl()的手册页显示,这是一小部分子功能的入口:

https://linux.die.net/man/2/fcntl

如果您可以告诉链接器所需的GLIBC函数的特定版本,那就太好了。但是我发现最接近的是在另一篇文章的答案中描述的这个技巧:

回答“在.so文件中链接到较旧的符号版本”

这有点复杂。 fcntl是不vffcntl带va_list的可变参数。在这种情况下,您无法转发可变参数函数的调用。:-(

当一个程序具有稳定的代码且具有较低的依赖关系时,就很难在当前的Ubuntu上构建它了……然后让可执行文件拒绝在仅一年前(近日)发布的另一个Ubuntu上运行。一个人有什么追索权?

Hos*_*ork 10

对此,人们有什么追索权?

事实上,GLIBC 没有办法#define USE_FCNTL_NOT_FCNTL64说明很多。不管是对是错,大多数操作系统+工具链制造商似乎已经决定,从较新的系统中针对较旧版本的系统定位二进制文​​件并不是一个高优先级。

阻力最小的方法是在构建项目的最古老的 OS+工具链周围保留一个虚拟机。每当您认为二进制文件将在旧系统上运行时,就使用它来制作二进制文件。

但...

  • 如果您认为您的用法属于不受偏移量大小更改影响的 fcntl() 调用的子集(也就是说您不使用字节范围锁)
  • 或者愿意审查您的偏移情况代码以使用向后兼容的结构定义
  • 并且不害怕伏都教

...然后继续阅读。

名称不同,fcntl 是可变参数,没有采用 va_list 的 vffcntl。在这种情况下,您不能转发对可变参数函数的调用。

...然后要应用提到的包装技巧,您必须逐行浏览 fcntl() 的接口文档,按原样解压缩可变参数,然后使用新的可变参数调用调用包装版本。

幸运的是,这不是一个困难的案例(fcntl 接受 0 或 1 个带有文档类型的参数)。为了尝试为其他人省去一些麻烦,这里是代码。确保将--wrap=fcntl64传递给链接器(-Wl,--wrap=fcntl64如果不直接调用 ld ):

asm (".symver fcntl64, fcntl@GLIBC_2.2.5");

extern "C" int __wrap_fcntl64(int fd, int cmd, ...)
{
    int result;
    va_list va;
    va_start(va, cmd);

    switch (cmd) {
      //
      // File descriptor flags
      //
      case F_GETFD: goto takes_void;
      case F_SETFD: goto takes_int;

      // File status flags
      //
      case F_GETFL: goto takes_void;
      case F_SETFL: goto takes_int;

      // File byte range locking, not held across fork() or clone()
      //
      case F_SETLK: goto takes_flock_ptr_INCOMPATIBLE;
      case F_SETLKW: goto takes_flock_ptr_INCOMPATIBLE;
      case F_GETLK: goto takes_flock_ptr_INCOMPATIBLE;

      // File byte range locking, held across fork()/clone() -- Not POSIX
      //
      case F_OFD_SETLK: goto takes_flock_ptr_INCOMPATIBLE;
      case F_OFD_SETLKW: goto takes_flock_ptr_INCOMPATIBLE;
      case F_OFD_GETLK: goto takes_flock_ptr_INCOMPATIBLE;

      // Managing I/O availability signals
      //
      case F_GETOWN: goto takes_void;
      case F_SETOWN: goto takes_int;
      case F_GETOWN_EX: goto takes_f_owner_ex_ptr;
      case F_SETOWN_EX: goto takes_f_owner_ex_ptr;
      case F_GETSIG: goto takes_void;
      case F_SETSIG: goto takes_int;

      // Notified when process tries to open or truncate file (Linux 2.4+)
      //
      case F_SETLEASE: goto takes_int;
      case F_GETLEASE: goto takes_void;

      // File and directory change notification
      //
      case F_NOTIFY: goto takes_int;

      // Changing pipe capacity (Linux 2.6.35+)
      //
      case F_SETPIPE_SZ: goto takes_int;
      case F_GETPIPE_SZ: goto takes_void;

      // File sealing (Linux 3.17+)
      //
      case F_ADD_SEALS: goto takes_int;
      case F_GET_SEALS: goto takes_void;

      // File read/write hints (Linux 4.13+)
      //
      case F_GET_RW_HINT: goto takes_uint64_t_ptr;
      case F_SET_RW_HINT: goto takes_uint64_t_ptr;
      case F_GET_FILE_RW_HINT: goto takes_uint64_t_ptr;
      case F_SET_FILE_RW_HINT: goto takes_uint64_t_ptr;

      default:
        fprintf(stderr, "fcntl64 workaround got unknown F_XXX constant")
    }

  takes_void:
    va_end(va);
    return fcntl64(fd, cmd);

  takes_int:
    result = fcntl64(fd, cmd, va_arg(va, int));
    va_end(va);
    return result;

  takes_flock_ptr_INCOMPATIBLE:
    //
    // !!! This is the breaking case: the size of the flock
    // structure changed to accommodate larger files.  If you
    // need this, you'll have to define a compatibility struct
    // with the older glibc and make your own entry point using it,
    // then call fcntl64() with it directly (bear in mind that has
    // been remapped to the old fcntl())
    // 
    fprintf(stderr, "fcntl64 hack can't use glibc flock directly");
    exit(1);

  takes_f_owner_ex_ptr:
    result = fcntl64(fd, cmd, va_arg(va, struct f_owner_ex*));
    va_end(va);
    return result;

  takes_uint64_t_ptr:
    result = fcntl64(fd, cmd, va_arg(va, uint64_t*));
    va_end(va);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

请注意,根据您实际构建的版本,如果其中一些标志部分不可用,您可能需要 #ifdef。

这会影响相当多的应用程序…… fcntl() 的手册页显示它是一小部分子函数的入口点

...这可能应该给人们一个教训:避免通过可变参数滥用来创建这样的“厨房水槽”功能。