Адресная каноническая форма и арифметика указателей
На архитектурах, совместимых с AMD64, адреса должны быть в канонической форме перед разыменовкой.
Из руководства Intel, раздел 3.3.7.1:
В 64-битном режиме адрес считается в канонической форме, если биты адреса от 63 до наиболее значимого реализованного бита микроархитектурой установлены на все единицы или все нули.
Теперь наиболее значимым реализованным битом в современных операционных системах и архитектурах является 47-й бит. Это оставляет нам 48-битное адресное пространство.
Особенно, когда ASLR включен, пользовательские программы могут ожидать получения адреса с установленным 47-м битом.
Если используются такие оптимизации, как тегирование указателя, а верхние биты используются для хранения информации, программа должна убедиться, что 48-й и 63-й биты установлены обратно в 47-й бит, прежде чем разыменовывать адрес.
Но рассмотрим этот код:
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;
}
Теперь посмотрим, что intArray
это сказать:
0000 0000 0000 0000 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100
После настройки it
в intArray
и увеличивается it
один раз, и учитывая sizeof(int) == 4
, это станет:
0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
47-й бит выделен жирным шрифтом. Здесь происходит то, что второй указатель, полученный с помощью арифметики указателей, недопустим, потому что не в канонической форме. Правильный адрес должен быть:
1111 1111 1111 1111 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
Как программы справляются с этим? Есть ли гарантия у ОС, что вам никогда не будет выделена память, диапазон адресов которой не меняется на 47 бит?
1 ответ
Канонические правила адресов означают, что в 64-битном виртуальном адресном пространстве есть гигантская дыра. 2^47-1 не является смежным со следующим действительным адресом над ним, поэтому один mmap
не будет включать ни одного из непригодного диапазона 64-битных адресов.
+----------+
| 2^64-1 | 0xffffffffffffffff
| ... |
| 2^64-2^47| 0xffff800000000000
+----------+
| |
| unusable |
| |
+----------+
| 2^47-1 | 0x00007fffffffffff
| ... |
| 0 | 0x0000000000000000
+----------+
Другими словами:
Есть ли гарантия у ОС, что вам никогда не будет выделена память, диапазон адресов которой не меняется на 47 бит?
Да. 48-битное адресное пространство, поддерживаемое текущим оборудованием, является деталью реализации. Правила канонического адреса гарантируют, что будущие системы смогут поддерживать больше битов виртуального адреса, не нарушая обратную совместимость в какой-либо значительной степени. Вам просто понадобится флаг compat, чтобы ОС не давала процессу какие-либо области памяти с высокими битами, не все одинаково. Будущее оборудование не будет нуждаться в поддержке какого-либо типа флага, чтобы игнорировать старшие биты или нет, потому что нежелательная память в старших битах в настоящее время является ошибкой.
Интересный факт: Linux по умолчанию отображает стек в верхней части нижнего диапазона допустимых адресов.
например
$ 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