Смекни!
smekni.com

ЯЗЫК МАКРОАССЕМБЛЕРА IBM PC (стр. 5 из 9)

С учетом такого распределения ролей сегментных регистров машинные программы обычно строятся так: все команды программы размещаются в од­ном сегменте памяти, начало которого заносится в регистр CS, а все данные размещаются в другом сегменте, начало которого заносится в ре­гистр DS; если нужен стек, то под него отводится третий сегмент памя­ти, начало которого записывается в регистр SS. После этого практически во всех командах можно указывать не полные адресные пары, а лишь сме­щения, т.к. сегментные регистры в этих парах будут восстанавливаться автоматически.

Здесь, правда, возникает такой вопрос: как по смещению определить, на какой сегмент памяти оно указывает? Точный ответ приведен ниже (см. 1.4.3), а в общих чертах он такой: ссылки на сегмент команд могут быть только в командах перехода, а ссылки практически во всех других коман­дах (кроме строковых и стековых) - это ссылки на сегмент данных. Нап­ример, в команде пересылки

MOV AX,X

имя X воспринимается как ссылка на данное, а потому автоматически вос­станавливается до адресной пары DS:X. В команде же безусловного пере­хода по адресу, находящемуся в регистре BX,

JMP BX

абсолютный адрес перехода определяется парой CS:[BX].

Итак, если в ссылке на какую-то ячейку памяти не указан явно сег­ментный регистр, то этот регистр берется по умолчанию. Явно же сегмен­тные регистры надо указывать, только если по каким-то причинам регистр по умолчанию не подходит. Если, например, в команде пересылки нам надо сослаться на стек (скажем, надо записать в регистр AH байт стека, по­меченный именем X), тогда нас уже не будет устраивать договоренность о том, что по умолчанию операнд команды MOV сегментируется по регистру DS, и потому мы обязаны явно указать иной регистр - в нашем случае ре­гистр SS, т.к. именно он указывает на стек:

MOV AH,SS:X

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

Отметим, что в MASM сегментный регистр записывается в самой коман­де непосредственно перед смещением (именем переменной, меткой и т.п.), однако на уровне машинного языка ситуация несколько иная. Имеется 4 специальные однобайтовые команды, называемые префиксами замены сегмен­та (обозначаемые как CS:, DS:, SS: и ES:). Они ставятся перед коман­дой, операнд-адрес которой должен быть просегментирован по регистру, отличному от регистра, подразумеваемому по умолчанию. Например, приве­денная выше символическая команда пересылки - это на самом деле две машинные команды:

SS:

MOV AH,X

1.4.3 Сегментирование, базирование и индексирование адресов Поскольку сегментирование адресов - это разновидность модификации

адресов, то в ПК адрес, указываемый в команде, в общем случае модифи­цируется по трех регистрам - сегментному, базовому и индексному. В це­лом, модификация адреса производится в два этапа. Сначала учитываются только базовый и индексный регистры (если они, конечно, указаны в ко­манде), причем вычисление здесь происходит в области 16-битовых адре­сов; полученный в результате 16-битовый адрес называется исполнитель­ным (эффективным) адресом. Если в команде не предусмотрено обращение к памяти (например, она загружает адрес в регистр), то на этом модифика­ция адреса заканчивается и используется именно исполнительный адрес (он загружается в регистр). Если же нужен доступ к памяти, тогда на втором этапе исполнительный адрес рассматривается как смещение и к не­му прибавляется (умноженное на 16) содержимое сегментного регистра, указанного явно или взятого по умолчанию, в результате чего получается абсолютный (физический) 20-битовый адрес, по которому реально и проис­ходит обращение к памяти.

Отметим, что сегментный регистр учитывается только в "последний" момент, непосредственно перед обращением к памяти, а до этого работа ведется только с 16-битовыми адресами. Если учесть к тому же, что сег­ментные регистры, как правило, не указываются в командах, то можно в общем-то считать, что ПК работает с 16-битовыми адресами.

Как уже сказано, если в ссылке на ячейку памяти не указан сегмент­ный регистр, то он определяется по умолчанию. Это делается по следую­щим правилам.

1) В командах перехода адрес перехода сегментируется по регистру CS и только по нему, т.к. абсолютный адрес команды, которая должна быть выполнена следующей, всегда определяется парой CS:IP (попытка из­менить в таких командах сегментный регистр будет безуспешной).

Отметим, что сегментиорвание по регистру CS касается именно адреса

перехода, а не адреса той ячейки, где он может находиться. Например, в команде безусловного перехода по адресу, находящемуся в ячейке X:

JMP X

имя X сегментируется по регистру DS, а вот адрес перехода, взятый из ячейки X, уже сегментируется по регистру CS.

2) Адреса во всех других командах, кроме строковых (STOS, MOVS, SCAS и CMPS), по умолчанию сегментируются:

- по регистру DS, если среди указанных регистров-модификаторов нет регистра BP;

- по регистру SS, если один из модификаторов - регистр BP.

Таким образом, адреса вида A, A[BX], A[SI], A[DI], A[BX][SI] и A[BX][DI] сегментируются по регистру DS, а адреса A[BP], A[BP][SI] и A[BP][DI] - по регистру SS, т.е. адреса трех последних видов использу­ются для доступа к ячейкам стека.

3) В строковых командах STOS, MOVS, SCAS и CMPS, имеющих два опе­ранда-адреса, на которые указывают индексные регистры SI и DI, один из операндов (на который указывает SI) сегментируется по регистру DS, а другой (на него указывает DI) - по регистру ES.

1.4.4 Программные сегменты. Директива ASSUME

Рассмотрим, как сегментирование проявляется в программах на MASM.

Для того чтобы указать, что некоторая группа предложений программы на MASM образуют единый сегмент памяти, они оформляются как программ­ный сегмент: перед ними ставится директива SEGMENT, после них - дирек­тива ENDS, причем в начале обеих этих директив должно быть указано од­но и то же имя, играющее роль имени сегмента. Программа же в целом представляет собой последовательность таких программных сегментов, в конце которой указывается директива конца программы END, например:


DT1 SEGMENT ;программный сегмент с именем DT1 A DB 0

B DW ? DT1 ENDS

;


DT2 SEGMENT ;программный сегмент DT2

C DB 'hello'

DT2 ENDS

;

CODE SEGMENT ;программный сегмент CODE

ASSUME CS:CODE, DS:DT1, ES:DT2

BEG: MOV AX,DT2

MOV DS,AX

MOV BH,C

...

CODE ENDS

END BEG ;конец текста программы

Предложения программного сегмента ассемблер размещает в одном сег­менте памяти (в совокупности они не должны занимать более 64Кб) начи­ная с ближайшего свободного адреса, кратного 16. Номер (первые 16 би­тов начального адреса) этого сегмента становится значением имени сег­мента. В MASM это имя относится к константным выражениям, а не адрес-

ным, в связи с чем в команде

MOV AX,DT2

второй операнд является непосредственным, поэтому в регистр AX будет записано начало (номер) сегмента DT2, а не содержимое начальной ячейки этого сегмента.

Имена же переменных (A, B, C) и метки (BEG) относятся к адресным выражениям, и им ставится в соответствие адрес их ячейки относительно "своего" сегмента: имени A соответствует адрес 0, имени B - адрес 1, имени C - адрес 0, а метке BEG - адрес 0.

Все ссылки на предложения одного программного сегмента ассемблер сегментирует по умолчанию по одному и тому же сегментному регистру. По какому именно - устанавливается специальной директивой ASSUME. В нашем примере эта директива определяет, что все ссылки на сегмент CODE долж­ны, если явно не указан сегментный регистр, сегментироваться по регис­тру CS, все ссылки на DT1 - по регистру DS, а все ссылки на DT2 - по регистру ES.

Встретив в тексте программы ссылку на какое-либо имя (например, на имя C в команде MOV AX,C), ассемблер определяет, в каком программном сегменте оно описано (у нас - в DT2), затем по информации из директивы ASSUME узнает, какой сегментный регистр поставлен в соответствие этому сегменту (у нас - это ES), и далее образует адресную пару иэ данного регистра и смещения имени (у нас - ES:0), которую и записывает в фор­мируемую машинную команду. При этом ассемблер учитывает используемое в ПК соглашение о сегментных регистрах по умолчанию: если в адресной па­ре, построенной им самим или явно заданной в программе, сегментный ре­гистр совпадает с регистром по умолчанию, то в машинную команду зано­сится лишь смещение. Если, скажем, в нашем примере встретится команда MOV CX,B, тогда по имени В ассемблер построит пару DS:1, но раз опе­ранд-адрес команды MOV по умолчанию сегментируется по регистру DS, то записывать этот регистр в машинную команду излишне и ассемблер записы­вает в нее только смещение 1.

Таким образом, директива ASSUME избавляет программистов от необхо­димости выписывать полные адресные пары не только тогда, когда исполь­зуются сегментные регистры по умолчанию (как в случае с именем B), но тогда, когда в машинной команде нужно было бы явно указать сегментный регистр (как в случае с именем C). В MASM сегментный регистр в ссылке на имя требуется указывать лишь тогда, когда имя должно по каким-либо причинам сегментироваться по регистру, отличному от того, что постав­лен в соответствие всему сегменту, в котором это имя описано.

Однако все это справедливо только при соблюдении следующих усло­вий. Во-первых, директива ASSUME должна быть указана перед первой ко­мандой программы. В противном случае ассемблер, просматривающий текст программы сверху вниз, не будет знать, как сегментировать имена из ко­манд, расположенных до этой директивы, и потому зафиксирует ошибку. Во-вторых, в директиве ASSUME следует каждому сегменту ставить в соот­ветствие сегментный регистр: если ассемблеру встретится ссылка на имя из сегмента, которому не соответствует никакой сегментный регистр, то он зафиксирует ошибку. Правда, в обоих случаях можно избежать ошибки, но для этого в ссылках необходимо явно указывать сегментный регистр.