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

В архитектуре, совместимой с 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 0 111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100

После установки it в intArray и увеличения it один раз и с учетом sizeof(int) == 4 он станет следующим:

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

47-й бит выделен жирным шрифтом. Здесь происходит то, что второй указатель, полученный из арифметики указателя, недействителен, потому что не в канонической форме. Правильный адрес должен быть:

1111 1111 1111 1111 1 000 0000 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-разрядное адресное пространство, поддерживаемое текущим оборудованием, является детальностью реализации. Правила канонических адресов гарантируют, что будущие системы могут поддерживать больше виртуальных битов адреса, не нарушая при этом обратной совместимости в какой-либо значительной степени. Вам просто нужен флаг совместимости, чтобы ОС не давала процессу каких-либо областей памяти с большими битами не все равно. Будущему аппарату не нужно поддерживать какой-либо флаг, чтобы игнорировать высокие биты адреса или нет, потому что в старших битах в настоящий момент ошибка.


Удовлетворяющий факт: 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