Ассемблер для блондинок (и блондинов). И блондинов
Скачать 0.75 Mb.
|
neg. Один операнд – регистр или память. Меняет знак числа. Особый случай если значение аргумента самое маленькое знаковое число для данного типа, например, –128 для байта или –32768 для слова (то есть в двоичном виде сначала 1, а все остальные нули. Числа с обратным знаком для этого случая не существует, операнд не меняется. Команда меняет флаги. Подробнее [3, стр. 51]. Команды adc и sbb работают точно также, как add и sub, но еще добавляют (вычитают) значение флага CF. Подробнее [3, стр. 52]. Использование команды adc. Один из случаев, когда используется эта команда – сложение целых чисел по частям. Например, если надо сложить "длинные" числа размера, например, 8 байт. Такие числа можно хранить в памяти, но команд для сложения и умножения для них нет. В этом случае надо складывать сначала четырехбайтовые "хвосты" чисел, а потом командой adc четырехбайтовые "головы. Можно использовать команду для сложения "некратных" чисел. У насесть возможность работать с однобайтовыми, двухбайтовыми и четырехбайтовыми числами. А если задача такова. что удобно использовать трехбайтовые числа В этом случае надо складывать сначала два правых байта числа, а потом с помощью adc - первые (левые) байты. После сложения надо по правильным адресам разместить результат. Сложение делается аналогично. Таким же образом (по частям) делается умножение – см. [3, стр. 62]. Далее приведена программа для сложения трехбайтовых чисел. Так как трехбайтовые числа сами по себе не вывести, для проверки используется вывод четырехбайтового числа (для этого после чисел добавляется дополнительный нолик. Сразу отметим, что с этим дополнительным ноликом можно было не мучиться и складывать числа обычной командой сложения add, не разделяя их на части. Сложение трехбай- товых чисел по предложенному алгоритму нужно, когда числа хранятся подряд, без дополнительных ноликов между ними (экономия памяти) include console.inc .data a db 0ffh,0ffh,0h; 0,0,1 - это 65536 db 0 ; промежуток в памяти между числами - для удобнорй печати b db 1,0,1 db 0 m dd 0 s db 0,0,0 db 0 .code Start: ClrScr ConsoleTitle " Сложение трехбайтовых чисел" Begin: outstr ' Трехбайтовое число первое' outwordln m-8 outstr ' Трехбайтовое число второе' outwordln m-4 mov ax,word ptr a add ax,word ptr b mov word ptr s,ax mov al,a+2 adc al,b+2 mov s+2,al outwordln Сумма' exit end Start Далее приведена программа сложения восьмибайтных чисел. Показано, как восьмибайтные числа отображаются на экране. include console.inc .data X dq 11234567899 Y dq 16712111001 Z dq ? .code Start: ClrScr ConsoleTitle " Сложение длинных чисел" outwordln X,,'X=' outwordln Y,,'Y=' mov eax,dword ptr X add eax,dword ptr Y mov dword ptr Z,eax mov eax,dword ptr X+4 adc eax,dword ptr Y+4 mov dword ptr Z+4,eax newline 2 outwordln Z,,'Z=' exit end Start Для проверки работы и лучшего понимания программ рекомендуется распечатать промежуточные результаты, сложить числа, которые не формируют и которые формируют флаг переноса. 10. Команды перехода Для выполнения перехода чаще всего нужна помеченная команда. Метка (имя с двоеточием) ставится перед командой (можно на той же строчке, можно и на предыдущей. Следует иметь ввиду, что метки являются именами, большие и маленькие буквы в них различаются, метки не должны совпадать со служебными словами. Безусловный переход jmp метка – от слова прыгать Условные переходы связаны со значениями флагов. Команда перехода по флагу состоит из двух букв j (от Jump) и первой буквы из названия флага jo, jc, jz, js. Команды перехода по флагу в случае его равенства нулю состоят из трех букв j, n и буквы флага, например, jno. При значении флага, обозначенного в названии команды, происходит переход по указанной метке, в противном случае выполняется команда, следующая за командой перехода. Еще есть команда перехода по значению регистра ECX. Она записывается jecxz метка, переход происходит, если значение регистра ECX=0. Следует понимать, что какое-то значение у флага есть всегда (возможно, это "мусорное" значение, данное ему до выполнения Вашей программы. Команда перехода может стоять как непосредственно после команды, которая установила использующийся в ней флаг, таки через несколько команд, если они флаги не меняют. Из изученных нами команд флаги меняют арифметические команды, не меняют команда mov и сами команды перехода. Команды ввода-вывода (кроме inint) флаги не меняют. Чтобы посмотреть, как работают команды перехода по флагам, напишем программу, которая будет складывать байтовые числа и печатать, правильный ли получился результат для беззнаковых чисел (помещается ли он в байт, то есть будет проверять CF. include Console.inc ConsoleTitle " Сложение с проверкой" Begin: inintln Введите целое число размером в байт' inintln А теперь еще одно' add al,ah jc Neverno; при неправильном результате - переход outwordln Результат беззнаковый правильный' jmp fin Neverno: outstr 'Результат беззнаковый неправильный' fin: MsgBox "Конец программы","Желаете ещѐ раз, \ MB_YESNO+MB_ICONQUESTION cmp eax,IDYES je Begin exit end Begin Так как приходится довольно часто программировать операторы типа if a>b then, тов Ассемблер введены команды перехода, имеющие мнемонические обозначения, связанные со сравнением двух чисел. Первая буква у этих команд, естественно j, вторая (и третья) при сравнении на равенство e (equal) или ne, при переходе по всяким знакам сравнения буквы расставляются так для знаковых чисел это g (greater), l (less), для беззнаковых a (above, выше) и b (below. ниже. Часто перед выполнением команды перехода бывает надо сравнить числа. Для этого существует команда аналог команды вычитания sub. Работает она точно также, только результат не заносится в первый операнд (операнд не портится. Используется эта команда для выработки флагов. Пример var A,B: Longword; if A>B then Действия else Действия На Ассемблере будет mov eax,A ; приходится так делать, чтобы в следующей команде оба операнда небыли из памяти cmp eax,B; if A>B ja D1; если больше перейди к действию 1 ; операторы, выполняющие действие 2 jmp PROD; надо перепрыгнуть ветку < Действия D1: ; операторы, выполняющие действие 1 PROD: ; операторы, которые должны выполняться после условного Следует понимать, что мнемонические команды перехода работают четко по флагам (многие команды являются дублями "флаговых" команд, на самом деле они ничего не сравнивают, они не обязаны стоять после команды cmp, будут работать по флагам, выработанным на данный момент любой командой. При программировании переходов на Ассемблере часто бывает полезно немножко "поколдовать" с выбором команд перехода, чтобы сделать программу более понятной, содержащей поменьше переходов. На примере предыдущего фрагмента мы видели, что программирование полного условного оператора требует двух меток, двух команд перехода. Программирование неполного условного оператора проще и, если его грамотно выполнить, потребует всего одного перехода, например if EAX=EBX then Действие реализуем так cmp eax,ebx jne PROD ; команды, выполняющие действие PROD: ; команды, выполняющиеся после условного оператора Попробуйте запрограммировать этот оператор с помощью команды je и посмотрите, насколько это будкт менее элегантным. Программирование циклов Цикл с предусловием. Надо ввести последовательность (возможно, пустую) знаковых чисел, в конце последовательности стоит число 0 (не является элементом последовательности. Найти сумму положительных чисел. Напишем фрагмент программы для решения этой задачи outstrln 'Вводите числа через пробел. В конце – ноль' sub eax,eax; здесь будем накапливать сумму CIKL: inint ebx; ввод очередного числа cmp ebx,0; проверяем не конец ли последовательности je KON; если последовательность закончилась, переходим наконец программы ; если последовательность не закончилась, надо проверять знак числа, но cmp выше уже есть jl CIKL; если число отрицательное, надо вводить следующее, продолжать цикл add eax,ebx; если положительное, его надо прибавить jmp CIKL; и продолжить цикл KON: outintln Ответ' Как видно, для организации цикла понадобилось 2 перехода вначале цикла наметку, чтобы прекратить цикл, если он ненужен, ив конце цикла наметку, чтобы продолжать цикл. Цикл с постусловием. Надо вводить беззнаковые числа и суммировать их, пока сумма не превысит 100. Вывести количество чисел. sub eax,eax; здесь будем накапливать сумму mov ecx,eax; здесь будем подсчитывать количество CIKL: inint ebx; ввод очередного числа inc ecx; увеличиваем счетчик add eax,ebx; получаем сумму cmp eax,100; сравниваем сумму с числом 100 jb CIKL; нужное значение не достигнуто (беззнаковый переход, продолжаем складывать outstr "введено чисел "; иначе цикл заканчивается, выводим ответ outwordln ecx Как видно, для организации цикла потребовался всего один переход. Обычно на ассемблере удобнее организуются циклы с постусловием. В Ассемблере есть специальные команды для управления циклом loop (и похожие. Про них можно прочитать у [3, стр. Отличие надо использовать регистра не CX). Следует учесть, что команды цикла loop, в отличие от других команд перехода реализуют так называемый короткий переход, те от команды перехода до метки должно быть примерно 30-40 команд. При этом следует иметь ввиду, что макрокоманды ввода-вывода обычно содержат много команд, часто добавление внутрь цикла макрокоманды приводит к нарушению работы. Когда такое случается, надо заменить команду цикла loop L двумя командами sub ecx,1 и jnz L. 11. Массивы Все команды ассемблера, необходимые для работы с массивами Вы уже знаете. Да, собственно, никаких особых средств нет и быть не может, потому как нет никакого специального отдельного понятия "массив, объекта, который как-то по-особому хранится и обрабатывается. Есть одинаковые данные, хранящиеся в памяти друг за другом, есть желание обрабатывать их в цикле. Итак, сначала соберем здесь команды и директивы, которые могут понадобятся Директива эквивалентности equ, нам она понадобится в виде имя equ число С ее помощью мы будем задавать размер (длину) массива. Пример N equ 10 ; аналог Const N=10; в Паскале Эту директиву можно размещать в любом месте программы (желательно до использования описанного в ней имени константы. Директивы определения данных Рассмотрим на примерах. Директива MAS dw 10,255,-3,44,5 описывает переменную MAS размером в слово и отводит память под 5 слов, помещая в нее числа 10, 255, –3, 44, 5. ALFA db 'абвгдеѐжзийклмнопрстуфхцчшщьыъэюя' описывает переменную ALFA размером в байт и отводит память длиной 33 байта, помещая в нее 33 буквы русского алфавита. Конструкция повторения dup часто используется при резервировании памяти К db 8 dup (0) ; отводится память 8 байт, они заполняются нулями T db 4 dup (1,0); отводится 8 байт 1 0 1 0 1 0 1 0 R dd 100 dup (?); 100 двойных слов безначального значения Часто эти директивы используются "парой" N equ 10 MAS dw N dup (?) Можно считать это эквивалентом описания на Паскале const N=10; var MAS: array[0..N-1] of integer; Внутри конструкции dup в скобках в свою очередь могут содержаться такие же конструкции, что дает возможность описать матрицу, мерный массив и т.п. При этом надо иметь ввиду, что матрица данные, расположенные в виде некоторой прямоугольной таблицы) – это наше представление, на самом деле данные лежат в памяти линейно, просто друг за другом. Следующие директивы резервируют память одинакового размера A dd 4 dup (3 dup (?)) A dd 3 dup (4 dup (?)) A dd 12 dup (?) Работа с элементами массива. [3, 5.1 стр Директивами, описанными выше, мы резервируем память под массив, при этом имя имеет только его первый, к последующим элементам можно обращаться с помощью этого имени, прибавляя к нему некоторое число. Пусть у насесть массивы, в которых хранятся данные разной размерности B db 10 dup (?) W dw 10 dup (?) D dd 10 dup (?) Во всех этих массивах, чтобы работать с первым значением, надо просто написать переменную – имя массива mov B,1 mov D,1 Эти команды запишут 1 в первую компоненту массива. Чтобы записать 2 во вторую компоненту массива, придется написать mov B+1,2 mov W+2,2 mov D+4,2 Заметим, что вместо B+1, W+2 можно писать и B[1], W[2]. В общем случае, если считать, что индексы элементов начинаются от 0, то Адрес (X[i])=X+(type X)*i где type X – тип переменной (1 для байта, 2 для слова, 4 для двойного слова, 8 для восьмибайтово- го. Если хочется обрабатывать компоненты массива в цикле, надо будет пользоваться модификаторами вспоминаем учебные машины – там говорилось о модификации программы для работы с массивами. О регистрах-модификаторах читаем (1. Глава 5, п, формат команд "регистр-память"). Вспомним, как можно записать адрес в команде. Операнд, который берется из памяти, может быть представлен просто адресом, может – адресом с одним регистром-модификатором и с двумя модификаторами (базовыми индексным регистрами. Операнд – адрес без модификатора mov X,1 mov X+3,8 mov [X][3],0; или [X+3],0 или X[3],0 Адрес может быть взят в квадратные скобки, от этого ничего не меняется, если в скобках нет регист- ров-модификаторов. Адрес может быть изменен на константу, константу можно писать в квадратных скобках, можно со знакомили Хоть это к массивами не относится, напомним еще раз адреса нельзя складывать, умножать и делить. Можно к адресу прибавлять число (отнимать – те. прибавлять отрицательное число) – получается адрес. Адреса можно вычитать – получается число (константа. Операнд – адрес с одним модификатором. Здесь возможны 2 вида записи – регистр-модификатор без множителя. В этом случаев качестве регистра-модификатора может выступать любой 32- разрядный регистр общего назначения. Регистр обязательно пишется в квадратных скобках и называется он базовым. Также как ив случае записи адреса без модификатора, адрес может быть увеличен или уменьшен на константу, например X[ebx]-2 X[ebx][-2] X-2[ebx] X[ebx-2] X[-2+ebx] Стоит помнить, что перед неотрицательной константой можно опускать знака не умножение, как мы привыкли в алгебре. Так, все выше написанные адреса – одинаковые, вычисляются X+EBX-2. Такое многообразие возможностей записи адреса часто порождает мнение, что можно использовать вообще любые операции, в том числе и с регистром. Нет. Над числами можно выполнять многие операции, например, вместо константы можно написать арифметическое выражение типа N div 2+5. А вот регистр можно писать только в квадратных скобках и передним может быть только знак "+", например, запись X[2-ESI] – неверная. – регистр-модификатор с множителем. Зачем множитель Вспомним формулу вычисления адресатам надо умножать индекс элемента массива на тип этого элемента. Множитель может быть равен 1, 2, 4 или 8. Регистр (он называется индексным) – здесь можно использовать любой разрядный общего назначения кроме ESP (уесть особое предназначение – работа со стеком, так что лучше бы его вообще не для чего кроме стека и не использовать. Множитель и регистр можно записывать в любом порядке, между ними обязательно ставится знак умножения *. Пара регистр-множитель записывается в квадратных скобках. Примеры X[2*ebx+6] X[esi*4][8] X[type X * ebx-10] В обоих случаях адрес (переменная) может отсутствовать. Ничего страшного в этом нет. Ведь при вычислении адресного выражения надо сложить адрес с содержимым индексного регистра. В случае отсутствия адреса он считается равным нулю (наделе просто адрес записывается в регистр, позже мы посмотрим, как это делается. Примеры [ebx-2] [esi*4][8] [ebx*type X] Заметим, что при этом мы "теряем" тип (размер) данных, с которыми хотим работать. Если такого типа адрес используется в команде с двумя операндами, то неизвестного тип одного операнда Ассемблер пытаетсяможет установить автоматически, по типу другого mov [esi],al – работа с байтами. Если же второго операнда нет или его тип неясен, надо использовать оператор явного указания типа ptr, пример mov byte ptr [esi],1 outint word ptr [2*esi] Макрокоманда outchar, несмотря на то, что она вроде бы "заточена" под работу с символами, тоже требует byte ptr. Операнд – с двумя регистрами-модификаторами Адрес, базовый регистр (B1) и индексный регистр (I2) с множителем могут все вместе (или по отдельности) присутствовать в записи индексного выражения, соединяться они могут знакомили вообще без какого бы тони было знака. Оба регистра-модификатора, присутствующие в записи адреса, должны быть в квадратных скобках, при этом каждый из них может быть в своих скобках, могут быть оба в одних скобках (соединенные знаком +). Получается такая "формула переменная A[esi+8][ebp] A+[esi]+[4*esi] [A+eax+eax+6] |