Начнем изучение языка ассемблера с рассмотрения простой программы, которая ничего не вычисляет и не обрабатывает, а всего лишь выводит на экран терминала строку.
text segment 'code' ;(1)Начало сегмента команд assume CS:text, DS:data ;(2)Сегментный регистр CS ;будет указывать на сегмент ;команд, а сегментный регистр DS – ;на сегмент данных begin: mov AX,data ;(3)Адрес сегмента данных сначала ;загрузим в AX, mov DS,AX ;(4)а затем перенесем из AX в DS mov AH,09h ;(5)Функция DOS 9h вывода на экран mov DX,offset mesg ;(6)Адрес выводимого сообщения ;должен быть в DX int 21h ;(7)Вызов DOS mov AH, 4Ch ;(8)Функция 4Ch завершения программы mov AL, 0 ;(9)Код 0 успешного завершения int 21h ;(10)Вызов DOS text ends ;(11)Конец сегмента команд data segment ;(12)Начало сегмента данных mesg db 'Работаем!$' ;(13)Выводимый текст data ends ;(14)Конец сегмента данных stk segment stack ;(15)Начало сегмента стека dw 128 dup(0) ;(16)Резервируем 128 слов для стека stk ends ;(17)Конец сегмента стека end begin ;(18)Конец текста программы ;с точкой входа
Следует заметить, что при вводе исходного текста программы с клавиатуры можно использовать как прописные, так и строчные буквы: транслятор воспринимает их одинаково.
Приведенная программа содержит 18 строк-предложений языка ассемблера. Первое предложение с помощью оператора segment открывает сегмент команд программы. Сегменту дается произвольное имя text. В конце предложения после точки с запятой располагается комментарий. Предложение языка ассемблера может состоять из четырех полей: имени, оператора, операндов и комментария, располагаемых в перечисленном порядке. Не все поля обязательны; так, в предложении 1 есть только имя, оператор и комментарий, а операнды отсутствуют; предложение 3 включает все четыре компонента: имя begin, оператор (команда процессора) mov, операнды этой команды AX и data и, наконец, после точки с запятой комментарий.
Любая программа должна обязательно состоять из сегментов – без сегментов программ не бывает. Обычно в программе задаются три сегмента: команд, данных и стека. В сегменте команд располагается собственно программа, т. е. описание (с помощью команд процессора) последовательности требуемых действий. В сегменте данных описываются данные, с которыми должна работать программа; в нашем примере это строка текста. В предложении 2 мы с помощью оператора assume сообщаем ассемблеру (ассемблером называется программа-транслятор, преобразующаяисходный текст программы в коды команд процессора), что сегментный регистр CS будет указывать на сегмент команд text, а сегментный регистр DS – на сегмент данных data. Сегментные регистры (а всего их в процессоре 4) играют очень важную роль. Когда программа загружается в память и становится известно, по каким адресам памяти она располагается, в сегментные регистры заносятся начальные адреса закрепленных за ними сегментов. В дальнейшем любые обращения к ячейкам программы осуществляются путем указания сегмента, в котором находится интересующая нас ячейка, а также номера того байта внутри сегмента, к которому мы хотим обратиться. Этот номер носит название относительного адреса или смещения. Транслятор должен знать заранее, через какие сегментные регистры будут адресоваться ячейки программы, и мы сообщаем ему об этом с помощью оператора assume (assume – предположим). При этом в регистр CS адрес начала сегмента будет загружен автоматически, а регистр DS нам придется инициализировать вручную. Обращение к стеку осуществляется особым образом, и ставить ему в соответствие сегментный регистр (конкретно – сегментный регистр SS) нет необходимости.
Первые два предложения программы служат для передачи служебной информации программе ассемблера. Ассемблер воспринимает и запоминает эту информацию и пользуется ею в своей дальнейшей работе. Однако в состав выполнимой программы, состоящей из машинных кодов, эти строки не попадут, так как процессору, выполняющему программу, они не нужны. Другими словами, операторы segment и assume не транслируются в машинные коды, а используются лишь самим ассемблером на этапе трансляции программы. Такие нетранслируемые операторы иногда называют псевдооператорами или директивами ассемблера в отличие от истинных операторов – команд языка.
Предложение 3, начинающееся с метки begin, является первой выполнимой строкой программы. Для того чтобы процессор знал, с какого предложения начать выполнять программу после ее загрузки в память, начальная метка программы указывается в качестве операнда самого последнего оператора программы end (предложение 18). Начиная от точки входа программа выполняется строка за строкой точно в том порядке, в каком эти строки написаны программистом.
В предложениях 3 и 4 выполняется инициализация сегментного регистра DS. Сначала значение имени text ( т. е. адрес сегмента text) загружается командой mov (move – переместить) в регистр общего назначения AX, а затем из регистра AX переносится в регистр DS. Такая двухступенчатая операция нужна потому, что процессор в силу некоторых особенностей своей архитектуры не может выполнить команду непосредственной загрузки адреса в сегментный регистр. Приходится пользоваться регистром AX в качестве "перевалочного пункта".
Предложения 5, 6 и 7 реализуют существо программы – вывод на экран строки текста. Делается это не непосредственно, а путем обращения к служебным программам операционной системы MS-DOS. Дело в том, что в составе команд процессора и, соответственно, операторов языка ассемблера нет команд вывода данных на экран (как и команд ввода с клавиатуры, записи в файл на диске и т. д.). Вывод даже одного символа на экран в действительности представляет собой довольно сложную операцию, для выполнения которой требуется длинная последовательность команд процессора. Конечно, эту последовательность команд можно было бы включить в нашу программу, однако гораздо проще обратиться за помощью к операционной системе. В состав DOS входит большое количество программ, осуществляющих стандартные и часто требуемые функции - вывод на экран и ввод с клавиатуры, запись в файл и чтение из файла и многие другие. Для того чтобы обратиться к DOS, надо загрузить в регистр общего назнеачения AH номер требуемой функции, в другие регистры – исходные данные для выполнения этой функции, после чего выполнить команду int 21h (interrupt – прерывание), которая передаст управление DOS. Вывод на экран строки текста можно осуществить с помощью различных функций DOS; мы воспользовались функцией 09h, которая требует, чтобы в регистре DX содержался адрес выводимой строки. В предложении 6 адрес строки mesg загружается в регистр DX, а в предложении 7 осуществляется вызов DOS.
В предложениях 5 и 7 указанные в тексте программы числа сопровождаются знаком h. Таким образом в языке ассемблера обозначаются шестнадцатеричные числа, в отличие от десятичных, которые никакого завершающего знака не требуют. В вычислительной технике чрезвычайно широко используется шестнадцатеричная система счисления, так как она позволяет в более наглядной форме описывать содержимое регистров или ячеек памяти. Опишем вкратце основы шестнадцатеричной система счисления.
Одна шестнадцатеричная цифра может принимать 16 различных значений, первые 10 из которых обозначаются обычными десятичными цифрами, а последние 6 – первыми буквами латинского алфавита от A до F. Шестнадцатеричное число очень просто перевести в двоичное. Для этого достаточно каждую цифру шестнадцатеричного числа представить в виде четырех двоичных цифр.
Обратное преобразование – из двоичной формы в шестнадцатеричную тоже не представляет труда. Надо разбить исходное двоичное число на группы по четыре бита и каждую такую группу представить в виде шестнадцатеричной цифры.
Вернемся к тексту программы. После того, как DOS выведет на экран текст, программа должна быть завершена. Для окончания работы программы DOS должна выполнить некоторые служебные действия, в том числе освободить занимаемую программой память. Для этого используется функция DOS с номером 4Ch. Эта функция предполагает, что в регистре AL находится код завершения нашей программы, который она передаст DOS. Если программа завершилась успешно, код завершения должен быть равен нулю, поэтому в предложении 9 мы загружаем 0 в регистр AL и вызываем DOS командой int 21h. Поскольку выполняемая часть программы закончилась нужно закрыть сегмент команд с помощью директивы ends (end segment – конец сегмента).
Вслед за сегментом команд описывается сегмент данных. Он, как и сегмент команд, начинается директивой segment, предваряемой произвольным именем нашего сегмента, и заканчивается директивой ends. У нас в качестве данных выступает строка текста. Текстовые строки вводятся в программу с помощью директивы ассемблера db (от define byte, определить байт) и заключается в апострофы или в кавычки. Для того чтобы в программе можно было обращаться к данным, поля данных, как правило, предваряются именами. В нашем случае таким именем является вполне произвольное обозначение mesg, с которого начинается предложение 13.
Выше, в предложении 6, мы через регистр DX передали DOS адрес начала выводимой на экран строки текста. Но как DOS определит, где эта строка закончилась? Некоторые функции DOS требуют указания в одном из регистров длины выводимой строки, однако функция 09h работает иначе. Она выводит текст до знака доллара ($), которым и завершается строка.
Сегмент стека, которому мы дали произвольное имя stk, начинается, как и остальные сегменты, директивой segment и заканчивается директивой ends. Стек представляет собой отдельный сегмент небольшого объема, в котором просто резервируется определенное количество пустых байтов. Для выделения в программе пустых байтов используется конструкция
db размер dup (заполнитель)
В нашем примере для стека выделено 256 байт, заполненных нулями.
Оператор segment, начинающий сегмент стека, имеет описатель stack. Указание этого обозначения приводит к тому, что при загрузке программы в память регистры процессора, используемые для работы со стеком, инициализируются системой должным образом. Конкретно, сегментный регистр стека Ssбудет настроен на начало сегмента стека, а указатель стека SP – на его конец (стек заполняется данными от конца к началу).
Последняя строка программы содержит директиву end, которая указывает на окончание программы. В качестве операнда этой директивы обычно указывается точка входа в программу, т. е. адрес первой выполнимой программной строки. В нашем случае это метка begin.
Процесс подготовки и отладки программы на языке ассемблера включает этапы подготовки файла с исходным текстом, его трансляции и компоновки и, наконец, отладки программы с помощью специальной программы интерактивного отладчика.
Подготовка исходного текста программы выполняется с помощью любого текстового редактора. Файл с исходным текстом должен иметь расширение .ASM. При выборе редактора для подготовки исходного текста программы следует иметь в виду, что многие текстовые процессоры (например, Microsoft Word) добавляют в выходной файл служебную информацию о формате и т. п. Поэтому следует использовать редактор, который выводит в выходной файл "чистый текст", без каких-либо управляющих символов. Можно воспользоваться простым редактором, например "Блокнот".
Трансляция исходного текста программы состоит в преобразовании предложений исходного языка в коды машинных команд и выполняется с помощью транслятора с языка ассемблера (т. е. с помощью программы ассемблера). Для этой цели можно использовать транслятор TASM.EXE корпорации Borland. В результате трансляции образуется объектный файл с расширением .OBJ.
Компоновка объектного файла выполняется с помощью программы компоновщика (редактора связей). Эта программа получила такое название потому, что ее основное назначение – подсоединение к файлу с основной программой файлов с подпрограммами и настройка связей между ними. Однако компоновать необходимо даже простейшие программы, не содержащие подпрограмм. Дело в том, что у компоновщика есть и вторая функция – изменение формата объектного файла и преобразование его в выполнимый файл, который может быть загружен в оперативную память и выполнен.Файл с программой компоновщика корпорации Borland имеет имя TLINK.EXE. В результате компоновки образуется загрузочный, или выполнимый, файл с расширением .EXE.
Для автоматизации процесса создания программы можно создать пакетный файл, содержащий требуемые команды, и запускаемый на выполнение в режиме командной строки. Примерный сценарий работы с таким файлом представлен на следующем рисунке.
Программное обеспечение, необходимое для работы с ассемблером, можно скачать здесь TASM.