Il puntatore void *
restituito da dlopen(0, RTLD_LAZY)
ti dà un struct link_map *
, che corrisponde al file eseguibile principale.
La chiamata dl_iterate_phdr
restituisce inoltre la voce per l'eseguibile principale sulla prima esecuzione di callback.
Probabilmente siete confusi dal fatto che .l_addr == 0
nella mappa dei collegamenti e da quello dlpi_addr == 0
quando si utilizza dl_iterate_phdr
.
Questo sta accadendo, perché l_addr
(e dlpi_addr
) non registrano effettivamente l'indirizzo di caricamento di un'immagine ELF. Piuttosto, registrano il riposizionamento che è stato applicato a quell'immagine.
Solitamente l'eseguibile principale è costruito per essere caricato 0x400000
(per Linux x86_64) oa 0x08048000
(per ix86 Linux), e vengono caricati in quello stesso indirizzo (cioè esse non vengono spostati).
Ma se si collega il vostro eseguibile con -pie
bandiera, allora saranno collegati al-0x0
, e saranno trasferiti a qualche altro indirizzo.
Quindi, come si arriva all'intestazione ELF? Facile:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <link.h>
#include <stdio.h>
#include <stdlib.h>
static int
callback(struct dl_phdr_info *info, size_t size, void *data)
{
int j;
static int once = 0;
if (once) return 0;
once = 1;
printf("relocation: 0x%lx\n", (long)info->dlpi_addr);
for (j = 0; j < info->dlpi_phnum; j++) {
if (info->dlpi_phdr[j].p_type == PT_LOAD) {
printf("a.out loaded at %p\n",
(void *) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr));
break;
}
}
return 0;
}
int
main(int argc, char *argv[])
{
dl_iterate_phdr(callback, NULL);
exit(EXIT_SUCCESS);
}
$ gcc -m32 t.c && ./a.out
relocation: 0x0
a.out loaded at 0x8048000
$ gcc -m64 t.c && ./a.out
relocation: 0x0
a.out loaded at 0x400000
$ gcc -m32 -pie -fPIC t.c && ./a.out
relocation: 0xf7789000
a.out loaded at 0xf7789000
$ gcc -m64 -pie -fPIC t.c && ./a.out
relocation: 0x7f3824964000
a.out loaded at 0x7f3824964000
Aggiornamento:
Perché la pagina man dire "l'indirizzo di base" e non delocalizzazione?
Si tratta di un bug ;-)
sto indovinando che la pagina è stata scritta molto tempo prima che prelink
e pie
e ASLR
esistito. Senza prelink, le librerie condivise sono sempre collegate per caricare all'indirizzo 0x0
, e quindi relocation
e base address
diventano una e la stessa cosa.
come mai dlpi_name punta a una stringa vuota quando le informazioni si riferiscono all'eseguibile principale?
È un incidente di implementazione.
Il modo in cui funziona, è che il kernel open(2)
s l'eseguibile e passa il descrittore di file aperto al caricatore (nel auxv[]
vettore, come AT_EXECFD
). Tutto il il caricatore sa dell'eseguibile ottenuto leggendo il descrittore di file.
Non esiste un modo semplice in UNIX per eseguire il mapping di un descrittore di file al nome con cui è stato aperto. Per prima cosa, UNIX supporta i collegamenti fisici e potrebbero esserci più nomi di file che fanno riferimento allo stesso file.
I nuovi kernel di Linux passano anche il nome che era stato utilizzato per execve(2)
l'eseguibile (anche in auxv[]
, come AT_EXECFN
). Ma questo è opzionale, e anche quando viene passato, glibc non lo inserisce in .l_name
/dlpi_name
per non interrompere i programmi esistenti che sono diventati dipendenti dal fatto che il nome sia vuoto.
Invece, glibc salva quel nome in __progname
e __progname_full
.
Il caricatore COUDreadlink(2)
il nome da /proc/self/exe
sui sistemi che non hanno utilizzato AT_EXECFN
, ma il /proc
file system non è garantito per essere montato, in modo che sarebbe ancora lasciare con un nome vuoto volte.
'read()' da '/ proc/self/exe' – Dave
Ah, ho dimenticato di menzionare, ho anche bisogno dell'indirizzo di base insieme all'intestazione ELF, e l'intestazione ELF dovrebbe essere dove l'indirizzo di base è –