linux memory x86-64 elf abi

linux - ¿Por qué se elige la dirección 0x400000 como inicio del segmento de texto en x86_64 ABI?



memory x86-64 (1)

En pocas palabras: algunas limitaciones técnicas que tiene amd64 en el uso de direcciones grandes sugieren dedicar el menor espacio de direcciones 2GiB al código y a los datos para la eficiencia. Por lo tanto, la pila se ha reubicado fuera de este rango.

En i386 ABI 1

  • la pila se encuentra antes del código, creciendo desde un poco menos de 0x8048000 hacia abajo. Que proporciona "un poco más de 128 MB para la pila y alrededor de 2 GB para texto y datos" (p.3-22).
  • Los segmentos dinámicos comienzan en 0x80000000 (2GiB),
  • y el kernel ocupa el "área reservada" en la parte superior que permite que la especificación sea de hasta 1GiB , comenzando en al menos 0xC0000000 (p.3-21) ( que es lo que normalmente hace ).
  • No se requiere que el programa principal sea independiente de la posición.
  • No se requiere una implementación para capturar el acceso al puntero nulo (p.3-21), pero es razonable esperar que parte del espacio de la pila por encima de 128MiB (que es 288KiB ) se reserve para ese fin.

amd64 ( uclibc.org/docs/psABI-x86_64.pdf está formulado como una enmienda al i386 one (p.9)) tiene un espacio de direcciones mucho mayor (48 bits) pero la mayoría de las instrucciones solo aceptan operandos inmediatos de 32 bits (que incluyen direcciones y compensaciones directas en instrucciones de salto ), requiriendo más trabajo y un código menos eficiente (especialmente cuando se toma en cuenta la interdependencia de las instrucciones) para manejar valores más grandes. Las medidas para evitar estas limitaciones son resumidas por los autores al introducir algunos "modelos de código" que recomiendan usar para "permitir que el compilador genere un mejor código". (p. 33)

  • Específicamente, el primero de ellos, "Modelo de código pequeño", sugiere usar direcciones "en el rango de 0 a 2 31 -2 24 -1 o de 0x00000000 a 0x7effffff ", lo que permite algunas referencias relativas muy eficientes e iteración de matriz. Esto es 1.98GiB que es más que suficiente para muchos programas.
  • El "modelo de código medio" se basa en el anterior, dividiendo los datos en una parte "rápida" bajo el límite anterior y la parte restante "más lenta" que requiere una instrucción especial para acceder. Mientras el código permanece bajo el límite.
  • Y solo el modelo "grande" no hace suposiciones sobre tamaños, lo que requiere que el compilador "use la instrucción movabs , como en el modelo de código medio, incluso para tratar direcciones dentro de la sección de texto. Además, se necesitan ramas indirectas cuando se ramifica en direcciones cuyo desplazamiento desde el puntero de instrucción actual es desconocido ". Continúan sugiriendo dividir la base de código en múltiples bibliotecas compartidas, ya que estas medidas no se aplican a las referencias relativas con compensaciones que se sabe que están dentro de los límites (como se describe en "Modelo de código independiente de posición pequeña").

Por lo tanto, la pila se movió debajo del espacio de la biblioteca compartida ( 0x80000000000 , 128GiB ) porque sus direcciones nunca son operandos inmediatos, siempre referenciados indirectamente o con lea / mov de otra referencia, por lo tanto, solo se aplican limitaciones relativas de desplazamiento.

Lo anterior explica por qué la dirección de carga se movió a una dirección inferior. Ahora, ¿por qué se movió exactamente a 0x400000 ( 4MiB )? Aquí, llegué vacío así que, resumiendo lo que he leído en las especificaciones de ABI, solo puedo adivinar que se sentía "perfecto":

  • Es lo suficientemente grande como para atrapar cualquier desplazamiento de estructura probablemente incorrecto, lo que permite unidades de datos más grandes en las que opera amd64 , pero lo suficientemente pequeño como para no desperdiciar gran parte del valioso 2GiB inicial de espacio de direcciones.
  • Es igual al tamaño de página práctico más grande hasta la fecha y es un múltiplo de todos los demás tamaños de unidades de memoria virtual que se pueda imaginar.

1 Tenga en cuenta que los Linux x32 reales se han estado desviando de este diseño cada vez more medida que pasa el tiempo. Pero estamos hablando de la especificación de ABI aquí ya que amd64 se basa formalmente en él en lugar de cualquier diseño derivado (ver su párrafo para citación).

En this documento en p. 27 dice que el segmento de texto comienza en 0x400000. ¿Por qué se eligió esta dirección particular? ¿Hay alguna razón para eso? La misma dirección se elige en GNU ld en Linux :

$ ld -verbose | grep -i text-segment PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;

Es sorprendente porque esta dirección es más grande en ejecutables x86 de 32 bits:

$ ld -verbose | grep -i text-segment PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x08048000)); . = SEGMENT_START("text-segment", 0x08048000) + SIZEOF_HEADERS;

Leí esta pregunta que explica por qué se eligió la dirección 0x080xxxxx para i386 pero no explica un cambio en x86_64. Es difícil encontrar alguna explicación sobre ese asunto. ¿Alguien tiene una pista?