Основы ассемблера
Я буду исходить из того, что ты уже знаком с программированием — знаешь какой-нибудь из языков высокого уровня (С, PHP, Java, JavaScript и тому подобные), тебе доводилось в них работать с шестнадцатеричными числами, плюс ты умеешь пользоваться командной строкой под Windows, Linux или macOS.
Если наборы инструкций у процессоров разные, то на каком учить ассемблер лучше всего?
Знаешь, что такое 8088? Это дедушка всех компьютерных процессоров! Причем живой дедушка. Я бы даже сказал — бессмертный и бессменный. Если с твоего процессора, будь то Ryzen, Core i9 или еще какой-то, отколупать все примочки, налепленные туда под влиянием технологического прогресса, то останется старый добрый 8088.
SGX-анклавы, MMX, 512-битные SIMD-регистры и другие новшества приходят и уходят. Но дедушка 8088 остается неизменным. Подружись сначала с ним. После этого ты легко разберешься с любой примочкой своего процессора.
Больше того, когда ты начинаешь с начала — то есть сперва выучиваешь классический набор инструкций 8088 и только потом постепенно знакомишься с современными фичами, — ты в какой-то миг начинаешь видеть нестандартные способы применения этих самых фич.
Что и как процессор делает после запуска программы
После того как ты запустил софтину и ОС загрузила ее в оперативную память, процессор нацеливается на первый байт твоей программы. Вычленяет оттуда инструкцию и выполняет ее, а выполнив, переходит к следующей. И так до конца программы.
Некоторые инструкции занимают один байт памяти, другие два, три или больше. Они выглядят как-то так:
Вернее, даже так:
Хотя погоди! Только машина может понять такое. Поэтому много лет назад программисты придумали более гуманный способ общения с компьютером: создали ассемблер.
Благодаря ассемблеру ты теперь вместо того, чтобы танцевать с бубном вокруг шестнадцатеричных чисел, можешь те же самые инструкции писать в мнемонике:
Согласись, такое читать куда легче. Хотя, с другой стороны, если ты видишь ассемблерный код впервые, такая мнемоника для тебя, скорее всего, тоже непонятна. Но мы сейчас это исправим.
Регистры процессора: зачем они нужны, как ими пользоваться
Что делает инструкция mov ? Присваивает число, которое указано справа, переменной, которая указана слева.
Переменная — это либо один из регистров процессора, либо ячейка в оперативной памяти. С регистрами процессор работает быстрее, чем с памятью, потому что регистры расположены у него внутри. Но регистров у процессора мало, так что в любом случае что-то приходится хранить в памяти.
Когда программируешь на ассемблере, ты сам решаешь, какие переменные хранить в памяти, а какие в регистрах. В языках высокого уровня эту задачу выполняет компилятор.
У процессора 8088 регистры 16-битные, их восемь штук (в скобках указаны типичные способы применения регистра):
- AX — общего назначения (аккумулятор);
- BX — общего назначения (адрес);
- CX — общего назначения (счетчик);
- DX — общего назначения (расширяет AX до 32 бит);
- SI — общего назначения (адрес источника);
- DI — общего назначения (адрес приемника);
- BP — указатель базы (обычно адресует переменные, хранимые на стеке);
- SP — указатель стека.
Несмотря на то что у каждого регистра есть типичный способ применения, ты можешь использовать их как заблагорассудится. Четыре первых регистра — AX , BX , CX и DX — при желании можно использовать не полностью, а половинками по 8 бит (старшая H и младшая L ): AH , BH , CH , DH и AL , BL , CL , DL . Например, если запишешь в AX число 0x77AA ( mov ax , 0x77AA ), то в AH попадет 0x77 , в AL — 0xAA .
С теорией пока закончили. Давай теперь подготовим рабочее место и напишем программу «Hello, world!», чтобы понять, как эта теория работает вживую.
Подготовка рабочего места
- Скачай компилятор NASM с www.nasm.us. Обрати внимание, он работает на всех современных ОС: Windows 10, Linux, macOS. Распакуй NASM в какую-нибудь папку. Чем ближе папка к корню, тем удобней. У меня это c : nasm (я работаю в Windows). Если у тебя Linux или macOS, можешь создать папку nasm в своей домашней директории.
- Тебе надо как-то редактировать исходный код. Ты можешь пользоваться любым текстовым редактором, который тебе по душе: Emacs, Vim, Notepad, Notepad++ — сойдет любой. Лично мне нравится редактор, встроенный в Far Manager, с плагином Colorer.
- Чтобы в современных ОС запускать программы, написанные для 8088, и проверять, как они работают, тебе понадобится DOSBox или VirtualBox.
Разница между таймером и счетчиком
Точки, которые отличают таймер от счетчика, следующие:
таймер | счетчик |
---|---|
Регистр увеличивается для каждого машинного цикла. | Регистр увеличивается с учетом перехода от 1 до 0 в соответствии с внешним входным выводом (T0, T1). |
Максимальная скорость счета составляет 1/12 от частоты генератора. | Максимальная скорость счета составляет 1/24 от частоты генератора. |
Таймер использует частоту внутренних часов и генерирует задержку. | Счетчик использует внешний сигнал для подсчета импульсов. |
Вызов процедуры в ассемблере.
Вызов процедуры в ассемблере осуществляется командой call (call — вызов). После вызова, процедура исполняет свой код и программа возвращается в точку возврата (выполняет дальнейший код, следующий за вызовом — командой call).
Процедура может иметь свои аргументы — данные, которые ей предоставляются для обработки и результат (входные и выходные данные — in/out).
Процедура в ассемблере обозначается названием и начинается оператором proc (procedure — процедура). Заканчивается процедур оператором end proc (end procedure — конец процедуры) и командой ret (return — возврат). В главной функции (main в нашем случае) команда ret после выхода в систему DOS не обязательна.
имя_процедуры proc
…
тело процедуры (код)
…
ret
endp
Для удобной читаемости кода можно указывать имя (название) соответствующей процедуры также перед оператором endp.
имя_процедуры proc
…
тело процедуры (код)
…
ret
имя_процедуры endp
Необходимо понимать, что фактически имя процедуры в ассемблере является указателем на процедуру. Это позволяет организовать прямой и косвенный вызовы процедуры (адрес процедуры можно поместить в регистр, записать в блок памяти).