Автор: 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" (переход строки). Очевидно, что это выглядит
немного неудобочитаемо в одной строке, поэтому принято писать так:
Обычно встраиваемый ассемблерный код взаимодействует 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;