Команды ассемблера
Команда lea для арифметики
Для выполнения некоторых арифметических операций можно использовать команду lea2Intel® 64 and IA-32 Architectures Optimization Reference Manual, 3.5.1.3 Using LEA. Она вычисляет адрес своего операнда-источника и помещает этот адрес в операнд-назначение. Ведь она не производит чтение памяти по этому адресу, верно? А значит, всё равно, что она будет вычислять: адрес или какие-то другие числа.
Вспомним, как формируется адрес операнда:
смещение(база, индекс, множитель)
Вычисленный адрес будет равен база + индекс ? множитель + смещение.
Чем это нам удобно? Так мы можем получить команду с двумя операндами-источниками и одним результатом:
movl $10, %eax movl $7, %ebx leal 5(%eax) ,%ecx /* %ecx = %eax + 5 = 15 */ leal -3(%eax) ,%ecx /* %ecx = %eax - 3 = 7 */ leal (%eax,%ebx) ,%ecx /* %ecx = %eax + %ebx ? 1 = 17 */ leal (%eax,%ebx,2) ,%ecx /* %ecx = %eax + %ebx ? 2 = 24 */ leal 1(%eax,%ebx,2),%ecx /* %ecx = %eax + %ebx ? 2 + 1 = 25 */ leal (,%eax,8) ,%ecx /* %ecx = %eax ? 8 = 80 */ leal (%eax,%eax,2) ,%ecx /* %ecx = %eax + %eax ? 2 = %eax ? 3 = 30 */ leal (%eax,%eax,4) ,%ecx /* %ecx = %eax + %eax ? 4 = %eax ? 5 = 50 */ leal (%eax,%eax,8) ,%ecx /* %ecx = %eax + %eax ? 8 = %eax ? 9 = 90 */
Вспомните, что при сложении командой add результат записывается на место одного из слагаемых. Теперь, наверно, стало ясно главное преимущество lea в тех случаях, где её можно применить: она не перезаписывает операнды-источники. Как вы это сможете использовать, зависит только от вашей фантазии: прибавить константу к регистру и записать в другой регистр, сложить два регистра и записать в третий… Также lea можно применять для умножения регистра на 3, 5 и 9, как показано выше.
Команда loop
loop метка
Принцип работы:
- уменьшить значение регистра %ecx на 1;
- если %ecx = 0, передать управление следующей за loop команде;
- если %ecx
, передать управление на метку.
Напишем программу для вычисления суммы чисел от 1 до 10 (конечно же, воспользовавшись формулой суммы арифметической прогрессии, можно переписать этот код и без цикла - но ведь это только пример).
.data
printf_format:
.string "%d\n "
.text
.globl main
main:
movl $0, %eax /* в %eax будет результат, поэтому в
начале его нужно обнулить */
movl $10, %ecx /* 10 шагов цикла */
sum:
addl %ecx, %eax /* %eax = %eax + %ecx */
loop sum
/* %eax = 55, %ecx = 0 */
/*
* следующий код выводит число в %eax на экран и завершает программу
*/
pushl %eax
pushl $printf_format
call printf
addl $8, %esp
movl $0, %eax
ret
#include <stdio.h >
int main()
{
int eax, ecx;
eax = 0;
ecx = 10;
do
{
eax += ecx;
} while(--ecx);
printf( "%d\n ", eax);
return 0;
}
Команды сравнения и условные переходы. Безусловный переход
Команда loop неявно сравнивает регистр %ecx с нулём. Это довольно удобно для организации циклов, но часто циклы бывают намного сложнее, чем те, что можно записать при помощи loop. К тому же нужен эквивалент конструкции if(){}. Вот команды, позволяющие выполнять произвольные сравнения операндов:
cmp операнд_2, операнд_1
Команда cmp выполняет вычитание операнд_1 - операнд_2 и устанавливает флаги. Результат вычитания нигде не запоминается.
Сравнили, установили флаги, - и что дальше? А у нас есть целое семейство jump-команд, которые передают управление другим командам. Эти команды называются командами условного перехода. Каждой из них поставлено в соответствие условие, которое она проверяет. Синтаксис:
jcc метка
Команды jcc не существует, вместо cc нужно подставить мнемоническое обозначение условия.
| Мнемоника | Английское слово | Смысл | Тип операндов |
|---|---|---|---|
| E | equal | равенство | любые |
| N | not | инверсия условия | любые |
| G | greater | больше | со знаком |
| L | less | меньше | со знаком |
| A | above | больше | без знака |
| B | below | меньше | без знака |
Таким образом, je проверяет равенство операндов команды сравнения, jl проверяет условие операнд_1 < операнд_2 и так далее. У каждой команды есть противоположная: просто добавляем букву n:
- je - jne: равно - не равно;
- jg - jng: больше - не больше.
Теперь пример использования этих команд:
.text
/* Тут пропущен код, который получает некоторое значение в %eax.
Пусть нас интересует случай, когда %eax = 15 */
cmpl $15, %eax /* сравнение */
jne not_equal /* если операнды не равны, перейти на
метку not_equal */
/* сюда управление перейдёт только в случае, когда переход не
сработал, а значит, %eax = 15 */
not_equal:
/* а сюда управление перейдёт в любом случае */if(eax == 15)
{
/* сюда управление перейдёт только в случае, когда переход не сработал,
а значит, %eax = 15 */
}
/* а сюда управление перейдёт в любом случае */Кроме команд условного перехода, область применения которых ясна сразу, также существует команда безусловного перехода. Эта команда чем-то похожа на оператор goto языка Си. Синтаксис:
jmp адрес
Эта команда передаёт управление на адрес, не проверяя никаких условий. Заметьте, что адрес может быть задан в виде непосредственного значения (метки), регистра или обращения к памяти.
Произвольные циклы
Все инструкции для написания произвольных циклов мы уже рассмотрели, осталось лишь собрать всё воедино. Лучше сначала посмотрите код программы, а потом объяснение к ней. Прочитайте её код и комментарии и попытайтесь разобраться, что она делает. Если сразу что-то непонятно - не страшно, сразу после исходного кода находится более подробное объяснение.
Программа: поиск наибольшего элемента в массиве
.data
printf_format:
.string "%d\n "
array:
.long -10, -15, -148, 12, -151, -3, -72
array_end:
.text
.globl main
main:
movl array, %eax /* в %eax будет храниться результат;
в начале наибольшее значение - array[0]*/
movl $array+4, %ebx /* в %ebx находится адрес текущего
элемента массива */
jmp ch_bound /* проверить границы массива */
loop_start: /* начало цикла */
cmpl %eax, (%ebx) /* сравнить текущий элемент массива с
текущим наибольшим значением из %eax
*/
jle less /* если текущий элемент массива меньше
или равен наибольшему, пропустить
следующий код */
movl (%ebx), %eax /* а вот если элемент массива
превосходит наибольший, значит, его
значение и есть новый максимум */
less:
addl $4, %ebx /* увеличить %ebx на размер одного
элемента массива, 4 байта */
ch_bound:
cmpl $array_end, %ebx /* сравнить адрес текущего элемента и
адрес конца массива */
jne loop_start /* если они не равны, повторить цикл снова*
/*
* следующий код выводит число из %eax на экран и завершает программу
*/
pushl %eax
pushl $printf_format
call printf
addl $8, %esp
movl $0, %eax
ret
Сначала мы заносим в регистр %eax число array[0]. После этого мы сравниваем каждый элемент массива, начиная со следующего (нам незачем сранивать нулевой элемент с самим собой), с текущим наибольшим значением из %eax, и, если этот элемент больше, он становится текущим наибольшим. После просмотра всего массива в %eax находится наибольший элемент. Отметим, что если массив состоит из 1 элемента, то следующий после нулевого элемента будет находиться за границей массива, поэтому перед циклом стоит безусловный переход на проверку границы.
Этот код соответствует приблизительно следующему на Си:
#include <stdio.h >
int main()
{
static int array[] = { -10, -15, -148, 12, -151, -3, -72 };
static int *array_end = &array[sizeof(array) / sizeof(int)];
int max = array[0];
int *p = array+1;
while (p != array_end)
{
if(*p > max)
{
max = *p;
}
p++;
}
printf( "%d\n ", max);
return 0;
}
Возможно, такой способ обхода массива не очень привычен для вас. В Си принято использовать переменную с номером текущего элемента, а не указатель на него. Никто не запрещает пойти этим же путём и на ассемблере:
.data
printf_format:
.string "%d\n "
array:
.long 10, 15, 148, -3, 151, 3, 72
array_size:
.long (. - array)/4 /* количество элементов массива */
.text
.globl main
main:
movl array, %eax /* в %eax будет храниться результат;
в начале наибольшее значение - array[0] */
movl $1, %ecx /* начать просмотр с первого элемента
*/
jmp ch_bound
loop_start: /* начало цикла */
cmpl %eax, array(,%ecx,4) /* сравнить текущий элемент
массива с текущим наибольшим
значением из %eax */
jle less /* если текущий элемент массива меньше
или равен наибольшему, пропустить
следующий код */
movl array(,%ecx,4), %eax /* а вот если элемент массива
превосходит наибольший, значит, его
значение и есть новый максимум */
less:
incl %ecx /* увеличить на 1 номер текущего
элемента */
ch_bound:
cmpl array_size, %ecx /* сравнить номер текущего элемента с
общим числом элементов */
jne loop_start /* если они не равны, повторить цикл снова */
/*
* следующий код выводит число в %eax на экран и завершает программу
*/
pushl %eax
pushl $printf_format
call printf
addl $8, %esp
movl $0, %eax
ret
Рассматривая код этой программы, вы, наверно, уже поняли, как создавать произвольные циклы с постусловием на ассемблере, наподобие do{} while(); в Си. Ещё раз повторю эту конструкцию, выкинув весь код, не относящийся к циклу:
loop_start: /* начало цикла */
/* вот тут находится тело цикла */
cmpl ... /* что-то с чем-то сравнить для
принятия решения о выходе из цикла */
jne loop_start /* подобрать соответствующую команду
условного перехода для повторения цикла */
В Си есть ещё один вид цикла, с проверкой условия перед входом в тело цикла (цикл с предусловием): while(){}. Немного изменив предыдущий код, получаем следующее:
jmp check
loop_start: /* начало цикла */
/* вот тут находится тело цикла */
check:
cmpl ... /* что-то с чем-то сравнить для
принятия решения о выходе из цикла */
jne loop_start /* подобрать соответствующую команду
условного перехода для повторения цикла */
