point elf virtual-address-space

point - ¿Por qué la dirección virtual del punto de entrada de ejecución de ELF tiene el formato 0x80xxxxx y no 0x0?



virtual-address-space (2)

Cuando se ejecute, el programa comenzará a ejecutarse desde la dirección virtual 0x80482c0. Esta dirección no apunta a nuestro procedimiento main() , sino a un procedimiento llamado _start que es creado por el enlazador.

Mi investigación en Google hasta ahora me llevó a algunas especulaciones históricas (vagas) como esta:

Existe el folklore de que 0x08048000 una vez fue STACK_TOP (es decir, la pila creció hacia abajo desde cerca de 0x08048000 hacia 0) en un puerto de * NIX a i386 que fue promulgado por un grupo de Santa Cruz, California. Esto fue cuando 128 MB de RAM era caro y 4 GB de RAM era impensable.

¿Alguien puede confirmar / negar esto?


¿Por qué no empezar en la dirección 0x0? Hay al menos dos razones para esto:

  • Debido a que la dirección cero es conocida como un puntero NULO, y es utilizada por los lenguajes de programación para controlar los punteros. No puedes usar un valor de dirección para eso, si vas a ejecutar el código allí.
  • El contenido real en la dirección 0 es a menudo (pero no siempre) la tabla de vectores de excepción y, por lo tanto, no es accesible en modos no privilegiados. Consulte la documentación de su arquitectura específica.

En cuanto al punto de entrada _start vs main : si se vincula con el tiempo de ejecución de C (las bibliotecas estándar de C), la biblioteca ajusta la función denominada main , por lo que puede inicializar el entorno antes de llamar a main . En Linux, estos son los parámetros argc y argv para la aplicación, las variables env y, probablemente, algunas primitivas de sincronización y bloqueos. También se asegura de que al regresar de main se pasa el código de estado y se llama a la función _exit , que termina el proceso.


Como señaló Mads, para capturar la mayoría de los accesos a través de punteros nulos, los sistemas similares a Unix tienden a hacer que la página en la dirección cero sea "no asignada". Por lo tanto, los accesos activan inmediatamente una excepción de CPU, en otras palabras, un segfault. Esto es bastante mejor que dejar que la aplicación se deshaga. Sin embargo, la tabla de vectores de excepción puede estar en cualquier dirección, al menos en procesadores x86 (hay un registro especial para eso, cargado con el lidt operación de lidt ).

La dirección del punto de inicio es parte de un conjunto de convenciones que describen cómo se distribuye la memoria. El enlazador, cuando produce un binario ejecutable, debe conocer estas convenciones, por lo que no es probable que cambien. Básicamente, para Linux, las convenciones de diseño de memoria se heredan de las primeras versiones de Linux, a principios de los 90. Un proceso debe tener acceso a varias áreas:

  • El código debe estar en un rango que incluya el punto de partida.
  • Debe haber una pila.
  • Debe haber un montón, con un límite que se incrementa con las brk() al sistema brk() y sbrk() .
  • Debe haber espacio para las llamadas al sistema mmap() , incluida la carga de bibliotecas compartidas.

Hoy en día, el montón, a donde va malloc() , está respaldado por las llamadas mmap() que obtienen trozos de memoria en cualquier dirección que el kernel considere adecuada. Pero en épocas anteriores, Linux era como los sistemas similares a Unix anteriores, y su montón requería una gran área en una porción ininterrumpida, que podría crecer hacia direcciones cada vez mayores. Entonces, cualquiera que fuera la convención, tenía que rellenar el código y apilar hacia direcciones bajas, y dar a cada trozo del espacio de direcciones después de un punto dado al montón.

Pero también está la pila, que suele ser bastante pequeña, pero en algunas ocasiones podría crecer considerablemente. La pila crece hacia abajo, y cuando la pila está llena, realmente queremos que el proceso se bloquee predeciblemente en lugar de sobrescribir algunos datos. Así que tenía que haber un área amplia para la pila, con, en el extremo inferior de esa área, una página sin asignar. Y lo Hay una página sin asignar en la dirección cero, para capturar las referencias erróneas de puntero nulo. Por lo tanto, se definió que la pila obtendría los primeros 128 MB de espacio de direcciones, excepto la primera página. Esto significa que el código tenía que ir después de esos 128 MB, en una dirección similar a 0x080xxxxx.

Como señala Michael, "perder" 128 MB de espacio de direcciones no fue un gran problema porque el espacio de direcciones era muy grande con respecto a lo que realmente podría usarse. En ese momento, el kernel de Linux estaba limitando el espacio de direcciones para un solo proceso a 1 GB, sobre un máximo de 4 GB permitido por el hardware, y eso no se consideraba un gran problema.