mov Программирование на Ассемблере без знаний Ассемблера, habr
Пролог
Решил поделиться своими мыслями и кратким двухдневным опытом написания (собирания по частям) программы на Ассемблере без чтения учебников, больших статей и в целом без опыта программирования на этом языке. На одном из форумов я набрёл на задачу вывода десятичного числа в консоль. Если на языке C или PHP эта операция совершенно элементарна, то на Ассемблере всё не так просто, как может показаться на первый взгляд. Для решения задачи я выбрал nasm (правда, выбора и не было), немножко поигравшись предварительно с вставками nasm (синтаксис AT&T) в код C (ссылка на форум с моими опытами в конце статьи).
Философское отступление
Остановись, дорогой читатель! И прежде чем читать дальше, задай себе вопрос: возможно ли начать ковать без обучения кузнечному делу?!
Я оставлю этот вопрос без ответа. Только скажу, что в нём нет ни капли иронии, издёвки, намёка на назидательность и т.д. Это вопрос без какого-либо дополнительного подтекста.
Поиски
Информации по Ассемблеру в Интернете очень много и заблудиться в разных видах Ассемблера (для различных систем) крайне просто. Я не единственный задавался вопросом в поиске «how to print a number in asm». Ответы на разных диалектах языка относительно легко можно найти на Stack Overflow, однако это совершенно не означает, что будет легко запустить найденный код на своей машине. Велика вероятность того, что что-нибудь не сойдётся. Научиться отличать синтаксис AT&T и intel можно за несколько минут, а вот с узнаванием tasm, fasm, masm, nasm - несколько сложнее. Единственное, что можно предположить и (почти) не прогадать: базовые инструкции во всех Ассемблерах имеют (почти) одинаковые мнемоники.
Ассоциации и первые впечатления
Если хотите, чтобы ваши соседи переехали, убедите себя в том, что играть на скрипке легко...
Самая распространённая операция - сдвиг одного регистра в другой - лично у меня ассоциируется с перемещением фигур на шахматной доске.
В этом смысле язык кажется лёгким, даже очень лёгким или лучше сказать, - удобным.
Хотя, как известно, и правила шахмат нельзя назвать слишком сложными, но играть бывает крайне трудно.
Итак, задача:
Собрать программу, печатающую на стандартный вывод (stdout) число.
Реализация
Принцип (его надо мысленно прокрутить в голове и понять, тогда можно считать, что мы не списываем):
Число делится на 10.
В регистр edx заносится остаток, в eax остальная часть.
Остаток в цикле записывается в буфер (stroka), начиная с конца, используется декремент, затем выводится
Дополнительно: при использовании инкремента можно напечатать строку в обратном порядке.
Код nasm (собран на архитектуре 64b, с моими и чужими комментариями)
section .data
stroka db 4;буфер для вывода
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
push rbp ;Работа со стеком
mov rbp, rsp
mov eax, 311 ; 311 - делимое
mov edi, 3 ; Переменная цикла.
loop: ; Начало цикла
dec edi
; Декремент, чтобы записать все значения остатка в stroka
mov ebx, 10 ; Делитель. Запишем в цикл, чтобы вернуть ему значение на новой итерации.
; Можно использовать для делителя только часть регистра ebx, а именно bx
xor edx, edx ; Обнулим остаток
div ebx ; Делим
add edx, 30h ; Добавим в остаток 0, равносильно add edx, '0'
mov [stroka + edi], dl ; Пишем в строку остаток в обратном порядке.
cmp edi, 0 ; Выходим
jne loop ; Возврат в цикл
; Собрали строку и далее выводим
mov ecx, stroka ; Кладём
mov edx, 4 ; Длина выводимой строки 4 (видимо, в байтах)
mov ebx, 1 ; file descriptor (stdout)
mov eax, 4 ; system call number (sys_write)
int 0x80 ; call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
Команда для сборки, линковки и выполнения
nasm -f elf64 print_num.asm -o print_num.o; ld -o print_num print_num.o; ./print_num
*** Для остатка можно использовать только часть регистра edx, а именно dx. Это же верно и для делителя - можно использовать bx, так как число 10 умещается в 2 байта (16 бит).
*** Для счётчика цикла, наверное, лучше использовать регистр ecx (как более канонический вариант), а остановку цикла осуществлять, когда в ax будет 0.
Оставлю для таких же как я дополнительную задачку и подсказку.
Задача: изменить программу так, чтобы число печаталось наоборот.
Подсказка
Hidden text
Менять нужно mov edi, 3; dec edi; cmp edi, 0
Дополнительно: как сделать из нашего "hello world" что-нибудь полезное?
Если немножко почитать про стек, а также регистры rsp, rbp и поиграть со смещением, то можно быстро переделать программу в генератор псевдослучайных чисел:
mov rbp, rsp
mov eax, [rbp +17] ; Забираем из стека по адресу 17 значение
add eax, [rbp +18] ; И ещё сместимся на 1 (видимо,1 байт) и добавим в eax
Далее самописный псевдогенератор
section .data
stroka db 4 ;буфер для вывод
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
push rbp ;Работа со стеком
mov rbp, rsp
mov eax, [rbp +17] ; Забираем из стека по адресу 17 значение
add eax, [rbp +18] ; И ещё сместимся 1 одно значение, добавим в eax
mov edi, 3 ; Переменная цикла.
loop: ; Начало цикла
dec edi
;Декремент, чтобы записать все значения остатка в stroka
mov ebx, 10 ; Делитель. Запишем в цикл, чтобы вернуть ему значение на новой итерации
xor edx, edx ; Обнулим остаток
div ebx ; Делим
add edx, 30h ; Добавим в остаток 0, равносильно add edx, '0'
mov [stroka + edi], dl ; Пишем в строку остаток в обратном порядке.
cmp edi, 0 ; Выходим
jne loop ; Возврат в цикл
; Собрали строку и далее выводим
mov ecx, stroka ; Кладём
mov edx, 4 ; Длина выводимой строки 4 (видимо, в байтах)
mov ebx, 1 ; file descriptor (stdout)
mov eax, 4 ; system call number (sys_write)
int 0x80 ; call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
Источники:
1) https://acm.mipt.ru/twiki/bin/view/Asm/PrintIntFunction
2) http://av-assembler.ru/asm/afd/asm-cpu-registers.htm
4) https://codetown.ru/assembler/delenie-umnozhenie/ (про умножение, деление и регистры)
Темы с моими опытами:
https://wasm.in/threads/vyvod-desjatichnogo-chisla.34675/
https://wasm.in/threads/assmembler-vstavkami.34672/
Интересное
Бонус: ссылка на интересный репозитарий, в нём ОС с линковщиком (из трёх файлов: загрзчик, линковщик, ядро,):
https://github.com/cirosantilli/x86-bare-metal-examples/blob/master/c_hello_world/run
Update:
Добавлю решённый мной пример поиска и замены значения в массиве (для таких же как я, на примере можно понять ветвления прогрммы (пропуски, переходы)): https://gitflic.ru/project/dcc0/mix-c-89-php/blob?file=search_and_change_in_array.asm
Данной заметкой цикл статей для хабра завершаю.
Всем спасибо. C наступающим 2023 годом!
Новогодняя микросистема для запуска в qemu:
https://gitflic.ru/project/dcc0/mix-c-89-php/blob?file=cat_on_boot.asm