
10. Язык Ассемблера
10.1. Что такое Ассемблер.
Ассемблер — программа для перевода мнемоники команд в машинный код. Дословно "Assembler" переводится как "сборщик", т.е. Ассемблер собирает коды. Язык, на котором записываются инструкции для программы Ассемблер, называется языком Ассемблера. Поэтому не совсем корректно говорить: "программа на Ассемблере". Правильно: "программа на языке Ассемблера".
С мини-Ассеблерами нам пришлось работать, когда мы создавали программы в отладчиках debug и TD. Напомним, какие при этом возникали основные проблемы:
-
Нам приходилось самостоятельно распределять память для данных и кода. При этом в памяти возникали "дыры", т.к. мы опасались перекрытия данных и кода при внесении изменений в код программы;
-
Приходилось корректировать команды перехода "вперед", т.к. мы не знали числового значения метки в момент ввода команды;
-
Весьма трудоемким оказывался процесс внесения изменений в программу, особенно когда в "середину" программы приходилось добавлять новые команды.
Теперь мы возложим все эти задачи на программы, которые входят в состав пакетов для разработки программ на языке Ассемблера. Эти пакеты включают не только Ассемблеры, но и компновщики, отладчики и прочие утилиты.
Таких пакетов в настоящее время существует несколько. Небольшой обзор с указанием ресурсов Интернет можно найти в [Программист, № 1, 2002]. Наибольшей популярностью пользуются MASM (Macro Assembler) фирмы Microsoft и TASM (Turbo Assembler) фирмы Borland. Мы будем составлять программы так, чтобы они проходили трансляцию и компоновку средствами обоих пакетов. Но основное внимание будет уделено TASM.
В чем заключается основное отличие Ассемблера от мини-Ассемблера, встроенного в отладчик? Ассемблер обрабатывает текстовый файл, содержащий программу. Эта программа содержит не только мнемоники команд, но и директивы, т.е. указания Ассемблеру для генерации кода и распределения данных.
10.2. Программа на языке Ассемблера.
В любом текстовом редакторе наберем текст программы, которая выводит на экран строку "Hello!". Если мы набираем эту программу в Microsoft Word, то ее нужно сохранять как текст, а не как документ.
файл first.asm
.MODEL small ; Малая модель памяти
.STACK 100h ; Для стека выделяется 100h байтов
.DATA ; Начало секции (сегмента) данных
msg DB "Hello!",0Dh,0Ah,'$' ; Строка для вывода
.CODE ; Начало секции (сегмента) кода
start: mov ax,@data ; Загрузить регистр DS адресом DGROUP,
mov ds,ax ; в который входят сегменты данных и стека.
mov ah,9h ; Вывод на экран
mov dx, OFFSET msg ; строки msg
int 21h ; с помощью 9-й функции 21-го прерывания.
mov ax,4C00h ; Завершение работы программы
int 21h ; с помощью функции 4Ch.
END start ; Завершение текста программы
Прежде всего обратим внимание, что в программе имеются директивы, т.е. указания для Ассемблера, которые он будет использовать на этапе компиляции программы. Директивы рекомендуется набирать прописными буквами, чтобы читателю было легче отличить их от команд, которые будут исполняться при запуске программы на выполнение.
Директива .MODEL small указывает, что программная секция данных (она начинается с директивы .DATA) и программная секция кода (начинается с директивы .CODE) будут занимать не более одного сегмента ОЗУ. Напомним, что размер сегмента — 64 Кбайт. Для стекового сегмента явно указан его размер: 256 байт.
В программе имеются метки: в секции данных метка msg, отделяемая пробелом от уже знакомой нам директивы определения данных DB. В секции кода первая команда снабжена меткой start. Метка отделена от команды двоеточием. Метка могла располагаться и в отдельной строке. Текст программы заканчивается директивой END, после которой указывается метка команды, с которой должно начаться выполнение программы.
В строке msg байты 0Dh и 0Ah — это коды возврата каретки и перевода строки. За меткой start следуют две команды, которые обеспечивают загрузку сегментного регистра DS адресом секции данных (более точно, адресом группы с именем DGROUP, в которую входят секции данных и стека). Этот адрес хранится во встроенной переменной @data. Команда mov ds,@data недопустима, поэтому приходится использовать промежуточный регистр AX. Операция OFFSET msg вычисляет смещение msg относительно начала секции данных. Так как это операция времени ассемблирования, набираем ее прописными буквами.
10.3. Трансляция и компоновка
Трансляция программы происходит по команде
с:\tasm\bin\tasm first.asm
На диске появляется файл first.obj. Объектный файл имеет весьма сложную структуру. В частности, там хранятся машинные коды команд. Компоновка происходит по команде
с:\tasm\bin\tlink first.obj
На диске появляется файл first.exe, который можно запустить на выполнение. Одновременно создается карта загрузки first.map.
Запустим файл first.exe на выполнение.
first.exe
Hello!
(Поучительно проследить, как изменяется на разных этапах подготовки программы число, обозначенное как @data.)
Транслятор tasm и компоновщик tlink имеют ключи, которые задают режимы создания объектного и исполнительного файлов. Чтобы получить листинг при трансляции введите команду: с:\tasm\bin\tasm/l first. Обратите внимание, что расширение файла можно и не указывать.
Если вы хотите включить в загрузочный файл отладочную информацию, а затем изучать программу в отладчике, то нужно ввести команды:
с:\tasm\bin\tasm/zi first
с:\tasm\bin\tlink/v first
с:\tasm\bin\td first
Ключи можно комбинировать, например:
с:\tasm\bin\tasm/zi/l first
10.4. Анализ листинга программы
Листинг программы first.lst состоит из трех частей. Проанализируем каждую часть отдельно. Для наглядности из текста программы перед трансляцией были удалены комментарии.
Turbo Assembler Version 4.1 19/04/02 00:33:35 Page 1
first.asm
1 0000 .MODEL small
2 0000 .STACK 100h
3 0000 .DATA
4 0000 48 65 6C 6C 6F 21 0D+ msg DB "Hello!",0Dh,0Ah,'$'
5 0A 24
6 0009 .CODE
7 0000 B8 0000s start: mov ax,@data
8 0003 8E D8 mov ds,ax
9 0005 B4 09 mov ah,9h
10 0007 BA 0000r mov dx, OFFSET msg
11 000A CD 21 int 21h
12 000C B8 4C00 mov ax,4C00h
13 000F CD 21 int 21h
14 END start
В первой части листинга информация расположена в четырех колонках. В первой колонке — порядковые номера строк листинга. Вторая колонка — текущее значение счетчика адреса (не путать с регистром IP — счетчиком команд). В начале секций данных (.DATA) и кода (.CODE) счетчик адреса сбрасывается, а затем возрастает с появлением в листинге новых данных и команд. В нашем случае, когда программа размещается в одном файле, значение счетчика адреса станет в дальнейшем значением смещения (offset). В третьей колонке показаны сгенерированные коды. В секции данных мы видим ASCII коды для строки "Неllo!" и управляющих символов. Коды не уместились на одной строке (символ переноса — знак '+'). По значению счетчика адреса 0009 можно заключить, что строка занимает 9 байтов.
В секции кода в третьей колонке размещены машинные коды команд. Обращает на себя внимание строка листинга
7 0000 B8 0000s start: mov ax,@data
Суффикс s (s — segment) в коде команды указывает, что непосредственный операнд связан с сегментным адресом. Когда мы будем изучать код программы в отладчике, то первый байт команды останется тем же — B8, а второй и третий байты изменятся, т.е. не будут соответствовать листингу. Об этом и предупреждает суффикс s.
В строке
10 0007 BA 0000r mov dx, OFFSET msg
суффикс r (r — relocatable — перемещаемый) сообщает, что смещение msg может измениться. В отладчике мы увидим код команды неизменным. Но если бы на этапе компоновки к нашему файлу был пристыкован другой файл с секцией данных, то смещение msg от начала сегмента изменилось бы. На такую возможность и указывает r.
Вторая часть листинга содержит таблицу символов. Приведем фрагмент этой таблицы.
Turbo Assembler Version 4.1 19/04/02 00:33:35 Page 2
Symbol Table
Symbol Name Type Value
??DATE Text "19/04/02"
??FILENAME Text "first "
??TIME Text "00:33:35"
…
@DATA Text DGROUP
…
MSG Byte DGROUP:0000
START Near _TEXT:0000
В первой колонке перечислены символические имена (Symbol Name), во второй колонке их тип (Type): текст, число. Для метки msg указано, что ее тип — байтовый, для метки start — что она "близкая" (это определяется выбранной моделью памяти small). В третьей колонке приведено значение (Value) символического имени. Все имена записаны прописными буквами. Нами в программе определены только два имени: msg и start (Ассемблер перевел их в верхний регистр, т.е. заменил все буквы прописными). Остальные имена являются встроенными.
В третьей, заключительной части листинга перечислены программные сегменты (секции) и группы, в которые они объединены.
Groups & Segments Bit Size Align Combine Class
DGROUP Group
STACK 16 0100 Para Stack STACK
_DATA 16 0009 Word Public DATA
_TEXT 16 0011 Word Public CODE
Сейчас мы не будем подробно анализировать эту часть листинга. Уместно сделать это позже, когда мы будем размещать текст программы в нескольких файлах. Сейчас отметим следующее. Секция кода (CODE) получила имя _TEXT, ее размер (Size) составляет 11h байтов. Секции стека с именем STACK и секция данных с именем _DATA объединены в группу DGROUP (группа размещается в одном сегменте оперативной памяти). Теперь ясно, почему в таблице символов введенные нами символические имена msg и start имеют сегментную часть адреса DGROUP и _TEXT. Конкретные числовые значения станут известны только на этапе загрузки .exe-файла в оперативную память.