assembler. Учебник для вузов_Юров В.И_2003 -637с (862834), страница 75
Текст из файла (страница 75)
По этойпричине их содержимое изменять не рекомендуется. Для произвольного доступак данным в стеке архитектура процессора имеет специальный регистр ЕВР\ВР (BasePoint — указатель базы). Так же как и для регистра ESP\SP, обращение к ЕВР\ВРавтоматически предполагает работу с сегментом стека. Перед использованием этогорегистра для доступа к данным стека содержимое стека необходимо правильноинициализировать, что предполагает формирование в нем адреса, который бы указывал непосредственно на переданные данные. Для этого в начало процедуры рекомендуется включить дополнительный фрагмент кода. Он имеет свое название —пролог процедуры.
Типичный фрагмент программы, содержащий вызов процедуры с передачей аргументов через стек, может выглядеть так:masmmodelsmallproc_l proc near;начало прологаpush bpmov bp.sp;конец прологаmov ax,[bp+4]mov ax, [bp+6]<20><22><23><24><25><2б><27><28><29><30>;"близкая" процедура (near) с п аргументами;доступ к аргументу arg_n для near-процедуры;доступ к аргументу arg_{n-l};команды процедурыподготовка к выходу из процедуры;начало эпилогаmov sp.bp;восстановление sp;восстановление значения старого bppop bp;до входа в процедуру;возврат в вызывающую программуret;конец эпилогаproc_l endp. codemain procmov ax,@datamov ds.axpush arg_lpush arg_2;запись в стек 1-го аргумента:запись в стек 2-го аргументаpush arg_n;запись в стек n-го аргументаcall proc_l;вызов процедуры ргос_1;действия по очистке стека после возврата из процедуры336<32><33><34><35><36>Глава 15.
Модульное программирование;..ml:_exitmain endpend mainКод пролога состоит всего из двух команд. Первая команда push bp сохраняетсодержимое ВР в стеке с тем, чтобы исключить порчу находящегося в нем значения в вызываемой процедуре. Вторая команда пролога mov bp,sp настраивает ВР навершину стека. После этого можно не волноваться о том, что содержимое SP перестанет быть актуальным, и осуществлять прямой доступ к содержимому стека. Чтообычно и делается. Для доступа к arg_n достаточно сместиться от содержимого ВРна 4, для arg_{n-l} — на 6 и т. д.
Но эти смещения подходят только для процедуртипа near. Для процедур типа far эти значения необходимо скорректировать ещена 2, так как при вызове процедуры дальнего типа в стек записывается полныйадрес — содержимое CS и IP. Поэтому для доступа к arg_n в строке 9 команда будетвыглядеть так: mov ax,[bp+6], а для arg_{n-l}, соответственно, mov ax,[bp+8] и т.
д.Конец процедуры также должен быть оформлен особым образом, обеспечиваякорректный возврат из процедуры. Фрагмент кода, выполняющего такие действия,имеет свое название — эпилог процедуры. Код эпилога должен восстановить контекст программы в точке вызова процедуры из вызывающей программы. При этом,в частности, нужно откорректировать содержимое стека, убрав из него ставшиененужными аргументы, передававшиеся в процедуру. Это можно сделать несколькими способами.*s Можно использовать последовательность из п команд pop xx. Лучше всегоэто делать в вызывающей программе сразу после возврата управления изпроцедуры.* Можно откорректировать регистр указателя стека SP на величину 2 • п, например, командой add sp,NN, где NN = 2 • п, а п — количество аргументов. Это такжелучше делать после возврата управления вызывающей процедуре.ii Можно использовать машинную команду RET n в качестве последней исполняемой команды в процедуре.
Здесь п — количество байтов, на которое нужно увеличить содержимое регистра ESP\SP после того, как со стека будут сняты составляющие адреса возврата. Видно, что этот способ аналогичен предыдущему,но выполняется процессором автоматически.В каком виде можно передавать аргументы в процедуру? Ранее упоминалось,что передаваться могут либо данные, либо их адреса (указатели на данные). В языке высокого уровня это называется передачей по значению и по адресу, соответственно.Наиболее простой способ передачи аргументов в процедуру — передача по значению. Этот способ предполагает, что передаются сами данные, то есть их значения. Вызываемая программа получает значение аргумента через регистр или черезстек. Естественно, что при передаче переменных через регистр или стек на их размер накладываются ограничения, связанные с размерностью используемых регистров или стека.
Другой важный момент заключается в том, что* при передаче аргументов по значению в вызываемой процедуре обрабатываются их копии. Поэтомузначения переменных в вызывающей процедуре не изменяются.Процедуры в языке ассемблера337Способ передачи аргументов по адресу предполагает, что вызываемая процедура получает не сами данные, а их адреса. В процедуре нужно извлечь эти адресатем же методом, как это делалось для данных, и загрузить их в соответствующиерегистры.
После этого, используя адреса в регистрах, следует выполнить необходимые операции над самими данными. В отличие от передачи данных по значению, при передаче данных по адресу в вызываемой процедуре обрабатывается некопия, а оригинал передаваемых данных. Поэтому при изменении данных в вызываемой процедуре они автоматически изменяются и в вызывающей программе, таккак изменения касаются одной области памяти.Использование директив EXTRN и PUBLICДирективы EXTRN и PUBLIC мы уже упоминали, когда рассматривали варианты взаимного расположения вызывающей программы и вызываемой процедуры. Ко всему сказанному добавим, что директивы EXTRN и PUBLIC также можно использоватьдля обмена информацией между модулями. Назначение и форматы этих директивостаются теми же, поэтому сейчас опишем только порядок их использования дляобмена данными.
Возможны несколько вариантов их применения:оба модуля используют сегмент данных вызывающей программы;у каждого из модулей есть собственный сегмент данных;модули используют атрибут комбинирования (объединения) сегментов privateв директиве сегментации SEGMENT.Рассмотрим эти варианты на примере программы, которая определяет в сегменте данных две символьные переменные и вызывает процедуру, выводящую этисимволы на экран.В первом варианте, в котором два модуля используют только сегмент данныхвызывающей программы, не требуется переопределения сегмента данных в вызываемой процедуре. В листинге 15.6 в вызывающей программе определены две переменные, вывод на экран которых осуществляет вызываемая программа (листинг 15.7).Листинг 15.6.
Первый вариант использования директив EXTRN и PUBLIC (модуль 1);prg!5_6..asmвызывающий модульinclude mac.incextrnmy_proc2:farpublic perl,per2stk segment stackdb 256 dup (0)stk endsdatasegmentperldb "1"per2db "2"dataendscodesegmentmainproc farassume c s : c o d e , d s : d a t a , s s : s t kmov a x , d a t amov d s .
a xcall my_proc2exitпродолжение тУ338Глава 15. Модульное программированиеЛистинг 15.6(продолжение)mainendpcodeendsend mainЛистинг 15.7. Первый вариант использования директив extrn и public (модуль 2);prg!5_7.asmвызываемый модульinclude mac.incextrn perl:byte,per2:bytepublic my_proc2codesegmentmy_proc2procfarassume csrcode;вывод символов на экранmov dl.perlOutCharmov dl,per2OutCharretmy_proc2endpcodeendsendСборка программы из двух модулей для этого и следующих вариантов осуществляется так же, как было показано в листингах 15.4 и 15.5.Во втором варианте у каждого из модулей есть свой собственный сегмент данных.
В этом случае для доступа к разделяемым переменным из другого модулятребуется переопределение сегмента данных в вызываемой процедуре (строки 1719 и 23-24 листинга 15.8).Листинг 15.8. Второй вариант использования директив extrn и public;prg!5_8.asm•.Вызывающий модуль - тот же, что и для предыдущего варианта.<1> ;Вызываемый модуль<2> include mac.inc<3> extrn perl:byte,per2:byte<4> public my_proc2<5> data segment<6> perO db "0"<7> data ends<8> code segment<9> my_proc2 procfar<10>assume cs:code,ds:data<11>;вывод символов на экран<12>mov ax,data<13>mov ds.ax<14>mov dl.perO<15>OutChar<16>pushds;сохранили ds<17>mov ax.seg perl ;сегментный адрес perl в ds<18>mov ds.ax<19>mov dl.perl<20>OutChar;вывод perl<21>mov dl,per2<22>OutChar;вывод per2<23>pop dsвосстановили ds<24>mov dl.perO<25>OutChar;и еще раз perO<26>retПроцедуры в языке ассемблера<27><28><29>339my_proc2 endpcode endsendРассмотрим улучшенный второй вариант программы (листинг 15.9).
В предыдущем случае мы использовали для адресации данных в разных сегментах данныходин регистр DS, а теперь для доступа к разделяемым переменным из другого модуля задействуем один из дополнительных сегментных регистров данных, к примеру ES. Заметьте, что обращение к данным другого сегмента осуществляется с помощью префикса замены сегмента (строки 18 и 20).Листинг 15.9. Улучшенный второй вариант использования директив extrn и public;prg!5_9.asmвызывающий модуль - тот же, что и для предыдущего варианта.<1>;Вызываемый модуль<2>include iomac.inc<3>extrnperl:byte,per2:byte<4>public my_proc2<5>data segment<6>per0 db "0"<7>data ends<8>code segment<9>my_proc2 procfar<10>assume csrcode.ds :data<il>;вывод символов на экран<12>mov ax,data<13>mov ds.ax<14>mov dl,per0<15>OutChar<16>mov ax.seg perl<17>mov es.ax<18>mov dl,es:perl<19>OutChar<20>mov dl,es:per2<21>OutChar<22>mov dl,perO<23>OutChar<24>ret<25>my_proc2 endp<26>codeends<27>endТретий вариант предполагает использование атрибута комбинирования (объединения) сегментов public в директиве сегментации SEGMENT для сегментов данных модулей (листинги 15.10и15.11).Это значение атрибута комбинирования заставляет компоновщик объединить последовательно сегменты с одинаковымиименами.