Quale chiamata di sistema viene utilizzata per caricare le librerie in Linux?


23

Negli straceoutput, i percorsi verso le librerie che gli eseguibili chiamano sono nelle chiamate a open(). È questa la chiamata di sistema utilizzata dagli eseguibili che sono collegati dinamicamente? Che dire dlopen()? open()non è una chiamata che avrei indovinato avrebbe avuto un ruolo nell'esecuzione dei programmi.

Risposte:


33

dlopennon è una chiamata di sistema, è una funzione di libreria nella libreria libdl . Vengono visualizzate solo le chiamate di sistema strace.

Su Linux e su molte altre piattaforme (in particolare quelle che utilizzano il formato ELF per gli eseguibili), dlopenviene implementato aprendo la libreria di destinazione con open()e mappandola in memoria con mmap(). mmap()è davvero la parte critica qui, è ciò che incorpora la libreria nello spazio degli indirizzi del processo, quindi la CPU può eseguire il suo codice. Ma devi prima avere open()il file mmap()!


2
"mmap () è davvero la parte critica": E poi il linker dinamico deve fare i trasferimenti, l'inizializzazione e così uno (ma questo non è visto a livello di chiamata di sistema).
ysdx,

1
Poiché il caricamento delle librerie viene eseguito da una funzione di libreria, ritengo sia importante aggiungere che l'eseguibile stesso e che ld-linuxsono mappati dal kernel come parte della execvechiamata di sistema.
Kasperd,

mmap secondo questa risposta. Si noti inoltre che dopo aver "aperto" ogni libreria, alcuni (832) byte vengono letti prima della chiamata mmap, presumo di verificare che la libreria sia valida.
Johan,

@kasperd Quindi il kernel Linux è a conoscenza del caricatore dinamico? Lo chiama quando viene eseguita l'applicazione? O l'applicazione stessa lo fa? Se quest'ultimo, in che modo un altro eseguibile ha accesso alla memoria dell'applicazione?
Melab,

@Melab Sì, il kernel è a conoscenza del linker dinamico. Il kernel leggerà il percorso del linker dinamico dall'intestazione dell'eseguibile. E il kernel mapperà entrambi in memoria. Non so se il punto di ingresso a cui il kernel trasferisce inizialmente il controllo sia all'interno del linker o dell'eseguibile. Se lo stavo implementando, probabilmente avrei il controllo del trasferimento del kernel in un punto di ingresso nel linker con un indirizzo di ritorno nello stack che punta al punto di ingresso dell'eseguibile.
Kasperd,

5

dlopen non ha nulla a che fare con le librerie condivise mentre le pensi. Esistono due metodi per caricare un oggetto condiviso:

  1. Dici al linker di compilazione (ld, sebbene di solito sia chiamato attraverso il compilatore) che vuoi usare le funzioni di una particolare libreria condivisa. Con questo approccio, è necessario sapere quale sarà il nome della libreria quando viene eseguito il linker in fase di compilazione, ma è possibile chiamare le funzioni della libreria come se fossero staticamente collegate al proprio programma. Quando l'applicazione viene eseguita, il linker dinamico runtime (ld.so) verrà chiamato poco prima della mainchiamata della funzione e imposta lo spazio del processo dell'applicazione in modo che l'applicazione trovi le funzioni della libreria. Ciò comporta open()l'inserimento del Lubrary e quindi l' mmap()ing, seguito dall'impostazione di alcune tabelle di ricerca.
  2. Dici al linker di compilazione che vuoi collegare libdl, da cui poi (usando il primo metodo) puoi chiamare dlopen()edlsym()funzioni. Con dlopen si ottiene un handle per la libreria, che è quindi possibile utilizzare con dlsym per ricevere un puntatore a una determinata funzione. Questo metodo è molto più complicato per il programmatore rispetto al primo metodo (dal momento che devi fare l'installazione manualmente, piuttosto che il linker lo fa automaticamente per te), ed è anche più fragile (poiché non ottieni la compilazione -time verifica che stai chiamando le funzioni con i tipi di argomento corretti come ottieni nel primo metodo), ma il vantaggio è che puoi decidere quale oggetto condiviso caricare in fase di esecuzione (o anche se caricarlo affatto), rendendo questa è un'interfaccia pensata per la funzionalità di tipo plug-in. Infine, l'interfaccia dlopen è anche meno portabile dell'altro modo, poiché i suoi meccanismi dipendono dall'esatta implementazione del linker dinamico (da qui quello di libtoollibltdl, che cerca di sottrarre queste differenze).

interessante; quindi le librerie caricate dinamicamente sono meglio chiamate librerie collegate dinamicamente, dal momento che il caricamento di file binari in memoria non è la parte difficile, ha senso avere gli indirizzi utilizzati in essa. Quando chiedo di caricare una libreria dinamica, in realtà sto chiedendo di collegare (o scollegare) la libreria nel (o fuori) dal mio spazio degli indirizzi.
Dmitry

4

Oggi, la maggior parte dei sistemi operativi utilizza il metodo per librerie condivise introdotto alla fine del 1987 da SunOS-4.0. Questo metodo si basa sulla mappatura della memoria tramite mmap ().

Dato che nei primi anni '90, Sun ha persino donato il vecchio codice a.out (a quel tempo Solaris era già basato su ELF) alle persone di FreeBSD e che questo codice è stato successivamente trasferito a molti altri sistemi (incluso Linux) , potresti capire perché non c'è grande differenza tra le piattaforme.


3

ltrace -Sl'analisi di un esempio minimo mostra che mmapè usato in glibc 2.23

In glibc 2.23, Ubuntu 16.04, in esecuzione latrace -Ssu un programma minimo che utilizza dlopencon:

ltrace -S ./dlopen.out

Spettacoli:

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

quindi vediamo immediatamente che dlopenchiama open+ mmap.

Il fantastico ltracestrumento traccia sia le chiamate in libreria che le chiamate di sistema ed è quindi perfetto per esaminare cosa sta succedendo in questo caso.

Un'analisi più ravvicinata mostra che openrestituisce il descrittore di file 3(quello successivo dopo stdin, out ed err).

readusa quindi quel descrittore di file, ma TODO perché mmapgli argomenti sono limitati a quattro, e non possiamo vedere quale fd è stato usato lì poiché quello è il quinto argomento . straceconferma come previsto che 3è quello e l'ordine dell'universo viene ripristinato.

Le anime coraggiose possono anche avventurarsi nel codice glibc, ma non sono riuscito a trovare il mmapdopo un grep veloce e sono pigro.

Testato con questo esempio minimo con build boilerplate su GitHub .


2

stracerapporti sulle chiamate di sistema (ovvero funzioni implementate direttamente dal kernel). Le librerie dinamiche non sono una funzione del kernel; dlopenfa parte della libreria C, non del kernel. L'implementazione di dlopenwill call open(che è una chiamata di sistema) per aprire il file della libreria in modo che possa essere letto.


5
Le chiamate in libreria possono essere visualizzate utilizzando ltrace.
Kasperd,

@kasperd ltrace -Sè perfetto per analizzarlo poiché mostra anche le syscalls: unix.stackexchange.com/a/462710/32558
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.