Курс, 4 семестр, 51 час Лекции Саратов 2007 Часть Системное программное обеспечение 3

Вид материалаЛекции
Приложение А
Атрибут выравнивания сегмента
Атрибут комбинирования сегментов
Атрибут размера сегмента
Атрибут класса сегмента
Подобный материал:
1   ...   9   10   11   12   13   14   15   16   17

Приложение А



1. Организация программы на ассемблере.

    1. Общий вид программы на ассемблере. Общий вид программного сегмента.


Каждая программа, написанная на любом языке программирования, состоит из одного или нескольких сегментов. Обычно область памяти, в которой находятся команды, называют сегментом кода, область памяти с данными – сегментом данных, область памяти, отведённую под стек – сегментом стека.


Общий вид программы на ассемблере:


<сегмент1>

…………..

<

сегментN

<точка входа>:

……………….

end <точка входа>

>

Обязательной частью является только последний сегмент. Точку входа и выход из программы можно обозначить несколькими способами, об этом будет сказано ниже. Теперь рассмотрим общий вид сегмента.


<имя_сегмента> segment readonly <выравнивание> <тип_комбинирования> <размер> ‘<класс>’


<директива ассемблера>

<команда ассемблера>

<макрокоманда ассемблера>

<строка комментария>


<имя_сегмента> ends


Все пять операндов необязательны.


Если присутствует операнд READONLY, MASM выдаст сообщение об ошибке на все команды, выполняющие запись в данный сегмент. Другие ассемблеры этот операнд игнорируют.

Атрибут выравнивания сегмента (тип выравнивания) сообщает компоновщику о том, что нужно обеспечить размещение начала сегмента на заданной границе. Это важно, поскольку при правильном выравнивании доступ к данным в процессорах i80x86 выполняется быстрее. Допустимые значения этого атрибута следующие:


° BYTE – выравнивание не выполняется. Сегмент может начинаться с любого адреса памяти;

° WORD – сегмент начинается по адресу, кратному двум, то есть последний (младший) значащий бит физического адреса равен 0 (выравнивание на границу слова);

° DWORD – сегмент начинается по адресу, кратному четырем, то есть два последних (младших) значащих бита равны 0 (выравнивание на границу двойного слова);

° РАRА – сегмент начинается по адресу, кратному 16, то есть последняя шестнадцатеричная цифра адреса должна быть 0h (выравнивание на границу параграфа);

° PAGE – сегмент начинается по адресу, кратному 256, то есть две последние шестнадцатеричные цифры должны быть 00h (выравнивание на границу страницы размером 256 байт);

° МЕMРАGЕ – сегмент начинается по адресу, кратному 4 Кбайт, то есть три последние шестнадцатеричные цифры должны быть 000h (адрес следующей страницы памяти размером 4 Кбайт).

По умолчанию тип выравнивания имеет значение PARA.

Атрибут комбинирования сегментов (комбинаторный тип) сообщает компоновщику, как нужно комбинировать сегменты различных модулей, имеющие одно и то же имя. По умолчанию атрибут комбинирования принимает значение PRIVATE. Значениями атрибута комбинирования сегмента могут быть:

° PRIVATE – сегмент не будет объединяться с другими сегментами с тем же именем вне данного модуля;

° PUBLIC – заставляет компоновщик соединить все сегменты с одинаковым именем. Новый объединенный сегмент будет целым и непрерывным. Все адреса (смещения) объектов, а это могут быть, в зависимости от типа сегмента, команды или данные, будут вычисляться относительно начала этого нового сегмента;

° СОMMОN – располагает все сегменты с одним и тем же именем по одному адресу. Все сегменты с данным именем будут перекрываться и совместно использовать память. Размер полученного в результате сегмента будет равен размеру самого большого сегмента;

° AT хххх – располагает сегмент по абсолютному адресу параграфа (параграф – объем памяти, кратный 16; потому последняя шестнадцатеричная цифра адреса параграфа равна 0). Абсолютный адрес параграфа задается выражением ххх. Компоновщик располагает сегмент по заданному адресу памяти (это можно использовать, например, для доступа к видеопамяти или области ПЗУ), учитывая атрибут комбинирования. Физически это означает, что сегмент при загрузке в память будет расположен, начиная с этого абсолютного адреса параграфа, но для доступа к нему в соответствующий сегментный регистр должно быть загружено заданное в атрибуте значение. Все метки и адреса в определенном таким образом сегменте отсчитываются относительно заданного абсолютного адреса;

° STACK – определение сегмента стека. Заставляет компоновщик соединить все одноименные сегменты и вычислять адреса в этих сегментах относительно регистра SS. Комбинированный тип STACK (стек) аналогичен комбинированному типу PUBLIC, за исключением того, что регистр SS является стандартным сегментным регистром для сегментов стека. Регистр SP устанавливается на конец объединенного сегмента стека. Если не указано ни одного сегмента стека, компоновщик выдаст предупреждение, что стековый сегмент не найден. Если сегмент стека создан, а комбинированный тип STACK не используется, программист должен явно загрузить в регистр SS адрес сегмента (подобно тому, как это делается для регистра DS).

Атрибут размера сегмента. Для процессоров i80386 и выше сегменты могут быть 16- или 32-разрядными. Это влияет, прежде всего, на размер сегмента и порядок формирования физического адреса внутри него. Атрибут может принимать следующие значения:

° USE16 – это означает, что сегмент допускал 16-разрядную адресацию. При формировании физического адреса может использоваться только 16-разрядное смещение. Соответственно, такой сегмент может содержать до 64 Кбайт кода или данных;

° USE32 – сегмент будет 32-разрядным. При формировании физического адреса может использоваться 32-разрядное смещение. Поэтому такой сегмент может содержать до 4 Гбайт кода или данных.

Атрибут класса сегмента (тип класса) – это заключенная в кавычки строка, помогающая компоновщику определить соответствующий порядок следования сегментов при сборке программы из сегментов нескольких модулей. Компоновщик объединяет вместе в памяти все сегменты с одним и тем же именем класса (имя класса, в общем случае, может быть любым, но лучше, если оно будет отражать функциональное назначение сегмента). Типичным примером использования имени класса является объединение в группу всех сегментов кода программы (обычно для этого используется класс “code”). С помощью механизма типизации класса можно группировать также сегменты инициализированных и неинициализированных данных.

    1. Упрощённые директивы определения сегментов.


Все сегменты сами по себе равноправны, так как директивы SEGMENT и ENDS не содержат информации о функциональном назначении сегментов. Для того чтобы использовать их как сегменты кода, данных или стека, необходимо предварительно сообщить транслятору об этом, для чего используют специальную директиву ASSUME. Эта директива сообщает транслятору о том, какой сегмент к какому сегментному регистру привязан. В свою очередь, это позволит транслятору корректно связывать символические имена, определенные в сегментах. Привязка сегментов к сегментным регистрам осуществляется с помощью операндов этой директивы, в которых имя сегмента должно быть именем сегмента, определенным в исходном тексте программы директивой SEGMENT или ключевым словом nothing. Если в качестве операнда используется только ключевое слово nothing, то предшествующие назначения сегментных регистров аннулируются, причем сразу для всех шести сегментных регистров. Но ключевое слово nothing можно использовать вместо аргумента имя сегмента; в этом случае будет выборочно разрываться связь между сегментом с именем имя сегмента и соответствующим сегментным регистром.

Для простых программ, содержащих по одному сегменту для кода, данных и стека, хотелось бы упростить их описание. Для этого в трансляторы MASM и TASM ввели возможность использования упрощенных директив сегментации. Но здесь возникла проблема, связанная с тем, что необходимо было как-то компенсировать невозможность напрямую управлять размещением и комбинированием сегментов. Для этого совместно с упрощенными директивами сегментации стали использовать директиву указания модели памяти MODEL, которая частично стала управлять размещением сегментов и выполнять функции директивы ASSUME (поэтому при использовании упрощенных директив сегментации директиву ASSUME можно не использовать). Эта директива связывает сегменты, которые, в случае использования упрощенных директив сегментации, имеют предопределенные имена с сегментными регистрами (хотя явно инициализировать DS все равно придется).


Формат директивы
(режим MASM)

Формат директивы
(режим IDEAL)

Назначение

.CODE [имя]

CODESEG [имя]

Начало или продолжение сегмента кода

.DATA

DATASEG

Начало или продолжение сегмента инициализированных данных. Также используется для определения данных типа near

.CONST

CONST

Начало или продолжение сегмента постоянных данных (констант) модуля

.DАТА?

UDATASEG

Начало или продолжение сегмента неинициализированных данных. Также используется для определения данных типа near

.STACK [размер]

STACK [размер]

Начало или продолжение сегмента стека модуля. Параметр [размер] задает размер стека

.FARDATA [имя]

FARDАТА [имя]

Начало или продолжение сегмента инициализированных данных типа far

.FARDATA? [имя]

UFARDATA [имя]

Начало или продолжение сегмента неинициализированных данных типа far

Идентификаторы, создаваемые директивой MODEL


Имя идентификатора Значение переменной




@code Физический адрес сегмента кода

@data Физический адрес сегмента данных тина near

@fardata Физический адрес сегмента данных типа far

@fardata? Физический адрес сегмента неинициализированных данных

@curseg Физический адрес текущего сегмента

@stack Физический адрес сегмента стека


Директива .CODE описывает основной сегмент кода.

.code имя_сегмента


эквивалентно

_TEXT segment word public ‘CODE’

для моделей TINY, SMALL, COMPACT и


name_TEXT segment word public ‘CODE’

для моделей MEDIUM, HUGE, LARGE (name – имя модуля, в котором описан данный сегмент). В этих моделях директива .CODE также допускает необязательный операнд – имя определяемого сегмента, но не все сегменты кода, описанные так в одном и том же модуле объединяютмя в один сегмент с именем NAME_TEXT.


Директива .STACK описывает сегмент стека.


.stack размер


эквивалентно

STACK segment para public ‘STACK’

Необязательный параметр указывает размер стека. По умолчанию он равен 1Кб.


Директива .DATA описывает сегмент данных.


.data


эквивалентно

_DATA segment word public ‘DATA’


Директива .DATA? описывает сегмент неинициализированных данных и соответствует


_BSS segment word public ‘BSS’

Этот сегмент обычно не включается в программу, а располагается за концом памяти, так что все описанные в нём переменные имеют на момент загрузки программы неопределённые значения.


Директива .CONST описывает сегмент неизменяемых данных и соответствует


CONST segment word public ‘CONST’


Директива .FARDATA имя_сегмента описывает сегмент дальних данных и соответствует


имя_сегмента segment para private ‘FAR_DATA’

Доступ к данным, описанным в этом сегменте, потребует загрузки сегментного регистра. Если не указан операнд, в качестве имени сегмента используется FAR_DATA.


Директива .FARDATA? имя_сегмента описывает сегмент дальних неинициализированных данных и соответствует


имя_сегмента segment para private ‘FAR_BSS’

Доступ к данным, описанным в этом сегменте, потребует загрузки сегментного регистра. Если не указан операнд, в качестве имени сегмента используется FAR_BSS.

    1. Управление программным счётчиком.


Программный счётчик – внутренняя переменная ассемблера, равная смещению текущей команды или данных относительно начала сегмента. Для преобразования меток в адреса именно значение этого счётчика. Значением счётчика можно управлять с помощью следующих директив:


org <выражение>

– устанавливает значение программного счётчика. Директива org с операндом 100h обязательна при написании файлов типа .com, которые загружаются в память после блока PSP.


even

– делает текущее значение счётчика кратным двум, вставляя команду NOP, если оно было нечётным. Это увеличивает скорость работы программы, так как для доступа к слову, начинающемуся с нечётного адреса, процессор должен считать два слова из памяти. Если при описании сегмента не использовалось выравнивание типа BYTE, счетчик в начале сегмента всегда чётный.


align <значение>

– округляет значение программного счётчика до указанного значения. Оно может быть любым чётным числом. Если счётчик не кратен указанному значению, эта директива вставляет необходимое число команд NOP.

    1. Процедуры.


Процедурой в ассемблере называется всё то, что в других языках называется подпрограммами, функциями, процедурами и т.п. Ассемблер не накладывает ограничений – на любой адрес программы можно передать управление командой call, и оно вернётся к вызвавшей процедуре, как только встретится команда ret. Такая свобода выражения легко может приводить к трудночитаемым программам, и в язык ассемблера были включены директивы логического оформления процедур.


<метка> proc язык тип USES регистры


для TASM или


<метка> proc тип язык USES регистры


для MASM и WASM




ret

<метка> endp


Все операнды PROC необязательны.

Тип может принимать значения NEAR и FAR, и если он указан, все команды RET в теле процедуры будут заменены соответственно на RETN и RETF. По умолчания подразумевается, что процедура имеет тип NEAR в моделях памяти TINY, SMALL, COMPACT.

Операнд язык действует аналогично такому же операнду директивы .MODEL, определяя взаимодействие процедуры с языками высокого уровня. В некоторых ассемблерах директива PROC позволяет также считать параметры, передаваемые вызывающей программой. В этом случае указание языка необходимо, т.к. различные языки высокого уровня используют различные способы передачи параметров.

USES – список регистров, значения которых изменяет процедура. Ассемблер помещает в начало процедуры набор команд PUSH, а перед командой RET – набор команд POP, так что значения перечисленных регистров будут восстановлены.

  1. Примеры программ на ассемблере.



    1. Оформление .com-файлов.


Листинг 1.

model tiny

.code

org 100h

start proc near

<тело программы>

ret

start endp


<данные>

<процедуры>


end start


model tiny – модель памяти TINY, в которой сегменты кода, данных и стека объединены. Эта модель предназначена для .com-файлов.

.code – начало сегмента кода.

org 100h – устанавливает значение программного счётчика в 100h, потому что при загрузке .com-файла в память DOS занимает первые 256 байт блоком данных PSP и располагает код программы только после этого блока.

Команда ret используется обычно для возвращения из процедуры. DOS вызывает .com-программы так, что команда ret корректно завершает программу.

end start – директивой end завершается любая программа на ассемблере. В качестве необязательного операнда выступает метка(выражение), определяющая адрес, с которого начинается программа. Если программа состоит из нескольких модулей, только один из них может содержать начальный адрес. В данном случае программа оформлена в виде главной процедуры, аналогично функции main в C. Однако тело программы необязательно оформлять в виде процедуры:


Листинг 2.

model tiny

.code

org 100h

start:

<тело программы>

ret


<данные>

<процедуры>


end start


Существует несколько упрощённый вариант оформления:


Листинг 3.

model tiny

.code

.startup


<тело программы>


.exit [код ошибки]


<данные>

<процедуры>


End


.STARTUP по умолчанию генерирует точку входа для .com-файла(модель памяти TINY) в 100h, инициализирует DS, SS, SP в соответствии с выбранной моделью памяти. .EXIT – возврат управления в DOS, аналогично функции DOS 4Ch, в al находится возвращаемый код ошибки.
    1. Простейшие программы и использование процедур


Программа Hello, world в .exe-формате.


Листинг 4.

model small

.stack

.data

Msg db 'Hello, world',0Ah,0Dh,'$'

.code

.startup

mov ah,09

lea dx,Msg

int 21h

mov ax,4C00h

int 21h

end


model small – обычно используется для написания .exe-файлов на ассемблере.

.stack – определили сегмент стека размером 1Кб.

.data – описали сегмент данных и объявили в нём строку, состоящую из байтов. После апострофа идут управляющие символы, с помощью которых осуществляется переход на следующую строку и символ окончания строки – '$'.

mov ax,4C00h

int 21h – эквивалентно .exit 0.


Листинг 5.

Программа(в .exe-формате) преобразования двузначного шестнадцатеричного числа в символьном виде в двоичное представление.


Вход: исходное шестнадцатеричное число из двух цифр, вводится с клавиатуры.

Выход: результат преобразования должен быть в регистре dl.


data segment para public 'data' ;сегмент данных

message db 'Введите две шестнадцатеричные цифры,$'

data ends

stk segment stack

db 256 dup ('?') ;сегмент стека

stk ends

code segment para public 'code' ;начало сегмента кода

main proc ;начало процедуры main

assume cs:code,ds:data,ss:stk

mov ax,data ;адрес сегмента данных в регистр ax

mov ds,ax ;ax в ds

mov ah,9

mov dx,offset message

int 21h

xor ax,ax ;очистить регистр ax

mov ah,1h ;1h в регистр ah

int 21h ;генерация прерывания с номером 21h

mov dl,al ;содержимое регистра al в регистр dl

sub dl,30h ;вычитание: (dl)=(dl)-30h

cmp dl,9h ;сравнить (dl) с 9h

jle M1 ;перейти на метку M1 если dl<9h или dl=9h

sub dl,7h ;вычитание: (dl)=(dl)-7h

M1: ;определение метки M1

mov cl,4h ;пересылка 4h в регистр cl

shl dl,cl ;сдвиг содержимого dl на 4 разряда влево

int 21h ;вызов прерывания с номером 21h

sub al,30h ;вычитание: (dl)=(dl)-30h

cmp al,9h ;сравнить (al) с 9h 28

jle M2 ;перейти на метку M2 если al<9h или al=9h

sub al,7h ;вычитание: (al)=(al)-7h

M2: ;определение метки M2

add dl,al ;сложение: (dl)=(dl)+(al)

mov ax,4c00h ;пересылка 4c00h в регистр ax

int 21h ;вызов прерывания с номером 21h

main endp ;конец процедуры main

code ends ;конец сегмента кода

end main ;конец программы с точкой входа main


Листинг 6.

Программа, аналогичная предыдущей, записанная с использованием упрощенных директив определения сегментов.

masm ;режим работы TASM: idea1 или masm

model sma11 ;модель памяти

.data ;сегмент данных

message db "Введите две шестнадцатеричные цифры,$"

.stack ;сегмент стека

db 256 dup ("?") ;сегмент стека

.code ;сегмент кода

main рrос ;начало процедуры main

mov ax,@data ;заносим адрес сегмента данных в регистр ах

mov ds,ax ;ах в ds

;далее текст программы идентичен тексту сегмента кода в листинге 3.1

mov ax,4c00h ;пересылка 4c00h в регистр ах

int 21h ;вызов прерывания с номером 21h

main endp ;конец процедуры main

end main ;конец программы с точкой входа main


Листинг 7.

Пример использования процедуры в программе.

model tiny

.code

org 100h

start:

mov bl,23h

call bcd_ascii

ret


bcd_ascii proc

.186 ;Разрешаем pusha, popa, shr x,4, shl x,4.

pusha ;Сохраняем регистры.

xor bh,bh ;Обнуляем bh.

shl bx,4 ;Сдвигаем bx на четыре бита вправо - в bh

shr bl,4 ;находится старшая цифра числа, в bl младшая

;цифра находится в старшем разряде, сдвигаем её

;обратно в младший разряд.

or bx,3030h ;Добавляем к цифрам 30h для преобразования в

;ASCII-символ.

mov ah,2 ;Выводим на экран.

mov dl,bh

int 21h

mov dl,bl

int 21h

popa ;Восстанавливаем регистры.

ret

bcd_ascii endp


end start