
- •Аппаратно-ориентированное программирование
- •Ббк 32.973.73
- •Удк 681.3 ббк 32.973.73ф 73
- •1. Основы программирования на ассемблере
- •1.1. Принципы построения ассемблерных программ
- •1.2. Понятие архитектуры компьютера
- •1.3. Регистры программиста в ia32
- •1.4. Описание сегментной структуры программы
- •2. Простейшие средства ассемблера
- •2.1. Средства описания данных
- •2.2. Обращения к функциям ос посредством прерываний
- •2.3. Средства преобразования в исполняемый файл
- •2.4. Управление строками при выводе и ввод данных
- •2.5. Простейшие способы адресации
- •3. Архитектурные элементы для построения программ
- •3.1. Организация условных переходов
- •3.2. Средства организации циклов
- •3.3. Особенности команд умножения и деления
- •3.4. Организация процедур
- •3.5. Неарифметические операции над кодами
- •4. Использование неэлементарных способов адресации
- •4.1. Косвенно-регистровая адресация
- •4.2. Использование индексной адресации данных
- •4.3. Базовая и индексно базовая адресации
- •4.4. Адресация с масштабированием
- •5. Взаимосвязи программных единиц
- •5.1. Многомодульная разработка программ
- •5.2. Использование библиотек объектных модулей
- •5.3. Организация стекового кадра подпрограммы
- •5.4. Программный доступ к системным функциям Win32
- •5.5. Особенности использования объектных файлов формата coff
- •5.6. Стандартный доступ к системным функциям Unix
- •6. Вспомогательные средства базовой архитектуры
- •6.1. Использование строковых команд пересылки
- •6.2. Применение строковых команд сравнения
- •7. Использование ассемблерных отладчиков
- •7.1. Особенности отладчика gdb для программ в Linux
- •7.2. Отладчики текстового режима для Windows
- •Библиографический список
- •Оглавление
1.4. Описание сегментной структуры программы
Действительная структура программы в машинных кодах включает так называемые сегменты памяти. Эта структура невидима из языков высокого уровня, но является неизбежной и даже благодарной реальностью машинных структур данных. Основное назначение таких сегментов в современных архитектурах - обеспечить защитой от ошибочного доступа машинные коды команд и области данных. (В область команд запрещается что-либо записывать в процессе выполнения программы, а из области данных управляющему устройству процессора запрещается читать коды, автоматически интерпретируя их как коды машинных команд.) Получается, что, как минимум, машинная программа должна иметь два сегмента: сегмент машинных кодов (кодов команд) и сегмент данных. Заметим, что только в однозадачной ОС MS-DOS, где не было или не использовались указанные выше средства защиты сегментов друг от друга, можно было все машинные коды программы - и данные, и коды команд - размещать в одном единственном сегменте, причем все сегменты в этой ОС могли иметь максимальный размер всего в 64 Кбайта.
Для описания сегментов служит специальное наименование директивы, обозначаемое словом SEGMENT, причем в этом случае можно записывать служебное слово как строчными, так и прописными буквами. (Синонимом этого служебного слова является слово SECTION, которое можно всегда использовать для обозначения сегмента.) В простейшем случае директива сегмента имеет единственный аргумент, который задает имя описываемого сегмента. С учетом ориентации ряда ОС на язык Си, как язык соглашений взаимодействия с системой, рекомендуемые названия сегментов кодов команд и данных есть, соответственно, .text и .data, причем начальная точка обозначения формальным образом входит в состав имени.
Другой неожиданной для начинающей особенностью ассемблерных программ является необходимость явного обозначения машинной инструкции, с которой должна начать выполняться программа. Это отвечает естественно заложенной в архитектуру компьютеров возможности начать выполнение любой программы с любого места в области памяти, занятой кодами машинных команд. Любая отметка места в памяти, относящаяся как к машинным командам, так и к данным, представляет собой на языке ассемблера так называемую метку. Метка на языке ассемблера есть просто имя, соотносимое началу строки ассемблерной программ и размещаемое в начале этой строки. Традиционная методика использования меток требует, чтобы обозначение метки в том месте, где она именует строку программы, завершалась служебным символом двоеточия. В ассемблере nasm это требование является необязательным, но мы будем им пользоваться для отметок в тексте собственно машинных команд. В ассемблерах типа Intel требуются метки, обозначающие места в тексте машинных команд, обязательно завершать двоеточием, а метки, обозначающие данные, как правило, используются без завершающих двоеточий.
Место, с которого начинает выполняться программа, называется точкой входа. Метка точки входа в программу на языке NASM должна обязательно быть объявлена видимой извне объектного модуля, который возникает из исходного текста при традиционной методике создания программы. Такое объявление осуществляет директива GLOBAL. Назначение и возможности этой директивы гораздо шире, чем указание с ее помощью точки входа, но более подробное обсуждение данного вопроса будет проведено позднее. Использование директивы GLOBAL заключается в записи после ключевого слова этой директивы того имени, которое предполагается задать как глобальное. (В общем случае в одной этой директиве может быть через запятые указано много имен, которые предполагается использовать как глобальные.)
Программа - компоновщик в Linux - по сложившейся традиции требует имени _start в качестве обозначения начала программы (так как точка входа программы всегда одна, то такой выбор не несет никакого мыслимого ограничения). Указанное имя начинается со служебного символа подчеркивания, которое считается также входящим в состав этого имени. В общем случае имена в NASM могут состоять из алфавитно-цифровых символов, начинающихся с буквы, причем символ подчеркивания считается буквой. Кроме того, служебные имена могут начинаться с одной или двух точек. Использование служебных имен будет описываться особо по ходу изложения.
В ассемблерах типа Intel было принято высокоумное, но несколько громоздкое в применении решение обозначать специальными служебными словами не только начала сегмента программы, но и конец сегмента программы. Для обозначения конца сегмента программ здесь предназначена директива со служебным именем ENDS, причем в этой директиве обязательно должно быть записано и имя того сегмента, который она завершает. Такое решение призвано сократить число ошибок при написании программы, но, в связи с крайне скромным числом сегментов в современных программах, теперь такое решение кажется скорее красивым, чем целесообразным. В указанных ассемблерах типа Intel именование как директивы SEGMENT, так и директивы ENDS производится записью имени перед служебным словом директивы. (Следует, правда, отметить, что так называемый Turbo ассемблер фирмы Borland/Inprise имеет режим, в котором имя сегмента следует за служебным словом, а не наоборот.) Кроме рассмотренной особенности описания сегментов, ассемблеры типа Intel используют специальную директиву END для указания завершения, по назначению и форме очень напоминающую одноименную директиву в языке Pascal. В указанных же ассемблерах директива END используется также для указания метки точки входа.
Таким образом, сегментная структура программы для NASM имеет вид
GLOBAL _start
SEGMENT .text
_start:
. . . ; задание машинных команд на данном ассемблере
SEGMENT .data
. . . ; описание данных на ассемблере NASM
причем сегменты .text и .data могут быть переставлены местами. Аналогичная же программа на ассемблере MASM или TASM будет иметь общий вид
имя_сегмента_кода SEGMENT
имя_точки_входа:
. . . ; задание машинных команд на данном ассемблере и служебных конструкций
имя_сегмента_кода ENDS
имя_сегмента_данных SEGMENT
. . . ; описание данных на ассемблере MASM или TASM
имя_сегмента_данных ENDS
END имя_точки_входа
где также сегменты данных и команд могут быть переставлены местами. Забегая вперед, отметим, что в современных ОС Windows (32-битных полноценных ОС, в отличие от уже почти забытой промежуточной 16-битной Windows и MS-DOS) имена сегментов кода и данных должны задаваться по некоторым фиксированным соглашениям, так что практически тоже могут считаться постоянными.
В современных ОС исходные описания сегментов используют не один сегмент данных, а по крайней мере две его модификации. Сегмент с традиционным наименованием .data или DATA применяется для инициализируемых данных. Характерной особенностью этих данных является задание начального значения для каждой единицы исходного описания данных. Для размещения данных, исходное описание которых не содержит задания начального значения, принято наименование .bss или BSS. Следует учитывать, что транслятор NASM по-разному «относится» к содержимому указанных модификаций сегментов данных. Если программист делает попытку помещения неинициализированных им данных в сегмент с именем .data или DATA, то выдается предупреждение, а область самих таких данных заполняется нулевыми байтами. В свою очередь, попытка объявить данные (т.е. описать области размещения данных без задания их значений) в сегментах .bss или BSS приводит к сообщаемой ошибке.