Современные решения

для защиты Windows приложений

и восстановления исходного кода

Директивы ассемблера NASM

Хотя 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, отличается от пользовательской тем, что одновременно принимает только один аргумент.

Перейти на содержание