2012-01-16 20 views
9

Para varios propósitos, estoy tratando de obtener la dirección del encabezado ELF del ejecutable principal sin analizar /proc/self/maps. He intentado analizar la cadena link_list dada por dlopen/dlinfo funciones pero no contienen una entrada donde l_addr apunta a la dirección base del ejecutable principal. ¿Hay alguna manera de hacer esto (estándar o no) sin analizar /proc/self/maps?Obtener el encabezado ELF del ejecutable principal

Un ejemplo de lo que estoy tratando de hacer:

#include <stdio.h> 
#include <elf.h> 
int main() 
{ 
    Elf32_Ehdr* header = /* Somehow obtain the address of the ELF header of this program */; 
    printf("%p\n", header); 
    /* Read the header and do stuff, etc */ 
    return 0; 
} 
+1

'leer()' 'de/proc/self/exe' – Dave

+0

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 –

Respuesta

16

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.

+0

Sí, supongo que estaba confundido. Pero me pregunto, ¿en qué parte de la página de manual dice que 'l_addr' o' dlpi_addr' es la dirección reubicada? Todas las páginas de manual que he leído solo dicen "dirección base" –

+0

Además, ¿cómo es que 'dlpi_name' apunta a una cadena vacía cuando' info' hace referencia al ejecutable principal? ¿No debería contener el nombre del ejecutable principal? –

+0

He actualizado la respuesta. Obtienes 3 respuestas por el precio de 1 ;-) –

0

No es función de la dl_iterate_phdr glibc(). No estoy seguro de que le dé exactamente lo que quiere, pero eso es lo más cercano que sé:

"La función dl_iterate_phdr() permite a una aplicación consultar en tiempo de ejecución para descubrir qué objetos compartidos ha cargado. " http://linux.die.net/man/3/dl_iterate_phdr

+0

Se obtiene todos los objetos compartidos que el programa ha cargado, lo que ya puedo hacer pasando por la cadena link_list, y eso es probablemente lo que hace en la función. Pero quiero la dirección base de la aplicación en sí, no los objetos compartidos que ha cargado. –

+0

¿Has probado que no devuelve la aplicación en sí? –

+0

Lo siento, estaba saliendo de lo que leí en la página de manual –

Cuestiones relacionadas