Tenho 2 bibliotecas de objetos compartilhados, por exemplo, libA.so
e libB.so
. Tenho um executável que chama funções de ambas as bibliotecas, por exemplo, funcA()
from libA
e funcB()
from libB
.
Infelizmente, ambos libA
e libB
têm algumas outras funções que usam o mesmo nome de símbolo, por exemplo, libA
tem a função f1()
e f2()
e libB
també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, libA
deve chamar apenas seu f1()
& f2()
e libB
deve 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 libA
e 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.script
ao 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 libA
e 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 libA
e libB
sã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.
Primeiro, uma ilustração mínima do seu problema. Para mantê-lo mínimo, vou cortar as funções ambivalentes de
f1()
,f2()
para apenasf1()
. Já que você diz: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
:Arquivos de origem e de cabeçalho para
libB.so
:Fonte para um programa:
Compilar todas as fontes da biblioteca compartilhada:
E a fonte do programa:
Vincule as bibliotecas compartilhadas:
E o programa:
Então vemos seu problema:
Ambos
funcA
efuncB
ambos chamam of1
fromlibA.so
, definido emA1.c
.Ao vincular novamente o programa, com
libA.so
elibA.so
na ordem inversa:podemos produzir o problema oposto:
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 delibA.so
elibB.so
: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ávelprog
. Observe o topo de sua seção dinâmica: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.so
elibA.so
, nessa ordem. ORUNPATH
/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
f1
serálibB.so
; essa é a definição deB1.c
, e ele vinculará todas as referências no programa a essa definição.Vamos voltar à ligação original de
prog
:Então, como você adivinhou, o topo da seção dinâmica do prog mostrará:
com
libA.so
carregado anteslibB.so
, elibA
a definição def1
será 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
. Deman ld
(ld
é o vinculador estático, invocado em seu nome porgcc
para executar vinculações):Então podemos revincular as bibliotecas compartilhadas assim:
(Aliás, costumamos
-Wl,<ld-option>
dizergcc
para passar a opçãold-option
diretamente parald
.)Revincule o programa com as novas bibliotecas:
e então cada biblioteca chama sua própria definição de
f1
: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.