Ligação dinâmica - Linux vs. janelas

10

No Windows, quando compilo o código C / C ++ em um projeto DLL no MSVC, estou recebendo 2 arquivos:

  1. MyDll.dll
  2. MyDll.lib

onde, tanto quanto eu entendo, MyDll.libcontém algum tipo de tabela de ponteiros indicando os locais das funções na dll. Ao usar essa dll, digamos em um arquivo exe, MyDll.libé incorporada ao arquivo exe durante o vínculo, portanto, em tempo de execução, ele "sabe" onde as funções estão localizadas MyDll.dlle pode usá-las.

Mas se eu compilar o mesmo código no Linux, estou obtendo apenas um arquivo MySo.sosem MySo.a(o equivalente ao libarquivo no Linux). Como um arquivo executável no Linux sabe onde as funções estão localizadas MySo.sose nada estiver incorporado durante a vinculação?

Benny K
fonte

Respostas:

1

No Linux, o vinculador (não o vinculador dinâmico) pesquisa nas bibliotecas compartilhadas especificadas no momento do link e cria referências a elas dentro do executável. Quando o vinculador dinâmico carrega esses executáveis, ele carrega as bibliotecas compartilhadas necessárias na memória e resolve os símbolos, o que permite que os binários sejam executados.

MySo.a, se criado, incluiria os símbolos a serem vinculados diretamente ao binário, em vez das "tabelas de pesquisa de símbolos" usadas no Windows.

a resposta da rustyx explica o processo no Windows mais detalhadamente do que eu; faz muito tempo desde que usei o Windows.

SS Anne
fonte
11
"O Windows adota uma abordagem diferente ... especifique para o sistema operacional exatamente onde os símbolos estão na DLL" - isso contradiz o wiki , que diz que os nomes das funções ainda são resolvidos (na inicialização ou na primeira chamada à função de biblioteca), mesmo quando você use ordinals (a menos que a ligação direta ao endereço seja usada, o que ninguém faria porque força os usuários da biblioteca a recompilar e reimplementar seu código sempre que a biblioteca for alterada).
yugr 29/10/19
@yugr Removida essa parte, eu estava agarrando os canudos de qualquer maneira.
SS Anne
4

O vinculador do MSVC pode vincular arquivos de objetos (.obj) e bibliotecas de objetos (.lib) para produzir um .EXE ou um .DLL.

Para vincular a uma DLL, o processo no MSVC é usar a chamada biblioteca de importação (.LIB) que atua como uma cola entre os nomes das funções C e a tabela de exportação da DLL (em uma DLL, uma função pode ser exportada por nome ou por ordinal - este último costumava ser usado para APIs não documentadas).

No entanto, na maioria dos casos, a tabela de exportação DLL possui todos os nomes das funções e, portanto, a biblioteca de importação (.LIB) contém informações amplamente redundantes (" função de importação ABC -> função exportada ABC ", etc).
É até possível gerar um .LIB a partir de um .DLL existente.

Os vinculadores em outras plataformas não possuem esse "recurso" e podem vincular-se diretamente às bibliotecas dinâmicas.

rustyx
fonte
"Vinculadores em outras plataformas não possuem esse recurso" - embora seja fácil de implementar (por exemplo, o Implib.so faz isso no Linux) para obter atrasos no carregamento e outras vantagens.
yugr 29/10/19
@ yugr: é por isso que "feature" está entre aspas - não é algo que você geralmente deseja fazer e é um trabalho extra que você deve fazer no Windows.
Chris Dodd
1

A diferença que você está vendo é mais um detalhe de implementação - sob o capô, Linux e Windows funcionam da mesma forma - o código chama uma função stub que está estaticamente vinculada no seu executável e esse stub carrega DLL / shlib, se necessário (em caso de atraso) carregando , caso contrário, a biblioteca é carregada quando o programa inicia) e (na primeira chamada) resolve o símbolo via GetProcAddress/ dlsym.

A única diferença é que no Linux essas funções de stub (chamadas de stubs PLT) são geradas dinamicamente quando você vincula seu aplicativo à biblioteca dinâmica (a biblioteca contém informações suficientes para gerá-los), enquanto no Linux elas são geradas quando a própria DLL é criado, em um .libarquivo separado .

As duas abordagens são tão semelhantes que é realmente possível imitar as bibliotecas de importação do Windows no Linux (consulte o projeto Implib.so ).

yugr
fonte
0

No Linux, você passa MySo.sopara o vinculador e ele pode extrair apenas o necessário para a fase do link, colocando uma referência MySo.sonecessária no tempo de execução.

AProgrammer
fonte
-3

.dllou .sosão bibliotecas compartilhadas (vinculadas em tempo de execução), enquanto .ae .libé uma biblioteca estática (vinculada em tempo de compilação). Isso não faz diferença entre Windows e Linux.

A diferença é como eles são tratados. Nota: a diferença está apenas nos costumes, como eles são usados. Não seria muito difícil criar o Linux no modo Windows e vice-versa, exceto que praticamente ninguém faz isso.

Se usarmos uma dll ou chamarmos uma função mesmo de nosso próprio binário, haverá uma maneira simples e clara. Por exemplo, em C, vemos que:

int example(int x) {
  ...do_something...
}

int ret = example(42);

No entanto, no nível ASM, pode haver muitas diferenças. Por exemplo, em x86, um callcódigo de operação é executado e o 42dado na pilha. Ou em alguns registros. Ou em qualquer lugar. Ninguém sabe que antes de escrever a dll , como ela será usada. Ou como os projetos vão querer usá-lo, possivelmente escrito com um compilador (ou em um idioma!) Que nem existe agora (ou é desconhecido para os desenvolvedores da dll).

Por exemplo, por padrão, C e Pascal colocam os argumentos (e obtêm os valores de retorno) da pilha - mas eles estão fazendo isso em ordem diferente . Você também pode trocar argumentos entre suas funções nos registradores por alguma otimização - dependente do compilador -.

Como você vê corretamente, o costume do Windows é criar uma dll, também criamos um mínimo .a/ .libcom ela. Essa biblioteca estática mínima é apenas um invólucro, os símbolos (funções) dessa dll são alcançados através dela. Isso faz as conversões de chamada no nível asm necessárias.

Sua vantagem é a compatibilidade. Sua desvantagem é que, se você tiver apenas uma .dll, poderá ter dificuldade em descobrir como é que suas funções serão chamadas. Isso torna o uso de dlls uma tarefa de hacking, se o desenvolvedor da dll não fornecer a você.a . Assim, ele serve principalmente para fins de fechamento, por exemplo, portanto é mais fácil obter dinheiro extra para os SDKs.

Outra desvantagem é que, mesmo que você use uma biblioteca dinâmica, é necessário compilar esse pequeno invólucro estaticamente.

No Linux, a interface binária das DLLs é padrão e segue a convenção C. Portanto, não .aé necessário e há compatibilidade binária entre as bibliotecas compartilhadas; em troca, não temos as vantagens do costume da microsoft.

peterh - Restabelecer Monica
fonte
11
Forneça um link de prova de que as funções de stub podem alterar a ordem dos argumentos. Eu nunca ouvi falar disso antes e é difícil de acreditar, dado o tamanho da sobrecarga de desempenho.
yugr 29/10/19
@yugr Um simples reordenamento de registro / pilha não é uma sobrecarga de desempenho. Se você usa dlls compiladas pelo msvc a partir de binários compilados pelo msvc, obviamente não acontecerá muita coisa, mas poderia.
peterh - Restabelece Monica
11
Poderíamos argumentar sobre isso, mas, caso você esteja certo, deve ser fácil fornecer provas de que as funções stub são capazes de processar processos não triviais de argumentos (e ser mais do que apenas trampolins fictícios).
yugr 29/10/19
@yugr Os stubs têm acesso às assinaturas de funções da dll, o que torna trivial o processamento não trivial.
peterh - Restabelece Monica
11
Eu sugiro que você complete sua resposta com poucos links de prova sobre o que a biblioteca de importação faz (porque algumas das reivindicações são questionáveis).
yugr 29/10/19