Погружение в assembler. полный курс по программированию на асме от ][

8.1.4 c32.mac: Вспомогательные макросы для 32-ух битного интерфейса с Си

Файл макроса c32.mac включен в архив NASM, в каталог
misc. В этом файле определены 3 макроса: proc,
arg
и endproc. Они введены для использования
в определении процедур в стиле Си, и она автоматизируют операции, необходимые
для соблюдения конвенции вызова.

Пример ассемблерной функции, использующей этот набор макросов приведен ниже:

          proc _proc32 
%$i       arg 
%$j       arg 
          mov eax, 
          mov ebx, 
          add eax, 
          endproc

Этот фрагмент определяет _proc32 как процедуру
с двумя аргументами, первый (i) является integer
и второй (j) является указателем на integer.
Процедура возвращяет i + *j.

Заметьте, что макрос arg при его разворачивании
содержит в первой строке EQU, которая в результате
определяет %$i как смещение от BP.
При этом используются контекстно-локальные переменные (локальные к контексту,
сохраняемому в контекстном стеке макросом proc
и удаляемому оттуда макросом endproc), поэтому
в других процедурах может быть использовано то же самое имя аргумента. Конечно,
вы можете этого не делать.

arg можно передать необязательный параметр, указывающий
размер аргумента. Если размер не задан, он предполагается равным 4-ем, потому
что абсолютное большинство параметров функции будут типа int
или указателями.

8.2 Написание разделяемых библиотек для NetBSD/FreeBSD/OpenBSD
и Linux/ELF

ELF замещает старый объектный формат a.out для
Линукса, потому что он поддерживает перемещаемый код (position-independent code
PIC), который позволяет писать разделяемые библиотеки намного проще.
NASM поддерживает особености перемещаемого кода для ELF,
поэтому вы можете писать разделяемый библиотеки для Линукс ELF на NASM.

NetBSD, и его близкие родственники FreeBSD и OpenBSD, используют другой подход,
добавив поддержку перемещаемого кода в формат a.out.
NASM поддерживает это как формат aoutb для скомпилированных
файлов, поэтому вы можете писать разделяемые библиотеки для BSD на NASM тоже.

Операционная система загружает разделяемую PIC (пермещаемый код)
библиотеку, делая
отображение в память файла библиотеки в произвольно выбранное
место в адресном пространстве выполняемого процесса. Поэтому содержимое
секции кода библиотеки должно не зависеть от места в памяти, куда она
загружена.

Поэтому, вы не можете обращаться к вашей переменной таким вот способом:

          mov eax,        ; ОШИБКА

Вместо этого, линковщик предоставляет область памяти, называемую глобальной
таблицей смещений (global offset table), или просто ГТС (GOT); ГТС расположена
на постоянном расстоянии от кода вашей библиотеки, поэтому если вы сможете узнать
куда загружена ваша библиотека (что обычно осуществляется комбинацией CALL
и POP), вы сможете получить адрес ГТС, и затем
загрузить адрес вашей переменной из сгенерированной линковщиком записи в ГТС.

Секция data PIC (перемещяемый код) разделяемой библиотеки не имеет
подобных ограничений: поскольку секция данных доступна для записи, она может
быть скопирована в память каким бы то ни было образом, не только отображением
в страницы из файла библиотеки, поэтому как только она будет скопирована, она
может быть также перемещена. Поэтому вы можете обычный способ для доступа в
секции данных, особо не заботясь об этом (но посмотрите в предостережения).

Почему следует изучать язык ассемблера?

В современной практике индустриального программирования языки ассемблера применяются крайне редко. Для разработки низкоуровневых программ практически в большинстве случаев используется язык си, позволяющий достигать тех же целей многократно меньшими затратами труда, причем с такой же, а иногда и большей эффективностью получаемого исполняемого кода (последнее достигается за счет применения оптимизаторов). На ассемблере сейчас реализуются очень специфические участки ядер операционных систем и системных библиотек. Более того, программирование на ассемблере было вытеснено и из такой традиционно ассемблерной области, как программирование микроконтроллеров. Большей частью прошивки для них также пишут на си. Тем не менее программирование на языке ассемблера очень часто применяется при написании программ, использующих возможности процессора, не реализуемые языками высокого уровня, а также при программировании всевозможных нестандартных программистских хитростей. Отдельные ассемблерные модули, как и ассемблерные вставки в текст на других языках, присутствуют и в ядрах операционных систем, и в системных библиотеках того же языка си и других языков высокого уровня. Сегодня едва ли кому придет в голову сумасшедшая мысль писать крупную программу на чистом ассемблере.

Так зачем же тратить время на его изучение? По ряду веских причин, и вот одна из них: ассемблер — это краеугольный камень, на котором покоится все бесконечное пространство программирования, начиная от рождения первого процессора. Каждый физик мечтает разгадать тайну строения вселенной, найти эти загадочные первичные неделимые (низкоуровневые) элементы, из которых она состоит, не удовлетворяясь лишь смутным о том представлением квантовой теории. Ассемблер же и есть та первичная материя, из которой состоит вселенная процессора. Он — тот инструмент, который дает человеку способность мыслить в терминах машинных команд. А подобное умение просто необходимо любому профессиональному программисту, даже если никогда в жизни он не напишет ни единой ассемблерной строчки. Нельзя отрицать того, что невозможно стать математиком, совершенно не имея понятия об элементарной арифметике. На каком бы языке вы ни писали программы, необходимо хотя бы в общих чертах понимать, что конкретно будет делать процессор, исполняя ваше высочайшее повеление. Если такого понимания нет, программист начинает бездумно применять все доступные операции, совершенно не ведая, что на самом деле он творит.

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

Иными словами, до тех пор пока существуют процессоры, ассемблер будет необходим.

В этом отношении совершенно не важно, какую конкретно архитектуру и язык какого конкретного ассемблера изучать. Зная один язык ассемблера, ты с успехом можешь начать писать на любом другом, потратив лишь некоторое время на изучение справочной информации

Но самое главное в том, что, умея мыслить языком процессора, ты всегда будешь знать, что, для чего, почему и зачем происходит. А это уже не просто уровень программирования мышкой, а путь к созданию программного обеспечения, несущего печать великого мастерства.

SIMD Parallelism

The XMM registers can do arithmetic on floating point values one operation at a time (scalar)
or multiple operations at a time (packed). The operations have the form:

xmmreg_or_memory, xmmreg

For floating point addition, the instructions are:

      do 2 double-precision additions in parallel (add packed double)
do just one double-precision addition, using the low 64-bits of the register (add scalar double)
do 4 single-precision additions in parallel (add packed single)
do just one single-precision addition, using the low 32-bits of the register (add scalar single)

Here’s a function that adds four floats at once:

add_four_floats.asm

; void add_four_floats(float x, float y)
; x += y for i in range(0..4)

        global   add_four_floats
        section  .text

add_four_floats:
        movdqa   xmm0,             ; all four values of x
        movdqa   xmm1,             ; all four values of y
        addps    xmm0, xmm1             ; do all four sums in one shot
        movdqa   , xmm0
        ret

and a caller:

test_add_four_floats.c

#include <stdio.h>
void add_four_floats(float[], float[]);

int main() {
    float x[] = {-29.750, 244.333, 887.29, 48.1E22};
    float y[] = {29.750,  199.333, -8.29,  22.1E23};
    add_four_floats(x, y);
    printf("%f\n%f\n%f\n%f\n", x, x, x, x);
    return 0;
}

Also see this nice
little x86 floating-point slide deck from Ray Seyfarth.

2.1.13 Переменная окружения NASM

Если вы определите переменную окружения NASM, программа будет интерпретировать
ее как список дополнительных ключей командной строки, обрабатывающихся раньше
«настоящих» параметров командной строки. Вы можете использовать эту возможность,
например для описания стандартных каталогов поиска включаемых файлов, поместив
в пременную NASM ключ -i.

Значение пременной разделяется пробелами, поэтому значение -s
-ic:\nasmlib
будет обработано как два отдельных ключа. В то же время
значение -dNAME=»my name» не будет воспринято так,
как вы хотите (внутри есть пробел) и командный процессор NASMа соответственно
не поймет двух бессмысленных параметров -dNAME=»my
и name».

Для разрешения этого введено следующее правило: если вы начинаете переменную
NASM некоторым символом, не являющимся знаком «минус»,
NASM будет воспринимать этот символ как разделитель опций. Таким образом, значение!-s!-ic:\nasmlib переменной NASM
эквивалентно -s -ic:\nasmlib, но зато теперь !-dNAME=»my
name»
будет работать правильно.

2.2 Пользователям MASM: Отличия

Если вы использовали для написания программ MASM,
или TASM в режиме совместимости с MASM, или a86,
прочитайте данный раздел, в котором приводятся основные отличия синтаксиса MASM
и NASM. Если же вы вообще не использовали раньше MASM, просто пропустите этот
раздел и читайте дальше.

6.5.3 elf-расширения директивы GLOBAL

Объектные файлы 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.1.3 Доступ к переменным

Чтобы получить доступ к Си-переменным, или чтобы объявить переменные, к которым
может обращаться Си, вам достаточно объявить имена как GLOBAL
или EXTERN. (Опять же, имена нужно предварять подчеркиванием,
как это описано в .) Таким образом,
Си-переменные, объявленные как int i могут быть
доступны из ассемблера как

          extern _i 
          mov eax,

И чтобы объявить ваши собственные переменные, которые будут доступны Си-программе
как int j, делайте это так (проверьте, что вы ассемблируете
это в _DATA сегменте, если это необходимо):

          global _j 
_j        dd 0

Чтобы обращаться с Си-массивами необходимо знать размер элементов этого массива.
Например, int переменные имеют размер 4 байта,
поэтому если в Си-программе объявлен массив как int a,
можно обращаться к a так: mov
ax,
. ( Смещение 12 полчается в результате умножения номера в массиве,
3 на размер элемента массива, 4.) Размеры базовых типов в 32-х разрядных Си-компиляторах:
1 для char, 2 для short,
4 для int, long и float,
и 8 для double. Указатель, содержащий 32-ух битный
адрес имеет также размер 4 байта.

Чтобы обращаться к структурам языка Си, необходимо знать смещение от базового
адреса структуры до интересующего вас поля. Вы можете просто это делать, конвертируя
определения Си-структур в определения структур NASM (STRUC),
или вычисляя одно смещение и используя только его.

Чтобы делать это проще, вам необходимо причитать руководство по вашему компилятору
языка Си и найти как он организует структуры данных. NASM не делает специальных
выравниваний членов структуры в его макросе STRUC,
поэтому вы должны указывать выравнивания самостоятельно, если Си-компилятор
генерирует их. Обычно вы можете обнаружить, что подобная структура

struct { 
    char c; 
    int i; 
} foo;

имеет размер не 5 байт, а 8, так как int поле
будет выровнено на границу двойного слова. Однако, эту возможность иногда можно
настроить в Си-компиляторе, используя параметры командной строки или #pragma
директивы, поэтому можно найти как ваш компилятор это делает.

Что такое ассемблер?

Само слово ассемблер (assembler) переводится с английского как «сборщик». На самом деле так называется программа-транслятор, принимающая на входе текст, содержащий условные обозначения машинных команд, удобные для человека, и переводящая эти обозначения в последовательность соответствующих кодов машинных команд, понятных процессору. В отличие от машинных команд, их условные обозначения, называемые также мнемониками, запомнить сравнительно легко, так как они представляют собой сокращения от английских слов. В дальнейшем мы будем для простоты именовать мнемоники ассемблерными командами. Язык условных обозначений и называется языком ассемблера.

На заре компьютерной эры первые ЭВМ занимали целые комнаты и весили не одну тонну, имея объем памяти с воробьиный мозг, а то и того меньше. Единственным способом программирования в те времена было вбивать программу в память компьютера непосредственно в цифровом виде, переключая тумблеры, проводки и кнопочки. Число таких переключений могло достигать нескольких сотен и росло по мере усложнения программ. Встал вопрос об экономии времени и денег. Поэтому следующим шагом в развитии стало появление в конце сороковых годов прошлого века первого транслятора-ассемблера, позволяющего удобно и просто писать машинные команды на человеческом языке и в результате автоматизировать весь процесс программирования, упростить, ускорить разработку программ и их отладку. Затем появились языки высокого уровня и компиляторы (более интеллектуальные генераторы кода с более понятного человеку языка) и интерпретаторы (исполнители написанной человеком программы на лету). Они совершенствовались, совершенствовались — и, наконец, дошло до того, что можно просто программировать мышкой.

Таким образом, ассемблер — это машинно ориентированный язык программирования, позволяющий работать с компьютером напрямую, один на один. Отсюда и его полная формулировка — язык программирования низкого уровня второго поколения (после машинного кода). Команды ассемблера один в один соответствуют командам процессора, но поскольку существуют различные модели процессоров со своим собственным набором команд, то, соответственно, существуют и разновидности, или диалекты, языка ассемблера. Поэтому использование термина «язык ассемблера» может вызвать ошибочное мнение о существовании единого языка низкого уровня или хотя бы стандарта на такие языки. Его не существует. Поэтому при именовании языка, на котором написана конкретная программа, необходимо уточнять, для какой архитектуры она предназначена и на каком диалекте языка написана. Поскольку ассемблер привязан к устройству процессора, а тип процессора жестко определяет набор доступных команд машинного языка, то программы на ассемблере не переносимы на иную компьютерную архитектуру.

Поскольку ассемблер всего лишь программа, написанная человеком, ничто не мешает другому программисту написать свой собственный ассемблер, что часто и происходит

На самом деле не так уж важно, язык какого именно ассемблера изучать. Главное — понять сам принцип работы на уровне команд процессора, и тогда не составит труда освоить не только другой ассемблер, но и любой другой процессор со своим набором команд

Реализация

Windows

В качестве ассемблера для NASM используется nasm 2.11.02, в качестве компоновщика — gcc 4.6.2 из MinGW (gcc 4.8.1 из MinGW64 в режиме x64) или ld 2.22 из MinGW (ld 2.23.2 из MinGW64 в режиме x64).

Версии ассемблеров и компоновщиков для NASM подобраны с учетом рекомендуемых программ для курса «Архитектура ЭВМ и язык ассемблера» ВМК МГУ 1-го потока.

Также в программу включен отладчик gdb 7.4 (7.6 для x64) из пакета MinGW и немного измененная для отладки библиотека макросов ввода-вывода.

Начиная с версии 3.0, в SASM включены fasm 1.71.39 и gas 2.23.1 из MinGW (gas 2.23.2 из MinGW64).

Ассемблер MASM невозможно было включить в сборку из-за его лицензии. Чтобы им воспользоваться, Вы должны установить MASM на Ваш компьютер с сайта https://www.masm32.com/ и указать пути до ассемблера (ml.exe, путь обычно «C:/masm32/bin/ml.exe») и до компоновщика (link.exe, путь обычно «C:/masm32/bin/link.exe») в соответствующих полях на вкладке «Построение».

Под Windows SASM после установки сразу готов к работе.

Linux

Для работы программы на Linux должны быть установлены: nasm или gas (если их планируется использовать, fasm уже включён в сборку), gcc, gdb (для отладки).

Больше информации о программе и её использовании можно получить в Wiki проекта на GitHub.

Базовая теория

Первое, что запускает процессор при включении компьютера — это код BIOS (или же UEFI, но здесь я буду говорить только про BIOS), который «зашит» в памяти материнской платы (конкретно — по адресу 0xFFFFFFF0).

Сразу после включения BIOS запускает Power-On Self-Test (POST) — самотестирование после включения. BIOS проверяет работоспособность памяти, обнаруживает и инициализирует подключенные устройства, проверяет регистры, определяет размер памяти и так далее и так далее.

Следующий шаг — определение загрузочного диска, с которого можно загрузить ОС. Загрузочный диск — это диск (или любой другой накопитель), у которого последние 2 байта первого сектора (под первым сектором подразумевается первые 512 байт накопителя, т.к. 1 сектор = 512 байт) равны 55 и AA (в шестнадцатеричном формате). Как только загрузочный диск будет найден, BIOS загрузит первые его 512 байт в оперативную память по адресу 0x7c00 и передаст управление процессору по этому адресу.

Само собой, в эти 512 байт не выйдет уместить полноценную операционную систему. Поэтому обычно в этот сектор кладут первичный загрузчик, который загружает основной код ОС в оперативную память и передает ему управление.

С самого начала процессор работает в Real Mode (= 16-битный режим). Это означает, что он может работать лишь с 16-битными данными и использует сегментную адресацию памяти, а также может адресовать только 1 Мб памяти. Но вторым мы пользоваться здесь не будем. Картинка ниже показывает состояние оперативной памяти при передаче управления нашему коду (картинка взята отсюда).

Последнее, о чем стоит сказать перед практической частью — прерывания. Прерывание — это особый сигнал (например, от устройства ввода, такого, как клавиатура или мышь) процессору, который говорит, что нужно немедленно прервать исполнение текущего кода и выполнить код обработчика прерывания. Все адреса обработчиков прерывания находятся в Interrupt Descriptor Table (IDT) в оперативной памяти. Каждому прерыванию соответствует свой обработчик прерывания. Например, при нажатии клавиши клавиатуры вызывается прерывание, процессор останавливается, запоминает адрес прерванной инструкции, сохраняет все значения своих регистров (на стеке) и переходит к выполнению обработчика прерывания. Как только его выполнение заканчивается, процессор восстанавливает значения регистров и переходит обратно, к прерванной инструкции и продолжает выполнение.

Например, чтобы вывести что-то на экран в BIOS используется прерывание 0x10 (шестнадцатеричный формат), а для ожидания нажатия клавиши — прерывание 0x16. По сути, это все прерывания, что нам понадобятся здесь.

Также, у каждого прерывания есть своя подфункция, определяющая особенность его поведения. Чтобы вывести что-то на экран в текстовом формате (!), нужно в регистр AH занести значение 0x0e. Помимо этого, у прерываний есть свои параметры. 0x10 принимает значения из ah (определяет конкретную подфункцию) и al (символ, который нужно вывести). Таким образом,

выведет на экран символ ‘x’. 0x16 принимает значение из ah (конкретная подфункция) и загружает в регистр al значение введенной клавиши. Мы будем использовать функцию 0x0.

Ассемблер — программирование или искусство?

Скажем так, все зависит от того, в чьих руках он находится. Ассемблер — это первичный элемент мира процессора, из сочетаний этих элементов складывается его душа, его самосознание. Подобно тому, как вся музыка, написанная в истории человечества, состоит из сочетаний семи нот, так и сочетание ассемблерных команд наполняет компьютерный мир цифровой жизнью. Кто знает лишь три аккорда — это «попса», кому же известна вся палитра — это классика.

Почему же наука так жаждет проникнуть в квантовые глубины и захватить в свои руки неуловимый первичный кирпичик материи? Чтобы получить над ней власть, изменять ее по своей воле, стать на уровень Творца Вселенной. В чьи руки попадет такая власть — это еще вопрос. В отличие от науки, в мире программирования тайн нет, нам известны кирпичики, его составляющие, а следовательно, и та власть над процессором, которую нам дает знание ассемблера.

Чтобы программирование на языке ассемблера поднялось на уровень искусства, нужно постичь его красоту, скрывающуюся за потоком единиц и нулей. Как и в любой отрасли человеческой деятельности, в программировании можно быть посредственностью, а можно стать Мастером. И то и другое отличает степень культуры, образования, труда и, главное, то, сколько души автор вкладывает в свое творение.

Ассемблер и терминатор

Не так давно Джеймс Кэмерон выпустил в свет 3D-версию второго «Терминатора», и в качестве интересного исторического факта можно отметить один любопытный момент из жизни киборга-убийцы…

Кадр из фильма «Терминатор»

Здесь мы видим «зрение» терминатора, а слева на нем отображается ассемблерный листинг. Судя по нему, знаменитый Уничтожитель работал на процессоре MOS Technology 6502 либо на MOS Technology 6510. Этот процессор впервые был разработан в 1975 году, использовался на компьютерах Apple и, помимо всего прочего, на знаменитых игровых приставках того времени Atari 2600 и Nintendo Entertainment System (у нас более известной как Dendy). Имел лишь три 8-разрядных регистра: А-аккумулятор и два индексных регистра X и Y. Такое малое их количество компенсировалось тем, что первые 256 байт оперативной памяти (так называемая нулевая страница) могли адресоваться специальным образом и фактически использовались в качестве 8-разрядных или 16-разрядных регистров. У данного процессора было 13 режимов адресации на всего 53 команды. У терминатора идет цепочка инструкций LDA-STA-LDA-STA… В семействе 6502 программы состояли чуть менее чем полностью из LDA/LDY/LDX/STA/STX/STY:

Чтение и запись в порты ввода-вывода также выполнялись этими командами, и программа терминатора имеет вполне осмысленный вид, а не представляет собой бестолковую фантазию сценариста: MOS Technology 6502 / Система команд.

6.5.4 elf-расширение директивы COMMON

ELF позволяет задавать требования к выравниванию общих переменных. Это делается
путем помещения числа (которое должно быть степенью двойки) после имени и размера
общей переменной и отделения этого числа (как обычно) двоеточием. Например,
массив двойных слов, который должен быть выровнен по двойным словам:

          common dwordarray 128:4

Эта строка объявляет массив размером 128 байт и требует, чтобы он был выровнен
по 4-байтной границе.

6.6 aout: Объектные файлы
a.out Линукс

Формат aout генерирует объектные файлы a.out
в форме, используемой устаревшими Линукс системами. (Он отличается от других
объектных файлов a.out магическим числом в первых
четырех байтах файла. Также некоторые реализации a.out,
например NetBSD, поддерживают позиционно-независимый код, который реализация
Линукс не знает).

Формат a.out подразумевает расширение выходных
файлов по умолчанию .o.

Этот формат очень простой. Он не поддерживает специальных директив и символов,
не использует SEG или WRT
и в нем нет расширений никаких стандартных директив. Он поддерживает только
три стандартных секции с именами .text, .data и
.bss.

6.7 aoutb: Объектные файлы
a.out NetBSD/FreeBSD/OpenBSD

Формат aoutb генерирует объектные файлы a.out
в форме, используемой различными BSD-клонами UNIX: NetBSD, FreeBSD и OpenBSD.
Для большинства объектных файлов этот формат не отличается от aout
за исключением магического числа в первых четырех байтах файла. Однако формат
поддерживает (как и формат elf) позиционно-независимый
код, поэтому вы можете использовать его для написания разделяемых библиотек
BSD.

Расширение объектных файлов формата aoutb по умолчанию
.o.

Формат не поддерживает специальных директив и символов и имеет только три стандартных
секции с именами .text, .data и .bss.
Несмотря на это, для обеспечения типов перемещений в позиционно-независимом
коде он поддерживает использование WRT так же,
как это делает elf. Более подробно это описано
в .

aoutb, как и elf
поддерживает также расширение директивы GLOBAL:
см. .

6.8 as86: Объектные файлы
as86 Линукс

16-битный ассемблер Линукс as86 имеет свой собственный
нестандартный формат объектных файлов. Хотя его компаньон компоновщик ld86
выдает что-то близкое к обычным бинарникам a.out,
объектный формат, используемый для взаимодействия между as86
и ld86, все же не является a.out.

NASM на всякий случай поддерживает данный формат как as86.
Расширение выходного файла по умолчанию для данного формата .o.

Формат as86 это очень простой объектный
формат (с точки зрения NASM). Он не поддерживает специальных директив и символов,
не использует SEG и WRT,
и в нем нет никаких расширений стандартных директив. Он поддерживает только
три стандартных секции с именами .text, .data и
.bss.

6.9 rdf: Перемещаемые динамические
объектные файлы

Выходной формат rdf создает объектные файлы RDOFF.
RDOFF это «доморощенный» формат объектных файлов, разработанный вместе
с NASM и отражающий в себе внутреннюю структуру ассемблера.

RDOFF не используется никакими широко известными операционными системами. Однако
тот, кто пишет собственную систему, возможно захочет использовать его в качестве
собственного объектного формата, так как разработан он прежде всего для упрощения
и содержит очень мало бюрократии в заголовках файлов.

Архив Unix NASM и архив DOS с исходниками имеют подкаталог rdoff,
содержащий набор RDOFF-утилит: RDF-компоновщик, менеджер статических библиотек,
утилита, делающая дамп RDF-файла, и программа, загружающая и выполняющая RDF-исполнимый
файл под Линукс.

Формат rdf поддерживает только стандартные секции
с именами .text, .data и .bss.

8.2.2 Нахождение ваших локальных переменных

Имея ГТС, вы можете использовать ее для получения адресов ваших переменных.
Большинство переменных будут постоянно находится в секциях, которые вы объявили;
к ним можно получить доступ, используя ..gotoff
специальный тип WRT. Вот как это работает:

          lea eax,

Выражение myvar wrt ..gotoff вычисляется, затем
разделяемая библиотека линкуется, чтобы оно стало смещением локальной переменной
myvar от начала ГТС Поэтому, прибавление его к
EBX, как это показано выше, помещает настоящий
адрес myvar в EAX.

Если объявить переменные как GLOBAL без указания
их размера, они разделяются между фрагментами кода в библиотеке, но не могут
быть экспортированы из библиотеки в программу, которая ее загрузила. Они будут
по прежнему в вашей обычных секциях data и BSS,
поэтому доступ к ним можно получить таким же методом, что и к локальным переменных,
используя описанный ранее механизм ..gotoff.

Обратите внимание, что из-за специфики в BSD a.out
формата описатели этого перемещаемого типа, должно существовать хотя бы одно
нелокальное имя в той же секции, что и адрес, к которому вы обращаетесь

Компиляция и компоновка

NASM компилирует программы под различные операционные системы в пределах x86-совместимых процессоров. Находясь в одной операционной системе, можно беспрепятственно откомпилировать исполняемый файл для другой.

Компиляция программ в NASM состоит из двух этапов. Первый — ассемблирование, второй — компоновка. На этапе ассемблирования создаётся объектный код. В нём содержится машинный код программы и данные, в соответствии с исходным кодом, но идентификаторы (переменные, символы) пока не привязаны к адресам памяти. На этапе компоновки из одного или нескольких объектных модулей создаётся исполняемый файл (программа). Операция компоновки связывает идентификаторы, определённые в основной программе, с идентификаторами, определёнными в остальных модулях, после чего всем идентификаторам даются окончательные адреса памяти или обеспечивается их динамическое выделение.

Для компоновки объектных файлов в исполняемые в Windows можно использовать свободный бесплатно распространяемый компоновщик alink(для 64-х битных программ компоновщик GoLink), а в Linux — компоновщик ld, который есть в любой версии этой операционной системы.

Для ассемблирования файла нужно ввести следующую команду:

nasm -f format filename -o output

Инструкции перехода

Компилятор обрабатывает текст программы в несколько проходов, благодаря чему можно инструкции перехода размещать до объявления соответствующих меток.

В командах условного и безусловного () переходов используется по умолчанию ближний тип переходов — . Поэтому при возможности короткого перехода, чтобы не завысить размер программы на лишний байт, необходимо специально указать тип перехода . С версии 0.98.09b были добавлены опции оптимизации -Ox, которые позволяют автоматически оптимизировать размер инструкций перехода, в более ранних версиях или без таких опций минимальный размер программы можно получить только ручной модификацией исходного кода.

Оцените статью
Рейтинг автора
5
Материал подготовил
Андрей Измаилов
Наш эксперт
Написано статей
116
Добавить комментарий