Разработка и отладка программ на языке Ассемблера

Изучение языка ассемблера целесообразнее всего начать с разработки простой программы, например, такой:

text    segment            ;(1)Начало сегмента команд 
assume  CS:text,DS:data    ;(2)Сегментный регистр CS будет указывать на сегмент 
			   ;команд, а сегментный регистр DS - на сегмент данных 
begin: 	mov AX,data        ;(3)Адрес сегмента данных сначала загрузим в АХ, 
	mov DS,AX          ;(4)а затем перенесем из АХ в 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)Начало сегмента стека 
        db 256 dup (0)     ;(16)Резервируем 256 байт для стека 
stk     ends               ;(17)Конец сегмента стека 
        end begin          ;(18)Конец текста программы с точкой входа 

Данная программа ничего не вычисляет и не обрабатывает, а всего лишь выводит на экран терминала строку с фразой "Начинаем!".

Чтобы создать такую программу и увидеть, как она работает, на компьютере должен быть установлен набор программ Turbo Assembler, позволяющий создавать из представленного исходного текста программы исполнимый файл, представляющий собой готовую программу, которую можно запускать на исполнение. Обычно такой набор программ расположен в каталоге TASM. Если на вашем компьютере такого набора нет его можно скачать здесь. Данный файл представляет собой архив, содержащий каталог, в котором находится требуемый набор программ. Разверните этот каталог на вашем компьютере в удобном для вас месте. Используя Блокнот создайте в этом каталоге файл, содержащий представленную программу и сохраните его с прозвольным именем и расширением asm.

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

Содержимое строк достаточно вводить до точки с запятой, означающих начало комментария.

Прежде чем продолжить работу с программой внимательно прочтите следующее краткое описание того, как работает данная программа.

Программа содержит 18 строк-предложений языка ассемблера. Первое предложение с помощью оператора segment открывает сегмент команд программы. Сегменту дается произвольное имя text. В конце предложения после точки с запятой располагается комментарий. Предложение языка ассемблера может состоять из четырех полей: имени, оператора, операндов и комментария, располагаемых в перечисленном порядке. Не все поля обязательны; так, в предложении 1 есть только имя, оператор и комментарий, а операнды отсутствуют; предложение 3 включает все 4 компонента: имя begin, оператор (команда процессора) mov, операнды этой команды АХ и data и, наконец, после точки с запятой комментарий; в предложении 4 (и многих последующих) отсутствует имя.

Любая программа должна обязательно состоять из сегментов - без сегментов программ не бывает. Обычно в программе задаются три сегмента: команд, данных и стека. В сегменте команд располагается собственно программа, т. е. описание (с помощью команд процессора) последовательности требуемых действий. В сегменте данных описываются данные, с которыми должна работать программа; в нашем примере это строка текста. Назначение сегмента стека будет описано ниже.

В предложении 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 - переместить) в регистр общего назначения процессора АХ, а затем из регистра' АХ переносится в регистр DS. Такая двухступенчатая операция нужна потому, что процессор в силу некоторых особенностей своей архитектуры не может выполнить команду непосредственной загрузки адреса в сегментный регистр. Приходится пользоваться регистром АХ в качестве "перевалочного пункта".

Предложения 5, 6 и 7 реализуют существо программы - вывод на экран строки текста. Делается это не непосредственно, а путем обращения к служебным программам операционной системы MS-DOS. Дело в том, что в составе команд процессора и, соответственно, операторов языка ассемблера нет команд вывода данных на экран (как и команд ввода с клавиатуры, записи в файл на диске и т. д.). Вывод даже одного символа на экран в действительности представляет собой довольно сложную операцию, для выполнения которой требуется длинная последовательность команд процессора. Конечно, эту последовательность команд можно было бы включить в нашу программу, однако гораздо проще обратиться за помощью к операционной системе. В состав DOS входит большое количество программ, осуществляющих стандартные и часто требуемые функции - вывод на экран и ввод с клавиатуры, запись в файл и чтение из файла, чтение или установка текущего времени, выделение или освобождение памяти и многие другие.

Для того чтобы обратиться к DOS, надо загрузить в регистр общего назначения АН номер требуемой функции, в другие регистры - исходные данные для выполнения этой функции, после чего выполнить команду int 2lh, (int - от interrupt - прерывание), которая передаст управление DOS. Вывод на экран строки текста можно осуществить с помощью различных функций DOS; мы воспользовались функцией 09h, которая требует, чтобы в регистре DX содержался адрес выводимой строки. В предложении 6 адрес строки mesg загружается в регистр DX, а в предложении 7 осуществляется вызов DOS.

В предложениях 5 и 7 указанные в тексте программы числа сопровождаются знаком h. Таким образом в языке ассемблера обозначаются шестнадцатеричные (далее - 16-ричные) числа, в отличие от десятичных, которые никакого завершающего знака не требуют.

После окончания работы программы DOS должна выполнить некоторые служебные действия. Надо освободить занимаемую нашей программой память, чтобы туда можно было загрузить следующую программу. Надо вызвать компонент операционной системы, который выведет на экран запрос DOS (как правило, в виде символа >, предваряемого именем текущего каталога) и будет ждать следующей команды оператора. Все эти действия выполняет функция DOS с номером 4Ch. Эта функция предполагает, что в регистре AL находится код завершения нашей программы, который она передаст DOS. Если программа завершилась успешно, код завершения должен быть равен нулю, поэтому в предложении 9 мы загружаем 0 в регистр AL и вызываем DOS уже знакомой нам командой int 21h. Поскольку выполняемая часть программы на этом закончилась, можно (и нужно) закрыть сегмент команд, что выполняется с помощью директивы ends (от end segment, конец сегмента), перед которой для наглядности обычно указывается имя закрываемого сегмента, в данном случае сегмента text.

Вслед за сегментом команд описывается сегмент данных. Он, как и сегмент команд, начинается директивой segment, предваряемой произвольным именем нашего сегмента, и заканчивается директивой ends. У нас в качестве данных выступает строка текста. Текстовые строки вводятся в программу с помощью директивы ассемблера db (от define byte, определить байт) и заключаются в апострофы или в кавычки. Для того чтобы в программе можно было обращаться к данным, поля данных, как правило, предваряются именами. В нашем случае таким именем является вполне произвольное обозначение mesg (от message, сообщение), с которого начинается предложение 13.

Выше, в предложении 6, мы через регистр DX передали DOS адрес начала выводимой на экран строки текста. Но как DOS определит, где эта строка закончилась? Хотя нам конец строки в программе отчетливо виден, однако в машинных кодах, из которых состоит выполнимая программа, он никак не отмечен, и DOS, выведя ча экран последний символ строки - восклицательный знак, продолжит вывод байтов памяти, расположенных за фразой "Начинаем!". Поэтому DOS следует передать информацию о том, где кончается строка текста. Некоторые функции DOS требуют указания в одном из регистров длины выводимой строки, однако функция 09h работает иначе. Она выводит текст до знака доллара ($), которым мы и завершили нашу фразу.

Сегмент стека, которому мы дали произвольное имя stk, начинается, как и остальные сегменты, оператором segment и заканчивается оператором ends. Стек представляет собой отдельный сегмент обычно небольшого объема, в котором просто резервируется определенное количество пустых байтов. Для выделения в программе группы байтов используется конструкция

db размер dup (заполнитель)

В нашем примере для стека выделено 256 байт, заполненных нулями.

Оператор segment, начинающий сегмент стека, имеет описатель stack. Указание этого обозначения приводит к тому, что при загрузке программы в память регистры процессора, используемые для работы со стеком, инициализируются системой должным образом. Конкретно, сегментный регистр стека SS будет настроен на начало сегмента стека, а указатель стека SP - на его конец (стек заполняется данными от конца к началу).

Последняя строка программы содержит директиву end, которая говорит программе ассемблера, что закончился вообще весь текст программы и больше ничего транслировать не нужно. В качестве операнда этой директивы, как уже отмечалось, обычно указывается точка входа в программу, т. е. адрес первой выполнимой программной строки. В нашем случае это метка begin.

Hosted by uCoz