Следующая глава | Предыдущая глава | Содержание | Указатель
Глава 6: Выходные форматы |
Перевод: AsmOS
group, © 2001
|
Как упоминалось в параграфе 2.1.1, NASM выбирает имя по умолчанию для выходного файла на основе имени входного файла и указанного выходного формата. При этом расширение входного файла (.asm, .s или др.) удаляется и вместо него подставляется расширение соответствующего выходного формата. Расширения файлов приводятся ниже в описаниях каждого формата.
Формат bin не создает объектных файлов: в выходной файл не генерируется ничего, кроме написанного вами кода. Такие "чисто бинарные" файлы используются в MS-DOS: как исполнимые файлы .COM и драйвера устройств .SYS. Бинарный формат полезен также при разработке операционных систем и загрузчиков.
Формат bin поддерживает только три стандартные секции с именами .text, .data и .bss. В файле, сгенерированном NASM, сначала будет идти содержимое секции .text, а затем содержимое секции .data, выровненное по 4-байтной границе. Секция .bss не хранится в выходном файле, но предполагается, что она будет расположена сразу после секции .data, опять же выровненная по 4-байтной границе.
Если вы явно не указываете директиву SECTION, написанный вами код будет направлен по умолчанию в секцию .text.
Использование формата bin переводит NASM по умолчанию в 16-битный режим (см. параграф 5.1). Чтобы использовать его для написания 32-битного кода (например, ядра ОС), вам необходимо явно указать директиву BITS 32.
По умолчанию bin не создает расширение файла: он просто оставляет исходное имя файла, удалив предварительно его расширение. Таким образом, NASM по умолчанию будет ассемблировать binprog.asm в бинарный файл binprog.
Формат bin предусматривает дополнительную к списку главы 5 директиву ORG. Функция этой директивы задавать начальный адрес программы, с которого она располагается при загрузке в память.
Например, следующий код будет генерировать двойное слово 0x00000104:
org 0x100
dd label
label:
В отличие от директивы ORG, применяемой MASM-совместимыми ассемблерами, которые позволяют перемещаться в объектном файле и переписывать уже сгенерированный код, NASM-овская ORG означает только то, что и соответствующее слово: origin (начало). Ее единственная функция задавать смещение, которое будет прибавляться ко всем ссылкам на адреса внутри файла; она не допускает такого жульничества, как MASM. Дополнительные комментарии содержатся в параграфе 10.1.3.
Выходной формат bin расширяет директиву SECTION (или SEGMENT), позволяя вам указывать требования к выравниванию сегментов. Это делается путем добавления в конец строки определения секции спецификатора ALIGN. Например, строка
section .data align=16
переключается на секцию .data и также указывает, что эта секция должна быть выровнена в памяти по границе параграфа.
Спецификатор ALIGN указывает, сколько младших бит в начальном адресе секции должны быть установлены в ноль. Указываемое значение выравнивания может быть любой степенью двойки.
Файлы формата obj (исторически в NASM они называются obj, а не omf), создаваемые MASMом и TASMом, обычно "скармливаются" 16-битным DOS-компоновщикам, на выходе которых получаются .EXE файлы. Этот формат используется также в OS/2.
Формат obj предполагает расширение выходного файла по умолчанию .obj.
obj не является исключительно 16-битным форматом: NASM полностью поддерживает 32-битные расширения этого формата. В частности, 32-битный obj формат используется Win32-компиляторами Borland, которые не применяют новый объектный формат win32 от Майкрософт.
Формат obj не определяет специальных имен сегментов: вы можете называть ваши сегменты как угодно. Типичными именами сегментов obj формата обычно являются CODE, DATA и BSS.
Если в вашем исходном файле до явного указания директивы SEGMENT содержится код, NASM будет помещать его в собственный сегмент с именем __NASMDEFSEG.
Когда вы определяете сегменты в obj файле, NASM воспринимает их имена как символы, поэтому вы можете легко получить адрес любого определенного сегмента, например:
segment data
dvar: dw 1234
segment code
function: mov ax,data ; получение сегментного адреса данных
mov ds,ax ; и помещение его в DS
inc word [dvar] ; теперь эта ссылка работает
ret
Формат obj разрешает также использование операторов SEG и WRT, поэтому вы можете писать код, делающий что-то вроде
extern foo
mov ax,seg foo ; получение сегмента foo
mov ds,ax
mov ax,data ; получение другого сегмента
mov es,ax
mov ax,[ds:foo] ; это доступ к 'foo'
mov [es:foo wrt data],bx ; так это делается
Выходной формат obj расширяет директиву SEGMENT (или SECTION), позволяя задавать различные характеристики определяемого сегмента. Это делается путем добавления в конец строки определения сегмента дополнительных спецификаторов. Например,
segment code private align=16
определяет сегмент code, объявляя его при этом закрытым и требуя, чтобы код этого модуля был выровнен по границе параграфа.
Имеются следующие спецификаторы:
Атрибуты сегмента по умолчанию в NASM это PUBLIC, ALIGN=1, без класса, без оверлея и USE16.
Формат obj позволяет группировать сегменты, благодаря чему для ссылки на все сегменты группы может использоваться один сегментный регистр. NASM для этого поддерживает директиву GROUP. Вы можете написать:
segment data
; некоторые данные
segment bss
; некоторые неинициализированные данные
group dgroup data bss
При ассемблировании этого кода будет создана группа dgroup, содержащая сегменты data и bss. Как и в случае SEGMENT, имя директивы GROUP определяется как символ, поэтому вы можете ссылаться на переменную var в сегменте data как var wrt data или как var wrt dgroup, в зависимости от того, какое значение в данный момент находится в сегментном регистре.
Однако если вы ссылаетесь на var, объявленную в сегменте группы, NASM по умолчанию предоставляет смещение относительно начала группы, а не сегмента. Вследствие этого SEG var также будет возвращать базу группы, а не базу сегмента.
NASM позволяет сегменту являться частью более одной группы, однако при этом он будет выдавать предупреждение. Переменные, объявленные в таком сегменте, по умолчанию будут относиться к первой включающей данный сегмент группе.
Группа может не иметь ни одного сегмента; однако при этом вы можете делать WRT ссылки на группу, которая не содержит нужную переменную. OS/2, например, определяет специальную группу FLAT без всяких сегментов.
В отличие от NASM, некоторые OMF компоновщики нечувствительны к регистру символов; вследствие этого иногда полезно заставлять NASM генерировать объектные файлы в одном регистре. Директива формата UPPERCASE перед записью в объектный файл переводит имена всех сегментов, групп и символов в верхний регистр. В пределах исходного файла NASM остается регистро-чувствительным; однако объектные файлы, если это необходимо, могут быть составлены целиком в верхнем регистре.
UPPERCASE должна оставаться единственной в строке; она не требует никаких параметров.
Директива IMPORT определяет символ, импортируемый из DLL и используемый например для написания в NASM библиотеки импорта. При использовании директивы IMPORT вы должны объявить символ как EXTERN.
Директива IMPORT требует указания двух параметров, разделенных пробелом и являющихся соответственно именем импортируемого символа и именем библиотеки, из которой он импортируется. Например:
import WSAStartup wsock32.dll
Третьим (необязательным) параметром является имя, под которым импортируемый символ известен в соответствующей библиотеке. Этот параметр применяется в том случае, если вы хотите, чтобы имя символа, используемое внутри вашего кода, не совпадало с именем того же символа в библиотеке. Например:
import asyncsel wsock32.dll WSAAsyncSelect
Директива EXPORT определяет глобальный символ, экспортируемый как DLL-символ в создаваемую DLL (если вы пишете DLL в NASM). Чтобы использовать директиву EXPORT, вы должны объявить символ как GLOBAL.
EXPORT требует один параметр, являющийся именем экспортируемого символа (в том виде, в каком он определен в вашем исходном файле). Необязательный второй параметр (отделенный пробелом от первого) представляет собой внешнее имя символа: это имя, под которым данный символ будет известен использующим эту DLL программам. Если это имя то же самое, что и внутреннее, можете оставить второй параметр пустым.
Для определения атрибутов экспортируемого символа могут быть заданы дополнительные параметры. Эти параметры, как и второй, отделяются друг от друга пробелами. Если дополнительные параметры задаются, внешнее имя также должно указываться, неважно, совпадает ли оно с внутренним или нет. Имеются следующие дополнительные атрибуты:
Например:
export myfunc
export myfunc TheRealMoreFormalLookingFunctionName
export myfunc myfunc 1234 ; экспортирование по ординалу
export myfunc myfunc resident parm=23 nodata
OMF-компоновщики требуют, чтобы один из компонуемых объектных файлов определял точку входа в программу (место, откуда начнется выполнение программы после загрузки). Если этот файл ассемблируется при помощи NASM, вы можете указать точку входа путем объявления в этом месте специального символа ..start.
Если вы объявляете внешний символ директивой
extern foo
то ссылки наподобие mov ax,foo будут давать вам смещение foo относительно базы его предопределенного сегмента (как задано в модуле, где определен foo). Таким образом, доступ к содержимому foo вы будете обычно осуществлять следующим образом
mov ax,seg foo ; получение базы сегмента
mov es,ax ; перемещение ее в ES
mov ax,[es:foo] ; и использование смещения 'foo' от нее
Это получается немного громоздко, особенно если вы знаете, что внешний символ будет доступен из данного сегмента или группы (например, dgroup). Так, если DS уже содержит dgroup, вы можете написать
mov ax,[foo wrt dgroup]
Однако необходимость каждый раз это печатать для получения доступа к foo очень утомительна; NASM позволяет вам объявить foo в альтернативной форме
extern foo:wrt dgroup
Эта форма заставляет NASM делать вид, что базовым сегментом foo на самом деле является dgroup; так, выражение seg foo теперь будет возвращать dgroup, а выражение foo будет эквивалентно foo wrt dgroup.
Описанный WRT-механизм может быть использован для "проявления" внешних символов по отношению к любой группе или сегменту в программе. Он также может быть применен к общим переменным (см. параграф 6.2.8 ниже).
Формат obj позволяет общим переменным быть как ближними, так и дальними; NASM обеспечивает этот механизм при помощи следующего синтаксиса:
common nearvar 2:near ; 'nearvar' - ближняя переменная
common farvar 10:far ; 'farvar' - дальняя переменная
Дальние общие переменные могут иметь размер больше 64 Кб, поэтому спецификация OMF говорит, что они объявляются как число элементов данного размера. Так, 10-байтная дальняя переменная должна быть объявлена как 10 однобайтных элементов, 5 двухбайтных, 2 пятибайтных или 1 десятибайтный элемент.
Некоторые OMF-компоновщики при разрешении общих переменных, объявленных более чем в одном модуле, требуют совпадения размеров элемента и переменной. Поэтому NASM должен позволять задавать размер элемента дальних переменных. Он делает это при помощи следующего синтаксиса:
common c_5by2 10:far 5 ; два пятибайтных элемента
common c_2by5 10:far 2 ; пять двухбайтных элементов
Если размер элемента не задан, он предполагается по умолчанию равным 1. Не требуется также и явное указание ключевого слова FAR, так как только дальние общие переменные могут иметь размер элемента. Исходя из этого, приведенные выше объявления будут эквивалентны следующим:
common c_5by2 10:5 ; два пятибайтных элемента
common c_2by5 10:2 ; пять двухбайтных элементов
В дополнение ко всему вышесказанному, директива COMMON в obj поддерживает также и спецификацию WRT-умолчаний наподобие тому, как это работает для EXTERN (см. параграф 6.2.7). Поэтому вы можете объявлять такие, например, вещи:
common foo 10:wrt dgroup
common bar 16:far 2:wrt data
common baz 24:wrt data:6
Выходной формат win32 генерирует объектные файлы Microsoft Win32, передаваемые обычно компоновщикам Microsoft. Заметьте, что компиляторы Borland Win32 не используют этот формат, вместо него они используют obj (см. параграф 6.2).
win32 подразумевает по умолчанию расширение выходного объектного файла .obj.
Имейте в виду, что хотя Майкрософт и утверждает, что объектные файлы Win32 следуют стандарту COFF, объектные файлы, созданные компиляторами Microsoft Win32, не совместимы с COFF-компоновщиками (например, DJGPP) и наоборот. Это происходит из-за разницы во взглядах на семантику таблицы перемещений. Для создания COFF-совместимых выходных файлов для DJGPP используйте выходной формат coff NASMа; обратное также справедливо файлы, полученные в объектном формате coff, не обрабатываются корректно компоновщиками Win32.
Как и формат obj, win32 позволяет вам указывать в строке с директивой SECTION дополнительную информацию, предназначенную для управления типом и свойствами определяемой секции. Обычно тип секции и ее свойства для стандартных имен .text, .data и .bss генерируются NASM автоматически, но при помощи приведенных спецификаторов могут быть и переопределены.
Имеются следующие спецификаторы:
Если вы не укажете ни одного описанного выше спецификатора, NASM принимает следующие значения по умолчанию:
section .text code align=16
section .data data align=4
section .bss bss align=4
Любые другие имена секций обрабатываются так же, как и .text.
Выходной формат coff создает COFF-объектные файлы, обрабатываемые компоновщиком DJGPP. Этот формат предусматривает по умолчанию расширение выходных файлов .o.
Формат coff поддерживает те же самые расширения директивы SECTION, что и win32, однако спецификатор align и секция типа info не поддерживаются.
Выходной формат elf генерирует объектные файлы ELF32 (Executable and Linkable Format), используемые в Линукс. Этот формат использует по умолчанию расширение .o выходных файлов.
Как и формат obj, elf позволяет вам указывать в строке с директивой SECTION дополнительную информацию, предназначенную для управления типом и свойствами определяемой секции. Обычно тип секции и ее свойства для стандартных имен .text, .data и .bss генерируются NASM автоматически, но при помощи приведенных спецификаторов могут быть и переопределены.
Имеются следующие спецификаторы:
Если вы не укажете ни одного описанного выше спецификатора, NASM принимает следующие значения по умолчанию:
section .text progbits alloc exec nowrite align=16
section .data progbits alloc noexec write align=4
section .bss nobits alloc noexec write align=4
section other progbits alloc noexec nowrite align=1
(Любые секции, не являющиеся .text, .data или .bss обрабатываются так же, как и секция other).
Спецификация ELF содержит достаточное число возможностей написания позиционно-независимого кода (PIC), благодаря которым разделяемые библиотеки ELF весьма гибкие. Однако это также означает, что NASM должен быть способен генерировать разнообразные замысловатые типы релокейшнов объектных ELF-файлов.
Так как ELF не поддерживает сегментные ссылки, оператор WRT для своей обычной цели не используется; вместо этого формат elf использует WRT для других целей, а именно для специальных PIC-типов перемещений.
elf определяет пять специальных символов, которые вы можете использовать с правой стороны оператора WRT для получения PIC-типов перемещений. Это ..gotpc, ..gotoff, ..got, ..plt и ..sym. Их функции описаны ниже:
Более полное объяснение использования типов перемещений для написания в NASM разделяемых библиотек дано в параграфе 8.2.
Объектные файлы ELF могут содержать больше информации о глобальном символе, чем просто его адрес: они могут содержать размер символа, а также его тип. Это сделано не только для удобства при отладке, это просто необходимо, если программа пишется в виде разделяемой библиотеки. Для задания дополнительной информации NASM поддерживает некоторые расширения директивы GLOBAL.
Поставив после имени глобальной переменной двоеточие и написав function или data (object является синонимом data), вы можете указать, чем является глобальная переменная функцией или объектом данных. Например, строка
global hashlookup:function, hashtable:data
экспортирует глобальный символ hashlookup как функцию, а hashtable как объект данных.
После ввода спецификатора вы можете также указать в виде числового выражения (которое может включать метки и даже опережающие ссылки) размер ассоциированных с символом данных, например:
global hashtable:data (hashtable.end - hashtable)
hashtable:
db this,that,theother ; здесь некоторые данные
.end:
Это заставит NASM автоматически подсчитать длину таблицы и поместить эту информацию в символьную таблицу ELF.
Объявление типа и размера глобальных символов требуется при написании разделяемых библиотек. Дополнительную информацию вы можете найти в параграфе 8.2.4.
ELF позволяет задавать требования к выравниванию общих переменных. Это делается путем помещения числа (которое должно быть степенью двойки) после имени и размера общей переменной и отделения этого числа (как обычно) двоеточием. Например, массив двойных слов, который должен быть выровнен по двойным словам:
common dwordarray 128:4
Эта строка объявляет массив размером 128 байт и требует, чтобы он был выровнен по 4-байтной границе.
Формат aout генерирует объектные файлы a.out в форме, используемой устаревшими Линукс системами. (Он отличается от других объектных файлов a.out магическим числом в первых четырех байтах файла. Также некоторые реализации a.out, например NetBSD, поддерживают позиционно-независимый код, который реализация Линукс не знает).
Формат a.out подразумевает расширение выходных файлов по умолчанию .o.
Этот формат очень простой. Он не поддерживает специальных директив и символов, не использует SEG или WRT и в нем нет расширений никаких стандартных директив. Он поддерживает только три стандартных секции с именами .text, .data и .bss.
Формат aoutb генерирует объектные файлы a.out в форме, используемой различными BSD-клонами UNIX: NetBSD, FreeBSD и OpenBSD. Для большинства объектных файлов этот формат не отличается от aout за исключением магического числа в первых четырех байтах файла. Однако формат поддерживает (как и формат elf) позиционно-независимый код, поэтому вы можете использовать его для написания разделяемых библиотек BSD.
Расширение объектных файлов формата aoutb по умолчанию .o.
Формат не поддерживает специальных директив и символов и имеет только три стандартных секции с именами .text, .data и .bss. Несмотря на это, для обеспечения типов перемещений в позиционно-независимом коде он поддерживает использование WRT так же, как это делает elf. Более подробно это описано в параграфе 6.5.2.
aoutb, как и elf поддерживает также расширение директивы GLOBAL: см. параграф 6.5.3.
16-битный ассемблер Линукс as86 имеет свой собственный нестандартный формат объектных файлов. Хотя его компаньон компоновщик ld86 выдает что-то близкое к обычным бинарникам a.out, объектный формат, используемый для взаимодействия между as86 и ld86, все же не является a.out.
NASM на всякий случай поддерживает данный формат как as86. Расширение выходного файла по умолчанию для данного формата .o.
Формат as86 это очень простой объектный формат (с точки зрения NASM). Он не поддерживает специальных директив и символов, не использует SEG и WRT, и в нем нет никаких расширений стандартных директив. Он поддерживает только три стандартных секции с именами .text, .data и .bss.
Выходной формат rdf создает объектные файлы RDOFF. RDOFF это "доморощенный" формат объектных файлов, разработанный вместе с NASM и отражающий в себе внутреннюю структуру ассемблера.
RDOFF не используется никакими широко известными операционными системами. Однако тот, кто пишет собственную систему, возможно захочет использовать его в качестве собственного объектного формата, так как разработан он прежде всего для упрощения и содержит очень мало бюрократии в заголовках файлов.
Архив Unix NASM и архив DOS с исходниками имеют подкаталог rdoff, содержащий набор RDOFF-утилит: RDF-компоновщик, менеджер статических библиотек, утилита, делающая дамп RDF-файла, и программа, загружающая и выполняющая RDF-исполнимый файл под Линукс.
Формат rdf поддерживает только стандартные секции с именами .text, .data и .bss.
RDOFF содержит механизм "требования библиотеки", которая будет связана с модулем как во время загрузки, так и при выполнении. Это осуществляется директивой LIBRARY, которая принимает один аргумент, являющийся именем модуля:
library mylib.rdl
Выходной dbg формат в конфигурации NASM по умолчанию отсутствует. Если вы строите собственный исполнимый файл NASM из исходников, то можете для включения этого формата определить символ OF_DBG в файле outform.h или командной строке компилятора.
Формат dbg не создает объектных файлов как таковых; вместо этого он создает текстовый файл, содержащий полный список всех транзакций между ядром NASM и модулем выходных форматов. Это обычно нужно людям, намеревающимся написать собственные выходные драйвера и которые благодаря этому формату могут получить картину различных запросов основной программы к выходному драйверу и увидеть, в каком порядке они осуществляются.
Для простых файлов можно использовать dbg формат так:
nasm -f dbg filename.asm
в результате чего генерируется диагностический файл filename.dbg. Однако это не будет работать на файлах, разработанных под различные объектные форматы, так как каждый формат определяет собственные макросы (обычно пользовательские формы директив), не определенные в формате dbg. Поэтому здесь необходимо запускать NASM дважды, с препроцессированием выбранного объектного формата:
nasm -e -f rdf -o rdfprog.i rdfprog.asm
nasm -a -f dbg rdfprog.i
Здесь rdfprog.asm препроцессируется в rdfprog.i, оставляя при этом выбранным объектный формат rdf. Это нужно, чтобы специальные директивы RDF правильно конвертировались в примитивную форму. Затем препроцессированный исходный файл обрабатывается в формате dbg и при этом генерируется окончательный диагностический файл.
Такой обходной путь обычно не будет работать с программами, предназначенными для формата obj, так как директивы SEGMENT и GROUP последнего имеют побочные эффекты определения имен сегментов и групп как символов; dbg этого не делает, поэтому программа ассемблироваться не будет. Если вам позарез нужно получить dbg-трассировку исходников, написанных для obj, вы можете обойти это, определив символы самостоятельно (например, при помощи EXTERN).
Формат dbg принимает любые имена секций и любые директивы, протоколируя все их в свой выходной файл.
Следующая глава | Предыдущая глава | Содержание | Указатель