Pascal и ассемблер
Листинг 15.13 (продолжение) ![]() |
Процесс организации такой связи состоит из нескольких шагов. 1. Написать процедуру на ассемблере дальнего (far) или ближнего типа (near). Назовем ее для примера asmproc. В программе на языке ассемблера (назовем ее рrgl5_lЗ.аsm), в которую входит процедура asmproc, необходимо объявить имя этой процедуры внешним с помощью директивы PUBLIC: PUBLIC asmproc . Для того чтобы процедура на ассемблере при компоновке с программой на Pascal воспринималась компилятором Borland Pascal 7.0 как far или near, недостаточ- но просто объявить ее таковой в директиве PROC (строка 11 листинга 15.13). Кроме того, вам нужно включить или выключить параметр компилятора, дос- тупный через меню интегрированной среды: Options ► Compiler ► Force far calls. Установка этого параметра заставляет компилятор генерировать дальние вызовы подпрограмм. Альтернатива данного параметра — ключ {$F+} или {$F-} (соответ- ственно, включено или выключено) в программе. Это — локальные ключи, то есть в исходном тексте программы на Pascal их может быть несколько, и они могут, чередуясь друг с другом, поочередно менять форму генерируемых адресов пере- хода: для одних подпрограмм — дальние вызовы, для других — ближние. 2. Произвести компиляцию программы рrgl5_12.аsm с целью устранения синтак- сических ошибок и получения объектного модуля программы рrgl5_12.оbj: tasm /zi рrgi5_i2,,, 3. В программе рrдl5_12.раs на Pascal, которая будет вызывать внешнюю процедуру на ассемблере, следует вставить директиву компилятора {$L\путь\рrд_15_12.оbj}. Эта директива заставит компилятор в процессе компиляции программы рrдl5_12.раs загрузить с диска объектный модуль программы рrдl5_12.оbj. В программе рrдl5_12. раs необходимо объявить процедуру asmproc как внешнюю. В итоге последние два объявления в программе на Pascal будут выглядеть так: {$L mу_аsm}рrосеdurе аsmрrос(сh:сhаr;kоl,х,у:integer); external; 4. Если вы собираетесь исследовать в отладчике работу программы, то необходи- мо потребовать, чтобы компилятор включил отладочную информацию в гене- рируемый им исполняемый модуль. Для этого есть две возможности. Первая заключается в использовании глобального ключа {$D+}. Этот ключ должен быть установлен сразу после заголовка программы на Pascal. Вторая альтернативная возможность заключается в установке параметра компилятора: Options ► Com- piler ► Debug Information. 5. Выполнить компиляцию программы на Pascal. Для компиляции удобно исполь- зовать интегрированную среду. Для изучения особенностей связки Pascal — ассемблер удобно прямо в интегрированной среде перейти к работе в отладчи- ке командой Tools ► Turbo Debugger (или клавишами Shift-t-F4). Будет загружен отладчик. Его среда вам хорошо знакома; в данном случае в окне Module вы уви- дите текст программы на Pascal. Нажимая клавишу F7, вы в пошаговом режиме будете исполнять программу на Pascal. Когда очередь дойдет до вызова про- цедуры на ассемблере, отладчик откроет окно с текстом программы на ассемб- лере. Но наш совет вам — не ждать этого момента, так как вы пропустите неко- торые интересные вещи. Дело в том, что отладчик скрывает момент перехода из программы на Pascal в процедуру на ассемблере. Поэтому лучше всего испол- нять программу при открытом окне CPU отладчика. И тогда вы станете свидете- лями тех процессов, которые мы будем обсуждать далее. Если бы взаимодействие программ ограничивалось только передачей и возвра- том управления, то на этом обсуждение можно было бы и закончить. Но дело зна- чительно усложняется, когда требуется передать аргументы (в случае процедуры) или передать аргументы и возвратить результат (в случае функции). Рассмотрим процессы, которые при этом происходят. Передача аргументов при связи модулей на разных языках всегда производит- ся через стек. Компилятор Pascal генерирует соответствующие команды при обра- ботке вызова процедуры ассемблера. Это как раз те команды, которые отладчик пытался скрыть от нас. Они записывают в стек аргументы и генерируют команду CALL для вызова процедуры ассемблера. Чтобы убедиться в этом, просмотрите на исполняемый код программы в окне CPU отладчика. После обработки вызова про- цедуры и в момент передачи управления процедуре asmproc содержимое стека бу- дет таким, как показано на рис. 15.1,а. Для доступак этим аргументам можно приме- нять различные методы, наиболее удобный из них—использование регистра В Р. Регистр В Р, как уже отмечалось, специально предназначен для организации произвольного доступа к стеку. Когда мы рассматривали связь ассемблерных мо- дулей, то говорили о необходимости добавления в текст вызываемого модуля фраг- ментов, настраивающих его на передаваемые ему аргументы. При объединении разноязыких модулей также нужно вставлять подобные дополнительные фрагмен- ты кода. Они, кроме всего прочего, позволят учесть особенности конкретного язы- ка. Фрагмент, вставляемый в самое начало вызываемого модуля, называется про- логом модуля (процедуры). Фрагмент, вставляемый перед командами передачи управления вызывающему модулю, называется эпилогом модуля (процедуры). Его назначение — восстановление состояния вычислительной среды на момент вызо- ва данного модуля.
![]() |
Рассмотрим действия, выполняемые кодами пролога и эпилога при организа- ции связи Pascal—ассемблер. Действия, выполняемые кодом пролога. 1. Сохранить значение Ьр в стеке. Это делается с целью запоминания контекста вы- зывающего модуля. Стек при этом будет выглядеть, как показано на рис. 15.1, б. 2. Записать содержимое SP в ВР. Тем самым ВР теперь тоже будет указывать на вершину стека (рис. 15.1, в). После написания кода пролога все обращения к аргументам в стеке можно орга- низовывать относительно содержимого регистра ВР. Из рис. 15.1, в видно, что для обращения к верхнему и последующим аргументам в стеке содержимое Ьр необхо- димо откорректировать. Нетрудно посчитать, что величина корректировки будет различаться для процедур дальнего (far) и ближнего (near) типов. Причина понят- на: при вызове процедуры типа near в зависимости от установленного режима адресации (usеlб или usеЗ2) в стек записывается 2(4) байта в качестве адреса возврата (содержимое iр/еiр), а при вызове процедуры типа far в стек записы- вается 4(8) байта (содержимое IР/ЕIР и СS)1. Таким образом, коды пролога для процедур ближнего и дальнего типа соответ- ственно будут выглядеть следующим образом: asmproc procnear ■ ;пролог для процедуры типа near push bp mоv bр,sр ;к прологу можно добавить команду корректировки bр на 4 с тем, чтобы регистр bр ;указывал на верхний из передаваемых аргументов в стеке add bр,4;теперь bр указывает на kоl asmproc proc far Если действует режим адресации usеЗ2, то в стек записываются двойные слова. По этой причине запись 16-разрядного регистра cs также производится четырьмя байтами, при этом два старших бай- та этого значения нулевые. ' ;пролог для процедуры типа far push bp rаоvbр,sр „ ; к прологу можно добавить команду корректировки bр на 6 с тем, чтобы регистр bр ;указывал на верхний из передаваемых аргументов в стеке add bр,6;теперь bр указывает на kоl Далее доступ к переданным в стеке данным осуществляется, как показано в ли- стинге 15.7. Как видите, все довольно просто. Но если мы вдруг решили изменить тип на- шей процедуры ассемблера с far на near или наоборот, то нужно явно изменить и код пролога. Это не совсем удобно. TASM предоставляет выход в виде директи- вы ARG, которая служит для работы с аргументами процедуры. Синтаксис дирек- тивы ARG иллюстрирует рис. 15.2.
![]() |
Несколько слов об обозначениях на рисунке: я имя — идентификатор переменной, который будет использоваться в процедуре на ассемблере для доступа к соответствующей переменной в стеке; * тип — тип данных аргумента (по умолчанию WORD дляusеlб и DWORD для use32); Ш значение_1 определяет количество аргументов с данным именем. Место в стеке для них будет определено, исходя из расчета: значение _1 ∙ значение_2 ∙ размер_типа. По умолчанию значение _1 — 1; ш значение _2 определяет, сколько элементов данного типа задает данный аргу- мент. По умолчанию его значение равно 1, но для типа byte значение _2 = 2, так как стековые команды не работают с отдельными байтами. Хотя, если явно за- дать значение _2 = 1, то транслятор действительно будет считать, что в ячейку стека помещен один байт; я идентификатор — имя константы, значение которой присваивает транслятор. Об идентификаторе мы подробно поговорим чуть позже. Таким образом, директива ARG определяет аргументы, передаваемые в про- цедуру. Ее применение позволяет обращаться к аргументам по их именам, а не по смещениям относительно содержимого ВР. К примеру, если в начале рассматри- ваемой нами процедуры на ассемблере asmproc задать директиву ARG в виде arg kol:word,y:word, х:wоrd,сhr:bуtе, то к аргументам процедуры можно будет обращаться по их именам, без подсчета смещений. Ассемблер сам выполнит всю необходимую работу. В этом можно убедиться, запустив программу в отладчике. Обратите вни- мание: порядок следования аргументов в директиве arg является обратным поряд- ку их следования в описании процедуры (строка procedure аsmрrос(сh:сhаr;х,у, kоt:iпtедеr); external; в программе на Pascal). Процедура asmproc с директивой arg представлена в листинге 15.14.
![]() |
Листинг 15.14. Использование директивы arg После того как решена проблема передачи аргументов в процедуру и выполне- ны все необходимые действия, возникает очередной вопрос: как правильно воз- вратить управление? При возврате управления в программу на Pascal нужно помнить, что соглашения этого языка требуют, чтобы вызываемые процедуры са- мостоятельно очищали за собой стек. Программа на ассемблере также должна удов- летворять этому требованию и заботиться об очистке стека перед своим заверше- нием. Для этого необходимо составить эпилог. Действия, выполняемые кодом эпилога для связи Pascal—ассемблер. 1. Записать содержимое bp в sp командой mov sр,bр. Это действие восстанавлива- ет в sp значение, которое было на момент входа в процедуру. Необходимость в этом действии возникает в том случае, если в процедуре производилась рабо- та со стеком. В листинге 15.13 такой работы не было, поэтому код эпилога реа- лизует только следующие два действия. 2. Восстановить сохраненный в стеке регистр ВР. 3. Удалить из стека переданные процедуре аргументы. Для удаления из стека аргументов можно использовать различные способы. И Можно явно скорректировать значение SP, переместив указатель стека на необ- ходимое количество байтов в положительную сторону. Это — не универсаль- ный способ, к тому же он чреват ошибками, особенно при частых модификаци- ях программы. 11 Можно использовать в директиве arg после записи последнего аргумента опе- ранд, состоящий из символа равенства (=) и идентификатора, указанного за ним в следующей синтаксической конструкции: идентификатор В этом случае TASM при обработке директивы arg подсчитает количество бай- тов, занятых всеми аргументами, и присвоит их значение идентификатору. В нашем случае директиву arg можно определить так: arg сh:byte;X:word;у:word;kоl:wоrd=а_sizе TASM после обработки данной директивы присвоит имени а_sizе значение 8 (байт). Это имя впоследствии нужно будет указать в качестве операнда ко- манды ret: ret a_size Есть еще одна возможность организации данных Pascal—ассемблер — исполь- зовать операнды директивы MODEL. Вы помните, что она позволяет задать модель памяти и учесть соглашения языков высокого уровня о вызове процедур. Для свя- зи Pascal—ассемблер ее можно задавать в виде MODEL large,pascat Задание в таком виде директивы MODEL позволяет: * описать аргументы процедуры непосредственно в директиве рrос: asmproc proc near сh:bуtе,х:wоrd,у:wоrd,kоl:wоrd Ш автоматически сгенерировать код пролога и эпилога в процедуре на ассемблере; ϋ для доступа к аргументам, объявленным в РRОС, использовать их имена (в этом отношении данный вариант является аналогом предыдущего варианта с дирек- тивой ARG). Листинг 15.15 демонстрирует, как отражаются особенности данного варианта на тексте процедуры ассемблера. Обратите внимание на то, что пролога уже нет, так как он формируется транслятором автоматически; вместо эпилога обязатель- но нужно задавать команду RET, только без операндов. Интересно изучить текст листинга 15.16, который получается в результате трансляции листинга 15.15. В нем видны сформированные транслятором коды пролога и эпилога. Кроме того, транс- лятор заменил команду RET без операндов командой ret 0008, которая, в соответ- ствии с требованиями к взаимодействию с программами на Pascal, удалит из стека аргументы, переданные вызываемой процедуре. Листинг 15.15. Использование директивы MODEL {рrgl5_ 15.раs} {Программа на Pascal, вызывающая процедуру на ассемблере, полностью совпадает с листингом 15.12} ;рrgl5_15.asm МАSМ MODEL large, pascal продолжение & Листинг 15.15 (продолжение) BYTE,х:WORD,у:WORD,коl:WORD ; у в dh ; x в dl ;номер службы BIOS ;вызов прерывания BIOS ;номер службы BIOS ;символ - в al ;маска вывода символа ;kol в сх ;вызов прерывания BIOS ;конец процедуры ;конец пропраммы
![]() | ![]() | ![]() | |||||
![]() |
Листинг 15.16. Результат трансляции листинга 15.15 Таковы стандартные способы вызова ассемблерных процедур из программ на Pascal и передачи им аргументов. Эти способы будут работать всегда, но, совер- шенствуясь, компилятор может предоставлять и более удобные средства. Их мы рассматривать не будем, так как, в конечном счете, они сводятся к рассмотренной нами процедуре. Остались открытыми два вопроса. и Как быть с передачей данных остальных типов Pascal, ведь мы рассмотрели толь- ко данные размером в байт и слово? ш Как возвратить значение в программу на Pascal? Что касается ответа на первый вопрос, то необходимо вспомнить, что в языке Pascal существуют два способа передачи аргументов в процедуру: по ссылке и по значению. Тип аргументов, передаваемых по ссылке, совпадает с типом ассемблера dwоrd и с типом pointer в Pascal. По сути, это указатель из четырех байтов на некоторый объект. Структура указателя обычная: два младших байта — смещение, два стар- ших байта — значение сегментной составляющей адреса. С помощью такого указа- теля в программу на ассемблере передаются адреса следующих объектов: Ш всех аргументов, объявленных при описании в программе на Pascal как var, не- зависимо от их типа; , ⅝ аргументов pointer и lопgiпt; 1' строк string; Ш множеств; . Ш массивов и записей, имеющих размер более четырех байтов. Аргументы по значению передаются следующим образом: ⅜S для типов char и byte — как байт; ш для типа boolean — как байт со значением 0 или 1; 3ί для перечисляемых типов со значением 0...255 — как байт; более 255 — как два байта; Ш для типов integer и word — как два байта (слово); для типа real — как шесть байтов (три слова); массивы и записи, длина которых не превышает четырех байтов, передаются «как есть». ^ Заметим, что аргументы таких типов, как single, double, extended и comp, переда- ются через стек сопроцессора. Что касается ответа на второй вопрос, то мы выясним его на конкретном при- мере. Напомню, что мы рассматриваем вызов из программы на Pascal внешней процедуры на ассемблере. Понятно, что вызов ради вызова вряд ли нужен — вызы- ваемая процедура должна иметь возможность вернуть данные в вызывающую про- грамму. Поэтому такую вызываемую процедуру правильнее рассматривать как функцию. В связке Pascal—ассемблер для того, чтобы возвратить результат, про- цедура на ассемблере должна поместить его значение в строго определенное место (табл. 15.2).
Таблица 15.2. Возврат результата из процедуры на ассемблере в программу на Pascal ![]() |
В листинге 15.17 приведен текст вызывающего модуля на Pascal, а в листин- ге 15.18 — код вызываемого модуля на ассемблере. Программа на Pascal инициа- лизирует две переменные, valuel и vаluе2, после чего вызывает функцию на ассем- блере AddAsm для их сложения. Результат возвращается в программу на Pascal и присваивается переменной rez. Листинг 15.17. Вызывающая программа на Pascal {рrgl5_17.раs} рrодrаmргgl4lθl; {внешние объявления} function АddАsm:wоrd; external; {$L рrgl5_18.оbj> vаr vаluеl:wоrd;{здесь как внешние} vаluе2:wоrd; . rеz:wогd; begi n vаluеl:=2; vаluе2:=3; {вызов функции} геz:=АddАsm; writеlп("Результат: ",геz); end. Листинг 15.18. Вызываемая процедура на ассемблере ;рrgl5_18.аsm МАSМ MODEL small data segment word public ;сегмент данных .•объявление внешних переменных ■ ехtrп vаluеl:WОRD ехtrп vаluе2:WОRD data ends;конец сегмента данных . code assume ds:dаtа;привязка ds к сегменту ;данных программы на Pascal main : АddАsm рrоспеаr PUBLIC АddАsm ;внешняя mоv сх,ds: vаluеl;vаluеlв сх mоv dх,ds:vаluе2; vаluе2В dх add сх,dх ;сложение mоv ах,сх результат в ах, так как - слово ret ;возврат из функции АddАsm епdр ;конец функции end main;конец программы В последней программе следует обратить внимание еще на одну возможность доступа к разделяемым данным — с помощью сегментов типа PUBLIC (см. главу 5). Совместное использование сегментов данных стало возможным благодаря тому, что компилятор Pascal создает внутреннее представление программы в виде сег- ментов, как и положено программе, выполняющейся в архитектруре IA-32 на процессоре Intel. Сегмент данных в этом представлении тоже имеет название data, и директива SEGMENT для него со всеми вытекающими последствиями выглядит так: data segment word public
Еще по теме Pascal и ассемблер:
- Г.С.Иванова, Т.Н.Ничушкина, Е.К.Пугачев. Объектно- ориентированное программирование, 2001
- Таненбаум Э.. Архитектура компьютера. 5-е изд, 2007
- В. И. Юров. Assembler. Учебник для вузов. 2-е изд, 2003
- Л.О. Доліненко, В.О. Доліненко, С.О. Сарновська. Цивільне право України, 2006
- ЦИВІЛЬНЕ ПРАВО УКРАЇНИ
- ПЕРЕДМОВА
- Частина І ПРОГРАМА КУРСУ «ЦИВІЛЬНЕ ПРАВО УКРАЇНИ»
- Розділ І. Загальні положення цивільного права
- Тема 1. Поняття цивільного права. Предмет та метод, система цивільного права. Функції та принципи цивільного права
- Тема 2. Цивільне законодавство України
- Тема 3. Поняття, елементи та види цивільних правовідносин
- Тема 4. Здійснення цивільних прав і виконання обов’язків
- Тема 5. Захист цивільних прав та інтересів
- Тема 6. Об’єкти цивільних прав