Опубликован: 01.03.2016 | Уровень: для всех | Доступ: платный
Лекция 3:

Синтаксис ассемблера

< Лекция 2 || Лекция 3: 12 || Лекция 4 >

Команды

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

  • Конкретное значение, известное на этапе компиляции, - например, числовая константа или символ. Записываются при помощи знака $, например: $0xf1, $10, $hello_str. Эти операнды называются непосредственными.
  • Регистр. Перед именем регистра ставится знак %, например: %eax, %bx, %cl.
  • Указатель на ячейку в памяти (как он формируется и какой имеет синтаксис записи - далее в этом разделе).
  • Неявный операнд. Эти операнды не записываются непосредственно в исходном коде, а подразумеваются. Нет, конечно, компьютер не читает ваши мысли. Просто некоторые команды всегда обращаются к определённым регистрам без явного указания, так как это входит в логику их работы. Такое поведение всегда описывается в документации.

Внимание! Если вы забудете знак $, когда записываете непосредственное числовое значение, компилятор будет интерпретировать число как абсолютный адрес. Это не вызовет ошибок компиляции, но, скорее всего, приведёт к ошибке сегментации (segmentation fault) при выполнении.

Почти у каждой команды можно определить операнд-источник (из него команда читает данные) и операнд-назначение (в него команда записывает результат). Общий синтаксис команды ассемблера такой:

 Операция    Источник, Назначение

Для того, чтобы привести пример команды, я, немного забегая наперед, расскажу об одной операции. Команда mov источник, назначение производит копирование источника в назначение. Возьмем строку из hello.s:

movl    $4, %eax              /* поместить номер системного вызова
                                 write = 4 в регистр %eax           */

Как видим, источник - это непосредственное значение 4, а назначение - регистр %eax. Суффикс l в имени команды указывает на то, что ей следует работать с операндами длиной в 4 байта. Все суффиксы:

  • b (от англ. byte) - 1 байт,
  • w (от англ. word) - 2 байта,
  • l (от англ. long) - 4 байта,
  • q (от англ. quad) - 8 байт.

Таким образом, чтобы записать $42 в регистр %al (а он имеет размер 1 байт):

movb    $42, %al

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

Как формируется указатель на ячейку памяти? Синтаксис:

 смещение(база, индекс, множитель)

Вычисленный адрес будет равен база + индекс ? множитель + смещение. Множитель может принимать значения 1, 2, 4 или 8. Например:

  • (%ecx) адрес операнда находится в регистре %ecx. Этим способом удобно адресовать отдельные элементы в памяти, например, указатель на строку или указатель на int;
  • 4(%ecx) адрес операнда равен %ecx + 4. Удобно адресовать отдельные поля структур. Например, в %ecx адрес некоторой структуры, второй элемент которой находится "на расстоянии" 4 байта от её начала (говорят "по смещению 4 байта");
  • -4(%ecx) адрес операнда равен %ecx ? 4;
  • foo(,%ecx,4) адрес операнда равен foo + %ecx ? 4, где foo - некоторый адрес. Удобно обращаться к элементам массива. Если foo - указатель на массив, элементы которого имеют размер 4 байта, то мы можем заносить в %ecx номер элемента и таким образом обращаться к самому элементу.

Ещё один важный нюанс: команды нужно помещать в секцию кода. Для этого перед командами нужно указать директиву .text. Вот так:

.text
        movl    $42, %eax
        ...

Данные

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

  • .byte - размещает каждое выражение как 1 байт;
  • .short - 2 байта;
  • .long - 4 байта;
  • .quad - 8 байт.

Например:

.byte   0x10, 0xf5, 0x42, 0x55
.long   0xaabbaabb
.short  -123, 456

Также существуют директивы для размещения в памяти строковых литералов:

  • .ascii "STR" размещает строку STR. Нулевых байтов не добавляет.
  • .string "STR" размещает строку STR, после которой следует нулевой байт (как в языке Си).
  • У директивы .string есть синоним .asciz (z от англ. zero - ноль, указывает на добавление нулевого байта).

Строка-аргумент этих директив может содержать стандартные escape-последовательности, которые вы использовали в Си, например, \n, \r, \t, \\, \" и так далее.

Данные нужно помещать в секцию данных. Для этого перед данными нужно поместить директиву .data. Вот так:

.data
        .string "Hello, world\n"
        ...

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

.section .rodata
        .string "program version 0.314"

Приведём небольшую таблицу, в которой сопоставляются типы данных в Си на IA-32 и в ассемблере. Нужно заметить, что размер этих типов в языке Си на других архитектурах (или даже компиляторах) может отличаться.

Тип данных в Си Размер (sizeof), байт Выравнивание, байт Название
Char signed char 1 1 signed byte (байт со знаком)
Unsigned char 1 1 unsigned byte (байт без знака)
Short signed short 2 2 signed halfword (полуслово со знаком)
Unsigned short 2 2 unsigned halfword (полуслово без знака)
Int signed int long signed long enum 4 4 signed word (слово со знаком)
unsigned int unsigned long 4 4 unsigned word (слово без знака)
< Лекция 2 || Лекция 3: 12 || Лекция 4 >
Константин Белюстин
Константин Белюстин
Украина, г. Киев
Максим Барашков
Максим Барашков
Россия, Якутск