Lab 2: Базовые команды и кодирование для процессора #2

Merged
mrqiz merged 21 commits from labs/02 into main 2024-09-22 18:15:51 +03:00
5 changed files with 76 additions and 0 deletions
Showing only changes of commit b97816df15 - Show all commits

View File

@ -28,9 +28,35 @@
- `+rb`, `+rw`, `+rd`, `+ro` - встречается тогда, когда создатели процессора почему-то решили засунуть регистр прям в опкод операнда. Ну, не нам их за это судить. Фактически нам нужно просто глянуть в таблицу которая приведена самими Intel, чтобы определиться только с тем, какое число от 0 до 7 прибавлять. В целом это число является номером регистра, а идут они всегда в следующем порядке: rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, а также дополнительные регистры r8-r15 работают в том же режиме, то есть начинают нумероваться с нуля. Единственное отличие - бит в REX байте нужно поставить. А вообще табличка должен сказать весьма любопытная, поэтому с ней придется ознакомиться самому. Находится она на 45 странице руководства. - `+rb`, `+rw`, `+rd`, `+ro` - встречается тогда, когда создатели процессора почему-то решили засунуть регистр прям в опкод операнда. Ну, не нам их за это судить. Фактически нам нужно просто глянуть в таблицу которая приведена самими Intel, чтобы определиться только с тем, какое число от 0 до 7 прибавлять. В целом это число является номером регистра, а идут они всегда в следующем порядке: rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi, а также дополнительные регистры r8-r15 работают в том же режиме, то есть начинают нумероваться с нуля. Единственное отличие - бит в REX байте нужно поставить. А вообще табличка должен сказать весьма любопытная, поэтому с ней придется ознакомиться самому. Находится она на 45 странице руководства.
- `+i` - используется в операциях с плавающей точкой. Такие операции любят использовать стек сопроцессора (потому что на самом деле вся арифметика с плавающей точкой аппаратно ускоряется и у нее тоже есть собственная память). Так вот, такой стек обозначается ST(i). Где ST(0) - вершина стека. Не берусь утверждать, но по всей видимости в стеке всего 8 ячеек, потому что по мануалу i может принимать значения от 0 до 7. Соотвественно наша задача просто прибавить это число к байту слева от плюса и на этом все. Больше ничего не требуется - `+i` - используется в операциях с плавающей точкой. Такие операции любят использовать стек сопроцессора (потому что на самом деле вся арифметика с плавающей точкой аппаратно ускоряется и у нее тоже есть собственная память). Так вот, такой стек обозначается ST(i). Где ST(0) - вершина стека. Не берусь утверждать, но по всей видимости в стеке всего 8 ячеек, потому что по мануалу i может принимать значения от 0 до 7. Соотвественно наша задача просто прибавить это число к байту слева от плюса и на этом все. Больше ничего не требуется
## Как собрать ~~своего покемона~~ свою команду из ассемблера
*оно же: "Да как этой б\*\*\*ской таблицей пользоаться вообще*
![Структура команд](../assets/command_structure.png)
Повторно привожу это изображение, так как оно нам еще понадобится
Самое важное чиселко, которое тут есть - 16-ричное породы "понятно-написанное". Оно - фундамент всего опкода, его мы и берем. А дальше алгоритм следующий:
1. Смотрим, колонку instruction. В ней ищем глазами базу и венец - понятно написанный опкод (это будет скорее всего от двух и до шести 16-ричных цифр). Дальше смотрим, надо ли к нему непосредственно что-то прибавать, и если надо - прибавляем. Ура - мы получили opcode.
2. Как только мы получили опкод, начинаем смотреть налево - если есть приписка REX.W, значит пишем REX байт. Пока что просто ставим заглушку: 0b01001000 или 0x48. Он нам потребуется если мы захотим адресоваться к регистрам с r8 по r15, а до тех пор он будет в заглушечном состоянии
3. Далее присматриваемся, надо ли что-то прорезервировать (те самые `cb`) и если непосредственно после опкода ничего не требуется начинаем писать ModR/M байтx
4. Написание ModR/M байта пожалуй самый запутанный процесс, но с ним нам должна помочь табличка от Intel. Находятся они в районе 32-34 страниц. Но расскажу так. Поля у ModR/M следующие - 2 битовый mod, потом 3 битный reg, потом 3 битный r/m.
1. mod - указывает на то, как будет адресоваться r/m (третье поле). r/m - сокращение от register/memory. То есть как можно из названия догадаться, только в этом поле процессор может адресовать память компьютера. Это же и есть фундаментальная причина, по которой нельзя заассемблировать команду вроде `mov [addr1], [addr2]`. Возможное содержимое этого поля такого: `00` - Будет адресоваться оперативная память, при чем использоваться будут только регистры (`mov rax, [rdi + rsi*4]`). `01` - будет адресоваться оперативная память и помимо регистра будет еще и смещение, но длиной не больше 1 байта (`add [rbp + rcx - 2], rax`). `10` - то же, что и `01`, но смещение уже занимает 4 байта. `11` - адресоваться будут 2 регистра (`xor rax, rbx`). Если мы поставили что угодно кроме `11` - это надо запомнить, потому что число которое мы записали нужно будет включить в двоичный вид команды использовав взятое нами число байт, а разместить их надо будет либо после ModR/M, либо после SIB байта, если последний будет.
2. reg - указывает регистр, если в колонке opcode не стоит что-то из разряда `/4`. Если стоит, то в reg записывается это число в двоичной форме. Все неиспользованные разряды заполняются нулями. Если длина регистра не вмещается, то самую старшую единицу можно поставить в REX.R
3. R/M - указывает регистр или участок в памяти, который будет адрессоваться. Можем писать сюда номер любого регистра (если не влезет, то расширять его при помощи X бита в REX байте). Однако особенностью тут обладает значение `100`, потому что оно показывает процессору, что нужен SIB байт. При любом другом значении адрес (если mod != 0b11) будет взят из регистра и если есть смещение, оно будет добавлено к значению этого адреса. Единственное, что если mod взят 0b00, то значение 101 тоже магическое - оно затребует 32-битное смещение и использует его в качестве адреса (это точно сработает в 32 битном режиме, но не уверен, что сработает в 64 битах)
5. Если вы взяли такую комбинацию, что вам понадобился SIB байт (а это mod != 11, r/m == 100), то разберемся со структурой байта. Весь этот байт завязан исключительно на адресацию в памяти и состоит из трех побей Scale (SS) в 2 бита, index в 3 бита, base и 3 бита. для разбора полей возьмем следующий пример `[rbx + rcx * 4 + 3]`
1. SS - это scale - это то, на что будет умножаться регистр индекса, при чем это двухбитовое число можно считать степенью двойки. То есть получается 2^(SS) - это коэфициент на который мы умножаем и можем получается умножить на 1, 2, 4 и 8.
2. index - это регистр, который будет умножаться на 2^(SS). Если вы хотите вписать регистры с r8-r15, то невлезающую единицу можно записать в REX.X. В целом же связка SS и index и обеспечивает наличие в адресе в нашем примере `rcx * 4`
3. base - указывает на регистр, значение которого в лоб прибавится к адресу, то есть в нашем примере он отвечает за `rbx`. Если базовый регистр не нужен, на его место ставится `101`
6. После SIB идет displacement байты (1 или 4 в зависимости от поля mod в ModR/M). Заполняем их согласно выделенному количеству
7. После dispacement идет immediate байты - они могут встретиться если в табличке в колонке opcode на этой строке есть что-то похожее на `ib` или другие, которые мы упоминали. заполняем сколько надо
<!--- Пока что я думаю эта инфа лишняя, может потом верну и раскомментирую
### Чутка про префикс REX ### Чутка про префикс REX
Судя по всему, префикс REX стал почти обязателен при переходе на 64 разряда. Что ж, это не удивительно, так как в 64-разрядных системах прибавилось регистров, а их номера нужно где-то и как-то хранить, поэтому это вот такой вот "костылик". На самом деле в талмудике преведены схемы всех подключений, которые я пока не привожу, потому что это не самое главное, но может потом добавлю Судя по всему, префикс REX стал почти обязателен при переходе на 64 разряда. Что ж, это не удивительно, так как в 64-разрядных системах прибавилось регистров, а их номера нужно где-то и как-то хранить, поэтому это вот такой вот "костылик". На самом деле в талмудике преведены схемы всех подключений, которые я пока не привожу, потому что это не самое главное, но может потом добавлю
Вот что они пишут про префик REX во второй главе своего талмуда: "Префикс REX указывается не всегда в 64-разрядном режиме. Он необходим только тогда, когда инструкция адресуется к одному из рассширенных регистров или использует 64-разрядные операнды". То есть условно говоря если работам с 64 разрядами, то RAX нужен, а если нет - его может и не быть, если явно не сказано иное. Сами же REX - это 16 опкодов, которые берут пространство от 0x40 до 0x4F. В режиме режиме обратной совместимости и IA-32 отражают опкоды реальных инструкций, но нас естественно интересует режим 64 разрядов, а в нем они как отдельная инструкция не трактуются и идут только в связке. Также почему-то интел сокрушаются, что из-за этого однобайтовый опкод для инкреммента и декремента перестал существовать в 64 разрядных системах Вот что они пишут про префик REX во второй главе своего талмуда: "Префикс REX указывается не всегда в 64-разрядном режиме. Он необходим только тогда, когда инструкция адресуется к одному из рассширенных регистров или использует 64-разрядные операнды". То есть условно говоря если работам с 64 разрядами, то RAX нужен, а если нет - его может и не быть, если явно не сказано иное. Сами же REX - это 16 опкодов, которые берут пространство от 0x40 до 0x4F. В режиме режиме обратной совместимости и IA-32 отражают опкоды реальных инструкций, но нас естественно интересует режим 64 разрядов, а в нем они как отдельная инструкция не трактуются и идут только в связке. Также почему-то интел сокрушаются, что из-за этого однобайтовый опкод для инкреммента и декремента перестал существовать в 64 разрядных системах
-->