El puntero devuelto por void *
dlopen(0, RTLD_LAZY)
le da una struct link_map *
, que se corresponde con el ejecutable principal.
Calling dl_iterate_phdr
también devuelve la entrada para el ejecutable principal en la primera ejecución de devolución de llamada.
Es probable que se confunda por el hecho de que .l_addr == 0
en el mapa del enlace, y que dlpi_addr == 0
cuando usa dl_iterate_phdr
.
Esto está sucediendo, porque l_addr
(y dlpi_addr
) en realidad no registran la dirección de carga de una imagen ELF. En su lugar, registran la reubicación que se ha aplicado a esa imagen.
Por lo general, el ejecutable principal está construido para cargar en 0x400000
(para Linux x86_64) o al 0x08048000
(por ix86 Linux), y se cargan en la misma dirección (es decir, que no son reubicados).
Pero si enlaza el ejecutable con -pie
bandera, entonces se vinculará al-0x0
, y serán reubicados a alguna otra dirección.
Entonces, ¿cómo se llega al encabezado ELF? Fácil:
#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
Actualización:
¿Por qué la página de manual dicen "dirección base" y no la reubicación?
Es un error ;-)
que supongo que la página de manual fue escrito mucho antes prelink
y pie
y ASLR
existido. Sin prelink, las bibliotecas compartidas siempre están vinculadas a la carga en la dirección 0x0
, y luego relocation
y base address
se convierten en una sola y la misma.
¿cómo es que dlpi_name puntos en una cadena vacía cuando información se refiere al ejecutable principal?
Es un accidente de implementación.
La forma en que esto funciona, es que el núcleo open(2)
es el ejecutable y pasa el descriptor de archivo abierto en el cargador (en el vector auxv[]
, como AT_EXECFD
). Todo el cargador conoce el ejecutable que se obtiene al leer el descriptor de archivo.
No es fácil descifrar en UNIX un descriptor de archivo con el nombre con el que se abrió. Por un lado, UNIX admite enlaces duros, y podría haber varios nombres de archivos que hacen referencia al mismo archivo.
Los kernels de Linux más recientes también pasan el nombre que se usó para execve(2)
el ejecutable (también en auxv[]
, como). Pero eso es opcional, e incluso cuando se transfiere, glibc no lo coloca en .l_name
/dlpi_name
para no romper los programas existentes que pasaron a depender de que el nombre esté vacío.
En cambio, glibc guarda ese nombre en __progname
y __progname_full
.
El cargador coudreadlink(2)
el nombre de /proc/self/exe
en sistemas que no utilizan AT_EXECFN
, pero el sistema /proc
archivo no está garantizada para ser montado ya sea, por lo que aún dejaría con un nombre vacío a veces.
'leer()' 'de/proc/self/exe' – Dave
Ah, se me olvidó mencionar, también necesito la dirección de base junto con la cabecera ELF, y la cabecera ELF debe estar donde está la dirección base es –