Лекции по Вычислительным машинам, системам и сетям   

5. Программирование на языке Ассемблер

5.6. Система команд

Под программной моделью понимается набор внутренних регистров и флагов процессора, которые доступны программисту. Будем использовать программную модель процессора 8086, которая является базовой для всех микропроцессоров фирмы Intel, вплоть до процессора Pentium IV. Эта модель была описана ранее в разд. 2.6

Современные процессоры фирмы Intel имеют развитую систему машинных команд. Книга с описанием всех ассемблерных команд имеет объем более 1000 листов, но начинающему программисту в его программах понадобится от силы 10 – 15 машинных команд. Ни один программист никогда не помнит все эти машинные команды наизусть. Опытный программист просто помнит, «что такая команда есть» и, когда она ему понадобится, обращается к справочнику. Остановимся только на командах, с которыми сразу столкнется в своей работе новичок.

Команда MOV-приемник, источник.

Команда передает содержимое источника в приемник. В качестве источника могут выступать регистр, ячейка памяти и непосредственный операнд (передается число, непосредственно заданное в команде). Приемником могут быть регистр или ячейка памяти. Например:

mov cx, 0b800h

;команда загружает в cх число b800h

mov al, ah

;эта команда переписывает ;содержимое аh в al.

mov perem, si

;эта команда загружает в переменную, ;которую программист назвал perem, ;содержимое регистра si.

mov bp, [bx+4]

;эта команда загружает в регистр bp ;слово из ячейки памяти с адресом ;Аф = (ds)*16+(bx)+4.

Важным является следующий момент: в качестве источника и приемника в одной команде не могут одновременно выступать две ячейки памяти!! То есть команда mov perem, [bx+4] заставит транслятор сформировать сообщение об ошибке. Правильно надо было писать, например, так:

mov ax, [bx + 4]

mov perem, ax.

Отметим также, что все, что сказано выше об источнике и приемнике, справедливо и для всех остальных команд процессора. Приведем еще один пример:

mov [si], 7      ;команда заносит в память по адресу ;Аф = (ds)*16+(si) число 7.

Синтаксически команда написана правильно, а транслятор выдает предупреждение: Argument needs type override. Дело в том, что транслятор не может по такой записи понять, что надо передавать в память ‑ байт или слово? Он может сформировать неверный код операции. О каком формате числа идет речь в такой команде, транслятору должен сообщить программист, написав:

mov byte ptr [si], 7  ;(указатель на байт) речь идет о байте.

mov word ptr [si], 7 ;речь идет о слове.

Команды INC-приемник и DEC-приемник.

Команда inc (инкремент) прибавляет единицу к содержимому приемника. Команда dec (декремент) вычитает единицу из содержимого приемника. Например:

inc cl      ;содержимое регистра cl увеличивается на ;единицу;

dec di     ;содержимое регистра di уменьшается на ;единицу;

inc word ptr [bx]

dec perem.

Команды ADD-приемник, источник и SUB-приемник, источник.

Команда add прибавляет содержимое источника к содержимому приемника, результат заносится в приемник. Команда sub вычитает содержимое источника из содержимого приемника, результат заносится в приемник. Например:

add ah, 32 ;прибавить 32 к содержимому регистра ah

sub dl, ch ;вычесть содержимое ch из содержимого dl ;(результат в dl)

sub perem, bx

add ax, [di]

add byte ptr [bx+si+2], 3.

Команда CMP-приемник, источник.

Команда cmp (сравнение) вычитает содержимое источника из содержимого приемника, но, в отличие от команды sub, результат вычитания никуда не заносится. Результатом работы команды cmp является установка соответствующих флагов в регистре флагов. Команда cmp всегда используется в паре с одной из команд «условного перехода» (je-метка – «перейти, если равно», jne-метка – «перейти, если не равно» и др.). Например:

cmp al, 0

je m1

cmp ax, bx

jne not_equal

cmp byte ptr [si – 14], 0ffh

je exit.

Команда безусловного перехода JMP-метка.

Команда осуществляет безусловный переход на указанную метку. Вместо метки транслятор при трансляции подставит необходимое смещение (число). В качестве метки можно использовать выражение, начинающееся не с цифры. Чтобы транслятор понял, что это метка, после метки ставится двоеточие (не в команде!!). Например:

 _m1:     cmp ah, 3   ; в ah тройка?

jne _m2      ;если нет, прыгаем на _m2.

jmp _m1     ;прыгаем на _m1_m2:

add bx, 32

Команды условных переходов:

 je-метка        ;переход, если равно;

 jz-метка        ;переход, если результат равен нулю (флаг ;zf установлен в единицу). Собственно это ;другая запись команды je

 jne-метка     ;переход, если не равно (эквивалентная ;команда jnz);

 ja-метка        ;переход, если больше;

 jae-метка      ;переход, если больше или равно;

 jb-метка        ;переход, если меньше;

 jbe-метка      ;переход если меньше или равно.

Например:

sub ax, 40       ;вычитаем из ax 40

jnz m17              ;если результат не равен нулю,
; прыгаем на m17

cmp al, bh

jae povtor       ;если содержимое al больше или равно ;содержимому bh, прыгаем на povtor.

Достаточно часто приходится сталкиваться со случаем, когда на синтаксически правильной команде условного перехода транслятор выдает ошибку: Relative jump  out of range. Связано это с тем, что команда условного перехода может обеспечить прыжок только на плюс/минус 128 байт, т.е. приблизительно на 30 – 40 команд (вперед или назад по программе). А если надо прыгнуть на большее расстояние? Применяют команды безусловного перехода (jmp), обеспечивающие прыжок на плюс/минус 64 Кбайта. Например, рассмотрим фрагмент программы:

cmp ax, 0        ;в ax ноль?

je m100           ;если да, прыгаем на m100, если нет,
 ;идем на следующую команду

mov bx, 40

На команде je m100 транслятор выдает вышеуказанную ошибку. Перепишем этот фрагмент:

cmp ax, 0        ;в ax ноль?

jne m200         ;если нет, прыгаем на m200, если да,
;идем на следующую команду

jmp m100        ;прыгаем на m100

m200:  mov bx, 40

Логика программы не изменилась, а вот ошибки больше не будет.

Существует еще достаточно много других команд условных переходов, но их здесь рассматривать не будем.

Команда LOOP-метка.

Команда loop (цикл) вычитает единицу из содержимого регистра cx и, если в результате получился «не ноль», переходит на указанную метку. В качестве примера рассмотрим следующий фрагмент:

mov dh, 0

mov cx, 11      ;число повторений цикла

m1:

inc dh

loop m1

mov al, dh.

Данный фрагмент выполняется следующим образом: сначала в dh загружается 0. Затем в цикле к dh 11 раз прибавляется единица. В результате этого фрагмента мы будем иметь: cx = 0, dh = 11, al = 11. Конечно, тех же результатов можно было бы достичь проще:

mov cx, 0

mov dh, 11

mov al, dh,

но здесь нет цикла.

Распространенной ошибкой, приводящей к самым плачевным последствиям, является написание бесконечного цикла. Например, следующий фрагмент приведет к зависанию программы:

mov dh, 0

m1:

mov cx, 11

inc dh

loop m1.

В cx занесется 11, команда loop вычтет из cx единицу, получится «не ноль» и произойдет переход на метку m1, В cx снова занесется 11 и так до бесконечности. Метка m1 поставлена не там, где нужно (правильный вариант смотри выше).

Еще одной менее очевидной, но не менее неприятной по последствиям, ошибкой является занесение внутри цикла (по забывчивости программиста) в регистр cx новой информации, которая портит текущее значение счетчика цикла. Если же изменение cx внутри цикла нам «жизненно необходимо», то надо предварительно запомнить текущее содержимое cx (например, в стеке командой push cx), а затем восстановить это содержимое (pop cx) перед выполнением команды loop.

Команды IN al, адрес порта и OUT адрес порта, al.

Команда in передает байт из заданного в команде порта в регистр al. Команда out передает байт из регистра al в заданный в команде порт. В качестве адреса порта может выступать любое число, лежащее в диапазоне 0 – 255 (0 –ffh). Порт – это регистр, которому в системе присвоен адрес. Например, контроллер клавиатуры имеет 2 порта с адресами 20h и 21h, таймер – 4 порта с адресами 40h, 41h, 42h и 43h и.т.д. Приведем примеры команд:

in al, 60h         ;читаем скэн-код нажатой клавиши из ;порта клавиатуры

out 40h, al      ;заносим байт коэффициента пересчета
;
в 0-й канал таймера.

Обратите внимание, что обмен информацией с портами ведется только через регистр al (это не совсем правильно, поскольку имеются и другие варианты команд in и out, но для начинающего программиста проще использовать только рассмотренные выше команды).

Команда AND-приемник, источник.

Команда and (логическое И) производит поразрядное логическое умножение содержимого приемника на содержимое источника. Результат заносится в приемник. Например:

Источник:

&

10011101

Приемник:

01111010

Результат:

00011000

Команда and часто используется, когда надо сбросить в ноль конкретный бит (биты) в байте или слове, не меняя значение других битов этого байта (слова). Приведем пример:

in al, 61h         ;читаем 61 порт

and  al, 11111100b       ;обнуляем два младших бита

out 61h, al       ;записываем обратно в 61 порт.

Эти три команды запрещают звучание встроенного динамика (спикера). Сначала считываем содержимое порта 61h в регистр al. Затем обнуляем два младших бита al (запрещаем звук). При этом все остальные биты оставляем в их исходном состоянии, чтобы ненароком не нарушить работу системы. После этого отправляем измененную информацию обратно в порт 61h.

Команда and также часто используется, когда надо проверить значение конкретного бита в байте или слове. Например, надо проверить, установлен ли 1-й бит регистра al  в единицу. Эту проверку можно организовать так:

and al, 00000010b

jnz m99.

Если в 1-м бите стоял 0, в результате выполнения первой команды получится ноль. Вторая команда совершает прыжок на m99, если результатом первой команды был «не ноль», то есть если 1-й бит был установлен в единицу. Недостаток такой проверки – после нее содержимое al  будет испорчено.

Команда OR-приемник, источник.

Команда or (логическое ИЛИ) производит поразрядное логическое сложение содержимого источника и содержимого приемника. Результат заносится в приемник. Например:

Источник:

V

10011101

Приемник:

01111000

Результат:

11111101

Команда or часто используется, когда надо установить в единицу конкретный бит (биты) в байте или слове, не меняя значение других битов этого байта (слова). В качестве примера приведем последовательность команд, разрешающих звучание встроенного динамика:

in al, 61h

or al, 00000011b

out 61h, al.

Команда XOR-приемник, источник.

Команда xor (исключающее ИЛИ) производит поразрядное сложение по модулю 2 содержимого приемника и содержимого источника. Результат заносится в приемник. Например:

Источник:

 = 1

10011101

Приемник:

01111000

Результат:

11100101

Команда xor часто используется, если надо инвертировать значение какого-либо бита (битов) в байте или слове. Например:

xor al, 11100000b

Команда инвертирует значение трех старших битов в регистре al. Кроме того, команду xor удобно использовать для обнуления содержимого любого регистра:

xor ax, ax       ;после этого в ax будет 0.

Команда LEA-регистр, имя переменной.

Команда загружает в указанный в команде регистр эффективный адрес указанной в команде переменной (т.е. смещение этой переменной относительно начала сегмента). Например:

lea bx, perem      ;после этого в bx адрес perem.

Команда lea имеет ассемблерный эквивалент. Приведем команду, эквивалентную рассмотренной в примере:

mov bx, offset perem        ;после этого в bx адрес perem.

В дальнейшем будем использовать второй вариант записи. Он ничем не лучше первого, но более нагляден.

Команда DIV-регистр.

Команда div (деление) делит содержимое регистра ax на содержимое указанного в команде 8-разрядного регистра. Результат возвращается в al (частное) и в ah (остаток). Если в команде div  указан 16-разрядный регистр, то на его содержимое делится не ax, а регистровая пара dx:ax (старшие 2 байта в dx). Соответственно частное возвращается в ax, а остаток в dx. Например:

div cl          ;ax/cl , частное в al, остаток в ah

div bx         ;dx:ax/bx, частное в ax, остаток в dx.

Будем использовать эту команду, например, для перевода чисел из двоичной системы в десятичную.

Команда div таит в себе одну опасность. Если частное не помещается в отведенный для него регистр, происходит прерывание «Divided overflow». Например, если программа содержит такой фрагмент:

mov ax,1000

mov cl, 1

div cl,

то программа выполняться не будет, зато на экране появится надпись «Divided overflow». Связано это с тем, что при делении 1000 на 1 частное, равное 1000, не может поместиться в 8-разрядный регистр al, поскольку в al можно поместить максимум 255.

Команда INT-число.

Команда int n – программное прерывание (n – число, лежащее в диапазоне 0-255 или 0-ffh). С помощью таких команд программист вызывает сервисные подпрограммы DOS и BIOS. Эти подпрограммы принимают информацию с клавиатуры, выводят информацию на экран, работают с дисками, распределяют память и т.д. Параметры, передаваемые в подпрограмму, задаются перед вызовом int n в заранее оговоренных регистрах. Кроме того, поскольку одна и та же подпрограмма, задаваемая числом n  в команде int, часто выполняет целый набор различных сервисных функций, номер конкретной запрашиваемой функции задается перед вызовом в регистре ah. Если такая подпрограмма возвращает результаты, то они возвращаются в заранее оговоренных регистрах. Например:

mov ah, 0eh

mov al, ‘A

int 10h.

Это прерывание BIOS c номером 10h, функция 0eh. Это прерывание выводит на экран в текущую позицию курсора  символ, ASCII-код которого задан в регистре al. В нашем случае на экран выведется буква А. Никаких результатов в этом случае подпрограмма не возвращает. Второй пример:

mov ah, 7

int 21h.

Это 21-е (DOS) прерывание, функция 7. Программа в этом месте останавливается и ждет нажатия клавиши на клавиатуре. После того как клавиша нажата, ее код возвращается в регистре al.

Основные ошибки, которые допускает программист при использовании команд int:

- забыл поставить букву h в номере прерывания, в результате это оказалось совсем другое прерывание, выполняющее совсем другие функции;

- входные параметры заданы либо неправильно, либо не в тех регистрах.

Команды PUSH-регистр и POP-регистр.

Команда push заталкивает в стек содержимое регистра, а команда pop выталкивает в регистр информацию из вершины стека. Еще раз подчеркнем, что в этих командах недопустимы 8-разрядные регистры.

Команды SHR регистр, число и SHL-регистр, число.

Эти команды сдвигают содержимое указанного регистра вправо (shr) и влево (shl). Число, указанное в команд,е задает количество сдвигов (на сколько разрядов сдвигать).



*****

© 2009-2017 Банк лекций siblec.ru
Лекции для преподавателей и студентов. Формальные, технические, естественные, общественные, гуманитарные, и другие науки.