diff --git a/03-asm-bios/Makefile b/03-asm-bios/Makefile new file mode 100644 index 0000000..393f43d --- /dev/null +++ b/03-asm-bios/Makefile @@ -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)) diff --git a/03-asm-bios/README.md b/03-asm-bios/README.md index 76e4193..ac8cd73 100644 --- a/03-asm-bios/README.md +++ b/03-asm-bios/README.md @@ -2,3 +2,13 @@ ## Ассемблер и функции BIOS +В этой работе намного проще посмотреть непосредственно решения и почитать комментарии к коду, чем читать теоретическое приложение к работе. Если вам все же что-то не понятно - кидайте в issues + +Впрочем зная, что основная масса народу не будет делать эту лабу так, как сделал ее я, сюда вряд ли кто-то заглянет) + +### Касаемо Makefile + +Для того чтобы не писать много команд для однотипной и монотонной сборки проекта, был написан простой Makefile. Однако работает он следующим образом: он принимает название цели сборки и ищет файл с именем цели и расширением .asm. Если не находит - не собирает цель. + +Важно заметить, что он не умеет линковать другие файлы в ассемблер, потому что написан был не для этого. Он просто берет голый файл на NASM (обязательно) и выдает 64-битный ELF из этого единственного файла. Если вам необходимо что-то прилинковать к ассемблеру, то увы, придется собирать проект вручную или менять этот makefile + diff --git a/03-asm-bios/task2.asm b/03-asm-bios/task2.asm new file mode 100644 index 0000000..579373a --- /dev/null +++ b/03-asm-bios/task2.asm @@ -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 + diff --git a/03-asm-bios/task4.asm b/03-asm-bios/task4.asm new file mode 100644 index 0000000..37d281d --- /dev/null +++ b/03-asm-bios/task4.asm @@ -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 +