Home > itblogs > Сегментная адресация в x64.

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

March 25th, 2010

В 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).

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

  • Любой корректный селектор, загруженный в DS, ES или SS, будет работать совершенно одинаково.
  • В случае CS, становится важным тип сегмента (код/данные) и разрядность кода (32/64 бит).
  • В случае FS и GS, база сегмента задается через MSR (model specific register), так что в регистр с одинаковым результатом может быть загружен любой селектор.

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

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

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

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

  1. March 27th, 2010 at 06:02 | #1

    Ага~.
    Просто ради шутки, пусть заинтересованные читатели попробуют предсказать возможные варианты поведения следующего отрывка на промежутке (0;9), исполняющегося в юзермоде _системы_ Win x64 в любых возможных конфигурациях (условимся, что память по нулевому адресу не выделена, а значения сегментных регистров дефолтны):

    (0) nop
    (1) mov eax, [gs:0]
    (2) mov ecx, [fs:0]
    (3) mov ax, gs
    (4) mov cx, fs
    (5) mov fs, cx
    (6) mov gs, ax
    (7) mov ecx, [fs:0]
    (8) mov eax, [gs:0]
    (9) nop

  2. Slavik Gnatenko
    March 29th, 2010 at 07:44 | #2

    Т.е. в Wow64 GetThreadContext() просто сломан? Или в этом есть какое-то глубокое дизайновое решение?
    Кстати, а зачем вообще в ядре делать всё это “сегментное обрезание”? Какие проблемы манипулировать по-старому? Конечно, результат в EMT64 будет другим, но это уже пусть голова у пользователя болит.

  3. March 29th, 2010 at 09:01 | #3

    Slavik Gnatenko :
    Т.е. в Wow64 GetThreadContext() просто сломан? Или в этом есть какое-то глубокое дизайновое решение?

    Глубокий смысл состоит в том, чтобы не делать лишнюю работу, например, – не нужно сохранять сегментные регистры при каждом входе в ядро. Или заботится о них в десятке других мест.

    Кроме того, не стоит думать, что SetThreadContext на 32-х битной системе позволит записать произвольный селектор в регистры. Диапазон разрешенных значений не так уж и широк.

    Кстати, а зачем вообще в ядре делать всё это “сегментное обрезание”?

    Скорость.

    Какие проблемы манипулировать по-старому? Конечно, результат в EMT64 будет другим, но это уже пусть голова у пользователя болит.

    А зачем манипулировать по-старому, если в результате код будет медленней, а результат тот же?

  4. April 4th, 2010 at 18:46 | #4

    Как только вы поменяли работу, вы стали писать на много меньше. А жаль, было достаточно интересно читать заметки. Я уже иногда начал забывать про ваш блог. Сегодня случайно вспомнил и решил проверить, что нового, но… Или вас так сильно подкосило рождение ребенка?

    P.S. Сорри за офтоп

  5. April 4th, 2010 at 20:20 | #5

    Developer :
    … вы стали писать на много меньше …

    Ну это обычная история – времени не хватает. :-(

Comments are closed.