web-dev-qa-db-fra.com

Comment forcer la liaison avec une ancienne libc `fcntl` au lieu de` fcntl64`?

Il semble GLIBC 2.28 (publié en août 2018) a apporté un changement assez agressif à fcntl. La définition a été modifiée dans <fcntl.h> pour ne plus être une fonction externe, mais une #define à fcntl64 .

Le résultat est que si vous compilez votre code sur un système avec cette glibc - s'il utilise fcntl () du tout - le binaire résultant ne sera pas exécuter sur un système avant août 2018. Cela affecte une grande variété d'applications ... la page de manuel de fcntl () montre que c'est le point d'entrée pour un petit univers de sous-fonctions:

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

Ce serait bien si vous pouviez dire à l'éditeur de liens quelle version spécifique d'une fonction GLIBC vous vouliez. Mais le plus proche que j'ai trouvé était cette astuce décrite dans une réponse à un autre post:

Réponse à "Liaison avec une ancienne version de symbole dans un fichier .so"

C'est un peu plus compliqué. fcntl est variadique sans vffcntl qui prend une va_list. Dans de telles situations vous ne pouvez pas transmettre l'invocation d'une fonction variadique . :-(

Quand on a du code stable avec des dépendances délibérément faibles, c'est une déception de le construire sur un Ubuntu actuel ... et de voir l'exécutable refuser de s'exécuter sur un autre Ubuntu publié un an seulement (presque jour pour jour). De quel recours dispose-t-on?

De quel recours dispose-t-on?

Le fait que GLIBC n'avait aucun moyen de #define USE_FCNTL_NOT_FCNTL64 en dit long. Que ce soit bien ou mal, la plupart des fabricants de chaînes d'outils OS + semblent avoir décidé que le ciblage des binaires pour les anciennes versions de leurs systèmes à partir d'une version plus récente n'est pas une priorité élevée.

Le chemin de moindre résistance est de garder une machine virtuelle autour de la plus ancienne chaîne d'outils OS + qui construit votre projet. Utilisez-le pour créer des binaires chaque fois que vous pensez que le binaire sera exécuté sur un ancien système.

Mais...

  • Si vous pensez que vos utilisations se trouvent dans le sous-ensemble des appels fcntl () qui ne sont pas affectés par le changement de taille de décalage (c'est-à-dire que vous n'utilisez pas de verrous de plage d'octets)
  • OU êtes prêt à vérifier votre code pour que les cas de décalage utilisent une définition de structure rétrocompatible
  • ET n'ont pas peur du vaudou

... puis continuez à lire.

Le nom est différent, et fcntl est variadic sans vffcntl qui prend une va_list. Dans de telles situations, vous ne pouvez pas transmettre l'invocation d'une fonction variadique.

... puis pour appliquer l'astuce d'habillage mentionnée , vous devez parcourir ligne par ligne la documentation de l'interface de fcntl (), décompresser le variadic comme il le ferait, puis appeler la version encapsulée avec une nouvelle invocation variadique.

Heureusement, ce n'est pas si difficile un cas (fcntl prend 0 ou 1 arguments avec des types documentés). Pour essayer de sauver quelqu'un d'autre des ennuis, voici le code pour cela. Assurez-vous de passer - wrap = fcntl64 à l'éditeur de liens ( - Wl, - wrap = fcntl64 si vous n'appelez pas directement 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;
}

Notez que selon la version sur laquelle vous construisez réellement, vous devrez peut-être #ifdef supprimer certaines de ces sections d'indicateur si elles ne sont pas disponibles.

Cela affecte une grande variété d'applications ... la page de manuel de fcntl () montre que c'est le point d'entrée pour un petit univers de sous-fonctions

... et cela devrait probablement être une leçon pour les gens: éviter de créer de telles fonctions d '"évier de cuisine" par des abus variadiques.

Comment forcer la liaison avec une ancienne libc fcntl au lieu de fcntl64?

Compilez avec une ancienne version de libc. Période.

Parce que la glibc n'est pas compatible en amont , elle est seulement compatible en amont :

La bibliothèque GNU C est conçue pour être une bibliothèque ISO C rétrocompatible , portable et hautes performances. Elle vise à suivre toutes les normes pertinentes, notamment ISO C11, POSIX.1-2008 et IEEE 754-2008.

Sans aucune garantie de compatibilité ascendante, vous ne savez pas quoi d'autre ne fonctionnera pas correctement .

2
Andrew Henle