¿Cómo forzar el enlace a la antigua libc `fcntl` en lugar de` fcntl64`?

8

Parece que GLIBC 2.28 (lanzado en agosto de 2018) realizó un cambio bastante agresivo en fcntl. La definición se cambió <fcntl.h>para dejar de ser una función externa, sino una #definea fcntl64 .

El resultado es que si se compila el código en un sistema con este glibc - si se utiliza fcntl () en absoluto --el binario resultante no se ejecutará en un sistema de antes de agosto de 2018. Esto afecta a una gran variedad de aplicaciones .. La página de manual para fcntl () muestra que es el punto de entrada para un pequeño universo de subfunciones:

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

Sería bueno si pudiera decirle al vinculador qué versión específica de una función GLIBC desea. Pero lo más cercano que encontré fue este truco descrito en una respuesta a otra publicación:

Responda a "Vinculación con una versión de símbolo anterior en un archivo .so"

Esto es un poco más complicado. fcntles variadic sin un vffcntlque toma una va_list. En tales situaciones, no puede reenviar una invocación de una función variadic . :-(

Cuando uno tiene un código estable con dependencias intencionalmente bajas, es una decepción construirlo en un Ubuntu actual ... y hacer que el ejecutable se niegue a ejecutarse en otro Ubuntu lanzado solo un año antes (casi hasta el día). ¿Qué recurso tiene uno para esto?

HostileFork dice que no confíes en SE
fuente

Respuestas:

6

¿Qué recurso tiene uno para esto?

El hecho de que GLIBC no tuviera forma de #define USE_FCNTL_NOT_FCNTL64decir mucho. Sea correcto o incorrecto, la mayoría de los fabricantes de cadenas de herramientas de OS + parecen haber decidido que apuntar a los archivos binarios para versiones anteriores de sus sistemas desde uno nuevo no es una alta prioridad.

El camino de menor resistencia es mantener una máquina virtual alrededor de la cadena de herramientas OS + más antigua que construye su proyecto. Use eso para hacer binarios cada vez que piense que los binarios se ejecutarán en un sistema antiguo.

Pero...

  • Si cree que sus usos están en el subconjunto de llamadas fcntl () que no se ven afectadas por el cambio de tamaño de desplazamiento (es decir, no utiliza bloqueos de rango de bytes)
  • O están dispuestos a examinar su código para que los casos de desplazamiento utilicen una definición de estructura compatible con versiones anteriores
  • Y no tienen miedo al vudú

... entonces sigue leyendo.

El nombre es diferente, y fcntl es variable sin un vffcntl que toma una va_list. En tales situaciones, no puede reenviar una invocación de una función variadic.

... luego, para aplicar el truco de ajuste mencionado , debe ir línea por línea a través de la documentación de la interfaz de fcntl (), desempaquetar la variadic como lo haría y luego llamar a la versión envuelta con una nueva invocación variadic.

Afortunadamente, no es un caso tan difícil (fcntl toma 0 o 1 argumentos con tipos documentados). Para intentar salvar a alguien más de algún problema, aquí hay un código para eso. Asegúrese de pasar --wrap = fcntl64 al enlazador ( -Wl, - wrap = fcntl64 si no está llamando a ld directamente):

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;
}

Tenga en cuenta que, dependiendo de la versión en la que esté desarrollando, es posible que tenga que #desdefinir algunas de esas secciones de bandera si no están disponibles.

Esto afecta a una gran variedad de aplicaciones ... la página del manual para fcntl () muestra que es el punto de entrada para un pequeño universo de subfunciones

... y probablemente debería ser una lección para las personas: evite crear tales funciones de "fregadero de la cocina" a través del abuso variable.

HostileFork dice que no confíes en SE
fuente
Para aquellos que quieran discutir lo que motivó esta publicación ... aquí hay un hilo del discurso sobre el papel de la transferibilidad binaria en el mundo de hoy
HostileFork dice que no confíes en SE
¿Y qué rompería esto? Al dejar de usar fcntl64(), ¿se introducirán errores al acceder a archivos de más de 2 GB? La única forma de saber es hacer una prueba de regresión completa de todos los fcntl()usos. mantenga una máquina virtual alrededor de la cadena de herramientas OS + más antigua que construya su proyecto ESA es la respuesta real y la OMI debe estar al frente.
Andrew Henle
@AndrewHenle Reordenado y editado para detener lo que imagino que es incompatible si alguien realmente usa el bloqueo ... (no lo estaba).
HostileFork dice que no confíes en SE
1
"apuntar a los binarios para versiones anteriores de sus sistemas desde uno más nuevo no es una alta prioridad". - No es una "no alta prioridad", es un objetivo no explícito .
Empleado ruso
1
@AndrewHenle "¿Nunca surgirá nada en el futuro que no se ejecute contra esta biblioteca / en este sistema operativo"? Nadie puede hacer esa promesa, y usted no puede confiar en eso. " => Esto es evidentemente falso, en el sentido de que si se puede usar una versión anterior del sistema operativo / cadena de herramientas para un resultado, nuevas versiones de la cadena de herramientas podría tener un cambio para obtener ese resultado también. No hace mucho tiempo se habría considerado inaceptable tener un lanzamiento de un compilador que no pudiera construir binarios que se ejecutarían en un sistema considerado "el más reciente" el día anterior. ese barco parece haber navegado por muchos aquí.
HostileFork dice que no confíes en SE
2

¿Cómo forzar el enlace a una biblioteca anterior en fcntllugar de fcntl64?

Compilar contra una versión anterior de libc. Período.

Debido a que glibc no es compatible con versiones anteriores , solo es compatible con versiones anteriores :

La biblioteca GNU C está diseñada para ser una biblioteca ISO C compatible con versiones anteriores , portátil y de alto rendimiento. Su objetivo es seguir todos los estándares relevantes, incluidos ISO C11, POSIX.1-2008 e IEEE 754-2008.

Sin ninguna garantía de compatibilidad hacia adelante, no sabes qué más no funcionará correctamente .

Andrew Henle
fuente
Para agregar eso, si es posible, solo use el proceso normal de compilación del paquete, normalmente los pasos para compilar el paquete en la mayoría de las distribuciones incluyen la instalación de la versión de las bibliotecas utilizadas en esa distribución, de esa manera, si su proceso de compilación ejecuta pruebas, puede detectar cualquier problema con anticipación
XANi