Rambler's
Top100

Ассемблер для UNIX - Tutorial

Автор: lonesome TSH/Digital Daemons
Дата: .01.2003
Раздел: Низкоуровневое программирование в Linux

Итак, хаксор, ты решил сделать еще один шаг к совершенству и полностью отказаться от использования Windows. Одна из проблем, которая поджидает тебя в *nix'ах состоит в том, что местный стандартный ассемблер (команда 'as') имеет синакисис AT&T, и, следовательно, жутко непохож на всеми любимые мастдайные Tasm и Masm. Это вовсе не означает, что он хуже, напротив, он лишен привычки додумывать программу за программера (это характерно не только для виндовских ассемблеров, но и для Windows вообще %]).
Т.е. в Masm команда mov var, 3 могла произвести совершенно различные действия в зависимости от того, какой размер имеет переменная var. Таких глупостей нет в AT&T ассемблерах. Никакая команда не должна требовать уточнений, т.е. в подобной ситуации AT&T ассемблер попросту попросил бы указать с операндами какого размера имеет дело эта команда.

Еще один камень преткновения начинающих программеров на этом ассемблере - порядок следования операндов. Он полностью противоположен обычным ассемблерам, т.е. - сначала идет источник, а затем приемник. (Кажется нелогичным? Это только кажется %) Попробуй проговорить словами сначала команду с Intel-синтаксисом, а теперь поменяй операнды местами. Чувствуешь, как все стало логичнее?).

Как я уже говорил, любая команда принимающая операнды требует указания размерности этих операндов. Размер указывается добавлением к стандартной мнемонике команды (которые совпадают с мнемониками Masm) суффикса ('b' - операнды размером 1 байт, 'w' - 2 байта (word), 'l' - четыре байта (dword), 'q' - 8 байт, 's' - 4 байт с плавающей точкой, 'l' - 8 байт с плавающей точкой 't' - 10 байт с плавающей точкой).

Например:
movl %eax, %ebx |поместить EAX в EBX (размер операндов четыре байта)

Обозначения регистров начинаются со знака '%', непосредственных значений (в том числе и адресов переменных) - с '$'. То есть вместо 'offset variable' нужно писать '$variable'.
Если же в команде используется не адрес, а значение переменной то никаких символов перед ее именем добавлять не нужно.

pushl $variable | аналог push offset variable в Masm, если переменная имеет размер 4 байта.

Если последовательность символов заканчивается двоеточием, то она воспринимается как метка. В командах перехода типа jmp метки используются без префиксов, например:
lbl: jmp lbl

Префиксы замены сегментов являются отдельными командами ассемблера: segcs, segds, segfs и т.д.

Адресация в общем случае выглядит так:
смещение (базовый регистр, индексный регистр, множитель)
Например:
movl var(%ebx, %esi, 2), %edx | аналог mov edx, var[ebx+esi*2] в Masm
Если какой-нибудь элемент в адресации отсутствует (напр. нет базового регистра и т.п.), то он просто не пишется, но все запятые, разделяющие поля, сохраняются. Т.е: movl var(,%esi,2), %edx |аналог mov edx, var[esi*2] в Masm Обрати внимание, что множитель, хотя и является константой, но не требует символа '$'.

Основное применение ассемблера в Unix - встраивание процедур на ассемблере в программы на С, дабы оптимизировать и ускорить их выполнение. Коснемся этого момента поподробнее.
Наиболее широко распространенный компилятор на Unix-системах - это свободно распространяемый GCC (GNU Compiler Collection). Чтобы встроить кусок кода на ассемблере в программу, компилируемую GCC, достаточно написать:
__asm__("ассемблерный код");
Строка, переданная этой директиве в качестве параметра без изменений войдет в сгенерированный компилятором ассемблерный исходник (файл с расширением .s), т.е. команды приходится разделять символом "\n" (переход строки). Очевидно, что это выглядит немного неудобочитаемо в одной строке, поэтому принято писать так:


__asm__( "команда\n\t"
"команда\n\t"
...
);

Это имитирует обычный многострочный вид ассемблерного исходника.

Обычно встраиваемый ассемблерный код взаимодействует c переменными программы.
Использование локальных переменных влечет некоторые трудности (поскольку они хранятся в стеке, и ссылка на них имеет вид типа %esp+8) и поэтому лучше использовать глобальные переменные (определенные вне функций). При использовании GCC их имена можно писать без изменений (Но не забывай указывать правильный тип данных. (Обычно char=byte, short=word, int=dword, long=qword, single=single и т.д. Размер переменной в байтах на конкретной системе всегда можно выяснить оператором C sizeof(type). Например sizeof(int) на большинстве систем (но не всегда) будет равно 4.)

Также существуют компиляторы С для Windows, использующие ассемблер AT&T. Одним их таких является отличный компилятор LCC. Использование ассемблера в нем отличается от GCC следующим:
1. Ассемблер вызывается оператором _asm("ассемблерный код");
2. Для использования глобальных переменных перед их именем необходимо ставить символ '_' (подчеркивание).

Например:
_asm("movl $1, _a\n\t"); эквивалентно следующей строчке на С (a - глобальная переменная типа int):
a=1;

Rambler's Top100