Приложение В. Основные сведения об языке ассемблера.

  Читатель  этого  справочника, не знакомый с языком ассемблера,  скоро
поймет, что многие программистские трюки не могут быть достигнуты
другими  средствами.  Хотя изучение языка ассемблера требует  от-
дельной книги, в  этом  приложении  приводятся  основные понятия,
которые  помогут новичкам разобраться в примерах на  этом  языке.
Внимательный просмотр  разделов,  посвященных  среднему и низкому
уровням,  даст Вам возможность получить представление о том,  как
работает ассемблер, после чего намного легче изучить разные част-
ные  вопросы.  Здесь обсуждаются не все ассемблерные  инструкции,
встречающиеся в программах,  но  Вы  обнаружите,  что  около 95 %
инструкций, встреченных Вами в программах, описаны здесь, а  зна-
чение остальных может быть  понято благодаря комментариям к прог-
раммам.
   Микропроцессор 8088 имеет 13 16-разрядных регистров, каждый из
которых имеет свои  функции.  В  то  время  как в языках высокого
уровня  Вы можете поместить два числа в переменные, а затем  сло-
жить эти переменные, то в языке ассемблера эти числа помещаются в
регистры микропроцессора, а затем складываются значения, содержа-
щиеся в  регистрах.  Все  операции  в  языке ассемблера состоят в
обмене  данных  с регистрами, а затем выполнении операций на  ре-
гистрах, таких как изменение отдельных  битов, выполнение арифме-
тических  операций и т.д.  Одной из причин высокой  эффективности
языка ассемблера является  хранение  данных в регистрах микропро-
цессора;  компиляторы имеют тенденцию возвращать все  значения  в
память после выполнения операции, а доступ к памяти требует боль-
шого времени.  На рис. В-1 показаны 13 регистров микропроцессоров
8088 и 80286 (последний имеет дополнительные  средства для много-
задачной работы, которые мы не будем рассматривать здесь).
   Регистры  AX, BX, CX и DX являются регистрами общего  назначе-
ния.  Их особенность состоит в  том,  что операции могут произво-
диться  не только над содержимым всего регистра, но  также и  над
половиной. Каждый из четырех регистров делится на старшую и млад-
шую  части, например, AH обозначает старшую половину регистра AX,
а AL - младшую.  Точно так же  ассемблерная программа может иметь
доступ  к BH, BL, CH, CL, DH и DL.  Это свойство  очень  полезно,
поскольку часто программе  приходится работать с байтными величи-
нами.   Регистры  BP, SI и DI также достаточно удобны,  хотя  они
могут принимать только  16-битные  значения.  Каждый бит регистра
флагов сообщает о соответствующем статусе процессора, например, о
том, что при выполнении  арифметической  операции  был перенос за
разрядную сетку.
   В общем случае значения помещаются в регистры с помощью  инст-
рукции MOV. MOV  AX,BX  пересылает  содержимое  регистра BX в AX,
затирая  ранее содержащееся в AX значение.  MOV AH,BL приводит  к
пересылке байта из регистра в  регистр, но MOV AX,BL - недопусти-
мая  инструкция, так как значения должны иметь одинаковый размер.
Инструкция MOV можеть также передавать значения из памяти, напри-
мер,  MOV  AX,ACCT_NUMBER.  Здесь ACCT_NUMBER -  имя  переменной,
которую создал программист,  совсем  как в языке высокого уровня.
Переменная создается оператором вида ACCT_NUMBER DW 0.  Этот опе-
ратор оставляет  место  для  слова  (двух  байтов), присваивая им
значение 0.  Другие допустимые символы в этом операторе это DD  -
для двойного слова и DB - для байта  или строк.  Ассемблер следит


за адресами переменных, поэтому при ассемблировании оператора MOV
AX,ACCT_NUMBER имя переменной заменяется на ее адрес.
   Работа с именами переменных - самый простой способ идентифика-
ции данных в программах на языке ассемблера. Но имеются различные
способы хитрой  адресации,  которые  позволяют  программе хранить
массивы  или использовать указатели.  Например,  MOV  AX,[BX][SI]
посылает в AX значение, которое  содержится  по смещению, равному
сумме  значений регистров BX и SI.  Но от чего отсчитывать смеще-
ние? Ответ заключается в том, что все данные собраны в одну часть
программы,  а весь исполняемый код - в другую.  Часть, отведенная
под данные, называется сегментом данных,  а под программу - кодо-
вым  сегментом.  Все переменные, отведенные для хранения  данных,
адресуются через смещение относительно начала сегмента данных.
   Позиция в памяти, с которой начинается сегмент данных, хранит-
ся в регистре DS, одном из  четырех  сегментных  регистров. Как и
все  остальные регистры микропроцессора он 16-разрядный,  поэтому
он не может содержать числа, большие  чем 65535. Каким же образом
сегмент  даных может указывать на ячейки памяти, расположенные  в
верхней части мегабайтного адресного  пространства? Ответ состоит
в том, что сегментные регистры автоматически умножаются на 16,  а
результат указывает на  место  в  памяти,  с  которого начинается
сегмент.  Таким образом, сегменты всегда выравнены на  16-байтную
границу. После того как сегмент установлен, все остальные регист-
ры  могут  содержать смещения, указывающие на любой из  следующих
65535 байтов. Регистр дополнительного сегмента (ES) также исполь-
зуется для указания на данные, хранящиеся в памяти.
   Среди  ассемблерных инструкций, которые Вы часто будете встре-
чать в этой книге, есть инструкции  загрузки сегментных и относи-
тельных адресов переменных.  MOV AX,SEG ACCT_NUMBER помещает зна-
чение сегментного регистра,  в  котором  расположен ACCT_NUMBER в
AX, а впоследствии это значение будет переслано в DS. MOV BX,OFF-
SET ACCT_NUMBER помещает в BX  смещение  переменной ACCT_NUMBER в
сегменте данных.  После выполнения этих операций DS:BX будут ука-
зывать на ACCT_NUMBER. Если  ACCT_NUMBER является одномерным мас-
сивом,  то  для  указания на определенный элемент  массива  может
использоваться  добавочное  смещение.  Вы  часто будете встречать
также  инструкцию  LEA,  предоставляющую другой  способ  загрузки
смещения.
   Кодовый сегмент содержит  последовательность машинных инструк-
ций, составляющих программу.  Например, инструкция MOV существует
в виде нескольких байтов машинного кода, значение байтов которого
определяет  в какой регистр идет пересылка и откуда.  Регистр  IP
(счетчик команд) содержит величину смещения, которая указывает на
ту  инструкцию  в кодовом сегменте, которая сейчас должна  выпол-
няться.  После выполнения инструкции IP увеличивается таким обра-
зом,  чтобы  он указывал на следующую инструкцию.   В  простейшей
программе счетчик команд  будет  передвигаться  от  первого байта
кодового сегмента к последнему, где программа и завершится.   Но,
как и другие программы,  программа на языке ассемблера может быть
разбита на процедуры (подпрограммы), поэтому счетчик команд может
прыгать из одного места кодового сегмента в другое.
   Когда счетчик команд прыгает в другое место кодового сегмента,
то  его старое значение должно быть запомнено, с тем чтобы  можно
было вернуться в нужное место, так как это делает оператор RETURN


в  Бейсике, возвращая управление в то место, откуда была  вызвана
процедура. В языке ассемблера  процедуре присваивается имя, напр-
имер,  COMBINE_DATA, и оператор CALL COMBINE_DATA передает управ-
ление в процедуру.  Процедура  завершается инструкцией RET (возв-
рат).  При вызове процедуры процессор запоминает текущее значение
счетчика команд, заталкивая его на стек.
   Стек это область, используемая для временного хранения данных.
После завершения процедуры старое значение счетчика команд берет-
ся из стека и выполнение  программы  продолжается. Стек также со-
держится  в  отдельном сегменте, который, совершенно естественно,
называется сегментом стека. Ему  соответствует сегментный регистр
SS. В регистре SP хранится указатель стека, который всегда указы-
вает на вершину стека и  изменяется при засылке на стек и выборке
из стека.
   На  первый  взгляд стек кажется достаточно неуклюжим  способом
хранения информации, но у него есть  два преимущества. Во-первых,
доступ к его содержимому намного быстрее, чем к переменным,  хра-
нящимся в памяти,  а,  во-вторых,  стек  может использоваться для
многих  целей.   Он может хранить адреса возврата  из  процедуры,
вложенной в другую  процедуру.  Впоследствии,  то же самое прост-
ранство  может использоваться программистом для хранения  данных,
которые должны сейчас  обрабатываться,  но для которых не хватает
места в регистрах микропроцессора. Программа выталкивает содержи-
мое регистра на стек командой PUSH, а позднее забирает его оттуда
командой POP.  В ассемблерных программах, приведенных в этой кни-
ге, Вы не раз встретитесь с  инструкциями  типа PUSH BX и POP DX.
Неправильный  порядок  обмена данными со стеком -  лучший  способ
привести ассемблерную программу к краху.
   После того как программист на  ассемблере  установил  три сег-
ментных регистра (CS, DS и SS) и загрузил данные в регистры  мик-
ропроцессора он имеет широкий  набор встроенных средств, которыми
процессор может помочь программисту на ассемблере.  Вот  наиболее
распространенные из них:

ADD AX,BX   Прибавляет BX к AX. Существует также инструкция вычи-
            тания (SUB), а также варианты обеих этих инструкций.

MUL BL      Умножает BL на AX.  Имеется также инструкция  деления
            (DIV), а также варианты обеих этих инструкций.

INC BL      Увеличивает BL на 1.  Имеется также инструкция умень-
            шения (DEC).

LOOP XXX    Возвращает  программу назад к строке помеченной  XXX,
            повторяя процесс столько  раз, какое число содержится
            в CX (аналогично инструкции FOR ..  TO .. NEXT в Бей-
            сике).

OR AL,BL    Выполняет  операцию  логического  ИЛИ  над содержимым
            регистров  AL и BL, причем результат помещается в AL.
            Имеются также инструкции AND, XOR и NOT.


SHL AX,1    Сдвигает все биты, содержащиеся в AX, на одну позицию
            влево.   Это эквивалентно умножению содержимого AX на
            2.  Другие инструкции  сдвигают  биты вправо или осу-
            ществляют циклический сдвиг. Все эти инструкции очень
            полезны для  битовых  операций,  таких  как установка
            точек экрана.
IN AL,DX    Помещает в AX байт, обнаруженный в порте, адрес кото-
            рого указан в DX. Имеется также инструкция OUT.

JMP         Передает управление  в  другое  место  программы, как
            инструкция GOTO в Бейсике.  JMP YYY передает управле-
            ние на строку программы, имеющую метку YYY.

CMP AL,BL   Сравнивает содержимое  AL  и  BL.  За инструкцией CMP
            обычно следует инструкция условного перехода.  Напри-
            мер, если за инструкцией CMP  следует инструкция JGE,
            то переход произойдет только если BL больше или равно
            AL.  Инструкция CMP достигает того же результата, что
            и  инструкция  IF ..  THEN в Бейсике (на  самом  деле
            инструкция IF ..   THEN  переводится  интерпретатором
            Бейсика в инструкцию CMP).

TEST AL,BL  Проверяет  есть  ли среди битов, установленных в  BL,
            такие,  которые  установлены  также  и  в AL. За этой
            инструкцией обычно следует команда условного  перехо-
            да, так же как за CMP. TEST очень полезен при провер-
            ке  статусных  битов (битовые операции  очень  просто
            реализуются в языке ассемблера).

MOVS        Пересылает строку, длина  которой  содержится в CX, с
            места, на которое указывает SI, на место, на  которое
            указывает DI. Имеется еще  несколько  других инструк-
            ций, связанных с пересылкой и поиском строк.

Язык ассемблера обеспечивает несколько вариантов этих инструкций,
а  также ряд других специальных инструкций.  Имеется также  целый
класс инструкций, называемых псевдооператорами, которые помещают-
ся в текст программы с целью указания ассемблеру как обрабатывать
данную программу. Например, один  из типов псевдооператоров авто-
матически  вставляет часто используемый кусок кода по всей  прог-
рамме. Такая порция кода называется макросом и именно это свойст-
во ассемблера дало ему название "макроассемблер".
   И,  наконец,  ассемблер  имеет возможность,  которой  завидуют
(или, по крайней мере, должны  завидовать)  все кто программирует
только на языках высокого уровня. Имеется ввиду возможность опти-
мальным образом  использовать  прерывания  операционной  системы.
Ведь  это ничто иное, как готовые процедуры.  Однако вместо того,
чтобы вызывать их по CALL, они вызываются инструкцией INT. INT21H
вызывает  прерывание с шестнадцатиричным номером 21.  Имеется ряд
таких прерываний, как в базовой системе ввода/вывода ПЗУ, так и в
операционной системе, причем некоторые из этих процедур необычай-
но мощны.  На самом деле некоторые из них настолько тесно связаны
с  системой,  что Вы практически не можете сами написать  эквива-
лентную процедуру.  Языки  высокого уровня позволяют использовать


многие из этих прерываний. Они используют их для вывода на экран,
приема ввода с клавиатуры и доступа к дискам.  Но многие действи-
тельно полезные прерывания игнорируются языками высокого  уровня,
например такие,  которые  позволяют  запустить из одной программы
другую.   Некоторые  трансляторы (такие как Lattice C  или  Turbo
Pascal) позволяют доступ к этим  прерываниям,  если Вы знаете как
их готовить и Вы можете использовать разделы среднего уровня этой
книги для этой цели.
   Перед  вызовом  прерывания  некоторая  информация  должна быть
помещена в регистры процессора.  Например, прерывание, верикально
сдвигающее экран, должно знать  размеры  сдвигаемого  окна, число
строк  на  которое его надо сдвинуть и т.д.  Эти  значения  часто
называют входными регистрами.  Снова  и снова Вы будете встречать
слова "при входе BX должен содержать ...", описывающие специфика-
цию входных  регистров.   Аналогично,  при возврате из прерывания
некоторые  регистры возвращают значения или статусную информацию.
Они называются  выходными  регистрами  и  мы описываем их словами
"при выходе AX содержит ...".  Зачастую одно прерывание  содержит
много функций. В частности, операционная система впихнула практи-
чески все свои возможности в прерывание 21H.  Поэтому при  вызове
прерывания необходимо  указывать  номер  функции.  Все прерывания
(как  BIOS  так и DOS) передают номер функции в AH  (иногда в  AL
содержится номер подфункции).
   Все сказанное  в  основном  служит  только  чтобы  дать первое
представление о предмете.  Но если Вы будете внимательно просмат-
ривать простейшие примеры, содержащиеся  в этой книге, то Вы пой-
мете  стоящую  за ними логику.  Язык ассемблера  имеет  репутацию
трудного языка. Но то, что  Вы  только  что прочитали - настоящая
чепуха. Имеется достаточно сложностей и в языках высокого уровня.
И если ошибки в ассемблерной  программе бывает очень сложно обна-
ружить,  то в основном это связано с тем, что сам текст программы
намного длиннее, чем эквивалентный  текст на языке высокого уров-
ня  (однако ассемблерный код намного плотнее).  В настоящее время
многие профессионалы пишут  программы  на языке C, затем анализи-
руют  эффективность и переписывают критические кусочки программы,
которые расходуют много  времени,  на языке ассемблера. Невозмож-
ность  написания таких ассемблерных процедур может иногда  свести
усилия программиста к  нулю.  Поэтому  найдите хороший букварь по
ассемблеру и приступайте! Возможно самой большой наградой для Вас
станет момент, когда Вы  наконец  действительно  станете понимать
как же работает компьютер.
 

Вы находитесь в разделе: 
Также вам будет интересно:

Добавить коментарий