Estoy construyendo un emulador RISC-V que básicamente carga un archivo ELF completo en la memoria.
Hasta ahora, usé los binarios de prueba precompilados que proporcionó la fundación risc-v, que convenientemente tenían un punto de entrada exactamente al comienzo de la sección .text .
Por ejemplo:
> riscv32-unknown-elf-objdump ../riscv32i-emulator/tests/simple -d ../riscv32i-emulator/tests/simple: file format elf32-littleriscv Disassembly of section .text.init: 80000000 <_start>: 80000000: 0480006f j 80000048 <reset_vector> ... Al iniciar este proyecto, no sabía mucho sobre los archivos ELF, así que asumí que el punto de entrada de cada ELF es exactamente el mismo que el inicio de la sección .text .
El problema surgió cuando compilé mis propios binarios, descubrí que el punto de entrada real no siempre es el mismo que el inicio de la sección .text , pero podría estar en cualquier lugar dentro, como aquí:
> riscv32-unknown-elf-objdump a.out -d a.out: file format elf32-littleriscv Disassembly of section .text: 00010074 <register_fini>: 10074: 00000793 li a5,0 10078: 00078863 beqz a5,10088 <register_fini+0x14> 1007c: 00010537 lui a0,0x10 10080: 43850513 addi a0,a0,1080 # 10438 <__libc_fini_array> 10084: 3a00006f j 10424 <atexit> 10088: 00008067 ret 0001008c <_start>: 1008c: 00002197 auipc gp,0x2 10090: cec18193 addi gp,gp,-788 # 11d78 <__global_pointer$> ... Entonces, después de leer más sobre los archivos ELF, descubrí que la dirección del punto de entrada real la proporciona la entrada Entry en el encabezado de ELF:
> riscv32-unknown-elf-readelf a.out -h | grep Entry Entry point address: 0x1008cEl problema ahora es que esta dirección no es la dirección real en el archivo (compensada desde 0) sino una dirección virtual, por lo que, obviamente, si configuro el contador del programa de mi emulador en esta dirección, el emulador fallará.
Leyendo un poco más, escuché a la gente hablar sobre cálculos con respecto a las compensaciones de los encabezados del programa y otras cosas, pero nadie tenía una respuesta concreta.
Mi pregunta es: ¿cuál es la "fórmula" real de cómo obtiene exactamente la dirección del punto de entrada del procedimiento _start como un desplazamiento del byte 0?
Para que quede claro, mi emulador no es compatible con la memoria virtual y el binario es lo único que se carga en la memoria de mi emulador, por lo que no tengo uso para la abstracción de la memoria virtual. Solo quiero cada dirección de memoria como dirección física en el disco.
Mi pregunta es: ¿cuál es la "fórmula" real de cómo obtiene exactamente la dirección del punto de entrada del procedimiento _start como un desplazamiento del byte 0?
Primero, olvídate de las secciones . Sólo los segmentos importan en tiempo de ejecución .
En segundo lugar, use readelf -Wl para buscar segmentos. Le dicen exactamente qué parte del archivo ( [.p_offset, .p_offset + .p_filesz) ) va a qué región en memoria ( [.p_vaddr, .p_vaddr + .p_memsz) ).
El cálculo exacto de "en qué desplazamiento del archivo reside _start " es:
Elf32_Phdr que "cubre" la dirección contenida en Elf32_Ehdr.e_entry .phdr , el desplazamiento del archivo de _start es: ehdr->e_entry - phdr->p_vaddr + phdr->p_offset .Actualizar:
Entonces, ¿siempre estoy buscando el primer encabezado del programa?
No.
¿También por "cubiertas" quiere decir que el 1er phdr-> p_vaddr siempre es igual a e_entry?
No.
Está buscando el encabezado del programa (que describe la relación entre los datos en memoria y en archivo) que se superpone a ehdr->e_entry en la memoria. Es decir, está buscando el segmento para el que phdr->p_vaddr <= ehdr->e_entry && ehdr->e_entry < phdr->p_vaddr + phdr->p_memsz . Este segmento suele ser el primero, pero eso no está garantizado de ninguna manera. Véase también esta respuesta .