GNU/Linux >> Linux Esercitazione >  >> Linux

Perché e come alcune librerie condivise sono eseguibili, come se fossero eseguibili?

Quella libreria ha un main() funzione o punto di ingresso equivalente, ed è stato compilato in modo tale da essere utile sia come eseguibile che come oggetto condiviso.

Ecco un suggerimento su come farlo, anche se non funziona per me.

Eccone un'altra in risposta a una domanda simile su S.O, che plagio spudoratamente, modificherò e aggiungerò un po' di spiegazione.

Innanzitutto, fonte per la nostra libreria di esempio, 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;                       
}                   

Compilalo:

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

Qui stiamo compilando una libreria condivisa (-fPIC ), ma dicendo al linker che si tratta di un normale eseguibile (-pie ) e per rendere esportabile la sua tabella dei simboli (-Wl,-E ), in modo tale che possa essere utilmente collegato a.

E, sebbene file dirà che è un oggetto condiviso, funziona come un eseguibile:

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

Ora dobbiamo vedere se può davvero essere collegato dinamicamente. Un programma di esempio, program.c :

#include <stdio.h>

extern void sayHello (char*);

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

Usando extern ci evita di dover creare un'intestazione. Ora compilalo:

gcc program.c -L. -ltest

Prima di poterlo eseguire, dobbiamo aggiungere il percorso di libtest.so per il caricatore dinamico:

export LD_LIBRARY_PATH=./

Ora:

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

E ldd a.out mostrerà il collegamento a libtest.so .

Nota che dubito che questo sia il modo in cui glibc sia effettivamente compilato, dal momento che probabilmente non è portabile come glibc stesso (vedi man gcc per quanto riguarda il -fPIC e -pie interruttori), ma dimostra il meccanismo di base. Per i veri dettagli dovresti guardare il makefile sorgente.


Cerchiamo una risposta in un repository glibc casuale su GitHub. Questa versione fornisce un "banner" nel file version.c .

Nello stesso file ci sono alcuni punti interessanti:il __libc_print_version funzione che stampa il testo su stdout e il __libc_main (void) simbolo che è documentato come punto di ingresso. Quindi questo simbolo viene chiamato quando si esegue la libreria.

Allora come fa il linker o il compilatore a sapere esattamente che questa è la funzione del punto di ingresso?

Entriamo nel makefile. Nei flag del linker ce n'è uno interessante:

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

Quindi questo è il flag del linker per impostare il punto di ingresso per la libreria. Quando crei una libreria puoi fornire il -e function_name flag per il linker per abilitare un comportamento eseguibile. Cosa fa veramente? Diamo un'occhiata al manuale (un po' obsoleto ma ancora valido):

Il linguaggio di comando del linker include un comando specifico per definire la prima istruzione eseguibile in un file di output (il suo punto di ingresso). Il suo argomento è un nome di simbolo:

ENTRY(simbolo)

Come le assegnazioni di simboli, il comando ENTRY può essere posizionato come comando indipendente nel file di comando o tra le definizioni di sezione all'interno del comando SECTIONS, qualunque cosa abbia più senso per il tuo layout.

ENTRY è solo uno dei tanti modi per scegliere il punto di ingresso. Puoi indicarlo in uno dei seguenti modi (mostrati in ordine di priorità decrescente:i metodi più in alto nell'elenco sovrascrivono i metodi più in basso).

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. 

Ad esempio, puoi utilizzare queste regole per generare un punto di ingresso con un'istruzione di assegnazione:se nessun simbolo iniziale è definito all'interno dei tuoi file di input, puoi semplicemente definirlo, assegnandogli un valore appropriato---

inizio =0x2020;

L'esempio mostra un indirizzo assoluto, ma puoi utilizzare qualsiasi espressione. Ad esempio, se i tuoi file oggetto di input utilizzano un'altra convenzione di nome-simbolo per il punto di ingresso, puoi semplicemente assegnare il valore di qualunque simbolo contenga l'indirizzo iniziale per iniziare:

inizio =altro_simbolo;

(la documentazione attuale può essere trovata qui)

Il ld linker in realtà crea un eseguibile con una funzione punto di ingresso se fornisci l'opzione della riga di comando -e (che è la soluzione più popolare), fornire un simbolo di funzione start , o specificare un indirizzo di simbolo per l'assembler.

Tuttavia, tieni presente che non è garantito che funzioni con altri linker (non so se lld di llvm ha lo stesso flag). Non sono a conoscenza del motivo per cui questo dovrebbe essere utile per scopi diversi dal fornire le informazioni sul file SO.


Linux
  1. Come gestire le librerie dinamiche e statiche in Linux

  2. In un ambiente vuoto, come vengono trovati gli eseguibili?

  3. Perché le librerie condivise su Linux sono eseguibili?

  4. Come verificare se è installata una libreria condivisa?

  5. Perché ci sono `/lib` e `/lib64` ma solo `/bin`?

Filesystem virtuali in Linux:perché ne abbiamo bisogno e come funzionano

Come elencare le librerie condivise utilizzate dagli eseguibili in Linux

Linux:perché vero e falso sono così grandi?

Perché alcune Emoji in bianco e nero e altre sono troppo grandi?

Come verificare quali librerie condivise vengono caricate in fase di esecuzione per un determinato processo?

Come funziona il collegamento dinamico, il suo utilizzo e come e perché dovresti creare un dylib