Compare commits
11 Commits
labs/03
...
3ff51630f5
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ff51630f5 | |||
| b97816df15 | |||
| bfde784868 | |||
| aa38e902ae | |||
| 5bd7ad95b3 | |||
| 23bbc11bac | |||
| 9b7dada982 | |||
| c672fd4fcb | |||
| d5a00202e7 | |||
| 2f1c3787dd | |||
| 99b00ebcc0 |
@ -2,3 +2,68 @@
|
|||||||
|
|
||||||
## Система команд процессора, ее связь с кодами команд
|
## Система команд процессора, ее связь с кодами команд
|
||||||
|
|
||||||
|
## Кодирование команд для x86-64 архитектуры
|
||||||
|
|
||||||
|
Преподаватель на ресурсном курсе оставил огромный талмуд Intel (далее именуемый "талмудик" и "талмуд") на тему того, как кодируются команды у их процессоров. И пусть даже наш дорогой препод на лекции дал пояснения по конверсии и прочему, он оставил без ответа вопросы следующего толка: когда какие байты задействованы, где посмотреть опкоды команд и прочие мелочи жизни. Я тот еще программист, поэтому на меня тут не надейтесь, но помогу чем смогу
|
||||||
|
|
||||||
|
Перво-наперво структура команды. Приведена она и у препода и в талмудике Intel, повторяться не хочу, но картинку оставлю
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
На этой же картинке видно, что может быть от разное количество байт на КОП (который я по привычке именую опкодом), на Displacement, на Immediate и прочем. Да и еще проскакивают надписи `(if required)` и `(optional)`. Вопрос назревает сам собой - а где смотреть-то. И ответ у меня к сожалению не утешительный - в том самом великом и ужасном талмудике (по крайней мере я не нашел другого способа понадежнее). Но тут есть одна так сказать проблемка... Таблички по командам от Intel выглядят мягко скажем как-то так...
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Хтонь лично на мой взгляд довольно неприятная, но на самом деле она не так страшна, как вы подумали... Она значительно хуже...
|
||||||
|
|
||||||
|
В общем я тут приведу свои пояснения ко всему, что указывают сами Intel, но не в сухую по руководству, а на основе собственного опыта ручного ассемблирования (который, к слову, не очень богат, потому что я еще не успел настолько сойти с ума, чтобы делать работу ассемблера за него)
|
||||||
|
|
||||||
|
- `REX.W` - По идее этот префикс может означать много вещей, но на практике пока что я сталкивался с ним только в таком разрезе: если он есть в начале, значит в REX-байте нужно поставить единичку в 3 разряде (4-я цифра справа). Также это означает, что данный байт, вообще говоря, обязателен для функционирования этой команды
|
||||||
|
|
||||||
|
- `REX` - Такое встречается, на моей памяти, только рядом с восьмибитными инструкциями и всегда только для того, чтобы к ним тут же приложилось пояснение от intel, что какие-то там проблемы. В общем читайте все это в сносках, потому что сам по себе флаг означает простое наличие REX-байта перед опкодом по всей видимости
|
||||||
|
- `/digit` - можно порой встретить что-то типа `/0` или `/7`. Когда такое видите, это значит, что в ModR/M байте вместо поля reg нужно записать в двоичной системе то число, которое после слеша. То есть от `000` и до `111`. А все остальное адресуете как раньше
|
||||||
|
- `/r` - указывает на то, что в ModR/M байте все поля Mod, Reg и R/M используются в стандартном варианте
|
||||||
|
- `cb`, `cw`, `cd`, `cp`, `co`, `ct` - сам плохо понимаю, что это за покемоны такие. В 64-битных опкодах встречаются редко. Согласно мануалу показывают, сколько битов после опкода следует зарезервировать под смещение для сегмента кода (если вы откроете методичку Милицина, то это тот самый CS или Code Segment). Также согласно талмудику иногда оно может изменить значение сегментному регистру кода. Возможны варианты 1 байт, 2 байта, 4 байта, 6 байт, 8 байт, 10 байт соотвественно.
|
||||||
|
- `ib`, `iw`, `id`, `io` - Показывают, что после опкода, ModR/M байта (если есть) должен идти непосредственный операнд длиной 1, 2, 4, 8 байт соответственно. Встречается он в таблице обычно там же, где в колонке instruction в соответствующем месте производятся какие-то действия с непосредственными операндами. При чем надо понимать, что нельзя просто опустить байты, которые заполнены нулями, даже если очень хочется и мама разрешила. ставим столько, сколько требует спецификация
|
||||||
|
- `+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. Соотвественно наша задача просто прибавить это число к байту слева от плюса и на этом все. Больше ничего не требуется
|
||||||
|
|
||||||
|
## Как собрать ~~своего покемона~~ свою команду из ассемблера
|
||||||
|
|
||||||
|
*оно же: "Да как этой б\*\*\*ской таблицей пользоаться вообще*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Повторно привожу это изображение, так как оно нам еще понадобится
|
||||||
|
|
||||||
|
Самое важное чиселко, которое тут есть - 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` или другие, которые мы упоминали. заполняем сколько надо
|
||||||
|
|
||||||
|
NOTE: По какой-то причине это не указано в руководстве Intel, но по крайней мере если верить сайту, который я использовал для ассемблирования инструкций, то нужно обязательно учитыать префиксы к опкоду прежде чем начинать кодировать (опять же все по опыту):
|
||||||
|
|
||||||
|
1. `0x67` - ставится если команда **адресуется** при помощи 32-битных регистров
|
||||||
|
2. `0x66` - ставится, если программа иссользует хотя бы 1 16-битный регистр
|
||||||
|
|
||||||
|
Благо для 8-битных операций другие опкоды и хотя бы на них не надо префиксы запоминать)
|
||||||
|
|
||||||
|
<!--- Пока что я думаю эта инфа лишняя, может потом верну и раскомментирую
|
||||||
|
### Чутка про префикс REX
|
||||||
|
|
||||||
|
Судя по всему, префикс REX стал почти обязателен при переходе на 64 разряда. Что ж, это не удивительно, так как в 64-разрядных системах прибавилось регистров, а их номера нужно где-то и как-то хранить, поэтому это вот такой вот "костылик". На самом деле в талмудике преведены схемы всех подключений, которые я пока не привожу, потому что это не самое главное, но может потом добавлю
|
||||||
|
|
||||||
|
Вот что они пишут про префик REX во второй главе своего талмуда: "Префикс REX указывается не всегда в 64-разрядном режиме. Он необходим только тогда, когда инструкция адресуется к одному из рассширенных регистров или использует 64-разрядные операнды". То есть условно говоря если работам с 64 разрядами, то RAX нужен, а если нет - его может и не быть, если явно не сказано иное. Сами же REX - это 16 опкодов, которые берут пространство от 0x40 до 0x4F. В режиме режиме обратной совместимости и IA-32 отражают опкоды реальных инструкций, но нас естественно интересует режим 64 разрядов, а в нем они как отдельная инструкция не трактуются и идут только в связке. Также почему-то интел сокрушаются, что из-за этого однобайтовый опкод для инкреммента и декремента перестал существовать в 64 разрядных системах
|
||||||
|
-->
|
||||||
|
|||||||
18
02-cpu-commnads/hex2bin.py
Executable file
18
02-cpu-commnads/hex2bin.py
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def hex2bin(hexNum: str) -> None:
|
||||||
|
return bin(int(hexNum, 16))
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="convert hex number to bin right in terminal")
|
||||||
|
parser.add_argument('-s', '--stdin', action='store_true', help="if passed takes input from terminal arguments")
|
||||||
|
parser.add_argument('number', nargs='?')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.stdin:
|
||||||
|
print(hex2bin(input()))
|
||||||
|
elif args.number:
|
||||||
|
print(hex2bin(args.number))
|
||||||
|
else:
|
||||||
|
print("something went wrong")
|
||||||
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
ASM = nasm
|
|
||||||
ASM_FLAGS = -felf64 -g
|
|
||||||
LINK = ld
|
|
||||||
|
|
||||||
%: %.o
|
|
||||||
$(LINK) -o $@ $^
|
|
||||||
|
|
||||||
%.o: %.asm
|
|
||||||
$(ASM) $(ASM_FLAGS) $^ -o $@
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f *.o
|
|
||||||
rm -f $(subst .asm, $(empty), $(wildcard *.asm))
|
|
||||||
@ -2,13 +2,3 @@
|
|||||||
|
|
||||||
## Ассемблер и функции BIOS
|
## Ассемблер и функции BIOS
|
||||||
|
|
||||||
В этой работе намного проще посмотреть непосредственно решения и почитать комментарии к коду, чем читать теоретическое приложение к работе. Если вам все же что-то не понятно - кидайте в issues
|
|
||||||
|
|
||||||
Впрочем зная, что основная масса народу не будет делать эту лабу так, как сделал ее я, сюда вряд ли кто-то заглянет)
|
|
||||||
|
|
||||||
### Касаемо Makefile
|
|
||||||
|
|
||||||
Для того чтобы не писать много команд для однотипной и монотонной сборки проекта, был написан простой Makefile. Однако работает он следующим образом: он принимает название цели сборки и ищет файл с именем цели и расширением .asm. Если не находит - не собирает цель.
|
|
||||||
|
|
||||||
Важно заметить, что он не умеет линковать другие файлы в ассемблер, потому что написан был не для этого. Он просто берет голый файл на NASM (обязательно) и выдает 64-битный ELF из этого единственного файла. Если вам необходимо что-то прилинковать к ассемблеру, то увы, придется собирать проект вручную или менять этот makefile
|
|
||||||
|
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
global _start
|
|
||||||
|
|
||||||
%define STDIN 0
|
|
||||||
%define STDOUT 1
|
|
||||||
%define STDERR 2
|
|
||||||
|
|
||||||
section .data
|
|
||||||
src db 1, 2, 3, 4, 5, 6, 7, 8, 9, 0
|
|
||||||
src_size equ $-src
|
|
||||||
|
|
||||||
; резервируем 1 килобайт для буффера ввода и вывода
|
|
||||||
; также в отдельной переменной сохраняем размер этого буфера
|
|
||||||
print_buf: times 1024 db 0
|
|
||||||
buf_size equ $-print_buf
|
|
||||||
|
|
||||||
section .text
|
|
||||||
|
|
||||||
%macro DIGIT_TO_ASCII 1 ; макрос, принимающий один регистр
|
|
||||||
add %1, '0'
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
%macro PUSH_M 1-* ; push many; пушит в порядке следования
|
|
||||||
%rep %0
|
|
||||||
push %1
|
|
||||||
%rotate 1
|
|
||||||
%endrep
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
%macro POP_M 1-* ; pop many. читает в порядке следования
|
|
||||||
%rep %0
|
|
||||||
pop %1
|
|
||||||
%rotate 1
|
|
||||||
%endrep
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
%macro RPOP_M 1-* ; pop many. читает в обратном порядке
|
|
||||||
%rotate -1
|
|
||||||
%rep %0
|
|
||||||
pop %1
|
|
||||||
%rotate -1
|
|
||||||
%endrep
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
; Передачу аргументов будем делать при помощи ABI - стандартная практика для linux
|
|
||||||
; Аргументы передаются в следующем порядке: rdi, rsi, rdx, rcx, r8, r9. Все, что не влезло, пушится в стек
|
|
||||||
; У передачи через стек тоже есть особенности, но их мы пока касаться не будем
|
|
||||||
|
|
||||||
print_from_buf: ; word -> void
|
|
||||||
|
|
||||||
PUSH_M rax, rsi, rdx, rdi ; сохраним регистры, которые точно попортим
|
|
||||||
mov rdx, rdi ; сколько выводить, в rdi содержится единственный аргумент
|
|
||||||
mov rsi, print_buf ; откуда выводить. Адрес буфера
|
|
||||||
mov rdi, STDOUT; куда выводить. Дескриптор файла. В нашем случае стандартного вывода
|
|
||||||
mov rax, 1
|
|
||||||
syscall
|
|
||||||
|
|
||||||
RPOP_M rax, rsi, rdx, rdi ; вернем значения регистров
|
|
||||||
ret
|
|
||||||
|
|
||||||
_start:
|
|
||||||
mov rcx, src_size
|
|
||||||
mov rsi, src
|
|
||||||
mov rdi, print_buf
|
|
||||||
|
|
||||||
xor rax, rax ; обнуляем регистр
|
|
||||||
.transfer: ; в цикле передаем данные, попутно конвертируя их в ascii
|
|
||||||
lodsb
|
|
||||||
DIGIT_TO_ASCII rax
|
|
||||||
stosb
|
|
||||||
loop .transfer
|
|
||||||
|
|
||||||
mov [rdi + 1], BYTE `\n` ; Чтобы система не ругалась на отсутствие переноса
|
|
||||||
|
|
||||||
mov rdi, src_size
|
|
||||||
call print_from_buf
|
|
||||||
|
|
||||||
exit:
|
|
||||||
mov rax, 60
|
|
||||||
mov rdi, 0
|
|
||||||
syscall
|
|
||||||
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
global _start
|
|
||||||
|
|
||||||
%define STDIN 0
|
|
||||||
%define STDOUT 1
|
|
||||||
%define STDERR 2
|
|
||||||
|
|
||||||
section .data
|
|
||||||
|
|
||||||
; резервируем 1 килобайт для буффера ввода и вывода
|
|
||||||
; также в отдельной переменной сохраняем размер этого буфера
|
|
||||||
print_buf: times 1024 db 0
|
|
||||||
buf_size equ $-print_buf
|
|
||||||
|
|
||||||
input_buf: times 1024 db 0 ; буфер, в который будут читаться символы со стандартного ввода
|
|
||||||
input_size equ $-input_buf
|
|
||||||
|
|
||||||
array: times 512 dq 0 ; молимся, чтобы никому не пришло в голову писать так много
|
|
||||||
arr_size equ $-array
|
|
||||||
|
|
||||||
; Для poll
|
|
||||||
%define POLLIN 0x001 ; Есть ли что почитать с буфера ввода. Понадобится для продолжения ввода
|
|
||||||
input_pollfd: dd STDIN
|
|
||||||
dw POLLIN
|
|
||||||
revents: dw 0 ; возвращаемые события
|
|
||||||
|
|
||||||
section .text
|
|
||||||
|
|
||||||
%macro DIGIT_TO_ASCII 1 ; макрос, принимающий один аргумент (регистр или память)
|
|
||||||
add %1, '0'
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
%macro ASCII_TO_DIGIT 1 ; макрос, принимающий один аргумент (регистр или память)
|
|
||||||
sub %1, '0'
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
%macro PUSH_M 1-* ; push many; пушит в порядке следования
|
|
||||||
%rep %0
|
|
||||||
push %1
|
|
||||||
%rotate 1
|
|
||||||
%endrep
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
%macro POP_M 1-* ; pop many. читает в порядке следования
|
|
||||||
%rep %0
|
|
||||||
pop %1
|
|
||||||
%rotate 1
|
|
||||||
%endrep
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
%macro RPOP_M 1-* ; pop many. читает в обратном порядке
|
|
||||||
%rotate -1
|
|
||||||
%rep %0
|
|
||||||
pop %1
|
|
||||||
%rotate -1
|
|
||||||
%endrep
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
%macro PUSHR8 1; закинуть восьмибитный регистр в стек
|
|
||||||
dec rsp
|
|
||||||
mov [rsp], %1
|
|
||||||
%endmacro
|
|
||||||
|
|
||||||
; Передачу аргументов будем делать при помощи ABI - стандартная практика для linux
|
|
||||||
; Аргументы передаются в следующем порядке: rdi, rsi, rdx, rcx, r8, r9. Все, что не влезло, пушится в стек
|
|
||||||
; У передачи через стек тоже есть особенности, но их мы пока касаться не будем
|
|
||||||
|
|
||||||
clean_print_buf: ; none -> void
|
|
||||||
PUSH_M rax, rcx, rdi
|
|
||||||
mov rcx, buf_size
|
|
||||||
mov rdi, print_buf
|
|
||||||
xor rax, rax ; будем заносить нули во всю память
|
|
||||||
rep stosb
|
|
||||||
RPOP_M rax, rcx, rdi
|
|
||||||
ret
|
|
||||||
|
|
||||||
print_from_buf: ; qword -> void; пытается вывести данные из буфера. аргумент не может быть больше 1024
|
|
||||||
PUSH_M rax, rsi, rdx, rdi ; сохраним регистры, которые точно попортим
|
|
||||||
mov rdx, rdi ; сколько выводить, в rdi содержится единственный аргумент
|
|
||||||
mov rsi, print_buf ; откуда выводить. Адрес буфера
|
|
||||||
mov rdi, STDOUT; куда выводить. Дескриптор файла. В нашем случае стандартного вывода
|
|
||||||
mov rax, 1
|
|
||||||
push rcx
|
|
||||||
syscall
|
|
||||||
pop rcx
|
|
||||||
RPOP_M rax, rsi, rdx, rdi ; вернем значения регистров
|
|
||||||
ret
|
|
||||||
|
|
||||||
read_to_buf: ; none -> void. Пытается заполнить буфер из стандартного ввода
|
|
||||||
PUSH_M rdi, rsi, rdx
|
|
||||||
mov rdi, STDIN ; откуда читать (дескриптор файла)
|
|
||||||
mov rsi, input_buf ; куда читать
|
|
||||||
mov rdx, input_size ; Сколько пытаемся читать
|
|
||||||
mov rax, 0 ; системный вызов чтения
|
|
||||||
syscall
|
|
||||||
RPOP_M rdi, rsi, rdx ; rax содержит количество прочитанных байт, а это важно
|
|
||||||
ret
|
|
||||||
|
|
||||||
poll_stdin:
|
|
||||||
PUSH_M rdi, rsi, rdx
|
|
||||||
mov rsi, 1 ; следим только за одним потоком
|
|
||||||
mov rax, 7 ; poll syscall
|
|
||||||
mov rdi, input_pollfd
|
|
||||||
mov rsi, 1 ; одна структура данных (изначально просто вызов принимает кучу таких)
|
|
||||||
mov rdx, 0 ; не ждать
|
|
||||||
syscall
|
|
||||||
RPOP_M rdi, rsi, rdx
|
|
||||||
ret
|
|
||||||
|
|
||||||
print_number: ; qword (rdi) -> void
|
|
||||||
; наша задача - сформировать массив символов.
|
|
||||||
; Ну а раз мы не знаем точно сколько их будет, формировать его будем прямо в стеке. нам повезло, что он растет вниз
|
|
||||||
; Нам очень повезло, что он растет вниз
|
|
||||||
; создадим 2 локальные переменные - одну для размера массива, другую для делителя
|
|
||||||
push rbp
|
|
||||||
PUSH_M rdx, rdi, rsi ; сохранять регистры обязательно надо до того, как писать в стек символы
|
|
||||||
; создаем базу для адресации. Тогда первая будет на rbp - 8 - делитель, а вторая на rbp - 16 - количество
|
|
||||||
mov rbp, rsp
|
|
||||||
; [WARNING] тут надо будет сохранить регистры
|
|
||||||
push rsp ; сохраню, потому что после всей вакханалии я концов не сыщу
|
|
||||||
sub rsp, 16 ; выделяем место под 3 переменные
|
|
||||||
mov qword [rbp - 16], 10 ; пусть и жирно, но операнд обязан быть 64 разрядным для корректного деления
|
|
||||||
mov qword [rbp - 24], 0 ; счетчик
|
|
||||||
mov rax, rdi
|
|
||||||
push byte 0 ; при выводе он ориентируется на это как на конец строки
|
|
||||||
.division_loop:
|
|
||||||
xor rdx, rdx ; обнулим найденый остаток. (он просто еще и при делении принимает участие)
|
|
||||||
div qword [rbp - 16]
|
|
||||||
DIGIT_TO_ASCII dl
|
|
||||||
PUSHR8 dl ; поскольку в процессор не завезли возможность закинуть в стек 8 битный регистр, я им немного помог макросами
|
|
||||||
inc qword [rbp - 24] ; увеличиваем счетчик на единицу
|
|
||||||
test rax, rax ; делает and поразрядное с самим собой. Меня интересует, лежит ли в rax ноль
|
|
||||||
jnz .division_loop ; если в rax не ноль, то продолжаем цикл
|
|
||||||
; выводим число
|
|
||||||
mov rax, 1
|
|
||||||
mov rdi, STDOUT
|
|
||||||
mov rsi, rsp
|
|
||||||
mov rdx, [rbp-24] ; уже не надо очищать, потому что в конце я просто восстановлю как было
|
|
||||||
push rcx
|
|
||||||
syscall
|
|
||||||
pop rcx
|
|
||||||
|
|
||||||
mov rsp, [rbp - 8]
|
|
||||||
RPOP_M rdx, rdi, rsi
|
|
||||||
pop rbp
|
|
||||||
ret
|
|
||||||
|
|
||||||
_start:
|
|
||||||
mov rbp, rsp
|
|
||||||
; Создадим 2 локальные переменные для аккумулятора размером 8 байт и для математических нужд 8 байт.
|
|
||||||
; аккумулятор будет по адресу rbp - 8, а временная по rbp - 16
|
|
||||||
sub rsp, 16
|
|
||||||
; потом я не удержался и завел еще одну переменную - сколько мы успели написать в массив
|
|
||||||
sub rsp, 2 ; массив все равно размером всего 512, делать переменную больше нет смысла. rbp - 18
|
|
||||||
|
|
||||||
mov rsi, input_buf
|
|
||||||
mov rdi, array
|
|
||||||
.read_loop:
|
|
||||||
call read_to_buf ; системный вызов read вернет количество прочитаных байтов
|
|
||||||
mov rcx, rax ; сколько байтов прочиталось, столько и обработаем
|
|
||||||
; обработаем информацию
|
|
||||||
xor rax, rax ; обнулим на всякий пожарный
|
|
||||||
jmp .read_byte
|
|
||||||
|
|
||||||
.separator_occured:
|
|
||||||
dec rcx
|
|
||||||
mov rax, [rbp - 8]
|
|
||||||
stosq
|
|
||||||
xor rax, rax
|
|
||||||
inc word [rbp - 18]
|
|
||||||
mov qword [rbp - 8], 0
|
|
||||||
test rcx, rcx
|
|
||||||
jz .check_buf
|
|
||||||
|
|
||||||
.read_byte: ; цикл чтения
|
|
||||||
lodsb
|
|
||||||
; проверим, цифра ли это. Если нет, то записываем в память то, что хранилось в локальной переменной
|
|
||||||
cmp al, '0'
|
|
||||||
jl .separator_occured
|
|
||||||
cmp al, '9'
|
|
||||||
jg .separator_occured
|
|
||||||
|
|
||||||
ASCII_TO_DIGIT al ; Если цифра, то конвертируем ее из ascii
|
|
||||||
; Поскольку умножение и деление можно сделать только через регистр, придется извратиться
|
|
||||||
PUSH_M rax, rdx
|
|
||||||
mov rax, [rbp - 8]
|
|
||||||
mov qword [rbp - 16], 10
|
|
||||||
mul qword [rbp - 16]
|
|
||||||
mov [rbp - 8], rax
|
|
||||||
RPOP_M rax, rdx
|
|
||||||
add [rbp - 8], rax ; результат деления запишем в локальную переменную
|
|
||||||
loop .read_byte ; читаем буфер ввода до конца
|
|
||||||
|
|
||||||
.check_buf:
|
|
||||||
call poll_stdin
|
|
||||||
test dword [revents], POLLIN
|
|
||||||
jnz .read_loop
|
|
||||||
|
|
||||||
; Теперь выведем прочитанный массив на экран
|
|
||||||
xor rcx, rcx
|
|
||||||
mov cx, [rbp - 18]
|
|
||||||
mov rsi, array
|
|
||||||
call clean_print_buf
|
|
||||||
.output_loop:
|
|
||||||
lodsq
|
|
||||||
mov rdi, rax
|
|
||||||
call print_number
|
|
||||||
mov byte [print_buf], ' '
|
|
||||||
mov rdi, 1
|
|
||||||
call print_from_buf ; печатаем ровно 1 пробел
|
|
||||||
loop .output_loop
|
|
||||||
mov byte [print_buf], `\n`
|
|
||||||
mov rdi, 1
|
|
||||||
call print_from_buf
|
|
||||||
|
|
||||||
exit:
|
|
||||||
mov rax, 60
|
|
||||||
mov rdi, 0
|
|
||||||
syscall
|
|
||||||
|
|
||||||
BIN
assets/REX_scheme.png
Executable file
BIN
assets/REX_scheme.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
BIN
assets/command_structure.png
Executable file
BIN
assets/command_structure.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/opcode_table_example.png
Executable file
BIN
assets/opcode_table_example.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
Reference in New Issue
Block a user