Ассемблер для процессоров Intel Pentium
Шрифт:
Для 32-разрядных приложений шаблон исходного текста выглядит иначе:
.model flat
.stack
.data
: данные
.code
main:
. . .
: команды ассемблера
. . .
end main
end
Основное отличие от предыдущего примера – другая модель памяти (flat), предполагающая 32-разрядную линейную адресацию с атрибутом near.
Как видно из примера, «классический» шаблон 32-разрядного приложения содержит область данных (определяемую директивой .data), область стека (директива .stack) и область программного кода (директива .code). Может случиться так, что 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.
Листинг 4.3. Программа, вызывающая процедуру seg_ex
#include <stdio.h>
exterr "С" char* seg_ex(void);
int main(void)
{
printf(«EXTERNAL MODULE EXAMPLE: %s\n», seg_ex);
return 0;
}
Здесь процедура segex является внешней, поэтому объявлена как extern.
Результатом выполнения программы будет строка на экране дисплеяEXTERNAL MODULE EXAMPLE: Test STRING To Copy+
Глава 5Организация вычислительных циклов
Большинство программ, независимо от того, на каком языке они написаны, в процессе работы требуют изменения линейной последовательности выполнения операторов и перехода на другие ветви программного кода. В языках высокого уровня, таких, например, как C++ или Pascal, существуют специальные операторы, позволяющие выполнять ветвление программ, в то время как на языке ассемблера для организации переходов требуется писать специальный код.
Подобные операторы или группы операторов называют логическими структурами. Принципиально, существует два типа логических структур, на основе которых можно создать все остальные логические выражения и вычислительные алгоритмы:
– Структура, основанная на сравнении каких-либо величин и выборе варианта продолжения программы в зависимости от результата сравнения. Такую логическую структуру можно описать выражением «если А, то В, иначе С». В языках высокого уровня ее аналогом является оператор if ... else. Кроме того, существуют и расширенные варианты оператора if ... else, например switch ... case.
– Структура, в основе которой лежит алгоритм повторяющихся вычислений. Такой алгоритм выполняется при соблюдении определенных условий, которые проверяются в начале или в конце каждой итерации. Такие логические структуры в языках высокого уровня реализованы как операторы while, do ... while, for, repeat ... until и т. д.
Логические структуры языков высокого уровня придают особую гибкость и мощь этим языкам и позволяют относительно легко строить самые сложные алгоритмы. А как обстоят дела с языком ассемблера, ведь здесь нет подобных операторов? Несмотря на отсутствие подобных операторов, ассемблер обладает довольно мощными командами, позволяющими строить любые сколь угодно сложные логические структуры, в том числе и такие, которые довольно трудно, а иногда и невозможно реализовать в языках высокого уровня.
Хочу сразу же оговориться: мы не будем брать во внимание псевдомакросы логических структур, такие, как .IF и .WHILE, включенные в популярные языки ассемблера (в данном случае MASM). Псевдомакросы имитируют логические структуры высокого уровня, например if ... else, но значительно уступают им по возможностям, хотя и улучшают читабельность кода. Если внимательно посмотреть на программный код макросов, то вы увидите, что они содержат команды ассемблера, которые вполне можно написать самостоятельно. Кроме того, псевдомакросы скрывают механизм операции, что может быть полезно для программирования, но не для изучения алгоритмов функционирования. По этим причинам я сознательно отказался от использования псевдомакросов и макрорасширений ассемблера при рассмотрении программного кода.
Посмотрим, как можно создать логические структуры на языке ассемблера и использовать их в программах. В подавляющем большинстве случаев для создания логических структур и выражений на ассемблере, таких, например, как if ... else, while и так далее, требуется два условия:
– установить один или несколько битов в регистре флагов процессора;
– проверить состояние бита (группы битов) с помощью команд условного перехода и передать управление на соответствующую ветвь программы.
Большинство команд ассемблера после выполнения устанавливают биты регистра флагов определенным образом, поэтому у программиста есть довольно широкий выбор возможностей для организации вычислительных алгоритмов и логических структур. Естественно, необходимо учитывать особенности выполнения той или иной команды, поскольку можно получить совершенно неожиданные побочные эффекты.