Ассемблер для процессоров Intel Pentium
Шрифт:
.model flat. с
Здесь параметр flat указывает компилятору на то, что будет использоваться 32-разрядная линейная адресация. Второй параметр с указывает, что при вызове ассемблерной процедуры из другой программы (возможно, написанной на другом языке) будет задействован способ передачи параметров, принятый в языке С. Следующий пример:
.model large. с. farstack
Здесь используются модель памяти large, соглашение о передаче параметров языка С и отдельный сегмент стека (регистр SS не равен DS).
.model medium. pascal
В этом примере используются модель medi urn, соглашение о передаче параметров для Pascal
4.3. Структура программ на ассемблере MASM
Программа, написанная на ассемблере MASM, может состоять из нескольких частей, называемых модулями, в каждом из которых могут быть определены один или несколько сегментов данных, стека и кода. Любая законченная программа на ассемблере должна включать один главный, или основной (main), модуль, с которого начинается ее выполнение. Основной модуль может содержать программные сегменты, сегменты данных и стека, объявленные при помощи упрощенных директив. Кроме того, перед объявлением сегментов нужно указать модель памяти при помощи директивы . MODEL. Поскольку подавляющее большинство современных приложений являются 32-разрядными, то основное внимание в этом разделе мы уделим именно таким программам, хотя не обойдем вниманием и 16-разрядные программы, которые все еще используются. Начнем с 16-разрядных программ. В следующем примере показана 16-разрядная программа на ассемблере, в которой используются упрощенные директивы ассемблера MASM:
.model small, с ; эта директива указывается до объявления
; сегментов
.stack 100h ; размер стека 256 байт
.data ; начало сегмента данных
. . .
: данные
. . .
.code ; здесь начинается сегмент программ
main:
. . .
: команды ассемблера
. . .
end main
end
Здесь оператор end main указывает на точку входа main в главную процедуру. Оператор end закрывает последний сегмент и обозначает конец исходного текста программы. В 16-разрядных приложениях MS-DOS можно инициализировать сегментные регистры так, чтобы они указывали на требуемый логический сегмент данных. Листинг 4.1 демонстрирует это.
Листинг 4.1. Пример адресации сегментов в программе MS-DOS
.model large
.data
s1 DB «TEST STRINGS»
.code
mov AX. @data
mov DS. AX
lea DX. s1
mov AH. 9h
int 21h
mov ax. 4c00h
int 21h
end
Здесь на экран дисплея выводится строка si. При помощи следующих команд в сегментный регистр DS помещается адрес сегмента данных, указанного директивой .data:
mov AX. @data
mov DS. AX
Затем строка s1 , адресуемая через регистры DS:DX, выводится на экран с использованием прерывания 9h функции 21h MS-DOS. Попробуйте закомментировать проанализированные две строки кода и посмотреть на результат работы программы.
Для 32-разрядных приложений шаблон исходного текста выглядит иначе:
.model flat
.stack
.data
: данные
.code
main:
. . .
: команды ассемблера
. . .
end main
end
Основное отличие от предыдущего примера – другая модель памяти (flat), предполагающая 32-разрядную линейную адресацию с атрибутом near.
Как видно из примера, «классический» шаблон 32-разрядного приложения содержит область данных (определяемую
имя SEGMENT список атрибутов
. . .
имя ENDS
Замечу, что директива SEGMENT может применяться с любой моделью памяти, не только flat. При использовании директивы SEGMENT потребуется указать компилятору на то, что все сегментные регистры устанавливаются в соответствии с моделью памяти flat. Это можно сделать при помощи директивы ASSUME:
ASSUME CS: FLAT, DS:FLAT, SS:FLAT, ES:FLAT, FS:ERROR, GS:ERROR
Регистры FS и GS программами не используются, поэтому для них указывается атрибут ERROR.
Сейчас мы рассмотрим программный код 32-разрядной процедуры на ассемблере (она называется segex), в которой используются два логических сегмента данных. Процедура выполняет копирование строки sre, находящейся в сегменте данных datai, в область памяти dst в сегменте данных data2 и содержит один логический сегмент программного кода (code segment).
Успокою читателей, незнакомых с принципами работы процедур (они рассмотрены далее в книге): в данном случае нас будет интересовать код внутри процедуры segex (команды, находящиеся между директивами segex proc и segex endp). Исходный текст программного кода процедуры segex представлен в листинге 4.2.
Листинг 4.2. Использование двух логических сегментов данных в 32-разрядной процедуре
.586
.model flat
option casemap:none
data1 segment
src DB «Test STRING To Copy»
len EQU $-src
data1 ends
data2 segment public
dst DB len+1 DUPC + ')
data2 ends
code segment
_seg_ex proc
assume CS: FLAT,DS:FLAT, SS:FLAT, ES:FLAT, FS:ERR0R, GS:ERR0R
mov ESI, offset datai
mov EDI, offset data2
cld
mov CX, len
rep movsb
mov EAX, offset data2
ret
_seg_ex endp
code ends
end
При использовании модели flat доступ к данным осуществляется по 32-разрядному смещению, поэтому смысл показанных ниже команд, загружающих адреса логических сегментов (а заодно и адреса строк src и dst) в регистры ESI и EDI, думаю, понятен:
mov ESI, offset data1
mov EDI, offset data2
Группа следующих команд выполняет копирование строки src в dst, при этом регистр СХ содержит количество копируемых байтов:
cld
mov CX. len
rep movsb
В регистре ЕАХ возвращается адрес строки-приемника dst. Обращаю внимание читателей на то, что никакой инициализации сегментных регистров не требуется, поскольку это делается при помощи директивы . model flat. Еще один важный момент: программа, использующая модель flat, выполняется в одном физическом сегменте, хотя логических сегментов может быть несколько, как в нашем случае.
Работоспособность процедуры легко проверить, вызвав ее из программы на Visual C++ .NET (нужно только включить объектный файл процедуры в проект приложения). Исходный текст приложения приведен в листинге 4.3.