【问题标题】:How to force linkage to older libc `fcntl` instead of `fcntl64`?如何强制链接到旧的 libc `fcntl` 而不是 `fcntl64`?
【发布时间】:2020-02-16 18:08:14
【问题描述】:

GLIBC 2.28 (released August 2018) 似乎对 fcntl 进行了相当激进的更改。 <fcntl.h> 中的定义已更改为不再是外部函数 but a #define to fcntl64

结果是,如果您在使用此 glibc 的系统上编译代码——如果它使用 fcntl()——生成的二进制文件将不会在 2018 年 8 月之前的系统上执行. 这会影响到相当多的应用程序... fcntl() 的手册页显示它是一小部分子函数的入口点:

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

如果您能告诉链接器您想要的 GLIBC 函数的特定版本,那就太好了。但我发现的最接近的是另一个帖子的答案中描述的这个技巧:

Answer to "Linking against older symbol version in a .so file"

这有点复杂。 fcntl 是可变参数,没有采用 va_list 的 vffcntl。在这种情况下you cannot forward an invocation of a variadic function。 :-(

当一个人有稳定的代码并且有针对性地降低依赖性时,在当前的 Ubuntu 上构建它是令人失望的......然后让可执行文件拒绝在仅一年前(几乎是今天)发布的另一个 Ubuntu 上运行.对此有什么追索权?

【问题讨论】:

    标签: c ld variadic-functions glibc fcntl


    【解决方案1】:

    对此有什么追索权?

    GLIBC 无法访问#define USE_FCNTL_NOT_FCNTL64 的事实说明了很多。不管是对是错,大多数 OS+工具链制造商似乎都认为,从较新的系统中针对较旧版本的系统的二进制文件并不是一个高优先级。

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

    但是……

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

    ...然后继续阅读。

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

    ...然后要应用the wrapping trick mentioned,您必须逐行浏览 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;
    }
    

    请注意,根据您实际构建的版本,如果其中一些部分不可用,您可能必须#ifdef 将其标记出来。

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

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

    【讨论】:

    • 对于那些可能想讨论这篇文章的动机的人...here's a Discourse thread about the role of binary transferability in today's world
    • 这会破坏什么?通过放弃使用fcntl64(),现在在访问大于 2 GB 的文件时会引入错误吗?唯一知道的方法是对所有 fcntl() 使用进行完整的回归测试。 在构建您的项目的最古老的 OS+工具链周围保留一个虚拟机 是真正的答案,IMO 应该是最前面的。
    • “从较新的系统中针对较旧版本的系统的二进制文件不是高优先级。” -- 这不是“不高优先级”,而是明确的非目标
    • @AndrewHenle “未来不会出现任何不针对这个库/在这个操作系统上运行的东西”?没有人能做出这样的承诺,你不能依赖它。” => 这显然是错误的——从某种意义上说,如果一个人可以使用旧版本的操作系统/工具链来获得结果,那么新的工具链的版本可能也有一个开关来获得该结果。不久前,发布无法构建可在所考虑的系统上运行的二进制文件的编译器版本被认为是不可接受的就在前一天的“最新”。我看到这艘船似乎已经在这里航行了很多人。
    【解决方案2】:

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

    针对旧版本的 libc 进行编译。期间。

    因为glibc不是forward compatible,所以只有backwards-compatible

    GNU C 库被设计成一个向后兼容、可移植和高性能的 ISO C 库。它旨在遵循所有相关标准,包括 ISO C11、POSIX.1-2008 和 IEEE 754-2008。

    没有任何向前兼容性的保证,你不知道还有什么不能正常工作

    【讨论】:

    • 补充一点,如果可能的话,只使用正常的包构建过程,通常在大多数发行版中构建包的步骤包括安装该发行版中使用的库版本,这样如果你的构建过程运行测试你可以及早发现任何问题
    猜你喜欢
    • 1970-01-01
    • 2011-10-30
    • 1970-01-01
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 2014-08-12
    • 1970-01-01
    相关资源
    最近更新 更多