AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79028257
Accepted
Steven Dickinson
Steven Dickinson
Asked: 2024-09-27 00:22:30 +0800 CST2024-09-27 00:22:30 +0800 CST 2024-09-27 00:22:30 +0800 CST

Problemas ao vincular várias bibliotecas com os mesmos símbolos

  • 772

Tenho 2 bibliotecas de objetos compartilhados, por exemplo, libA.soe libB.so. Tenho um executável que chama funções de ambas as bibliotecas, por exemplo, funcA()from libAe funcB()from libB.

Infelizmente, ambos libAe libBtêm algumas outras funções que usam o mesmo nome de símbolo, por exemplo, libAtem a função f1()e f2()e libBtambém tem as funções f1()e f2(). Observe que as implementações de f1()e f2()são completamente diferentes entre libA e libB, elas são apenas chamadas pelo mesmo nome.

O código executável nunca chama f1()ou f2()diretamente e, é claro, libAdeve chamar apenas seu f1()& f2()e libBdeve chamar apenas seu f1()and f2().

Mas parece que algo deu errado e recebo uma falha de segmentação ao executar o executável.

Tenho uma solução para isso, que é usar um "version.script" ao compilar libAe libB, para que apenas as funções da API externa (ou seja, aquelas chamadas "func*") sejam expostas, ou seja, o seguinte script...

{
    global: func*;
    local: *; 
};

... e usar -Wl,--version-script=version.scriptao vincular com o gcc.

Isso funciona, mas me causa complicações porque tenho que usar uma cadeia de ferramentas de terceiros que torna complexo (ou seja, complicado) usar esse método version.script.

Existe uma maneira melhor/outra? (Observe que eu tenho a fonte para libAe libB, então alterar nomes de símbolos é possível, mas não é do meu agrado, pois esse código é gerado automaticamente por uma ferramenta de terceiros e eu realmente não quero entrar e editar todos os nomes. Claro, o código real não é apenas f1()e , f2()mas centenas de símbolos com nomes semelhantes).

Além disso, eu gostaria de entender a causa raiz do problema? Estou assumindo que libA(ou libB) fica "confuso" com qual f1()ou f2()deveria chamar, mas por que - já que ambos libAe libBsão compilados separadamente - algo a ver com bibliotecas de objetos compartilhados, eu suspeito?

Caso seja importante... a fonte para as bibliotecas e o executável é C, e estou usando gcc para compilar e vincular, e isso está no Linux.

gcc
  • 1 1 respostas
  • 27 Views

1 respostas

  • Voted
  1. Best Answer
    Mike Kinghan
    2024-09-27T03:06:11+08:002024-09-27T03:06:11+08:00

    Primeiro, uma ilustração mínima do seu problema. Para mantê-lo mínimo, vou cortar as funções ambivalentes de f1(), f2()para apenas f1(). Já que você diz:

    Gostaria de entender a causa raiz do problema?

    Vou explicar isso usando a ilustração mínima. Depois, vou mostrar como evitar isso sem um script de versão.

    O exemplo

    Arquivos de origem e de cabeçalho para libA.so:

    // A.h
    #pragma once
    extern void funcA(void);
    
    // A.c
    #include <stdio.h>
    #include "A1.h"
    
    void funcA(void)
    {
        puts(__FUNCTION__);
        f1();
    }
        
    // A1.h
    #pragma once
    extern void f1(void);
    
    // A1.c
    #include <stdio.h>
    #include "A1.h"
    
    void f1(void)
    {
        printf("%s from A1.c\n",__FUNCTION__);
    }
    

    Arquivos de origem e de cabeçalho para libB.so:

    // B.h
    #pragma once
    extern void funcB(void);
    
    // B.c
    #include <stdio.h>
    #include "B1.h"
    
    void funcB(void)
    {
        puts(__FUNCTION__);
        f1();
    }
        
    // B1.h
    #pragma once
    extern void f1(void);
    
    // B1.c
    #include <stdio.h>
    #include "B1.h"
    
    void f1(void)
    {
        printf("%s from B1.c\n",__FUNCTION__);
    }
    

    Fonte para um programa:

    // prog.c
    #include <A.h>
    #include <B.h>
    
    int main(void)
    {
        funcA();
        funcB();
        return 0;
    }
    

    Compilar todas as fontes da biblioteca compartilhada:

    $ gcc -c -fPIC A*.c B*.c
    

    E a fonte do programa:

    $ gcc -c -I . prog.c
    

    Vincule as bibliotecas compartilhadas:

    $ gcc -shared -o libA.so A*.o 
    $ gcc -shared -o libB.so B*.o
    

    E o programa:

    $ gcc -o prog prog.o -L . -lA -lB -Wl,-rpath=$(pwd)
    

    Então vemos seu problema:

    $ ./prog
    funcA
    f1 from A1.c
    funcB
    f1 from A1.c
    

    Ambos funcAe funcBambos chamam o f1from libA.so, definido em A1.c.

    Ao vincular novamente o programa, com libA.soe libA.sona ordem inversa:

    $ gcc -o prog prog.o -L . -lB -lA -Wl,-rpath=$(pwd)
    

    podemos produzir o problema oposto:

    $ ./prog
    funcA
    f1 from B1.c
    funcB
    f1 from B1.c
    

    O que não deixa de ser um problema.

    A explicação

    O símbolo da função ambivalente f1é definido nas tabelas de símbolos dinâmicos de libA.soe libB.so:

    $ readelf -W --dyn-syms libA.so
    
    Symbol table '.dynsym' contains 9 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTable
         2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
         3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
         4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
         6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
         7: 0000000000001182    31 FUNC    GLOBAL DEFAULT   14 funcA
         8: 0000000000001159    41 FUNC    GLOBAL DEFAULT   14 f1
         
    $ readelf -W --dyn-syms libB.so
    
    Symbol table '.dynsym' contains 9 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTable
         2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
         3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@GLIBC_2.2.5 (2)
         4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
         6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
         7: 0000000000001159    41 FUNC    GLOBAL DEFAULT   14 f1
         8: 0000000000001182    31 FUNC    GLOBAL DEFAULT   14 funcB
         
    

    Portanto, em ambas as bibliotecas compartilhadas, ele fica visível no tempo de execução para o vinculador dinâmico como uma definição elegível para referências a f1, e o vinculador dinâmico vinculará, por padrão, todas essas referências à primeira definição que encontrar durante o carregamento e a vinculação das bibliotecas compartilhadas recursivamente exigidas pelo processo em construção.

    Para executar prog, essa construção começa com o carregamento do executável prog. Observe o topo de sua seção dinâmica:

    $ readelf --dynamic prog
    
    Dynamic section at offset 0x2d90 contains 30 entries:
      Tag        Type                         Name/Value
     0x0000000000000001 (NEEDED)             Shared library: [libB.so]
     0x0000000000000001 (NEEDED)             Shared library: [libA.so]
     0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
     0x000000000000001d (RUNPATH)            Library runpath: [/home/imk/develop/so/scrap]
     ...[cut]...
     
    

    Essas são informações que o vinculador estático escreveu lá para o vinculador dinâmico ler e agir. O executável precisa carregar libB.soe libA.so, nessa ordem. O RUNPATH /home/imk/develop/so/scrap é um diretório que o vinculador de tempo de execução pode pesquisar para encontrar as bibliotecas compartilhadas necessárias (além de seus diretórios de pesquisa padrão). Esse é o resultado do -rpath=$(pwd)que adicionei ao vínculo do programa (porque não vou me incomodar em instalar corretamente essas bibliotecas descartáveis).

    Então, neste caso, a primeira biblioteca compartilhada carregada na qual o vinculador dinâmico encontra uma definição para f1será libB.so; essa é a definição de B1.c, e ele vinculará todas as referências no programa a essa definição.

    Vamos voltar à ligação original de prog:

    $ gcc -o prog prog.o -L . -lA -lB -Wl,-rpath=$(pwd)
    

    Então, como você adivinhou, o topo da seção dinâmica do prog mostrará:

    $ readelf --dynamic prog
    
    Dynamic section at offset 0x2d90 contains 30 entries:
      Tag        Type                         Name/Value
     0x0000000000000001 (NEEDED)             Shared library: [libA.so]
     0x0000000000000001 (NEEDED)             Shared library: [libB.so]
     0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
     0x000000000000001d (RUNPATH)            Library runpath: [/home/imk/develop/so/scrap]
     ...[cut]...
     
    

    com libA.socarregado antes libB.so, e libAa definição de f1será a que vencerá, como vimos.

    A correção

    Seu problema surge do comportamento padrão do vinculador estático ao criar uma biblioteca compartilhada: referências dentro da biblioteca compartilhada para símbolos dinâmicos que ela define não são vinculadas preventivamente à definição interna 1 . Há uma opção de vinculador que substituirá o comportamento padrão para fazer com que cada biblioteca chame sua própria definição de f1. De man ld( ldé o vinculador estático, invocado em seu nome por gccpara executar vinculações):

    -Bsimbólico

    Ao criar uma biblioteca compartilhada, vincule referências a símbolos globais à definição dentro da biblioteca compartilhada, se houver. Normalmente, é possível para um programa vinculado a uma biblioteca compartilhada substituir a definição dentro da biblioteca compartilhada. Esta opção só é significativa em plataformas ELF que suportam bibliotecas compartilhadas.

    Então podemos revincular as bibliotecas compartilhadas assim:

    $ gcc -shared -o libA.so A*.o -Wl,-Bsymbolic
    $ gcc -shared -o libB.so B*.o -Wl,-Bsymbolic
    

    (Aliás, costumamos -Wl,<ld-option>dizer gccpara passar a opção ld-optiondiretamente para ld.)

    Revincule o programa com as novas bibliotecas:

    $ gcc -o prog prog.o -L . -lA -lB -Wl,-rpath=$(pwd)
    

    e então cada biblioteca chama sua própria definição de f1:

    $ ./prog
    funcA
    f1 from A1.c
    funcB
    f1 from B1.c
    

    Você provavelmente também poderia resolver o problema usando o atributo de visibilidade dinâmica do GCC , mas isso exigiria algumas modificações na fonte das bibliotecas.


    1. Esse comportamento do vinculador GNU com bibliotecas compartilhadas é intencional: é o mesmo que aconteceria se as bibliotecas fossem estáticas, em consonância com os esforços do vinculador GNU para fazer com que a resolução de símbolos seja semelhante tanto para arquivos estáticos quanto para bibliotecas compartilhadas.
    • 1

relate perguntas

  • Como construir quando __builtin_va_arg_pack() é usado

  • GCC+STM32: faltando um elemento na matriz

  • Comportamento de pré-processamento 'cpp' vs 'clang'

  • g++ "/ld.exe: não é possível encontrar l:mylib.a: Esse arquivo ou diretório não existe

  • CMAKE_WARN_DEPRECATED não desativa avisos obsoletos

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve