Опубликован: 01.03.2016 | Доступ: свободный | Студентов: 583 / 62 | Длительность: 03:55:00
Лекция 5:

Команды ассемблера

Команда 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  \ne 0, передать управление на метку.

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