Сегментная адресация в x64

В x64 сегментная адресация работает совсем не так, как в привычном x86. Прикладные программисты, живущие в плоском мире, могли бы этого не заметить, но, к счастью или несчастью, «уши» этих отличий торчат и в user mode.

Вот что говорит по этому поводу “Intel® 64 and IA-32 Architectures Software Developer’s Manual”:

3.2.4 Segmentation in IA-32e Mode

… In 64-bit mode, segmentation is generally (but not completely) disabled, creating a flat 64-bit linear-address space. The processor treats the segment base of CS, DS, ES, SS as zero, creating a linear address that is equal to the effective address. The FS and GS segments are exceptions…

А также:

3.4.4 Segment Loading Instructions in IA-32e Mode

Because ES, DS, and SS segment registers are not used in 64-bit mode, their fields (base, limit, and attribute) in segment descriptor registers are ignored. Some forms of segment load instructions are also invalid (for example, LDS, POP ES).

Фактически, это полная капитуляция сегментной модели. Дескрипторы сегментов теперь используются только для указания уровня привилегий, типа доступа (код/данные), разрядности кода (3264 бит) и т.п. Сегментные регистры никуда не делись и в них даже можно загрузить значение, однако обычно совершенно неважно какой селектор загружен в регистр. Важнее то, в какой именно регистр этот селектор загружен:

В Windows это «сегментное обрезание» тоже имеет место быть. Например, все способы манипуляции с контекстом потоков вроде GetThreadContext и SetThreadContext игнорируют сегментные регистры: SetThreadContext игнорирует флаг CONTEXT_SEGMENTS; GetThreadContext – возвращает заранее определённые константы. Различные места в ядре, включая диспетчер исключений и диспетчер потоков, принудительно сбрасывают сегментные регистры в предопределённое состояние.

Существует отдельная категория совершенно феерических в своей необъяснимости багов, вызванных таким положением дел. Дело в том, что все сказанное выше справедливо только для 64-х битного кода. 32-х битный код, по-прежнему подчиняется всем правилам сегментной адресации. Теперь представьте себе, как будет выглядеть Access Violation, спровоцированный неверным селектором, загруженным в сегментный регистр? Отладчик получает контекст от GetThreadContext, который всегда рапортует корректный селектор вне зависимости от реального значения в регистре процессора. Более того, если попытаться продолжить выполнение с того же места все пойдет как по маслу. Достаточно переключиться между потоками, чтобы восстановить правильное значение в сегментном регистре.

А уж если, не дай бог, затереть FS…

К счастью такие ошибки очень редки. Я видел такое только два раза за все время работы над Wow64.

comments powered by Disqus