Расширенный ассемблер: NASM

Следующая глава | Предыдущая глава | Содержание | Указатель

Глава 5: Директивы ассемблера

Перевод: AsmOS group, © 2001
Хотя NASM и пытается избежать бюрократизм ассемблеров наподобие MASM и TASM, он вынужден поддерживать несколько директив. Все они описаны в этой главе.

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

В дополнение к описанным в этой главе универсальным директивам каждый объектный формат может опционально предоставлять дополнительные директивы, служащие для управления особенностями этого формата. Такие дополнительные директивы описываются далее в главе, посвященной выходным форматам файлов (глава 6).

5.1 BITS: Указание разрядности выполняемого кода

Директива BITS указывает, код какой разрядности должен генерировать NASM: для процессора, работающего в 16-битном режиме, или для процессора в 32-битном режиме. Соответствующим синтаксисом будет BITS 16 или BITS 32.

В большинстве случаев вам не потребуется использовать BITS явно. Объектные форматы aout, coff, elf и win32, разработанные для 32-битных операционных систем, вынуждают NASM выбирать 32-битный режим по умолчанию. Объектный формат obj позволяет вам описывать каждый сегмент как USE16 или USE32, поэтому NASM будет соответственно настраивать режим работы и здесь использование директивы BITS также не требуется.

Наиболее вероятным использование директивы BITS представляется при написании 32-битного плоского бинарного файла. (Выходной формат bin по умолчанию предназначен для 16-битного режима, т.к. он наиболее часто используется для написания DOS .COM программ, DOS .SYS драйверов устройств, а также загрузчиков).

Не нужно задавать BITS 32 для использования 32-битных инструкций в 16-битных DOS-программах; если вы это сделаете, ассемблер сгенерирует некорректный код, так как он получится 32-битным и на 16-битных платформах будет не работоспособен.

Когда NASM находится в состоянии BITS 16, инструкции, использующие 32-битные данные, префиксируются байтом 0х66, а 32-битные адреса √ байтом 0х67. В состоянии BITS 32 справедливо обратное: 32-битные инструкции не нуждаются в префиксе, инструкции, использующие 16-битные данные, префиксируются байтом 0х66, а 16-битные адреса √ байтом 0х67.

Директива BITS имеет абсолютно эквивалентные примитивные формы: [BITS 16] и [BITS 32]. Пользовательская форма инструкции является макросом, который не делает ничего, кроме как вызывает соответствующую примитивную форму.

5.2 SECTION или SEGMENT: Описание и изменение секций

Директива SECTION (SEGMENT — это абсолютно эквивалентный синоним) указывает, в какую секцию выходного файла будет ассемблирован код, который вы пишете. В некоторых объектных форматах количество и имена секций фиксированы; в других пользователь может сделать их столько, сколько захочет. Поэтому если вы захотите переключиться на секцию, которая в данный момент не существует, директива SECTION может либо вызвать сообщение об ошибке, либо создать новую секцию.

Объектные форматы Unix и bin поддерживают стандартизованные имена секций: .text — для кода, .data — для данных и .bss — для неинициализированных данных. Формат obj наоборот, не признает эти имена секций специальными и более того, удаляет ведущую точку в имени любой секции.

5.2.1 Макрос __SECT__

Директива SECTION необычна тем, что ее пользовательская форма функционально отличается от примитивной. Примитивная форма [SECTION xyz] просто переключает текущую секцию на указанную. Пользовательская форма SECTION xyz сначала определяет однострочный макрос __SECT__ для примитивной директивы [SECTION], которую собирается выдать, и затем выдает ее. Таким образом, пользовательская директива

SECTION .text

развернется в две строки:

%define __SECT__ [SECTION .text] [SECTION .text]

Пользователи могут использовать это в своих собственных макросах. Например, макрос writefile, описанный в параграфе 4.2.3, может быть с успехом переписан в следующей, более изощренной форме:

%macro writefile 2+ [section .data] %%str: db %2 %%endstr: __SECT__ mov dx,%%str mov cx,%%endstr-%%str mov bx,%1 mov ah,0x40 int 0x21 %endmacro

Данная форма макроса, записывающего строку в файл, сначала временно переключается на секцию данных, используя при этом примитивную форму директивы SECTION, не модифицирующую __SECT__. Далее в секции данных объявляется строка и затем вызывается __SECT__, переключающий контекст на любую секцию, в которой пользователь работал до этого. Это исключает необходимость использования инструкции JMP (как в предыдущей версии макроса) для "перескакивания" данных, а также предотвращает возникновение ошибок в модуле OBJ формата, где пользователь потенциально может использовать различные секции кода.

5.3 ABSOLUTE: Определение абсолютных меток

О директиве ABSOLUTE можно думать как об альтернативной форме SECTION: она направляет последующий код не в физическую секцию, а в гипотетическую, начинающуюся с указанного абсолютного адреса. В данном режиме вы можете использовать только инструкции семейства RESB.

ABSOLUTE используется следующим образом:

absolute 0x1A kbuf_chr resw 1 kbuf_free resw 1 kbuf resw 16

В данном примере область данных PC BIOS описана как сегмент, начинающийся с адреса 0х1А: приведенный выше код определяет kbuf_chr по адресу 0x1A, kbuf_free по адресу 0x1C и kbuf по адресу 0x1E.

Пользовательская форма ABSOLUTE, так же, как и SECTION, переопределяет макрос __SECT__ в месте своего вызова. Директивы STRUC и ENDSTRUC определены как макросы, использующие директиву ABSOLUTE (и соответственно, __SECT__).

ABSOLUTE в качестве аргумента принимает не только абсолютные константы: это может быть выражение (на самом деле критическое выражение: см. параграф 3.7), а также какое-то значение в сегменте. Например, TSR может реутилизировать свой настроечный код в качестве run-time BSS следующим образом:

org 100h ; это .COM - программа jmp setup ; код setup идет последним ; здесь расположена резидентная часть TSR setup: ; теперь идет код, инсталлирующий TSR absolute setup runtimevar1 resw 1 runtimevar2 resd 20 tsr_end:

Здесь определяется несколько переменных "на верхушке" setup-кода, так что после завершения его работы это пространство может быть реутилизировано как хранилище данных для работающей TSR. Символ 'tsr_end' может быть использован для расчета общего размера резидентной части TSR.

5.4 EXTERN: Импорт символов из других модулей

Директива EXTERN подобна директиве MASM EXTRN и ключевому слову extern в С: она используется для объявления символа, который определен в некотором другом модуле. Не все объектные форматы поддерживают внешние переменные: формат bin этого не может.

Директива EXTERN принимает столько аргументов, сколько вам необходимо. Каждый аргумент является именем символа:

extern _printf extern _sscanf,_fscanf

Некоторые объектные форматы обеспечивают дополнительные возможности директивы EXTERN. В любом случае, дополнительный синтаксис отделяется от имени символа двоеточием. Например, формат obj при помощи следующей директивы позволяет вам объявить, что базой сегмента внешних символов по умолчанию должна быть группа dgroup:

extern _variable:wrt dgroup

Примитивная форма EXTERN отличается от пользовательской тем, что одновременно может принять только один аргумент: поддержка списка аргументов реализуется на уровне препроцессора.

Вы можете объявить одну и ту же переменную как EXTERN более одного раза: NASM спокойно проигнорирует второе и последующие переопределения.

5.5 GLOBAL: Экспорт символов в другие модули

Директива GLOBAL — это обратная сторона EXTERN: если один модуль объявляет символ как EXTERN и ссылается на него, то для предотвращения ошибок компоновщика необходимо, чтобы некоторый другой модуль определил этот символ и объявил его как GLOBAL. Некоторые ассемблеры для этой цели используют директиву PUBLIC.

Директива GLOBAL, применяемая к символу, должна появляться перед определением этого символа. Она использует тот же самый синтаксис, что и EXTERN, за исключением того, что ссылается на символ, определяемый в этом же модуле. Например:

global _main _main: ; некоторый код

GLOBAL, как и EXTERN, позволяет вводить после двоеточия специфичный синтаксис объектных форматов. К примеру объектный формат elf позволяет вам указать, чем являются глобальные символы: функциями или данными:

global hashlookup:function, hashtable:data

Как и в случае EXTERN, примитивная форма GLOBAL отличается от пользовательской восприятием одновременно только одного аргумента.

5.6 COMMON: Определение общих данных

Директива COMMON используется для объявления общих переменных. Общая переменная — это глобальная переменная, объявленная в секции неинициализированных данных, поэтому

common intvar 4

работает так же, как и

global intvar section .bss intvar resd 1

Отличие состоит в том, что если одна и та же переменная определена в разных модулях, во время связывания (сборки) эти переменные будут объединены и ссылки на intvar во всех модулях будут указывать на одно и то же место в памяти.

Директива COMMON, так же как GLOBAL и EXTERN, поддерживает специфичный синтаксис объектных форматов. Например, формат obj позволяет общим переменным быть близкими (near) или дальними (far), а формат elf — задать их выравнивание:

common commvar 4:near ; работает в OBJ common intarray 100:4 ; работает в ELF: выравнивание про 4-байтной границе

И наконец, примитивная форма COMMON, как и в случае EXTERN и GLOBAL, отличается от пользовательской тем, что одновременно принимает только один аргумент.

Следующая глава | Предыдущая глава | Содержание | Указатель