Qual chamada de sistema é usada para carregar bibliotecas no Linux?

23

Nas stracesaídas, os caminhos para as bibliotecas que os executáveis ​​chamam estão nas chamadas para open(). É a chamada do sistema usada pelos executáveis ​​que estão vinculados dinamicamente? Que tal dlopen()? open()não é uma ligação que eu imaginaria que teria um papel na execução de programas.

Melab
fonte

Respostas:

33

dlopennão é uma chamada do sistema, é uma função de biblioteca na biblioteca libdl . Apenas as chamadas do sistema são exibidas em strace.

No Linux e em muitas outras plataformas (especialmente aquelas que usam o formato ELF para executáveis), dlopené implementado abrindo a biblioteca de destino open()e mapeando-a na memória mmap(). mmap()é realmente a parte crítica aqui, é o que incorpora a biblioteca no espaço de endereço do processo, para que a CPU possa executar seu código. Mas você precisa open()arquivar antes de poder mmap()!

Celada
fonte
2
"mmap () é realmente a parte crítica": E o vinculador dinâmico precisa fazer as realocações, a inicialização e assim por diante (mas isso não é visto no nível de chamada do sistema).
Ysdx
1
Como o carregamento de bibliotecas é feito por uma função de biblioteca, acho importante adicionar o próprio executável e ld-linuxser mapeado pelo kernel como parte da execvechamada do sistema.
kasperd
mmap conforme esta resposta. Observe também que, após "abrir" em cada biblioteca, alguns bytes (832) são lidos antes da chamada do mmap, presumo verificar se a biblioteca é válida.
Johan
@kasperd Então, o kernel do Linux está ciente do carregador dinâmico? Ele chama quando o aplicativo é executado? Ou o próprio aplicativo faz isso? Nesse último caso, como outro executável tem acesso à memória do aplicativo?
Melab
@ Melab Sim, o kernel está ciente do vinculador dinâmico. O kernel lerá o caminho para o vinculador dinâmico a partir do cabeçalho do executável. E o kernel irá mapear ambos na memória. Não sei se o ponto de entrada ao qual o kernel transfere o controle está dentro do vinculador ou do executável. Se eu o estivesse implementando, provavelmente teria o controle de transferência do kernel para um ponto de entrada no vinculador com um endereço de retorno na pilha apontando para o ponto de entrada do executável.
28816 kasperd
5

O dlopen não tem nada a ver com bibliotecas compartilhadas como você pensa nelas. Existem dois métodos para carregar um objeto compartilhado:

  1. Você diz ao vinculador em tempo de compilação (ld, embora geralmente seja chamado pelo compilador) que deseja usar funções de uma biblioteca compartilhada específica. Com essa abordagem, você deve saber qual será o nome da biblioteca quando o vinculador em tempo de compilação for executado, mas você pode chamar as funções da biblioteca como se estivessem estaticamente vinculadas ao seu programa. Quando o aplicativo é executado, o vinculador dinâmico em tempo de execução (ld.so) será chamado imediatamente antes domain função ser chamada e configurará o espaço de processo do aplicativo para que o aplicativo encontre as funções da biblioteca. Isso envolve open()o lubrificante e mmap(), em seguida, o mesmo, seguido da configuração de algumas tabelas de pesquisa.
  2. Você diz ao vinculador em tempo de compilação com o qual deseja vincular libdl, a partir do qual você (usando o primeiro método) pode chamar o dlopen()edlsym()funções. Com o dlopen, você obtém um identificador para a biblioteca, que pode ser usado com o dlsym para receber um ponteiro de função para uma função específica. Esse método é muito mais complicado para o programador do que o primeiro (já que você precisa fazer a instalação manualmente, em vez de o vinculador fazer automaticamente por você), e também é mais frágil (já que você não obtém a compilação -time verifica se você está chamando funções com os tipos de argumentos corretos, conforme você começa no primeiro método), mas a vantagem é que você pode decidir qual objeto compartilhado carregar no tempo de execução (ou mesmo se deve carregá-lo), tornando essa é uma interface destinada à funcionalidade do tipo plug-in. Finalmente, a interface dlopen também é menos portátil do que o contrário, pois sua mecânica depende da implementação exata do vinculador dinâmico (portanto, o libtool'slibltdl, que tenta abstrair essas diferenças).
Wouter Verhelst
fonte
interessante; portanto, as bibliotecas carregadas dinamicamente são mais chamadas de bibliotecas vinculadas dinamicamente, pois o carregamento de arquivos binários na memória não é a parte mais difícil, fazendo sentido os endereços usados ​​nela. Quando peço para carregar uma biblioteca dinâmica, na verdade estou pedindo para vincular (ou desvincular) a biblioteca no (ou fora) meu espaço de endereço.
Dmitry
4

Hoje, a maioria dos sistemas operacionais usa o método para bibliotecas compartilhadas introduzido no final de 1987 pelo SunOS-4.0. Este método é baseado no mapeamento de memória via mmap ().

Dado o fato de que, no início dos anos 90, a Sun doou o antigo código baseado em a.out (o Solaris naquela época já era baseado em ELF) ao pessoal do FreeBSD e que esse código foi posteriormente entregue a muitos outros sistemas (incluindo Linux) , você pode entender por que não há grande diferença entre plataformas.

esperto
fonte
3

ltrace -SA análise de um exemplo mínimo mostra que mmapé usado na glibc 2.23

No glibc 2.23, Ubuntu 16.04, executando latrace -Sem um programa mínimo que usa dlopencom:

ltrace -S ./dlopen.out

mostra:

dlopen("libcirosantilli_ab.so", 1 <unfinished ...>
SYS_open("./x86_64/libcirosantilli_ab.so", 524288, 06267650550)      = -2
SYS_open("./libcirosantilli_ab.so", 524288, 06267650550)             = 3
SYS_read(3, "\177ELF\002\001\001", 832)                              = 832
SYS_brk(0)                                                           = 0x244c000
SYS_brk(0x246d000)                                                   = 0x246d000
SYS_fstat(3, 0x7fff42f9ce30)                                         = 0
SYS_getcwd("/home/ciro/bak/git/cpp-cheat"..., 128)                   = 54
SYS_mmap(0, 0x201028, 5, 2050)                                       = 0x7f1c323fe000
SYS_mprotect(0x7f1c323ff000, 2093056, 0)                             = 0
SYS_mmap(0x7f1c325fe000, 8192, 3, 2066)                              = 0x7f1c325fe000
SYS_close(3)                                                         = 0
SYS_mprotect(0x7f1c325fe000, 4096, 1)                                = 0

então vemos imediatamente que dlopenchama open+ mmap.

A ltraceferramenta impressionante rastreia chamadas de biblioteca e chamadas de sistema e, portanto, é perfeita para examinar o que está acontecendo neste caso.

Uma análise mais detalhada mostra que openretorna o descritor de arquivo 3(o próximo é gratuito após stdin, out e err).

readentão usa esse descritor de arquivo, mas os argumentos do TODO why mmapsão limitados a quatro, e não podemos ver qual fd foi usado lá, pois esse é o quinto argumento . straceconfirma como esperado que 3é esse, e a ordem do universo é restaurada.

Almas corajosas também podem se aventurar no código glibc, mas não consegui encontrar o mmapapós um grep rápido e sou preguiçoso.

Testado com este exemplo mínimo com build clichê no GitHub .

Ciro Santilli adicionou uma nova foto
fonte
2

stracerelatórios sobre chamadas do sistema (ou seja, funções implementadas diretamente pelo kernel). Bibliotecas dinâmicas não são uma função do kernel; dlopenfaz parte da biblioteca C, não do kernel. A implementação de dlopenirá chamar open(que é uma chamada do sistema) para abrir o arquivo da biblioteca para que possa ser lido.

cjm
fonte
5
Chamadas de biblioteca podem ser vistas usando ltrace.
kasperd
@kasperd ltrace -Sé perfeito para analisar isso como ele também mostra syscalls: unix.stackexchange.com/a/462710/32558
Ciro Santilli新疆改造中心法轮功六四事件