Ассемблеры

Ассемблеры

Компьютеры первого поколения были ламповыми, но для нас важно другое – их набор машинных команд был в пределах трёх десятков (в МЭСМ – была реализована всего 21 машинная команда), да и памяти тогда было мало – одна две сотни машинных слов. Поэтому программисту было ещё достаточно просто писать программу в машинных кодах.

Напомню, что машинная команда обычно содержит в первой байте код операции (что делает команда), а в оставшейся части – адреса данных, адрес перехода или регистры с операндами этой команды. Соответственно, бывают одноадресные, двухадресные и трёхадресные машины. Это крупными штрихами, поскольку разных компьютерных архитектур очень много и там бывают разные заморочки, часто присутствуют команды разной длины и с разным числом операндов.

Очень быстро число машинных команд у процессоров увеличивалось (у некоторых современных микропроцессоров их уже по несколько тысяч), объём памяти тоже рос и первая естественная мысль сделать свою работу более комфортной – это заменить двоичный, восьмеричный или шестнадцатеричный код операции машинной команды на содержательное символьное (мнемоническое) название: например, назвать операцию сложения ADD, а пересылку – MOV. Так команды легче запоминаются, и повышается читаемость программы. В первых отечественных машинах часто использовали мнемоническое название команды на кириллице, например СЛЖ – для сложить и ПЕР – для переслать. Но впоследствии все перешли на латинскую мнемонику.

Следующий шаг – введение мнемонических имён для участков памяти – теперь мы говорим, что это имена переменных. Довольно долго память в языках программирования под переменные распределялась статически, т. е. во время написания программы. Мы говорим о динамическом распределении памяти, если оно происходит во время исполнения программы, но это случилось ещё не скоро.

Последний элемент, который нужно было обрабатывать ассемблерам – это метки. Командам переходов (условным и безусловным), а также вызовам подпрограмм требовались адреса, которые было гораздо удобнее также задавать символическими именами. В качестве рудимента тех времён цифровые метки остались в Фортране и Бейсике, хотя потом и в них тоже разрешили символьные метки.

Итак, появились программы, которые стали называть ассемблеры, преобразующие исходный текст программы в объектный код, который можно было загружать в компьютер для исполнения (заморочки с компоновкой объектных модулей и самой загрузкой мы здесь опустим). Процесс работы ассемблера называется ассемблированием. Отмечу, что каждый процессор или микроконтроллер, имеющий собственный уникальный набор команд, имеет как минимум один Ассемблер. (С заглавной буквы я пишу Ассемблер как название языка, а со строчной, когда говорю о программе, выполняющей ассемблирование.) Если на конкретном процессоре базируется несколько систем программного обеспечения, например Linux и Windows, то каждая из них также имеет один или несколько ассемблеров (обычно от разных компаний).

Теперь рассмотрим вопрос, какими бывают ассемблеры. Самый простой ассемблер может за час написать абсолютно каждый программист. Я предпочитал делать это на Бейсике, потому что сразу же происходила и отладка. Итак, первая характеристика – число просмотров исходного текста программы. Каждый такой просмотр текста от начала до конца называется проходом. Самый простой ассемблер однопроходный – он пробегает по всему текстовому файлу один раз. При этом есть существенное ограничение – метки должны располагаться в программе так, чтобы в ней не было ссылок на предшествующий участок программы, ссылок назад – все ссылки только вперёд и, встретив команду перехода, ассемблер должен знать, какой адрес памяти в неё подставить.

Ассемблер, разумеется, собирает все встретившиеся метки в особой таблице, в которую записывается каждая новая метка и адрес памяти, где она расположена. Если в команде перехода встретилась метка, которой нет в таблице переходов, то это синтаксическая ошибка. Таблицу меток потом можно было распечатать, чтобы упростить отладку программы. Позже эту таблицу стали передавать отладчику, и тогда он при работе в пошаговом режиме выводил имена переменных, которые использовались в текущей команде.

Символьные метки позволили менять куски программы местами. При записи программы в машинных кодах весь код был привязан к абсолютным, т. е. физическим, адресам памяти, и чтобы часть кода переместить нужно было вручную исправлять эти адреса.

Чтобы разрешить ставить переходы в любом порядке (вперёд и назад), потребовались двухпроходные ассемблеры – на первом проходе создаётся таблица меток, а на втором – подставляются ранее недоступные адреса и генерируется объектный код. Схема с проходами используется и в компиляторах, только там проходов много больше – обычно до восьми.

Разумеется, нет предела совершенству – существуют и многопроходные ассемблеры, но для этого сам язык снабжают различными дополнениями, типа макросов и псевдокоманд. О них поговорим позже. Сейчас ассемблеры применяются чаще всего в следующих случаях:
1. при программировании микроконтроллеров (МК). МК – это такой крохотный компьютер на кристалле – у него есть арифметическое устройство, набор регистров, встроенная постоянная и оперативная память, таймер, могут быть различные дискретные и аналоговые интерфейсы. В мире вокруг нас работают многие миллиарды микроконтроллеров. В хорошем автомобиле их пара сотен штук. Для экономии у МК ограниченный объём памяти – это важно и для массового производства таких микросхем – проще схема, дешевле производство. Поэтому часто в МК помещается только программа, например объёмом в 2 или 4 Кбайта, обеспечивающая его функционирование в устройстве, для которого этот микроконтроллер предназначен. Поэтому часто программы для МК разрабатывают на других машинах, которые называют инструментальными, а сам МК в этом случае именуется целевой машиной. Ассемблер на инструментальной машине должен быть специальным – он должен генерировать объектный код для целевой машины, поэтому их называют кросс-ассемблерами. Аналогично существуют и кросс-компиляторы, часто их пишут для языка Си. Как отлаживают объектный код для целевой машины – это отдельная песня. Соответственно пишется система кросс-отладки. Мне довелось разработать такую систему для до сих пор очень популярного восьмиразрядного микроконтроллера Intel 8051, а также для машины М-7000.

2. Ассемблеры нужны, когда у вас есть то, что называют голым металлом, т.е. когда разработан совершенно новый микропроцессор, сопроцессор или МК. Здесь, как правило, сначала пишется кросс-ассемблер, а затем и другое кросс-ПО.
3. Системным программистам иногда требуется сделать заплатки в других программах, где нужно напрямую получить доступ к аппаратуре, либо повысить скорость счёта.

4. И, конечно же, системы реального времени, которые, как известно, делятся на системы жёсткого и мягкого реального времени. В системах жёсткого реального времени (СЖРВ) задача не может завершиться позже заданного лимита времени, например ей выделено всего 10 миллисекунд. Вот и приходится писать код на Ассемблере, да ещё и учитывать время исполнения каждой машинной команды. Программист может расписать выполнение каждого шага программы разными способами, но в СЖРВ ищется способ максимально эффективный по времени.

Процесс обратный ассемблированию называется дизассемблированием – это когда с помощью специальной программы, дизассемблера, мы из объектного кода получаем программу, которая очень похожа на ассемблерную. Поскольку при ассемблировании у нас в объектной программе теряются метки, то дизассемблер обычно генерирует метки сам. Дизассемблеры используются в отладчиках и при так называемой обратной инженерии (это когда дисассемблировав машинный код нужно понять как работает то или иное устройство, например, чтобы банально его отремонтировать). Наконец, знание ассемблера позволяет детально разобраться в архитектуре конкретного процессора. Часто молодой программист, выучив язык высокого уровня (ЯВУ), довольно слабо представляет, а как это внизу всё устроено. Для некоторых направлений ИТ, например для информационной безопасности, полагаю, разбираться в работе железа совершенно необходимо, ну как и в сетевых протоколах и множестве других вещей.

Москва, май 2023 г.

Автор благодарен Владимиру Биллигу, инициатору написания этого текста и сделавшему по нему ряд полезных замечаний.

Об авторе: Директор Виртуального Компьютерного Музея
Помещена в музей с разрешения автора 15 июня 2023