Адресная каноническая форма и арифметика указателей

На архитектурах, совместимых с 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
Другие вопросы по тегам