Dirección de forma canónica y aritmética de puntero

En las architectures compatibles con AMD64, las direcciones deben estar en forma canónica antes de desreferenciarse.

Del manual de Intel, sección 3.3.7.1 :

En el modo de 64 bits, se considera que una dirección está en forma canónica si los bits de dirección 63 hasta el bit implementado más significativo por la microarchitecture se establecen en uno o todos ceros.

Ahora, el bit más significativo implementado en las architectures y sistemas operativos actuales es el 47º bit. Esto nos deja con un espacio de direcciones de 48 bits.

Especialmente cuando ASLR está habilitado, los progtwigs de usuario pueden esperar recibir una dirección con el bit 47 establecido.

Si se utilizan optimizaciones como el etiquetado de puntero y los bits superiores se utilizan para almacenar información, el progtwig debe asegurarse de que los bits 48 a 63 se vuelvan a establecer en 47 bits antes de desreferenciar la dirección.

Pero considere este código:

int main() { int* intArray = new int[100]; int* it = intArray; // Fill the array with any value. for (int i = 0; i < 100; i++) { *it = 20; it++; } delete [] intArray; return 0; } 

Ahora considere que intArray es, por ejemplo:

0000 0000 0000 0000 0 111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100

Después de configurarlo en intArray y boostlo una vez, y considerando sizeof(int) == 4 , se convertirá en:

0000 0000 0000 0000 1 000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

El bit 47 está en negrita. Lo que ocurre aquí es que el segundo puntero obtenido mediante la aritmética del puntero no es válido porque no está en forma canónica. La dirección correcta debe ser:

1111 1111 1111 1111 1 000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

¿Cómo lidian los progtwigs con esto? ¿Existe una garantía por parte del SO de que nunca se le asignará memoria cuyo rango de direcciones no varíe en el bit 47?

Las reglas de dirección canónica significan que hay un agujero gigante en el espacio de direcciones virtuales de 64 bits. 2 ^ 47-1 no es contiguo a la siguiente dirección válida que se encuentra encima, por lo que un mmap único no incluirá el rango inutilizable de direcciones de 64 bits.

 +----------+ | 2^64-1 | 0xffffffffffffffff | ... | | 2^64-2^47| 0xffff800000000000 +----------+ | | | unusable | | | +----------+ | 2^47-1 | 0x00007fffffffffff | ... | | 0 | 0x0000000000000000 +----------+ 

En otras palabras:

¿Existe una garantía por parte del SO de que nunca se le asignará memoria cuyo rango de direcciones no varíe en el bit 47?

Sí. El espacio de direcciones de 48 bits admitido por el hardware actual es un detalle de implementación. Las reglas de dirección canónica garantizan que los sistemas futuros puedan admitir más bits de direcciones virtuales sin romper la compatibilidad hacia atrás en un grado significativo. Solo necesitarás una bandera de compatibilidad para que el sistema operativo no le dé al proceso ninguna región de memoria con bits altos que no sean iguales. El hardware futuro no necesitará admitir ningún tipo de indicador para ignorar los bits de alta dirección o no, porque la basura en los bits altos es actualmente un error.


Dato curioso: Linux se predetermina a mapear la stack en la parte superior del rango inferior de direcciones válidas.

p.ej

 $ gdb /bin/ls ... (gdb) b _start Function "_start" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (_start) pending. (gdb) r Starting program: /bin/ls Breakpoint 1, 0x00007ffff7dd9cd0 in _start () from /lib64/ld-linux-x86-64.so.2 (gdb) p $rsp $1 = (void *) 0x7fffffffd850 (gdb) exit $ calc 2^47-1 0x7fffffffffff