HOME FORUMS MEMBERS RECENT POSTS LOG IN  
× Авторизация
Имя пользователя:
Пароль:
Нет аккаунта? Регистрация
Баннер 1   Баннер 2
НОВЫЕ ТОРГОВАЯ НОВОСТИ ЧАТ
loading...
Скрыть
Вернуться   ANTICHAT > БЕЗОПАСНОСТЬ И УЯЗВИМОСТИ > Электроника и Фрикинг
   
 
 
Опции темы Поиск в этой теме Опции просмотра

  #1  
Старый 20.04.2022, 00:38
Marylin
Постоянный
Регистрация: 01.09.2019
Сообщений: 378
С нами: 3526561

Репутация: 0
По умолчанию

64-разрядная архитектура ворвалась в нашу жизнь как торнадо, затягивая в себя всё больше инженеров и прикладных программистов. Новые термины стали появляться как грибы после дождя, а давать им толкового объяснения никто не торопился. В данной статье рассматриваются фундаментальные основы кодинга на платформах Win64, с реальными примерами в отладчике.

Оглавление:


1. Вводная часть;
2. Организация памяти в режиме “Long-mode” х64;
3. Доп.регистры GPR и префикс REX;
4. RIP-относительная адресация;
5. Соглашение “Fast-Call” – быстрый вызов функций;
6. Заключение.

1. Вводная часть

С обзором архитектур связано и без того множество нестыковок, а выход на сцену в 2003 году 64-битных ОС ещё больше усугубил ситуацию. Более того, в результате модификаций одних и тех-же инженерных решений, в литературе встречаются и синонимы, которые сбивают логическую нить. Так-что сразу обозначим здесь основные моменты..

1.1. Первое, на что стоит обратить внимание – это названия архитектур процессоров, согласно их разрядности. Так, на данный момент 64-битная от AMD называется AMD64, а в закрытых офисах Intel отдали предпочтение имени Intel-64. И тут нас ожидает мозговой штурм!

Дело в том, что на заре 64-бит эры Intel окрестила свою архитектуру как IA-32e, всего-то добавив к привычной аббревиатуре литер(е) “Extended”, расширенный. Но на презентации критики посчитали такое название не отражающим разрядность в 64-бит, и буквально спустя год монархи переименовали её в EM64T – теперь это звучит громко и подразумевает «Extended Memory 64 Technology». Такой зоопарк имён привёл к тому, что имеем шесть разных названий одной и той-же архитектуры: х86-64, IA-32e, EM64T, Intel-64, AMD64, AA64 (последнее от «Amd Architecture»).

А вот что не вписывается ни в какие ворота, это когда Intel-64 обзывают как IA-64. Предположение в корне не верно, т.к. под IA-64 кроется «Itanium Architecture», которая никак не пересекается с Intel-64. Процессоры «Intel Itanium» имели ядро RISC (когда одну команду исполняют множество простых инструкций), а процессоры IA-32 построены уже на ядре CISC (подмножество простых инструкций собираются в одну сложную). Написанные для процессоров Itanium приложения не будут работать на Intel-64, и наоборот.

1.2. Второй момент – это режимы работы 64-битных процессоров, с которыми мы не раз столкнёмся в данном треде. Согласно докам их всего три, и программное обеспечение ОС переключает эти режимы при помощи битов в модельно-специфичных регистрах MSR. От младшего к старшему следуют они в таком порядке:


• Legacy-mode. Унаследованный от древних 16/32-разрядных процессоров х86. Режим впитал в себя четыре моды: RM, SMM, VM, PM (Real, Sys-Management, Virtual, и Protected соответственно). Он был родным для отправленных на свалку истории Win2k и Windows-XP.

• Compatibility-mode. Режим совместимости х64 с х32. Процессор переключается в этот режим, когда на 64-бит платформе запускаются 32-бит приложения. Чтобы поддерживать совместимость со-старым софтом, инженерам пришлось заключить с ним мезальянс и тащить на себе всё это бремя вплоть до наших времён. Режим не поддерживает Virtual-моду унаследованного, поэтому софт реального режима RM в нём не работает. Со стороны ОС поддержка осуществляется технологией WOW64 (Windows on Windows).

• Long-mode. Родной для х64 режим с плоской организацией памяти Flat. Сегментация в привычном виде отсутствует. К восьми имеющимся добавлены ещё 8 регистров R8-R15 общего назначения GPR (General Purpose Registers), и разрядность всех 16-ти расширена с 32 до 64-бит. Написанный для х32 код должен быть перекомпилирован с учётом 64-битной архитектуры.

Вот несколько ссылок по теме на документацию Intel и AMD в формате *.pdf

Лично на мой взгляд доки от AMD предпочтительней, хотя выводы можно сделать только в сравнении:

- Большая библиотека документов Intel и AMD (SDM = Software Developer Manual, APM = AMD Programming Manual);

- Intel volume(1) – описание архитектуры Intel-64 & IA-32;

- Intel volume(3) – руководство по программированию для разработчиков;

- AMD volume(1) – руководство по программированию процессоров AMD;

- AMD volume(2) – описание архитектуры AMD64.

2. Организация памяти в режиме “Long-mode” х64

Нельзя назвать плавным переход на х64 – в архитектуру было внесено множество новых решений, и в большей степени это коснулось организации вирт.памяти. В связи с тем, что стало доступным огромное пространство, инженеры без зазрения совести просто удалили львиную долу системных механизмов. А ведь в этом есть смысл..

Вспомним, как на программном уровне представляется сегментная модель вирт.памяти в архитектуре IA-32.

Значит имеем общее пространство размером 4GB, которое делится на несколько сегментов – кода, данных, стека. Свойства их определяют 8-байтные дескрипторы, которые собраны в глобальную таблицу GDT – Global Descriptor Table.

В самих 16-битных сегментных регистрах CS,DS,ES,SS,FS,GS лежат селекторы с полем индекса (порядкового номера) соответствующих дескрипторов в таблице GDT. При этом 3-младших бита в селекторах обнуляются (биты RPL и TI), так-что индекс всегда кратен 8-ми, по размеру дескриптора. Непосредственно в дескрипторах указывается уже тип сегмента, его базовый адрес, размер (лимит), и атрибуты защиты.




Теперь начнём с того, что расширенные до 64-бит регистры позволяют адресовать линейную память размером: 2^64=16 эксаБайт, или 16 млн.тераБайт. Если даже взять самое тяжеловесное в природе приложение, в таком объёме оно тупо потеряется и будет занимать порядка 0.001%. Зато системные расходы на её поддержку выйдут за рамки разумного: как-минимум потребуется таблица страниц «PageTable» исполинских размеров, и соответственно на поиск адреса в ней уйдёт больше времени.

Поэтому инженеры ограничили указатель на вирт.память 48-битами, в результате чего аппетиты системы поубавились до 256 тераБайт, хотя моя Win7-х64 использует только 16 из них (8 приложениям, 8 ядру), а Win10 юзает уже на полную катушку 256. В то-же время, во-внешней шине памяти под адрес отводится итого меньше: всего 40-бит, что влечёт за собой поддержку мат.платами физической DDR общим размером в 1 тераБайт. Сомневаюсь, что у большинства из нас установлено хотя-бы 64 гига из них, но видимо у инженеров далеко идущие планы. В итоге имеем макс.256ТБ виртуальной, и до 1ТБ физ.памяти. На этом фоне потолок IA-32 в 4ГБ кажется ничтожно малым.




Такой расклад навёл инженеров на мысль, что пришло время вообще избавиться от устаревшей сегментной модели, ведь памяти теперь предостаточно и располагай хоть 1000 программных секций следом, одна за другой. Они приняли идею с энтузиазмом, и наконец затенили три сегментных регистра DS,ES,SS. Три оставшихся CS,FS,GS на костылях всё-же функционируют, но и в их дескрипторах игнорируются все поля, кроме атрибутов. Отменим, что полностью изкоренить сегментную модель нельзя – она жёстко прошита в CPU на аппаратном уровне. Придётся в корне менять микро-архитектуру CPU, что приведёт к краху пока ещё держащихся на плаву 32-бит приложений. Может на следующем витке эволюции, но не на этом точно.

В доках AMD имеется фрагмент ниже, где серым обозначены игнорируемые биты. Здесь видно, что размер дескрипторов как и прежде остался 8-байт, а база, лимит и некоторые атрибуты, отправлены инженерами в утиль. Как только процессор переходит в режим Long-mode, база во-всех сегментных регистрах выставляется в нуль, а лимит на 48-битный максимум. О том, что это дескриптор 64-битного сегмента, свидетельствует взведённый бит(21) под ником(L). Если-же процессор находится в режиме совместимости с х32, бит(L) будет сброшен, все затенённые поля авто-активируются, и мы получаем обратно сегментную модель IA-32.


Цитата:

• Бит(D) – Default-Operand Size (размер операндов: 0=32, 1=64 бит);

• Бит(L) – Long mode (1=режим х64, 0=совместимости);

• Бит(P) – Present (1=сегмент загружен в память);

• Бит(DPL) – Descriptor Privilege Level (уровень кольца защиты 0-3);

• Бит(C) – Conforming (соответствие привилегий: 0=доступ напрямую, 1=через системный шлюз).



А вот с дескрипторами сегментных регистров FS и GS дела обстоят немного иначе.

Инженеры оставили их, чтобы использовать в качестве доп.базовых регистров при вычислении адреса. Базы этих сегментов могут иметь отличные от нуля значения, что облегчает доступ к определенным структурам системы. К примеру по адресу
Код:
[GS:60h]
можно найти указатель на инфо-структуру TЕВ 64-битного потока (Thread Environment Block), под которую выделяется отдельный сегмент.

Поле базы FS и GS расширено до 64 бит, и хранится теперь не в самом дескрипторе (в нём уже нет места, см.скрин выше), а в MSR-регистрах
Код:
IA32_FS_BASE
и
Код:
IA32_GS_BASE
(см.доки Intel том.4). Проверить, что базы реально прописаны в MSR (а не в дескрипторах) можно при помощи
Код:
CPUID.8000_0001
– запрос должен вернуть взведённый бит(29) в регистре EDX. Для чтения\записи этих значений с уровня драйверов, были введены новые инструкции:
Код:
rdfs(gs)base
и
Код:
wrfs(gs)base
.


C-подобный:


Код:
mov     eax
,
0x80000001
;
// IA32_FS_BASE support
cpuid
;
// Requst..
bt      edx
,
29
;
// Bit-Test
jnc     @f
;
// Jump no Carry flag
rdfsbase  rax
;
// Else: save FS-base to RAX
rdgsbase  rbx
;
//       save GS-base to RBX
@@
:
nop
Модель плоской Flat памяти – это простейшая форма сегментации. Здесь базовые адреса всех сегментов имеют значение нуль, а лимиты выставлены на макс (хотя и в сегментной модели IA-32 мастдая можем наблюдать такую-же картину). Сброс базы ниже плинтуса фактически отключает трансляцию сегментов, и он охватывает всё пространство вирт.адресов – дескрипторы ссылаются теперь на этот единственный плоский сегмент.

Не нужно думать, что в модели Flat отсутствуют атрибуты защиты-доступа. Конечно-же они есть, только зарыты не в дескрипторах как прежде, а на более высоком уровне, в 64-битных записях РТЕ «Page Table Entry» системной таблицы-страниц. Если учесть, что большие сегменты всегда чекаются на мелкие 4К-страницы (привет своп вирт.памяти), то атрибуты этих страниц имеют приоритет над атрибутами в дескрипторах сегментов, поэтому механизм функционирует исправно. Надеюсь скрин прояснит выше-сказанное:




3. Расширенные регистрыGPR и префиксREX

Теперь про регистры и логику обращения к ним..

Выше упоминалось, что инженеры не только расширили имеющиеся 32-битные регистры до 64-бит, но и удвоили их кол-во. Такой размах нужен для того, чтобы минимизировать обращения к сравнительно медленной памяти ОЗУ. Раньше, при отсутствии свободных регистров мы сохраняли данные в стек, для чего контролёру нужно было сначала дождаться освобождения внешней шины, и лишь потом за’PUSH’ить содержимое регистра в стековую память.

Именно поэтому в процессор был добавлен кэш, куда сбрасывались все последние обращения к ОЗУ. То-есть следующий PUSH уже не запрашивал шину, а обращался к 64-байтной линейке кэш. И регистры CPU, и кэш построен на одном типе памяти – статической SRAM (на триггерах, а не как DRAM на конденсаторах), поэтому никаких задержек при обмене данными ядра с кэшем не возникает.

В таблице ниже видно, что инженеры породили 8 новых векторных AVX-регистров: YMM8-YMM15 (Advanced Vector Extensions), такое-же число потоковых SSE: XMM8-XMM15 (Streaming SIMD Extensions), и бонусом 8 регистров общего назначения GPR: R8-R15 (General Purpose Registers). В последних процессорах регистры AVX расширены до 512-бит (AVX512) и ожидается увеличение их разрядности вплоть до 1024-бит. Для поддержки таких монстров кол-во регистров AVX было увеличено с 16 до 32-х, и называются они теперь ZMM0-ZMM31. В столбце «Бит» данной таблицы указана разрядность одного регистра:




Обратите внимание, что регистры MMX являются частью 80-битных регистров сопроцессора FPU, занимая в них младшие 64-бита мантиссы так, что для инструкций MMX остаётся исключительно целочисленная арифметика, без дробной части. При таком раскладе, в любой момент времени (внутри одного блока кода) мы можем использовать или регистры FPU, или MMX, но не оба сразу – в противном случае получим винегрет из данных.

Чтобы решить эту проблему, инженеры ввели независимый от FPU пул 128-битных регистров SSE, который позже был расширен до 256-бит AVX. Здесь наблюдаем аналогичную картину, когда SSE живут внутри AVX. Соответственно их инструкции так-же нельзя комбинировать. Отличительной особенностью SSE\AVX является доступность им операций как со-скалярными числами (тогда инструкции заканчиваются на «ss», scalar), так и с упакованными (окончание «ps», packet) – это значительно расширяет область их применения. А вообще в эти регистры можно пихать любые типы данных:




Обратим свой взор на упакованный формат. Это четыре отдельных 32-битных числа с плавающей запятой (одинарной точности), над которыми за теоретический такт процессора можно произвести независимые вычисления. На аналогичную операцию сопр FPU потратил-бы в 4 раза больше времени! Основным требованием здесь является «правильная» организация массива данных (в виде непрерывного потока Stream), и обязательное выравнивание массива на 8-байтную границу (для скалярных типов это не принципиально). Типичный пример использования фишки – 3D графика.

Инструкции AVX пошли ещё дальше, и могут иметь уже не два, а три\четыре операнда для тех случаев, когда значения операндов-источников нужно сохранить для последующих вычислений. К таким инструкциям привязывается не рассматриваемый здесь префикс «VEX».

А вот как выглядят «мемберы» новых регистров общего назначения GPR.

Четыре первых регистра RAX..RDX как и прежде позволяют обращаться к любым своим частям, в том числе и к старшим байтам 16-битных слов AH..DH. Как видно из рисунка, в регистрах R8..R15 это поле уже отсутствует, и мы можем использовать лишь мл.байты слов R8b..R15b. Непонятно зачем были добавлены и мл.байты индексных + базовых регистров под никами SIL..SPL, которых не было в IA-32. Видимо просто в поле «Opcode» инструкций оставались свободные биты. Указатель RIP остался в девственном виде, с мин.значением в 16-бит. Интересным моментом является то, что запись 32-битных значений в 64-бит регистры всегда обнуляет старшую их часть:




3.1. Назначение префикса REX

Теперь спустимся в тёмные подвалы микро-архитектуры и рассмотрим, как кодируются инструкции в 64-битном коде. Для этого откроем доку Intel том(2) «Instruction Set Reference», где на стр.35 представлен формат ниже.

Значит макс.размер инструкции составляет 15-байт, из которых обязательно лишь поле «Opcode». Этот код операции ограничен разрядность 24-бита (три байта), а если их не хватает, то инструкция «отжимает» биты[5:3] у следующего поля «Mod R/M». Итого получаем 27-битный опкод, в которых можно закодировать 134 лярда теоретических инструкций. Впечатляет..

Далее идёт байт «SIB» – он кодирует регистры при относительной адресации, например конструкцию с базой и индексом
Код:
[EBX+ESI]
, или при обращениях к стековому фрейму
Код:
[EBP+8]
. В последнем случае значение(8) считается отклонением и хранится в поле «Displacement». Адрес может вычисляться и в более извращённой форме
Код:
[EBX+4*ESI+2]
. Тогда значение(4) будет определять масштаб, под который отводятся 2-бита «Scale» байта SIB.

Последний дворд «Immediate» выделяется для непосредственных значений, типа число в арифметике, адрес вызываемой функции, и т.д. Обратите внимание, что два этих поля в режиме х64 не расширяются до 64-бит: они по-прежнему ограничены 32-битным числом со-знаком. Однако поддерживаются некоторые 64-бит смещения, и непосредственные формы инструкции MOV – в этом случае два этих поля объединяются.

Инструкции опционально могут иметь префиксы, а при обращении к 64-битным регистрам префикс «REX» является вообще обязательным. Он всегда занимает позицию после унаследованного Legacy-префикса, чтобы в режиме совместимости с IA-32 им можно было принебречь.

Старшая тетрада 1-байтного REX жёстко прошита значением 0100=4, а потому диапазон его значений лежит в пределах 40..4Fh. Младшая-же тетрада является флагами расширений так, что если в ней взведён старший бит(W), то операндом инструкции считается весь 64-бит регистр, а если он сброшен, только 8/16/32-битная его часть. Таким макаром, при обращении к регистрам RAX-R15 значения этого префикса будет больше\равно 48h, иначе меньше. Оставшаяся триада с битами(RXB) конфискует перечисленные ниже поля в байтах «Mod R/M» и «SIB», чтобы в них можно было закодировать 64-битные регистры:




Посмотрим в отладчике х64Dbg на код, в котором я собрал обращения к разным частям 64-бит регистров: Byte, Word, Dword, Qword. Как видно из скрина, отладчик разделяет опкод от его префикса двоеточием. Первые 4 инструкции предваряются префиксом
Код:
REX=48h
, значит их операндом является весь 64-бит регистр. Три байта
Код:
83.EC.08
первой строки занимают в инструкции поля «Opcode, ModR/M, Immediate» соответственно. Обратите внимание, что второй операнд(8) инструкции SUB не расширяется до 64-бит, хотя и был отправлен в регистр RSP. Инструкция по адресу 0х00401019 имеет уже 2-байтый префикс
Код:
F3.48
, где Legacy(F3) олицетворяет повторение, а А5h – это опкод:




[FONT="verdana"]Начиная с адреса 0х00401020 видим запись значения(5) в младшие части 64-битного регистра R8.

Здесь уже бит REX.W сброшен, и его значение равно 41h
mov dword
[
osVer
]
,
sizeof
.
OSVERSIONINFOA
invoke GetVersionEx
,
osVer
invoke GlobalMemoryStatusEx
,
ms
;
//------------- Версия Windows.
;
// printf() поместит в RCX первый свой аргумент в виде строки спецификаторов,
;
// поэтому начинаем передавать аргументы со-второго RDX
mov edx
,
[
osVer
.
dwMajorVersion
]
;
// запись 32-бит обнуляет старшую часть 64-бит регистра
mov r8d
,
[
osVer
.
dwMinorVersion
]
mov r9d
,
[
osVer
.
dwBuildNumber
]
mov r10d
,
osVer
.
szCSDVersion
cinvoke printf
,

,
\
rdx
,
r8
,
r9
,
r10
;
//------------- Физической памяти
mov rdx
,
[
ms
.
dwTotalPhys
]
;
// всего
shr rdx
,
20
;
// ..(в Мбайтах)
mov r8
,
[
ms
.
dwAvailPhys
]
;
// свободно
shr r8
,
20
;
// ..(в Мбайтах)
mov r9
,
100
;
// ...и в процентах
sub r9d
,
[
ms
.
dwMemoryLoad
]
cinvoke printf
,

,
rdx
,
r8
,
r9
;
//------------- Файл подкачки
movsd xmm0
,
[
ms
.
dwTotalPageFile
]
divsd xmm0
,
[
gb
]
;
// в гигабайтах
movsd
[
doubleA
]
,
xmm0
movsd xmm0
,
[
ms
.
dwAvailPageFile
]
divsd xmm0
,
[
gb
]
movsd
[
doubleB
]
,
xmm0
mov rdx
,
[
doubleA
]
mov r8
,
[
doubleB
]
cinvoke printf
,

,
rdx
,
r8
;
//------------- Виртуальная
movsd xmm0
,
[
ms
.
dwTotalVirtual
]
divsd xmm0
,
[
gb
]
movsd
[
doubleA
]
,
xmm0
movsd xmm0
,
[
ms
.
dwAvailVirtual
]
divsd xmm0
,
[
gb
]
movsd
[
doubleB
]
,
xmm0
mov rdx
,
[
doubleA
]
mov r8
,
[
doubleB
]
cinvoke printf
,

,
rdx
,
r8
endf
;
//------------- Вызов своей процедуры с 8 аргументами
fastcall Convert
,
szHelp
,
szCapt
,
rax
,
rbx
,
\
r10
,
[
printf
]
,
[
SetConsoleTitle
]
,
[
GlobalMemoryStatusEx
]
cinvoke getch
cinvoke exit
,
0
;
//-----------------------------
proc Convert a
,
b
,
c
,
d
,
e
,
f
,
h
,
g
mov
[
a
]
,
rcx
mov
[
b
]
,
rdx
mov
[
c
]
,
r8
mov
[
d
]
,
r9
cinvoke printf
,
%s'
,
10
,
' ---> %s'
,
\
10
,
' RAX: 0x%016I64x'
,
\
10
,
' RBX: 0x%016I64x'
,
\
10
,
' R10: 0x%016I64x'
,
10
,
\
10
,
' 0x%016I64x
,
\
[
a
]
,
[
b
]
,
[
c
]
,
[
d
]
,
[
e
]
,
[
f
]
,
[
h
]
,
[
g
]
ret
endp
;
//-----------------
section
'.idata'
import data readable
library kernel32
,
'kernel32.dll'
,
msvcrt
,
'msvcrt.dll'
import kernel32
,
GlobalMemoryStatusEx
,
'GlobalMemoryStatusEx'
,
\
SetConsoleTitle
,
'SetConsoleTitleA'
,
GetVersionEx
,
'GetVersionExA'
import msvcrt
,
printf
,
'printf'
,
getch
,
'_getch'
,
exit
,
'exit'
[/CODE]



Посмотрим на выхлоп функции GlobalMemoryStatus().

Моя тестовая машина с 64-битной семёркой на борту вполне себе сносно функционирует даже при 1.5 ГБ физ.памяти. И это при том, что запущен браузер Хром с несколькими вкладками, тотал, офис и фотошоп. В файле-подкачки свободен гиг, а виртуальной 8 тераБайт для приложений, и столько-же для ядра.

А вот логи десятки..

Из четырёх физических доступен всего один, но и этого ей недостаточно, т.к. шесть из десяти в подкачке занято. Зато виртуальной хоть отбавляй, аж 128 ТБайт, только юзать их (кроме игр) как-правило некому. Все системные либы грузятся в верхнюю область пользовательского 48-бит пространства, хотя на семёрке Kernel32.dll тусуется где-то между небом и землёй, по середине.




6. Заключение

Что-то букаф в треде получилось много, видимо пора заканчивать..

В скрепку кладу исполняемый *.exe для тестов, и файл «fasmw.ini» для тех, кто хочет поменять визуальную тему FASM’a на Black. У меня он установлен по-пути D:\Install\FASM, а вам нужно открыть его в блокноте, и указать свой путь до инклуд. Сделайте бэкап оригинального *.ini на случай, если что-то пойдёт не так. Всем удачи, пока!
 
Ответить с цитированием
 



Предыдущая тема Следующая тема

Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT ™ © 2001- Antichat Kft.