Compare commits
27 Commits
1e09eb6b96
...
labs/06
| Author | SHA1 | Date | |
|---|---|---|---|
| b559db2cc1 | |||
| 8fe6e0ba63 | |||
| f57aa67887 | |||
| eb85d6dd9a | |||
| 68c5f8388b | |||
| 0fff8ca9d1 | |||
| a2ac5eedcc | |||
| b161ff5465 | |||
| 51e5f729da | |||
| 8fc6112f58 | |||
| 23b82657d4 | |||
| 78a4638401 | |||
| e1aeb3f2df | |||
| ed21dfe07a | |||
| 6c1198df73 | |||
| 58897fedb7 | |||
| 85ba08abb9 | |||
| 51e220410a | |||
| 49d3c02d7d | |||
| 0faffcd5c8 | |||
| 3e6a77c710 | |||
| daec968bcb | |||
| c3ea0cb506 | |||
| e5c09afc6d | |||
| cacddfbad2 | |||
| f804c0bddf | |||
| 995260cdde |
@ -74,11 +74,95 @@
|
||||
|
||||
NOTE: По какой-то причине это не указано в руководстве Intel, но по крайней мере если верить сайту, который я использовал для ассемблирования инструкций, то нужно обязательно учитыать префиксы к опкоду прежде чем начинать кодировать (опять же все по опыту):
|
||||
|
||||
1. `0x67` - ставится если команда **адресуется** при помощи 32-битных регистров
|
||||
2. `0x66` - ставится, если программа иссользует хотя бы 1 16-битный регистр
|
||||
1. `0x66` - ставится, если программа иссользует хотя бы 1 16-битный регистр
|
||||
2. `0x67` - ставится если команда **адресуется** при помощи 32-битных регистров
|
||||
|
||||
Благо для 8-битных операций другие опкоды и хотя бы на них не надо префиксы запоминать)
|
||||
|
||||
## Примеры переводов
|
||||
|
||||
Голая теория никого никогда не радовала, поэтому постараюсь в меру своих сил переконвертировать несколько примеров
|
||||
|
||||
**`mov rax, rbx`** - исходная инструкция
|
||||
|
||||
Для начала в талмудике нам потребуется найти опкод операции. Поэтому ищем табличку для этой операции. В ней нас интересует один из двух покодов: `mov r/m64, r64` и `mov r64, r/m64`. Я возьму второй из этих вариантов, просто чтобы порядок следования регистров совпадал с нашей исходной иструкцией (потом поймете). Для выбранной инструкции приведен следующий opcode: `REX.W + 8B/r`
|
||||
|
||||
Для начала посередине ставим опкод (8b в двоичной):
|
||||
|
||||
[ 1000 1011 ]
|
||||
|
||||
REX байт у нас появляется в любом случае. REX.W - это флаг для REX, который переназначает операнды в размеры 64 бита. Если бы оба наших регистра были бы 32 битные, то этого флага бы не было. Ну а раз REX у нас обязателен, то можно немного определиться, будут ли у нас еще какие-то флаги в этом байте. Еще какие-то флаги могут появиться если вы используете регистры r8-r15, в остальных случаях REX будет выглядеть как [ 0100 1000 ]. У нас эти регистры не используются, поэтому оставим его в таком виде
|
||||
|
||||
Промежуточный результат: [ 0100 1000 ] [ 1000 1011 ]
|
||||
|
||||
Далее ModR/M. Первые 2 бита от него определяют будут ли использовать адреса и смещения. Подробнее смотрите выше. В нашем случае мы пересылаем из регистра в регистр, поэтому нам нужен mod = 11. Далее идет поле рег. Конкретно в случае `mov r64, r/m64` по контексту можно догадаться что reg у нас слева. Тремя битами кодируется номер регистра. У rax номер 000, а у rbx номер 011. Как я уже и сказал, в нашем случае регистр приемника слева, поэтому ModR/M: [ 11 000 011 ]
|
||||
|
||||
Итого: [ 0100 1000 ] [ 1000 1011 ] [ 11 000 011 ]
|
||||
|
||||
*примечание: ответ мог бы получиться немного другой, если бы мы взяли первый попавшийся опкод, но можете использовать его в качестве упражнения (ну а что, составители учебника так могут, а я нет?)*
|
||||
|
||||
**`mov r8, r11`**
|
||||
|
||||
Все действия и рассуждения у нас аналогичны примеру выше. Я хотел лишь пару слов сказать о том, как кодировать регистры r8-r15. Как раз для этих целей нам поможет REX байт. Как я уже писал, REX имеет следующую структуру: 0100 WRXB. Бит R используется для расширения поля reg, а бит B используется для расширения поля r/m. Когда идет обращение к регистру из диапазона r8-r15 в соответствующих дополняющих байтах должна стоять единица. То есть r8 в поле reg будет записываться как REX.R == 1 + 000, а r11 в поле r/m как REX.B == 1 + 011. Так что для данной команды повторится все, кроме REX байта, который в свою очередь примет вид [ 0100 1101 ]
|
||||
|
||||
Итого: [ 0100 1101 ] [ 1000 1011 ] [ 11 000 011 ]
|
||||
|
||||
**`lea rbx, [rbp + r12 * 4 + 33]`**
|
||||
|
||||
Для начала повторяем наш процесс поиска в талмудике опкода. В этот раз у нас разночтений нет, у нас есть только вариант с инструкцией `lea r64, m`. Для нее поле opcode выглядит довольно знакомо: `REX.W 8D /r`. REX байт у нас уже задействован и 100% появится, однако я вижу тут регистр r12, что должно сказать мне, что REX байт не ограничится простым [ 0100 1000 ]. Заполним мы его чуть позже, а пока только опкод. [ 1000 1101 ]
|
||||
|
||||
Далее смотрим ModR/M. в нашем случае мы явно будем использовать адрес из оперативной памяти и число 33 для сдвига, поэтому нам нужен режим mod = 01 (можно и 10, но тогда будет куча бесполезных нулей). поле reg не вызывает вопросов - используется регистр rbx, поэтому в REX.R = 0, а reg = 011, а вот с полем r/m все любопытнее. Число, которое идет с плюсом можно пока мысленно вынести за скобки (но не убирать далеко, оно нам еще пригодится), а вот регистры мы уже не можем проморгать. Как мы помним поле r/m хранит только 3 бита, как же адресовать эту адову хренотень? Оказывается в r/m последовательность 100 зарезервирована под добавление еще одного байта - SIB байта. ModRM = [ 01 011 100 ]
|
||||
|
||||
SIB байт состоит из 2 битов SS, которые представляют собой степень двойки, 2^(ss) я буду называть коэфициентом. В нашем случае хочу умножить на 4, поэтому ss = 10. Далее идет 3 бита на индекс - это регистр, который будет умножен на коэфициент. В нашем случае мы хотим умножить r12 на 4. Index регистр расширяется из REX.X (потому что inde**X**). Поэтому нам нужно записать двоичной системе 12 - 1100. Старший бит отправится в REX.X, а остальное в поле index, то есть REX.X == 1 index == 100. Далее идет 3 бита под регистр базы. Он расширяется из REX.B, в нашем случае хочется использовать регистр rbp, он имеет номер 0101, поэтому используем REX.B == 0, base == 101. SIB = [ 10 100 101 ]
|
||||
|
||||
Уже на этом этапе мы видим, что нужно проставить еще одну единицу в REX.X, в остальных же местах используются обычные регистры, поэтому REX = [ 0100 1010 ].
|
||||
|
||||
Помните, мы запоминали число 33? Ну вот настало его время. Дело в том, что число, которое надо прибавить к итоговому адресу. В нашем случае надо закодировать число 33, это будет 0b00100001, это мы засунем в displecement байт
|
||||
|
||||
Итого: [ 0100 1010 ] [ 1000 1101 ] [ 01 011 100 ] [ 10 100 101 ] [ 0010 0001 ]
|
||||
|
||||
**`inc WORD PTR [2 * rsi + 31]`**
|
||||
|
||||
*Интересный факт, в качестве index не может использоваться rsp*
|
||||
|
||||
Не буду много повторяться. Внутри опокад стоит `WORD PTR`, что значит, что я самолично попросил ассемблер относиться к содержимому скобок, как к указателю на 1 машинное слово. Возможно также отнестись как к указателю на байт `BYTE`, двойное слово `DWORD` и четверное слово `QWORD`. Instruction `inc r/m16`. Opcode `FF /0`. `/0` означает, что в ModR/M в поле reg нужно записать 3 нуля. Остальное адрессуется как обычно, поэтому самое время обсудить вот какую вещь. Если нам необходимо опустить базу, то в SIB байте мы поставим в поле base 101. Однако для этого в mod нужно поставить 00 и автоматически придется записать 4 байта смещения.
|
||||
|
||||
[ 0110 0110 ] [ 1111 1111 ] [ 00 000 100 ] [ 01 110 101 ] [ 0001 1111 ] [ 0000 0000 ] [ 0000 0000 ] [ 0000 0000 ]
|
||||
|
||||
Замечу так же, что поскольку не используется ни REX.W ни один из расширенных регистров, REX байт принимал значение 0100 0000, но в таком случае спецификация Intel позволяет этот байт опускать. А вот что опускать нельзя - это префикс переназначения операнда, потому что используется 16 битный регистр.
|
||||
|
||||
Число 31 у нас записано справа от SIB байта и у многих наверное появился вопрос, а почему оно выглядит имено так? А точнее - почему 3 последних байта заполнены нулями. Отвечаю - бог его знает и на самом деле, это зависит от процессора, и, возможно, от того, использует ли от little endian или big endian. Little endian - это когда число в памяти записывается как мы привыкли, то есть чем ливее циферка, тем она значительнее. Так что 0x1c1b так в память и запишутся - [ 0001 1100 ] [ 0001 1011 ]. Но вот в случае big endian запись идет по байтам, и если внутри байта все стандартно, то вот сами байты идут уже от младшего к старшему, поэтому то же число запишется уже в обратном порядке как [ 0001 1011 ] [ 0001 1100 ]. Думаю тут такая же фигня
|
||||
|
||||
Однако тут есть еще один важный момент Мы видим, что в данном случае есть один интересный момент. Вроде mod == 00, но тут 4 байта dispasement. Дело в том, что если mod == 00 а base == 101, то будет адресация вида index * scale + disp32. Довольно весело. Это я к чему? даже если вы знаете все номера регистры, таблицу смотреть все равно надо
|
||||
|
||||
**Послесловие**
|
||||
|
||||
Это далеко не исчерпывающий набор примеров, но этого хватит для начала.
|
||||
|
||||
## Решение остальных пунктов
|
||||
|
||||
*В целом в этом репозитории лежат файлы, в которых я приложил пока еще не протестированное, но решение для первых нескольких пунктов. Однако в силу того, что память у нас 64-битная, а также я не могу залеть напрямую в видеопамять если не буду собирать модуль ядра. Возможно конечно от нечего делать я сделаю модуль ядра, который позволит выворачивать такие приколы, но это будет точно не на время этого курса.*
|
||||
|
||||
### 01 - простучать команды ассемблеру
|
||||
|
||||
Тут нечего сказать - есть просто колонка с коммандами и просят их использовать. Тут гугл в помощь.
|
||||
|
||||
А вот по поводу полей в команде могу сразу сказать - в 64 битном процессоре все это будет выглядеть немного иначе. Поэтому предлагаю 16-битные регистры заменить в команде на 64 битные и закодировать как для 64 разрядной системы. подробная инструкция как это бы надо бы сделать у меня приведена выше, поэтому тут не буду на этом останаваливаться.
|
||||
|
||||
### 02 - Пересылка массива при помощи loop и lea
|
||||
|
||||
`lea` - это сокращение от "load effective adress". Она использует использует классическую операцию обращения к памяти, но саму память не затрагивает, а просто записывает высчитанный адрес в переменную. `loop` в свою очередь прыгает на определенную метку пока в rcx не окажется 0 и при каждом прыжке уменьшает значение в rcx на 1.
|
||||
|
||||
### 03 - Пересылка данных через LODS, MOVS, STOS
|
||||
|
||||
LODS и STOS - парные команды. Первая читает из памяти в rax (или его часть), STOS наоборот - пишет в память содержимое rax (или его часть). `movs` перемещает содержимое из [rsi] в [rdi], после чего увеличивает адрес на размер элемента. Это очень хорошо сочетается с префиксом rep, который заставляет повторяет команду пока в rcx не будет 0, а после каждого повторения уменьшает rcx на 1
|
||||
|
||||
### 05 - Запись в произвольную память
|
||||
|
||||
В линуксе вся память виртуальная, а если попытаться в лоб попробовать написать что-то в рандомный адрес, ядро выдаст segfault. Чтобы этого не произошло необходимо промапать память. Для этого используется системный вызов mmap, про его особенности написано внутри файла. Здесь хотелось бы пояснить вот какой момент: этот системный вызов использует кучу флагов, которые изначально не особенно нам известны. Так вот. Самый быстрый способ найти их - обратиться к include вашего компилятора. Для mmap все лежит в файле <sys/mmap.h>. Эти значения я решил занести в define, чтобы код был чуть читаемее
|
||||
|
||||
У mmap есть и другая особенность - он мапает виртуальную память, а не физическую, поэтому то, что в оригинальной методичке мы на самом деле использовали видеобуфер, для нас не имеет реального значения. Я также использовал анонимный приватный маппинг, чтобы не портить жизнь другим процессам и не грузить ничего из файла, поэтому даже попортить жизнь другим процессам у меня не получится
|
||||
|
||||
<!--- Пока что я думаю эта инфа лишняя, может потом верну и раскомментирую
|
||||
### Чутка про префикс REX
|
||||
|
||||
|
||||
@ -4,8 +4,8 @@ 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 = 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 stdin")
|
||||
parser.add_argument('number', nargs='?')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
29
02-cpu-commnads/task2.asm
Normal file
29
02-cpu-commnads/task2.asm
Normal file
@ -0,0 +1,29 @@
|
||||
global _start
|
||||
|
||||
section .data
|
||||
|
||||
source: db 1, 2, 3, 4, 5, 6, 7, 8
|
||||
s_size equ $-source
|
||||
|
||||
section .bss
|
||||
|
||||
dest: resb 8
|
||||
|
||||
section .text
|
||||
|
||||
_start:
|
||||
|
||||
lea rsi, [source]
|
||||
lea rdi, [dest]
|
||||
mov rcx, s_size
|
||||
|
||||
.loop:
|
||||
|
||||
mov al, [rsi + rcx]
|
||||
mov [rdi + rcx], al
|
||||
|
||||
loop .loop
|
||||
|
||||
mov rax, 60
|
||||
mov rdi, 0
|
||||
syscall
|
||||
23
02-cpu-commnads/task3.asm
Normal file
23
02-cpu-commnads/task3.asm
Normal file
@ -0,0 +1,23 @@
|
||||
global _start
|
||||
|
||||
section .data
|
||||
source: db 1, 2, 3, 4, 5, 6, 7, 8
|
||||
s_size equ $-source
|
||||
|
||||
section .bss
|
||||
|
||||
dest: resb 8
|
||||
|
||||
section .text
|
||||
|
||||
_start:
|
||||
|
||||
mov rsi, source
|
||||
mov rdi, dest
|
||||
mov rcx, s_size
|
||||
|
||||
rep movsb
|
||||
|
||||
mov rax, 60
|
||||
mov rdi, 0
|
||||
syscall
|
||||
64
02-cpu-commnads/task5.asm
Normal file
64
02-cpu-commnads/task5.asm
Normal file
@ -0,0 +1,64 @@
|
||||
global _start
|
||||
|
||||
section .text
|
||||
|
||||
%define SRC 0xB8000
|
||||
%define DST 0xB9000
|
||||
%define ARR_SIZE 10
|
||||
|
||||
%define PROT_READ 0x1
|
||||
%define PROT_WRITE 0x2
|
||||
%define MAP_PRIVATE 0x02
|
||||
%define MAP_ANONYMOUS 0x20
|
||||
|
||||
_start:
|
||||
|
||||
; Из-за особенностей ядра линукса нужно сначала промапать произвольную память
|
||||
mov rax, 0x9 ; mmap
|
||||
mov rdi, SRC ; где
|
||||
mov rsi, ARR_SIZE ; сколько
|
||||
mov rdx, PROT_READ ; флаги чтения
|
||||
or rdx, PROT_WRITE ; флаги записи
|
||||
mov r10, MAP_PRIVATE ; приватная память
|
||||
or r10, MAP_ANONYMOUS ; не связана с файлом
|
||||
mov r9, 0 ; офсет должен быть 0
|
||||
syscall
|
||||
|
||||
|
||||
mov rsi, rax ; ставлю так, так как ядро линукса выделяет ближайшую область памяти, а не точно заказанную - проклятое выравнивание
|
||||
|
||||
; заполню чем-нибудь массив
|
||||
mov rcx, ARR_SIZE
|
||||
mov rbx, 0
|
||||
|
||||
.fill_src_loop:
|
||||
|
||||
mov [rsi + rbx], bl
|
||||
inc rbx
|
||||
|
||||
loop .fill_src_loop
|
||||
|
||||
push rsi
|
||||
|
||||
mov rax, 0x9 ; mmap
|
||||
mov rdi, DST ; где
|
||||
mov rsi, ARR_SIZE ; сколько
|
||||
mov rdx, PROT_WRITE ; флаги чтения
|
||||
; or rdx, PROT_WRITE ; флаги записи
|
||||
mov r10, MAP_PRIVATE ; приватная память
|
||||
or r10, MAP_ANONYMOUS ; не связана с файлом
|
||||
mov r9, 0 ; офсет должен быть 0
|
||||
syscall
|
||||
|
||||
mov rdi, rax
|
||||
; заполню чем-нибудь массив
|
||||
|
||||
mov rcx, ARR_SIZE ; сколько байт копируем
|
||||
|
||||
pop rsi
|
||||
rep movsb
|
||||
|
||||
mov rax, 60
|
||||
mov rdi, 0
|
||||
syscall
|
||||
|
||||
13
03-asm-bios/Makefile
Normal file
13
03-asm-bios/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
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,3 +2,13 @@
|
||||
|
||||
## Ассемблер и функции BIOS
|
||||
|
||||
В этой работе намного проще посмотреть непосредственно решения и почитать комментарии к коду, чем читать теоретическое приложение к работе. Если вам все же что-то не понятно - кидайте в issues
|
||||
|
||||
Впрочем зная, что основная масса народу не будет делать эту лабу так, как сделал ее я, сюда вряд ли кто-то заглянет)
|
||||
|
||||
### Касаемо Makefile
|
||||
|
||||
Для того чтобы не писать много команд для однотипной и монотонной сборки проекта, был написан простой Makefile. Однако работает он следующим образом: он принимает название цели сборки и ищет файл с именем цели и расширением .asm. Если не находит - не собирает цель.
|
||||
|
||||
Важно заметить, что он не умеет линковать другие файлы в ассемблер, потому что написан был не для этого. Он просто берет голый файл на NASM (обязательно) и выдает 64-битный ELF из этого единственного файла. Если вам необходимо что-то прилинковать к ассемблеру, то увы, придется собирать проект вручную или менять этот makefile
|
||||
|
||||
|
||||
81
03-asm-bios/task2.asm
Normal file
81
03-asm-bios/task2.asm
Normal file
@ -0,0 +1,81 @@
|
||||
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
|
||||
|
||||
219
03-asm-bios/task4.asm
Normal file
219
03-asm-bios/task4.asm
Normal file
@ -0,0 +1,219 @@
|
||||
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
|
||||
|
||||
@ -2,3 +2,12 @@
|
||||
|
||||
## Подпрограммы, программные прерывания и особые случаи
|
||||
|
||||
### АХТУНГ
|
||||
|
||||
**НАСТОЯТЕЛЬНО РЕКОМЕНДУЮ НЕ ЗАГРУЖАТЬ КОД ИЗ ЭТОЙ ЛАБЫ**. Дело в том, что Linux считает прерывания его личной заботой и поэтому как правило сам отвечает за их обработку. Для того, чтобы сделать прикол из лабы с подменой таблицы векоторов прерываний необходимо написать модуль ядра.
|
||||
|
||||
Для тех, кто в танке, это означает следующее: **ЕСЛИ Я ОШИБСЯ ХОТЬ ГДЕ-ТО (А Я ВЕРОЯТНЕЕ ВСЕГО ГДЕ-ТО ОШИБСЯ), ВАШЕЙ СИСТЕМЕ МОЖЕТ ПРИЙТИ ПИЗДА. И ОЧЕНЬ БЫСТРО**
|
||||
|
||||
## Теперь для смелых
|
||||
|
||||
Милицын препод лояльный, поэтому было принято решение не грохать в лоб системную таблицу, а расширить ее своими прерываниями. Делается это в основном при помощи `request_irq`, в который передается куча инфы о прерывании
|
||||
|
||||
69
06-subroutines-interruptions/fuck-system.c
Normal file
69
06-subroutines-interruptions/fuck-system.c
Normal file
@ -0,0 +1,69 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <asm/desc.h>
|
||||
|
||||
// Смотрим у производителя процессора номер прерывания
|
||||
// деления на ноль (я сижу на x86-64, а этот номер зависит от архитектуры процессора)
|
||||
#define ZERODIV_NR 0x00
|
||||
|
||||
extern void store_idt(struct desc_ptr *storage);
|
||||
|
||||
static void example_handler(void)
|
||||
{
|
||||
pr_warn("entered the handler");
|
||||
return;
|
||||
}
|
||||
|
||||
static int __init fuck_system(void)
|
||||
{
|
||||
pr_info("start to fuck system");
|
||||
|
||||
struct desc_ptr newidtreg;
|
||||
struct desc_ptr oldidtreg;
|
||||
gate_desc *oldidt, *newidt;
|
||||
|
||||
pr_info("nessesary variables created");
|
||||
// Выделяем память под 256 прерываний
|
||||
unsigned long new_page = __get_free_page(GFP_KERNEL);
|
||||
if (!new_page)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
pr_info("new page allocated");
|
||||
store_idt(&oldidtreg);
|
||||
pr_info("IDT register stored");
|
||||
newidtreg.address = new_page;
|
||||
newidtreg.size = oldidtreg.size;
|
||||
newidt = (gate_desc *)newidtreg.address;
|
||||
memcpy(newidt, oldidt, newidtreg.size);
|
||||
pr_info("IDT register saved in backup");
|
||||
|
||||
pack_gate(
|
||||
(newidt + ZERODIV_NR),
|
||||
GATE_INTERRUPT,
|
||||
(unsigned long) example_handler,
|
||||
0,
|
||||
0,
|
||||
__KERNEL_CS
|
||||
);
|
||||
pr_info("packed new gate");
|
||||
|
||||
load_idt(&newidtreg);
|
||||
pr_info("loaded new interrupts");
|
||||
int x = 1 / 0; // триггерим прерывание деления на ноль
|
||||
pr_info("return from interrupt");
|
||||
load_idt(&oldidtreg);
|
||||
free_page(new_page);
|
||||
pr_info("Unloaded everything");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit unfuck_system(void)
|
||||
{
|
||||
pr_info("start to unfuck system");
|
||||
}
|
||||
|
||||
module_init(fuck_system);
|
||||
module_exit(unfuck_system);
|
||||
MODULE_LICENSE("GPL");
|
||||
8
06-subroutines-interruptions/low-level-asm.asm
Normal file
8
06-subroutines-interruptions/low-level-asm.asm
Normal file
@ -0,0 +1,8 @@
|
||||
global store_idt
|
||||
|
||||
section .text
|
||||
|
||||
store_idt: ; rdi - указатель
|
||||
sidt [rdi]
|
||||
ret
|
||||
int3
|
||||
Reference in New Issue
Block a user