Por que e como algumas bibliotecas compartilhadas são executáveis, como se fossem executáveis?

56

Em sistemas Linux de 32 bits, chamando isso

$ /lib/libc.so.6

e em sistemas de 64 bits isso

$ /lib/x86_64-linux-gnu/libc.so.6

em um shell, fornece uma saída como esta:

GNU C Library stable release version 2.10.1, by Roland McGrath et al.
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.4.0 20090506 (Red Hat 4.4.0-4).
Compiled on a Linux >>2.6.18-128.4.1.el5<< system on 2009-08-19.
Available extensions:
    The C stubs add-on version 2.1.2.
    crypt add-on version 2.1 by Michael Glad and others
    GNU Libidn by Simon Josefsson
    Native POSIX Threads Library by Ulrich Drepper et al
    BIND-8.2.3-T5B
    RT using linux kernel aio
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.

Por que e como isso acontece, e como é possível fazer o mesmo em outras bibliotecas compartilhadas?

Eu olhei /usr/libpara encontrar executáveis ​​e encontrei /usr/lib/libvlc.so.5.5.0. A execução levou a uma falha de segmentação . : - /

Ho1
fonte
Além de todas as respostas a seguir, lembre-se de que, se você definir o bit x em uma biblioteca compartilhada, foi (talvez ainda seja) possível carregá-lo de um executável, mesmo com o bit r limpo. Era uma vez considerada uma boa prática de segurança banir r bit do mundo em executáveis ​​e bibliotecas do sistema. Devido ao amplo código aberto, isso realmente se aplica apenas ao diretório / bin / ls do ftp anônimo. Para mim, deixar o conjunto de bits x parece um rosto dessa antiga prática.
Joshua Joshua

Respostas:

53

Essa biblioteca possui uma main()função ou ponto de entrada equivalente e foi compilada de tal maneira que é útil como um objeto executável e como um objeto compartilhado.

Aqui está uma sugestão sobre como fazer isso, embora não funcione para mim.

Aqui está outro em resposta a uma pergunta semelhante sobre o SO , que plagiarei descaradamente, ajustarei e acrescentarei um pouco de explicação.

Primeiro, fonte para nossa biblioteca de exemplos test.c:

#include <stdio.h>                  

void sayHello (char *tag) {         
    printf("%s: Hello!\n", tag);    
}                                   

int main (int argc, char *argv[]) { 
    sayHello(argv[0]);              
    return 0;                       
}                   

Compile que:

gcc -fPIC -pie -o libtest.so test.c -Wl,-E

Aqui, estamos compilando uma biblioteca compartilhada ( -fPIC), mas informando ao vinculador que é um executável comum ( -pie) e para tornar sua tabela de símbolos exportável ( -Wl,-E), para que possa ser útil utilizá-la.

E, embora filediga que é um objeto compartilhado, ele funciona como um executável:

> ./libtest.so 
./libtest.so: Hello!

Agora precisamos ver se ele realmente pode ser vinculado dinamicamente. Um programa de exemplo program.c:

#include <stdio.h>

extern void sayHello (char*);

int main (int argc, char *argv[]) {
    puts("Test program.");
    sayHello(argv[0]);
    return 0;
}

Usar externnos impede de criar um cabeçalho. Agora compile isso:

gcc program.c -L. -ltest

Antes de podermos executá-lo, precisamos adicionar o caminho libtest.sopara o carregador dinâmico:

export LD_LIBRARY_PATH=./

Agora:

> ./a.out
Test program.
./a.out: Hello!

E ldd a.outmostrará a ligação para libtest.so.

Note que duvido que seja assim que o glibc é realmente compilado, uma vez que provavelmente não é tão portátil quanto o próprio glibc (veja man gccem relação aos switches -fPICe -pie), mas demonstra o mecanismo básico. Para os detalhes reais, você teria que consultar o makefile de origem.

Cachinhos Dourados
fonte
11
Ótima resposta, obrigado! :-) Tentei usar nmna biblioteca compartilhada, mas não era uma versão de depuração. Então, por que libvlce outros falham?
• Ho1
11
Como a maioria das bibliotecas compartilhadas não pretende ser executável, o GNU libcé uma exceção.
Goldilocks
Eu encontrei dois outros: lde libpthread.
• Ho1
@ Ho1 ld.soé especial de outras maneiras. Até certo ponto, é mais um executável real do que um executável vinculado dinamicamente normal.
usar o seguinte código
11
As opções acima, embora criem a biblioteca compartilhada executável, mas estão incompletas no sentido de sinalizar erro, quando algum executável tenta vincular a isso. Referência de amostra detalhada adicionada aqui: unix.stackexchange.com/a/479334/152034
parasrish
21

Vamos procurar uma resposta no repositório glibc aleatório no github. Esta versão fornece um "banner" no arquivo version.c.

No mesmo arquivo, há alguns pontos interessantes: __libc_print_versiona função que fornece impressão para imprimir o mesmo texto e símbolo, __libc_main (void)que está documentado como um ponto de entrada. Portanto, esse símbolo é chamado ao executar a biblioteca.

Então, como o vinculador / compilador sabe que essa é exatamente a função do ponto de entrada?

Vamos mergulhar no makefile . Nos sinalizadores de vinculador, há um sinalizador interessante:

# Give libc.so an entry point and make it directly runnable itself.
LDFLAGS-c.so += -e __libc_main

Portanto, esse é um sinalizador de vinculador para definir o ponto de entrada na biblioteca. Ao criar uma biblioteca, você pode fornecer um -e function_namecomportamento executável para criação de vinculador. O que realmente faz? Vejamos o manual (um pouco datado, mas ainda válido) :

A linguagem de comando do vinculador inclui um comando especificamente para definir a primeira instrução executável em um arquivo de saída (seu ponto de entrada). Seu argumento é um nome de símbolo:

ENTRADA (símbolo)

Como as designações de símbolos, o comando ENTRY pode ser colocado como um comando independente no arquivo de comandos ou entre as definições de seção no comando SECTIONS - o que fizer mais sentido para o seu layout.

ENTRY é apenas uma das várias maneiras de escolher o ponto de entrada. Você pode indicá-lo de uma das seguintes maneiras (mostradas em ordem decrescente de prioridade: os métodos mais altos na lista substituem os métodos mais abaixo).

the `-e' entry command-line option;
the ENTRY(symbol) command in a linker control script;
the value of the symbol start, if present;
the address of the first byte of the .text section, if present;
The address 0. 

Por exemplo, você pode usar essas regras para gerar um ponto de entrada com uma instrução de atribuição: se nenhum início de símbolo for definido em seus arquivos de entrada, você poderá simplesmente defini-lo, atribuindo um valor apropriado ---

start = 0x2020;

O exemplo mostra um endereço absoluto, mas você pode usar qualquer expressão. Por exemplo, se os arquivos do objeto de entrada usarem alguma outra convenção de nome e símbolo para o ponto de entrada, você poderá atribuir o valor de qualquer símbolo que contenha o endereço inicial:

start = outro_símbolo;

(a documentação atual pode ser encontrada aqui )

Realmente, o ldvinculador cria um executável com função de ponto de entrada se você fornecer a opção de linha de comando -e(solução mais prática), fornecer símbolo de função startou injetar endereço de símbolo no assembler.

No entanto, observe que claramente não é garantido que funcione com outros vinculadores (não sei se o lld do llvm tem o mesmo sinalizador). Por que isso deve ser útil para outros fins, além de fornecer informações sobre esse arquivo, não sei.

IBr
fonte
11
Se fosse python, forneceria testes de unidade.
Erik Aronesty 19/03/19