Ассемблер для блондинок (и блондинов). И блондинов
Скачать 0.75 Mb.
|
jb @B; меньше начала диапазона, переход на ввод cmp bl,dh ja @B; больше конца диапазона, переход на ввод sub bl,dl; - номер буквы (начиная сна метку "начало ввода" @@: ; обработка содержимого стека mov ecx,[ebp-4]; восстановили длину диапазона outstrln 'вот сколько раз встретился каждый символ из заданного диапазона' @@: outchar dl pop eax outintln eax,3 inc dl dec ecx; это замена LOOP - он здесь не проходит jnz @B newline @kon: ; выходные действия процедуры, эпилог pop edi pop edx pop ecx pop ebx pop eax add esp,4; можно mov esp,ebp pop ebp ret 4*1; удаляем из стека параметр Letter endp ; ================== Start: ClrScr push 1234; для последующей проверки правильности очистки стека mov ebp,223344; для последующей проверки правильности восстановления регистров mov ecx,665577 ConsoleTitle "Процедура с локальным массивом" xor eax,eax mov al,'a' mov ah,'m' push eax; 'am#0#0' call Letter outstr 'введите еще один диапазон - два символа' inchar bl inchar bh push ebx call Letter newline pop ebx outword ebp,10 outword ecx,10 outwordln ebx,10 exit end Start В написанной процедуре мы со стеком работаем в основном "обычным" образом записываем в него нули командой push (и заодно отводим память под локальный массива в конце читаем информацию командой pop (и заодно освобождаем стек от локального массива. Чаще память в стеке отводят ивы- свобождают с помощью изменения значения регистра ESP. Задача. Напишем похожую процедуру. Процедура вводит текст сточкой на конце ив качестве результата выдает в регистре AL букву, которая в этом тексте чаще всего встречается (будем считать, что хотя бы одна буква в тексте есть, если несколько букв встречаются одинаково часто, выведем любую. Локальные данные такой процедуры – массив количеств букв – будем хранить в стеке. Никаких параметров у процедуры нет, результат она передает через регистр AL. include console.inc .code MAXLET proc push ebp mov ebp,esp sub esp,26*4; порождение локального массива из 26 двойных слов comment * [ebp-26*4]; начало массива это адрес первого элемента, адрес второго [ebp- 26*4 +4] и т.п. можно было не заводить массив изменением ESP, так как в него надо положить начальные значения, можно было "запушить" 26 нулей. * push ecx push ebx push edx outstrln "Вводите текст сточкой в конце" mov ecx,26 xor eax,eax Obnul: ; обнуление 26 позиций в стеке mov dword ptr [ebp-26*4+4*eax],0 inc eax loop Obnul vvod: ; ввод строки символов и обработка inchar al cmp al,'.'; строка заканчивается точкой je kon cmp al,'a' jb vvod cmp al,'z' ja vvod ; сюда попали только если была введена маленькая буква sub al,'a'; получили номер буквы inc dword ptr [ebp-26*4+4*eax]; двойные слова, поэтому умножаем на 4 jmp vvod kon: outstrln "печать" mov ebx,0 mov dl,'a' mov ecx,0 print: outchar dl; очередная буква outstr ' - ' outword dword ptr [ebp-26*4+4*ebx] cmp dword ptr [ebp-26*4+4*ebx],ecx; сравниваем текущее значение и миксимальное - cl jbe @F mov ecx,dword ptr [ebp-26*4+4*ebx]; меняем максимум mov al,dl @@: outstr' ' inc ebx inc dl; переходим к следующей букве cmp dl,'z' jbe print newline pop edx; Эпилог работаем со стеком в обратном порядкен pop ebx pop ecx add esp,26*4; освобождаем стек от локального массива pop ebp ret MAXLET endp Start: call MAXLET outchar al exit end Start 15. Многомодульные программы Чтобы хорошо понять данный материал, надо прочитать соответствующие главы в учебнике [1]. Здесь только приводятся примеры работающих программ. Как происходит работа многомодульной программы 1. Имеем несколько модулей, написанных на одном или нескольких языках программирования, между модулями имеются некоторых связи (попеременным, пометкам. Эти связи должны быть явно обозначены. Если из модуля совершается переход по некоторой метке, описанной в другом модуле или используется переменная, описанная в другом модуле, без указания, что эти объекты являются внешними, модуль компилироваться не будет (куда идти, если метки нет. Компилятору дается указание extern (и описываются внешние объекты, тогда так компилятор поймет, что искать данные объекты в данном модуле не надо, их отсутствие ошибкой не является. Если переменная (процедура, метка) описана в данном модуле, но ею будет пользоваться другой модуль (например, переходить по этой метке, данный факт тоже надо обозначить. Такой модуль без специальных указаний компилятору компилироваться будет (если есть процедура, которая не вызывается, это не ошибка, но при компиляции теряются все имена, данные пользователем, восстановить их уже будет нельзя. Модули компилируются по отдельности (каждый – с помощью "своего" компилятора, в зависимости оттого, на каком языке написан. В результате компиляции получается так называемый объектный файл. В объектном файле не содержится сведений, на каком языке был написан исходный модуль. Выполняться по отдельности модули обычно не могут. В нашем случае модули, написанные на Ассемблере, надо откомпилировать с помощью компилятора языка Ассемблер. Сделать это удобно в той папке, в которой вы выполняли программы на ассемблере, так как из нее удобным образом прописаны пути к нужным файлам. Модуль, написанный на Паскале надо откомпилировать компилятором Паскаль. Это удобно делать также в той папке, в которую вы помещали программы, которые запускали для работы на Паскале, так как именно для этой папке прописаны пути к нужным файлам для компилятора Паскаль. Чтобы программа из нескольких модулей заработала, надо все модули собрать в одну папку, в туже папку поместить программу-сборщик (линковщик). Понятно, что, если все модули на ассемблере, делать это надо в папке с ассемблерными программами. Если один из модулей на Паскале, удобнее переписать объектные файлы всех модулей в папку с Паскаль-программой, тогда сборку можно поручить Паскаля обычный запуск из Паскаль-среды кнопкой RUN). Отметим, что описанный здесь способ не является единственным, можно помещать модули в разные папки, аккуратно прописывая пути к нужным файлам. Можно компилировать Паскаль-программу не из Паскаль-среды, а из командной строки и т.д. Примеры. 1. Головной и вспомогательный модуль на ассемблере Головной модуль (вызывает процедуру, которая описана в другом модуле. include console.inc comment * головной модуль, вызывает процедуру на ассемблере в другом модуле * .data X dd ? .code Start: ; в головном модуле (и только в нем) обязательно должна ; быть метка начала работы (повторяется после END) extrn PROST@0:near ; внешнее имя. К названию процедуры добавлено @0. Эта ; процедура описана во вспомогательном модуле, из ; головного она вызывается. outstrln 'Это работает головной модуль' inint Введите число' outintln Введено ' push offset X ; параметр-адрес передается в процедуру через стек call PROST@0; К имени процедуры добавлено @0 ; Процедура выполняет действие X:=X+111 outintln Получено' ; можно видеть, что процедура проработала, изменила значение X exit end Start; уголовного модуля после end пишется метка, ; с которой начинается работа программы Вспомогательный модуль. В нем описана процедура, которая вызывается из головного модуля. include console.inc ; вспомогательный модуль .code public PROST; внешнее имя _prost@0 ; Эта процедура будет вызываться из других модулей PROST proc ; здесь всѐ обычным образом, имя процедуры без всяких добавок push ebp mov ebp,esp; стандартные действия входа в процедуру push ebx; сохраняем регистр, он понадобится mov ebx,[ebp+8]; это адрес переменной из стека add dword ptr [ebx],111; увеличиваем переменную на 111 pop ebx pop ebp ret 4; в стеке был один параметру вспомогательного модуля в конце стоит END без метки Компиляция модулей В файле, которым мы пользуемся для выполнения программ на Ассемблере, содержатся команды для компиляции, сборки и выполнения программы. Модуль надо только откомпилировать (получить объектный файл. Это можно сделать с помощью такого файла. В первую строку надо записать имя модуля (без расширения. Если компиляция прошла успешно, на экране появится сообщение о создании объектного файла, а в папке появится файл стем же именем и расширением OBJ @echo off set Name=prost set path=..\bin;..\..\bin set include=..\include;..\..\include set lib=..\lib;..\..\lib ml /c /coff /Fl %Name%.asm if errorlevel 1 goto errasm echo ______ Now you have a object file _____ goto TheEnd :errasm echo Assembler Error !!!!!!!!!!!! goto TheEnd :TheEnd pause Когда все модули откомпилированы (имеются объектные файлы, их надо собрать (LINK), получить выполняемый файл (EXE) и выполнить его. Это можно сделать с помощью такого файла. В первую и вторую строки надо записать имена модулей (без расширения, в третью строку – имя, которые Вы хотите дать получившемуся выполняемому файлу (может совпадать с именем одного из модулей. Если все пройдет успешно, в папке появится файл с заданным Вами именем и расширением EXE, а сформированная программа будет выполнена, результаты ее работы Вы увидите на экране. @echo off set Name1=mod_gl set Name2=mmm set NameRez=mod_mmm set path=..\bin;..\..\bin set include=..\include;..\..\include set lib=..\lib;..\..\lib link /subsystem:console /out:%NameRez%.exe %Name1%.obj %Name2%.obj if errorlevel 1 goto errlink echo Link is sucseccful %NameRez%.exe goto TheEnd :errlink echo Link Error !!!!!!!!!!!!!!!!! goto TheEnd :TheEnd pause 2. Головной модуль на Паскале, вспомогательный - на ассемблере. Во вспомогательном модуле процедура, которая вызывается из программы на Паскале. Если при подготовке вспомогательного модуля выполнены все соглашения о связях, описанную в нем процедуру можно вызвать из Паскаля. Вспомогательный модуль транслируется точно также, подготавливается объектный файл, который помещается в папку с программой на Паскале. Вот программа на Паскале, делающая тоже самое, что головной модуль на ассемблере в предыдущем примере (вызывает процедуру из описанного выше модуля. program Connect; procedure PROST(var a:longint); В процедуре 1 параметр, передается по ссылке - то есть в стеке должен быть адрес stdcall; такое соглашение о связях external name '_PROST@0'; Внешнее имя у этой процедуры будет таким {$L PROST.obj} здесь имя файла, в котором находится скомпилированный модуль var ss:longint; begin Введите целое число Readln(ss); PROST(ss); процедура вызывается также, как если бы была описана в этой же программе на Паскале Результат работы процедуры ', ss) ; readln end. Программы из примера 1 и из примера 2 работают одинаково, пользуясь одними тем же модулем с процедурой. 3. Головной модуль на Паскале, вспомогательный на Ассемблере. Вызывается процедура с двумя параметрами, которые передаются один по ссылке, другой по значению. Параметры закладываются в стек справа налево. Таким образом, первый (самый левый параметр) будет в стеке по адресу [ebp+8], второй – [ebp+12] и т.д. Головной модуль на Паскале program Connect; procedure SUMPR(p:longint; var s:longint); stdcall; external name '_SUMPR@0'; {$L SUMPR.obj} { Процедура SUMPR в модуле SUMPR.OBJ, внешнее имя _Sumpr@0 } var ss:longint; begin Программа ss:=23; SUMPR(345,ss); Результат Writeln(ss); readln end. Вспомогательный модуль с процедурой на ассемблере include console.inc comment * модуль для работы с паскаль-программой Два параметра sumpr(a,b) Выполняется действие b:=a+b * .code public SUMPR; внешнее имя _SUMPR@0 SUMPR proc push ebp mov ebp,esp push eax push ebx mov eax,[ebp+8]; первый параметр (слева) mov ebx,[ebp+12]; второй параметр add [ebx],eax pop ebx pop eax pop ebp ret 8 SUMPR endp end. 4. Головной модуль на Паскале, вспомогательный – на ассемблере. Вызывается функция, описанная во вспомогательном модуле. Все точно также. Значение функции передается через регистр AL, AX или EAX в зависимости от типа результата (byte, integer, longint). Файл на Паскале program Connect; function Fun(a,b:longint):longint; stdcall; external name '_FUN@0'; {$L confu.obj} Функция в модуле, внешнее имя _FUN@0} var ss:longint; begin Программа ss:=23; ss:=Fun(111,222); Результат Writeln (ss); readln end. Файл на ассемблере include console.inc comment * функция для работы с Паскаль-программой Вычитает из первого параметра второй * .code public FUN FUN proc push ebp mov ebp,esp mov eax,[ebp+8]; й параметр-значение sub eax,[ebp+12]; й параметр параметр pop ebp ret 8 FUN endp end 5. Оба модуля на ассемблере. Передают друг другу управление пометкам. Во вспомогательном модуле описана переменная, в нем она вводится, а в головном – печатается. Головной модуль include console.inc comment * головной модуль - передает управление вспомогательному, там вводится значение, которое выводится в головном модуле * .code extern VVOD:near; extern A:dword; это имя описано в другом модуле public METKA ; это имя описано здесь, будет использоваться в другом модуле Beg: outstrln 'Работает головной модуль' outstrln 'Управление передается на вспомогательный модуль' jmp VVOD METKA: outstrln 'Работает снова головной модуль' outstr 'Было введено значение ' outintln A exit end Beg Вспомогательный include console.inc ; модуль public A; внешнее имя _A public VVOD extern METKA: near; .data A dd ? .code VVOD: outstrln 'Работает вспомогательный модуль' outstr 'Введите значение переменной' inint A outstrln 'Значение введено. Управление передается на головной модуль' jmp METKA end |