Команды ассемблера
Команда 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 /* подобрать соответствующую команду условного перехода для повторения цикла */