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 . : - /


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.


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.


11
Se fosse python, forneceria testes de unidade.
— Erik Aronesty 19/03/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.