Como forçar a ligação a libc `fcntl` mais antiga em vez de` fcntl64`?


8

Parece que o GLIBC 2.28 (lançado em agosto de 2018) fez uma alteração bastante agressiva no fcntl. A definição foi alterada <fcntl.h>para não ser mais uma função externa, mas #definepara fcntl64 .

O resultado é que se você compilar o código em um sistema com esta glibc - se ele usa fcntl () em tudo --o binário resultante não será executado em um sistema de antes de Agosto de 2018. Isso afeta toda uma variedade de aplicações .. .a página de manual de fcntl () mostra que é o ponto de entrada para um pequeno universo de sub-funções:

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

Seria bom se você pudesse dizer ao vinculador qual versão específica de uma função GLIBC você queria. Mas o mais próximo que encontrei foi esse truque descrito em uma resposta a outro post:

Responda a "Vinculando à versão mais antiga do símbolo em um arquivo .so"

Isso é um pouco mais complicado. fcntlé variável sem um vffcntlque leva uma va_list. Em tais situações, você não pode encaminhar uma invocação de uma função variável . :-(

Quando se tem código estável com dependências propositadamente baixas, é uma desilusão construí-lo em um Ubuntu atual ... e ter o executável se recusar a executar em outro Ubuntu lançado apenas um ano antes (quase o dia). Que recurso se tem para isso?

Respostas:


6

Que recurso se tem para isso?

O fato de o GLIBC não ter muito a #define USE_FCNTL_NOT_FCNTL64dizer diz muito. Seja certo ou errado, a maioria dos fabricantes de cadeias de ferramentas do OS + parece ter decidido que direcionar binários para versões mais antigas de seus sistemas a partir de uma mais nova não é uma alta prioridade.

O caminho de menor resistência é manter uma máquina virtual em torno da cadeia de ferramentas OS + mais antiga que cria seu projeto. Use isso para criar binários sempre que achar que o binário será executado em um sistema antigo.

Mas...

  • Se você acredita que seus usos estão no subconjunto de chamadas fcntl () que não são afetadas pela alteração do tamanho do deslocamento (ou seja, você não usa bloqueios de intervalo de bytes)
  • OU estão dispostos a verificar seu código nos casos de compensação para usar uma definição de estrutura compatível com versões anteriores
  • E não têm medo de vodu

... então continue lendo.

O nome é diferente e fcntl é variável sem um vffcntl que leva uma va_list. Em tais situações, você não pode encaminhar uma invocação de uma função variável.

... para aplicar o truque de empacotamento mencionado , você deve passar linha por linha pela documentação da interface de fcntl (), descompactar a variável como seria e, em seguida, chamar a versão empacotada com uma nova chamada variável.

Felizmente, não é um caso tão difícil (fcntl recebe 0 ou 1 argumentos com tipos documentados). Para tentar poupar a alguém mais problemas, aqui está o código para isso. Certifique-se de passar --wrap = fcntl64 para o vinculador ( -Wl, - wrap = fcntl64 se não estiver chamando ld diretamente):

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

Observe que, dependendo da versão em que você está realmente construindo, talvez seja necessário #ifdef algumas dessas seções de sinalizador, se não estiverem disponíveis.

Isso afeta diversas aplicações ... a página de manual do fcntl () mostra que é o ponto de entrada para um pequeno universo de sub-funções

... e provavelmente deve ser uma lição para as pessoas: evite criar essas funções de "pia da cozinha" por meio de abusos variados.



E o que isso quebraria? Ao abandonar o uso de fcntl64(), agora haverá erros introduzidos ao acessar arquivos maiores que 2 GB? A única maneira de saber é fazer um teste de regressão completo de todos os fcntl()usos. manter uma máquina virtual em torno dos mais antigos OS + toolchain que constrói seu projeto QUE é a resposta real e IMO deve estar na frente.
Andrew Henle


1
"segmentar binários para versões mais antigas de seus sistemas a partir de uma mais nova não é uma alta prioridade." - não é uma "prioridade não alta", é uma meta não autorizada .
Empregado russo

1
@AndrewHenle "Nada surgirá no futuro que não funcione contra esta biblioteca / neste sistema operacional"? Ninguém pode fazer essa promessa, e você não pode confiar nisso. " => Isso é claramente falso - no sentido de que se alguém puder usar uma versão mais antiga do OS / toolchain para obter um resultado, novas versões do toolchain também poderia haver uma opção para obter esse resultado. Há pouco tempo, seria considerado inaceitável o lançamento de um compilador que não podia criar binários que rodariam em um sistema considerado "o mais recente" no dia anterior. esse navio parece ter navegado para muitos por aqui.
HostileFork diz que não confia em SE

2

Como forçar a ligação à libc antiga em fcntlvez de fcntl64?

Compile em uma versão mais antiga do libc. Período.

Como o glibc não é compatível com versões anteriores , é apenas compatível com versões anteriores :

A GNU C Library foi projetada para ser uma biblioteca ISO C compatível com versões anteriores , portátil e de alto desempenho. O objetivo é seguir todos os padrões relevantes, incluindo ISO C11, POSIX.1-2008 e IEEE 754-2008.

Sem garantias de compatibilidade com a frente, você não sabe o que mais não funcionará corretamente .


Para adicionar isso, se possível, basta usar o processo normal de compilação de pacotes, normalmente as etapas para compilar o pacote na maioria das distribuições incluem a instalação de versões das bibliotecas usadas nessa distribuição, assim, se o processo de compilação executar testes, você poderá detectar problemas com antecedência
XANi 20/10/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.