Реферат по предмету "Разное"


Ассемблер. Компоновщик. Загрузчик. Макрогенератор

АССЕМБЛЕР. КОМПОНОВЩИК. ЗАГРУЗЧИК. МАКРОГЕНЕРАТОР.Полная схема трансляции и запуска на счет модуля, написанного на языке макроассемблера:модуль на ┌──────────────┐ модуль ┌─────────┐ объектный макроязыке --> │макрогенератор│ --> на языке --> │ассемблер│ --> модуль --> └──────────────┘ ассемблера └─────────┘ ┌--> др.модули --┘┌───────────┐ загрузочный модуль ┌─────────┐ -> │компоновщик│ --> (исполняемая пр-ма) --> │загрузчик│ --> счет -> └───────────┘ └─────────┘1. АССЕМБЛЕР^ 1.1 ОСНОВНАЯ ИДЕЯ АССЕМБЛИРОВАНИЯ. ДВА ПРОХОДА. Сначала будем предполагать, что транслируемая программа состоит только из одного модуля, т.е. в ней нет внешних и общих имен, а позже (в 1.5) рассмотрим, какие изменения надо внести в работу ассемблера, чтобы учесть особенности многомодульных программ. Отметим также, что ассемблер работает после макрогенератора, поэтому в программе, которая подается на вход ассемблеру, нет никаких макрокоманд и других директив макроязыка.^ Основная идея ассемблирования. Ассемблер постоянно находится во внешней памяти. Когда операционной системе (ОС) дан приказ MASM M.ASM, M.OBJ, M.LST; на трансляцию нашей программы, то ОС считывает ассемблер из внешней памяти (из файла MASM.EXE) в оперативную память (ОП) и передает ему управление. Свою работу ассемблер начинает с того, что считывает из внешней памяти (из файла M.ASM) в ОП программу на языке ассемблера (ЯА). Затем он просматривает ее текст строчка за строчкой и постепенно формирует соответствующие машинные коды, которые записывает в другое место ОП. Когда ассемблер полностью построит машинную программу, он записывает ее во внешнюю память (в файл M.OBJ) и на этом заканчивает свою работу (попутно в файл M.LST записывается листинг).┌───────>───────┐ ┌────────>───────┐ ────────────────────────────────────────────────────────────── ОП: │программа на ЯА│ │ ассемблер │ │маш.программа│ ──────────────────────────────────────────────────────────────    внеш.память: M.ASM MASM.EXE M.OBJОсновная идея перевода с ЯА на машинный язык проста. Надо: - заменить мнемонические названия команд на соответствующие цифровые коды операций (КОПы); - заменить имена переменных и меток на соответствующие адреса; - перевести данные в двоичное машинное представление. Если, к примеру, имя B обозначает ячейку с адресом 0001, а операция сложения слова из памяти с непосредственным операндом имеет код 81 06 (в ПК коды многих операций занимают два байта), тогда по символьной команде ADD B,260 ассемблер должен построить следующую машинную команду: 81 06 0001 0104 Как осуществляется такой перевод? Перевод чисел из 10-й системы счисления в 2-ю осуществляется по хорошо известным алгоритмам. Замена мнемокодов на цифровые КОПы осуществляется с помощью заранее составленной таблицы, в которой для каждого мнемокода (ADD, MOV, JMP и т.п.) указано, на какой цифровой КОП надо заменять этот мнемокод. Выделив из символьной команды мнемокод, ассемблер отыскивает в этой таблице строчку с данным мнемокодом и берет из нее нужный КОП, который и подставляет в формируемую машинную команду. Имя переменной (или метка) должно заменяться на его смещение, т.е. на адрес имени, отсчитанный от начала того сегмента, в котором описано это имя. Для подсчета смещений ассемблер в своей работе использует специальный счетчик размещения (СР), в котором всегда находится смещение первой свободной, еще не занятой ячейки текущего сегмента, т.е. ячейки, куда должна быть помещена очередная машинная команда. При появлении в программе на ЯА нового сегмента СР обнуляется, а затем увеличивается по мере просмотра предложений этого сегмента. Рассмотрим такой пример:имя адрес СР^ S SEGMENT 0 A DB ? A 0 1 B DW ? B 1 3 C DD ? C 3 7 ... При появлении директивы SEGMENT отсчет смещений должен начаться от 0, поэтому СР получает значение 0. Далее идет директива DB, описывающая переменную A. Эта переменная размещается с самого начала сегмента, т.е. имеет смещение 0, поэтому имени A ставится в соответствие адрес 0, т.е. текущее значение СР. Переменная A занимает 1 байт, поэтому СР увеличивается на 1 и имеет значение 1; это значит, что первая свободная ячейка в сегменте имеет адрес 1. С этого места размещается переменная B, поэтому имени B ставится в соответствие адрес 1. Так как на B отводится 2 байта, то СР увеличивается на 2 и теперь имеет значение 3. Именно этот адрес будет поставлен в соответствие имени C из следующей директивы. После этого СР увеличивается на 4, и т.д. Вот так ассемблер с помощью СР следит за тем, какие ячейки уже заняты, а какие свободны, и с помощью этого СР определяет, какой адрес какому имени соответствует. Эти соответствия запоминаются ассемблером и затем используются для замены имен на адреса. Такова общая идея перевода с ЯА на машинный язык. Она достаточно проста, но при ее реализации возникает ряд проблем, наиболее важные из которых рассматриваются далее.^ Проблема ссылок вперед. Два прохода ассемблера. Основная из этих проблем связана со ссылками вперед. Суть ее в следующем. Рассмотренный способ замены имен на адреса проходит только для ссылок назад, т.е. когда имя сначала описано (появилось в левой части какого-то предложения) и лишь затем используется (появилось в поле операндов). Ситуация существенно осложняется при ссылках вперед, т.е. когда имя используется до своего описания. Рассмотрим следующий пример: ADD B,260 ... B ... ... Встретив команду ADD, ассемблер еще не знает, где описано имя ^ B, и потому не знает его адреса, не знает, на что надо заменять имя B в этой команде. Что делать? Для решения проблемы со ссылками вперед ассемблер просматривает текст программы дважды. При первом просмотре ассемблер ничего не транслирует, ничего не переводит на машинный язык, а только собирает сведения обо всех именах, используемых в программе: каких они типов, каковы их адреса и т.д. И только при втором просмотре текста ассемблер, уже зная все необходимое об именах, осуществляет перевод символьных команд на машинный язык. Полный просмотр текста транслируемой программы принято называть проходом транслятора, поэтому ассемблер, который делает два прохода, называется двухпроходным. Прежде чем перейти к рассказу о действиях ассемблера на каждом из проходов, рассмотрим таблицы, которыми он пользуется.^ 1.2 ТАБЛИЦЫ АССЕМБЛЕРА. В своей работе ассемблер использует несколько таблиц. Часть из них создается заранее (одновременно с ассемблером), т.е. они попросту встроены в ассемблер, а другие ассемблер строит в процессе трансляции конкретной программы. К заранее составленным таблицам относятся таблица директив и таблица мнемокодов.^ Таблица директив. В этой таблице перечислены названия всех директив ЯА и указаны начальные адреса тех процедур ассемблера, которые занимаются обработкой этих директив: ASSUME - адрес процедуры обработки ASSUME DB - адрес процедуры обработки DB ... Эта таблица используется двояко. Прежде всего она нужна для того, чтобы отличать команды от директив: когда ассемблер обрабатывает очередное предложение программы на ЯА, то он выделяет из него название и определяет по этой таблице, имеется ли такое названием в таблице или нет. Если нет, то ассемблер считает данное предложение командой, а если да - то директивой. В последнем случае ассемблер по адресу из таблицы передает управление на ту из своих процедур, что занимается обработкой данной директивы.^ Таблица мнемокодов. В этой таблице перечислены мнемонические названия всех команд (ADD, MOV и т.п.) и соответствующие им цифровые КОПы. Эта информация нужна для того, чтобы ассемблер знал, на какой КОП надо заменять какой мнемокод. Отметим, что в ПК один и тот же мнемокод в зависимости от типов операндов может заменяться на разные КОПы, поэтому эта таблица представляет собой не просто список пар "мнемокод - КОП", а имеет более сложную структуру, примерно такую: мнемокод тип op1 тип op2 КОП размер команды ---------------------------------------------------------- ADD m16 i16 81 06 6 ADD r16 r16 ... 2 ... NEG r8 - ... 2 ... Эта таблица используется следующим образом. Когда ассемблеру встречается команда, например ADD B,260, то он выделяет из нее мнемокод (ADD) и определяет типы операндов (m16 и i16), после чего отыскивает в таблице строчку с таким мнемокодом и такими типами и берет из нее указанный КОП, который и подставляет в формируемую машинную команду. Отметим, что эта таблица используется также для проверки правильности записи мнемокодов и для проверки правильности типов операндов. Например, если в предложении встретилось название ADS, то, не найдя такого имени в таблице, ассемблер зафиксирует ошибку. Аналогично будет зафиксирована ошибка в команде ADD B,B, поскольку в таблице нет строки с мнемокодом ADD и типами операндов m16 и m16. Рассмотрим попутно, как решается проблема с модифицируемыми адресами, например в команде ADD B[BX],260. В машинных командах ПК информация о том, по каким регистрам-модификаторам происходит модификация операнда-адреса, указывается в последних трех разрядах второго байта КОПа (в поле m): 3 ┌───────────┐ ┌───────────┐ │ │ │ | m │ └───────────┘ └───────────┘ └──────── КОП ───────────┘ Например, m=000b означает, что адрес должен модифицироваться по двум регистрам BX и SI, m=110b - адрес не модифицируется, m=111b - адрес модифицируется по регистру BX и т.д.. Так вот, определив типы операндов команды без учета модификаторов и выбрав из таблицы мнемокодов соответствующий КОП, ассемблер затем просто слегка корректирует этот КОП, настраивая его на нужные модификаторы: в нашем примере с B[BX] код 81 06 (здесь m=110b) надо заменить на 81 07 (m=111b). Поэтому в самой таблице не надо хранить КОПы для всех сочетаний модификаторов, а достаточно хранить только один, так сказать, базовый КОП, отталкиваясь от которого уже просто получить окончательный КОП, учитывающий указанные модификаторы. В таблице мнемокодов также указывается размер машинных команд, их длина в байтах. Эта информация очень важна для ассемблера на 1-м проходе.Теперь рассмотрим таблицы, которые ассемблер строит в процессе своей работы и в которых собираются сведения о той программе, которую ассемблер сейчас транслирует. Вначале эти таблицы пусты, а затем по мере просмотра текста программы ассемблер на 1-м проходе заносит в них информацию о тех или иных объектах программы. Это - таблица имен, таблица сегментов и таблица распределения сегментных регистров.^ Таблица имен (ТИ). В ней записывается информация о метках, именах переменных, именах констант и т.п. Таблица имеет примерно такую структуру (все числа 16-ричные): имя тип значение сегмент -------------------------------------- X WORD 7048 S2 P FAR 001F S3^ K NUMBER 3 - ... В каждой строчке собрана информация об одном имени, указанном в первой колонке. В поле "тип" указывается класс объекта, обозначенного этим именем, и, если надо, его размер. Типы BYTE, WORD и DWORD указывают, что это имя переменной соответствующего размера. Типы NEAR и FAR указывают на метку или имя процедуры. Тип NUMBER указывает на имя числовой константы. Используются и другие типы (например, для структур и записей), но мы их не будем рассматривать. В поле "значение" указывается величина, на которую ассемблер будет заменять имя, когда оно встретится в качестве операнда какой-то команды или директивы. Для меток и имен переменных здесь указываются их адреса (смещения), а для констант - их значения. В поле "сегмент" указывается имя того сегмента программы, в котором было описано имя из первой колонки (для констант это поле пусто). Эта информация нужна для определения того, по какому сегментному регистру должно сегментироваться данное имя.^ Таблица сегментов (ТС). В эту таблицу ассемблер заносит имена всех сегментов программы и некоторые сведения о них. Примерный вид таблицы: имя сегмента начало размер класс ... -----------------------------------------------------^ S1 0 80 STACK S2 80 2405 DATA S3 2490 F27 - ... В поле "начало" указывается начальный адрес сегмента, отсчитанный от начала программы, а в поле "размер" - количество байтов, занимаемых всеми предложениями сегмента. Кроме того, для каждого сегмента указываются значения параметров из директивы SEGMENT, с которой начинается описание данного сегмента; ради простоты из всех этих параметров (выравнивание, объединение и класс) мы далее будем учитывать только параметр "класс". Отметим, что сам ассемблер не пользуется этой таблицей, а строит ее для компоновщика.^ Таблица распределения сегментных регистров (ТРСР). В этой таблице указывается, какому сегментному регистру какой сегмент программы поставлен в соответствие, например: сегм.регистр сегмент -------------------------- CS S3 DS S2 SS S1 ES -- Эта информация узнается из директивы ASSUME и используется для определения того, по каким сегментным регистрам надо сегментировать имена из тех или иных сегментов. При появлении в тексте программы каждой новой директивы ASSUME информация в этой таблице корректируется.^ 1.3 ПЕРВЫЙ ПРОХОД АССЕМБЛЕРА.Основные действия ассемблера на 1-м проходе. Цель 1-го прохода - выявить в программе все имена и собрать информацию о них; эта информация записывается в таблицу имен (ТИ) и таблицу сегментов (ТС), которые будут нужны на 2-м проходе. Вначале эти таблицы, а также таблица распределения сегментных регистров (ТРСР) пусты, а затем они заполняются по мере просмотра текста программы. Примеры обработки директив и команд на 1-м этапе.^ Директива EQU: K EQU 3 Это директива определения константы, которая "говорит", что в программе имя K будет обозначать число 3. Эти сведения ассемблер записывает в ТИ: имя - K, тип - number, значение - 3, сегмент - пусто (не играет роли).Директива SEGMENT:^ S2 SEGMENT 'DATA' С этой директивы начинается программный сегмент. Ассемблер записывается в ТС имя сегмента (S2), его начальный адрес, отсчитанный от начала программы (при необходимости ассемблер выравнивает этот адрес до ближайшего адреса, кратного 16), и имя класса (DATA), к которому отнесен сегмент. Ассемблер также обнуляет счетчик размещения (СР), т.к. отсчет смещений начинается заново, и запоминает, что начался сегмент S1.Директивы DB, DW, DD: X DW Y Y DB 3 DUP(0) По первой из этих директив ассемблер заносит в ТИ информацию об имени X: имя - X, тип - word, значение - текущая величина СР, сегмент - имя текущего сегмента (к примеру, S2), после чего СР увеличивается на 2. По второй директиве в ТИ заносится информация об имени Y: имя - Y, тип - byte, значение - текущая величина СР, сегмент - S2, после чего СР увеличивается на 3. Отметим, что на 1-м походе в эти два и три байта ничего (ни адрес Y, ни 0) не записывается, это будет сделано на 2-м проходе; сейчас важно лишь знать, сколько места будет отведено под эти переменные, на сколько надо увеличивать значение СР.^ Директива PROC: P PROG FAR Начинается описание процедуры. В ТИ заносится следующая информация об имени P: тип - far, значение - текущая величина СР, сегмент - имя текущего сегмента. Поскольку эта директива носит чисто информационный характер и по ней ничего не записывается в машинную программу, то СР не меняется.Директива ASSUME:^ ASSUME S2:DATA, CS:S3, SS:S1 По этой директиве ассемблер заполняет ТРСР, создавая пары DS и S2, CS и S3, SS и S1. Поскольку эта директива носит чисто информационный характер, то СР не меняется.^ Директива ENDS: S2 ENDS Ассемблер фиксирует, что сегмент S2 закрыт, и в ТС заносит размер этого сегмента, т.е. число байтов, занятых всеми предложениями сегмента. Этот размер определяется очень просто - он равен текущему значению СР. Таким образом, СР используется не только для определения соответствия между именами и адресами, но и для подсчета размеров сегментов.^ Обработка команды, например: ADD X,K На 1-м проходе ассемблер не формирует машинные команды, поэтому сейчас ему безразлично, на какой цифровой КОП надо заменять мнемокод ADD, на какой адрес заменять имя X и т.д. Единственное, что ему сейчас важно знать, - это сколько байтов в памяти займет соответствующая машинная команда, на сколько надо увеличить СР. Это число определяется так. Размер команды зависит (помимо мнемокода) от двух вещей: от типов операндов и от того, надо или нет перед этой командой ставить префикс сегментного регистра. (Тип операндов - байты это или слова - не влияет на размер команды, просто КОПы будут отличаться одним из битов.) Типы операндов определяются по ТИ. Ассемблер выделяет из команды первый операнд (имя ^ X), лезет в ТИ и узнает, что это имя переменной размером в слово, т.е. этот операнд имеет тип m16. Затем ассемблер выделяет второй операнд (имя K) и по ТИ узнает, что это имя константы со значением 3; это значение может быть байтом или словом, но поскольку тип 1-го операнда команды равен word, то и этой константе приписывается тип word, т.е. i16. (В общем случае операнды задаются более сложными выражениями, скажем BYTE PTR X или K/2+1, и их типы устанавливаются сложнее, однако, зная по ТИ типы простейших элементов этих выражений, можно установить и тип выражения в целом.) Узнав типы операндов, ассемблер лезет в таблицу мнемокодов и отыскивает в ней строку с нужным мнемокодом и нужными типами операндов, а из этой строки узнает размер соответствующей машинной команды. В нашем случае в строке для мнемокода ADD и типов m16 и i16 сказано, что размер команды равен 6. Однако это еще не окончательный размер команды, надо еще определить, должен ли в этой команде использоваться префикс или нет. Если бы этот префикс был указан в команде явно (типа DS:X), тогда здесь проблемы не было бы. Но, как правило, в программах на ЯА такой префикс опускается с расчетом, что, если надо, его подставит сам ассемблер. Для этого ассемблер по ТИ узнает, в каком сегменте описано имя X (пусть это сегмент S2), а по ТРСР узнает, какой сегментный регистр поставлен в соответствие этому сегменту (пусть это регистр DS). Тем самым ассемблер устанавливает, что X - это на самом деле сокращение адресной пары DS:X. После этого ассемблер смотрит, не совпадает ли сегментный регистр из этой пары с тем сегментным регистром, который подразумевается в данной команде по умолчанию. Как известно, в команде ADD по умолчанию подразумевается регистр DS. Это значит, что перед нашей машинной командой можно не ставить префикс DS:. Тем самым по данной символьной команде в памяти будет занято 6 байтов, поэтому ассемблер увеличивает СР на 6 и за этом заканчивает обработку данной команды. Но если бы имя X было описано в сегменте, на начало которого (согласно ТРСР) установлен иной регистр, скажем ES, который не совпадает с префиксом, подразумеваемым по умолчанию, тогда опускать префикс ES: перед машинной командой уже нельзя, поэтому всего символьная команда займет 6+1=7 байтов (префиксы DS:, ES: и т.п. - это самостоятельные однобайтовые машинные команды) и поэтому ассемблер увеличит СР на 7. ^ Директива END По этой директиве ассемблер узнает, что текст программы закончился, поэтому он завершает свой 1-й проход. Цель этого прохода - построение ТИ и ТС - достигнута.^ Особые случаи на первом проходе. Таковы в общих чертах действия ассемблера на 1-м проходе. Однако есть ряд моментов, которые осложняют его работу на этом проходе. Напомним, что необходимость в 1-м проходе обусловлена проблемами, связанными со ссылками вперед. Но оказывается, что некоторые из этих проблем проявляются уже на 1-м проходе. Рассмотрим соответствующие случаи и то, как ассемблер реагирует на них.^ Первый случай. Пусть в программе имеется такой фрагмент: Y DB K DB DUP(0) X DW Y K EQU 3 Когда ассемблер встретит первую из этих директив, то он еще не будет знать, что означает имя ^ K. Конечно, по смыслу можно предположить, что K - это имя константы, но вот чему равно ее значение - предположить нельзя. А знать это значение очень важно уже на 1-м проходе, т.к. от этого зависит, на сколько надо увеличивать значение СР при обработке директивы DB, от этого зависит адрес имени X. Таким образом, не зная значения K, ассемблер не может правильно продолжить свою работу. Что делает ассемблер? Поскольку в данной ситуации никаких разумных действий (кроме забегания по тексту вперед, которое требует массы времени) он предпринять не может, то он фиксирует ошибку "ссылка вперед здесь недопустима". Учитывая этот и другие подобные случаи, авторы ЯА ввели в язык ограничение: в константных выражениях нельзя использовать ссылки вперед.^ Второй случай. Рассмотрим такой фрагмент программы: CALL P L: ... ... P PROC FAR Здесь обращение к процедуре P встретилось раньше ее описания, и это ставит перед ассемблером следующую проблему на 1-м проходе. Если P - имя близкой процедуры, тогда машинная команда, соответствующая символьной команде CALL, займет 3 байта памяти (она имеет вид КОП ofs, где ofs - смещение имени P), и потому ассемблер должен увеличивать СР на 3. Но если P является именем дальней процедуры, тогда соответствующая машинная команда займет 5 байтов (она имеет вид КОП ofs seg), и потому СР должен быть увеличен на 5. Так на сколько же надо увеличивать СР - на 3 или 5? А это важно знать, от этого зависит адрес метки L и всех последующих меток. Как видно, и здесь из-за ссылки вперед ассемблер не знает, что ему делать уже на 1-м проходе. Однако фиксировать в данной ситуации ошибку неразумно, т.к. в реальных программах такие ситуации встречаются очень часто и этих ошибок было бы слишком много. Кроме того, в данной ситуации можно сделать вполне разумное предположение относительно имени P, а именно предположить, что это имя близкой процедуры (так чаще всего и бывает в реальных программах). Учитывая все это, ассемблер в данной ситуации не фиксирует ошибку, а делает предположение, что P - это имя близкой процедуры, и далее уже действует согласно этому предположению, т.е. считает, что данная команда CALL будет транслироваться в машинную команду близкого вызова, и потому увеличивает СР на 3. Но если затем окажется, что это предположение ошибочно (как в нашем примере), тогда ассемблер уже зафиксирует ошибку.^ Третий случай. Предположим, в программе переменная X описана в конце сегмента команд. Тогда имя X будет использовано в команде ADD до своего описания: ADD X,K ... X DW Y Встретив команду ADD, ассемблер еще не будет знать, что обозначает имя ^ X (переменную или что-то иное), и не будет знать, в каком сегменте описано имя X, а потому не будет знать, по какому сегментному регистру должно сегментироваться это имя, надо или нет перед этой командой ставить префикс. А от всего этого зависит, сколько байтов в памяти займет соответствующая машинная команда - 6 или 7. И здесь ассемблер не фиксирует ошибку, а предполагает, что имя ^ X обозначает переменную (а не константу или что-то иное) и что в данной команде не должен использоваться префикс, т.е. что переменная X будет описана в сегменте, на начало которого показывает регистр, подразумеваемый по умолчанию в данной команде. Сделав такое предположение, далее ассемблер действует уже согласно ему, а именно определяет, что эта команда в целом займет в памяти 6 байтов. И опять же, если это предположение окажется ошибочным (например, у нас имя X описано в сегменте команд и потому должно сегментироваться по регистру CS), то затем будет зафиксирована ошибка. Таковы основные случаи, когда из-за ссылок вперед ассемблер уже на 1-м проходе не знает в точности, что ему делать. Как видно, реакция ассемблера на эти случаи может быть двоякой. Если он не может сделать никаких разумных предположений относительно ссылки вперед (как в случае с константами), то он фиксирует ошибку; при этом в ЯА вводятся соответствующие ограничения. Но если можно сделать какое-то разумное предположение относительно ссылки вперед, то ассемблер делает такое предположение и далее действует согласно ему. Отметим, что эти предположения берутся не "с потолка": из всех возможных интерпретаций ссылки вперед в качестве предположения берется вариант, который наиболее часто встречается в реальных программах. Например, процедуры чаще всего бывают близкими, и именно этот вариант выбирается в команде CALL.^ 1.4 ВТОРОЙ ПРОХОД АССЕМБЛЕРА.Теперь рассмотрим действия ассемблера на 2-м проходе. К этому моменту в ТИ и ТС уже собрана вся информация об именах программы. Ассемблер заново просматривает строчка за строчкой текст программы и, используя информацию из ТИ, уже переводит программу с ЯА на машинный язык. Формируемые машинные команды ассемблер записывает в последовательные ячейки памяти начиная с некоторого адреса, кратного 16. Какой это конкретно адрес - не важно. Дело в том, что машинная программа, сформированная ассемблером, не будет тут же выполняться, а будет лишь записана во внешнюю память, поэтому ее можно формировать в любом месте памяти. Учитывая это, мы будем указывать адрес первой свободной ячейки, отсчитанный от начала этого места, и будем обозначать этот адрес как АДР. В начале АДР=0. Примеры обработки директив и команд на 2-м проходе.Директивы EQU и PROG игнорируются, т.к. вся информация из них уже извлечена на 1-м проходе.Директива SEGMENT:^ S2 SEGMENT 'DATA' Начинается новый сегмент. Поскольку каждый сегмент должен начинаться с адреса, кратного 16, то ассемблер, если надо, увеличивает значение АДР до ближайшего адреса, кратного 16, пропуская в памяти все промежуточные байты (что в них было в это время, то и останется).Директивы DB, DW, DD: X DW Y Y DB 3 DUP(0) По этим директивам ассемблер резервирует место в памяти (начиная с текущего значения ^ АДР), записывает в него начальные значения переменных (адрес имени Y узнается из ТИ) и увеличивает АДР на соответствующее число (на 2 и на 3). Если переменная описана без начального значения (типа X DW ?), то в ее ячейку ассемблер ничего не записывает - что в ней было к этому моменту, то и останется.Директива ASSUME:^ ASSUME DS:S2, CS:S3, SS:S1 По этой директиве на 1-м проходе уже была построена ТРСР. Однако в программе директива ASSUME может встречаться многократно, поэтому состояние этой таблицы в конце 1-го прохода может не соответствовать ее состоянию после первой из директив ASSUME. В связи с этим ассемблер на 2-м проходе заново строит ТРСР после первой из директив ASSUME и затем меняет таблицу после каждой новой такой директивы.Команда: ADD X,K Обработка команд на 2-м проходе во многом осуществляется так же, как и на 1-м проходе. По ТИ ассемблер узнает, что имя ^ X описано в сегменте S2, а по ТРСР узнает, что этому сегменту поставлен в соответствие сегментный регистр DS. Следовательно, запись X - это сокращение адресной пары DS:X. Поскольку регистр DS из этой пары совпадает с регистром, подразумеваемого по умолчанию в команде ADD, то перед этой командой ассемблер не вставит префикс сегментного регистра. (Если бы имя X было описано в сегменте, на который, согласно ТРСП, указывает регистр ES, то ассемблер записал бы префикс ES: в очередной свободный байт памяти и затем увеличил бы АДР на 1.) Далее ассемблер формирует собственно команду. По ТИ он узнает типы операндов (m16 и i16) и затем по таблице мнемокодов узнает, что команда сложения при таких типах операндов имеет КОП 81 06, который записывает в следующие два байта памяти. После этого ассемблер формирует операнды машинной команды: узнав по ТИ адрес имени X и значение константы K, ассемблер записывает этот адрес и это число в очередные байты памяти. На этом формирование машинной команды закончено. АДР увеличивается на число байтов, занятых командой.^ Директива END. Встретив эту директиву, ассемблер завершает 2-й проход. Машинная программа сформирована, ассемблер записывает ее во внешнюю память и на этом заканчивает свою работу. Как видно, 2-й проход выполняется достаточно просто. Это объясняется тем, что значительная часть работы была проделана на 1-м проходе.^ 1.5 МНОГОМОДУЛЬНЫЕ ПРОГРАММЫ. Мы рассмотрели основные действия ассемблера, выполняемые при трансляции программы, написанной на ЯА, в том случае, когда программа состоит только из одного модуля, когда в этом модуле нет внешних и общих имен. Теперь рассмотрим, какие изменения надо внести в работу ассемблера в случае многомодульной программы.^ Структура объектного модуля. Начнем со следующего важного замечания: в общем случае ассемблер не может довести до конца трансляцию программы, данной ему на входе, и основных причин тому две. Первая - наличие внешних имен. Если ассемблер транслирует один из модулей многомодульной программы и в нем используются внешние имена, т.е. имена из других модулей, то, транслируя этот модуль независимо от других, ассемблер, естественно, не может оттранслировать эти имена, т.е. не может заменить их на соответствующие им адреса. Эти адреса станут известными позже, на этапе объединения модулей в единую программу, только тогда и появится возможность сделать эти замены. Вторая причина - наличие имен сегментов. Например, в командах MOV AX,S2 MOV DS,AX имя сегмента S2 должно быть заменено на начальный адрес (без последнего 0) соответствующего сегмента памяти, но этот адрес зависит от того, с какого места памяти будет располагаться вся программа при счете. Если, скажем, сегмент S2 является самым первым в программе и если программа размещена с адреса 50000h, тогда имя S2 надо заменять на 5000h, но если программа размещена с адреса 70000h, то имя S2 надо заменять на 7000h. Заранее же начальный адрес программы неизвестен, поэтому ассемблер и не знает, на что заменять имена сегментов. Это станет известным позже, непосредственно перед выполнением программы, тогда и появится возможность сделать эти замены. Отметим, что адреса, зависящие от места расположения программы в памяти, принято называть перемещаемыми адресами. Имена сегментов - пример таких адресов. (Других перемещаемых адресов в языке MASM нет, хотя в иных ЯА имеются и другие примеры перемещаемых адресов.) Так что второй причиной, по которой ассемблер не может довести трансляцию до конца, являются перемещаемые адреса. Отметим также, что проблема с этими адресами возникает в любой программе - как многомодульной, так и одномодульной. Итак, имеется ряд вещей, которые ассемблер не может оттранслировать, которые можно дотранслировать только позже. Учитывая это, ассемблер поступает так: все, что может, он транслирует, а то, что не может оттранслировать, он пропускает, оставляет как бы пустые места, но при этом запоминает информацию об этих "пустых" местах, по которой затем можно будет их дотранслировать. В связи с этим при трансляции модуля ассемблер выдает на выходе на самом деле не модуль в полностью оттранслированном виде, а некоторую заготовку его, которую принято называть объектным модулем (ОМ). Объектный модуль состоит из двух частей - заголовка и тела. Тело модуля - это машинный код модуля, правда, некоторые места в нем, как уже сказано, недотранслированы. В заголовке же собрана информация, по которой затем можно будет дотранслировать эти места и объединить этот модуль с другими модулями программы. В упрощенном виде структура заголовка ОМ состоит из следующих разделов: 1) таблица сегментов; 2) точка входа; 3) сегмент стека; 4) таблица общих имен; 5) таблица вхождений внешних имен; 6) таблица перемещаемых адресов. Прежде чем объяснить смысл этой информации, сделаем такое замечание. В заголовке приходится ссылаться на ячейки внутри тела модуля. Эти ссылки задаются в виде пар s:ofs, где s - символьное имя сегмента, которому принадлежит ячейка, а ofs - смещение ячейки внутри этого сегмента.^ Таблица сегментов. Напомним, что во время своей работы ассемблер строит несколько таблиц, в том числе таблицу сегментов. Эта таблица и переносится в заголовок ОМ.^ Точка входа. Если модуль является головным в программе, т.е. с него должно начинаться выполнение программы, тогда в его директиве END указывается точка входа - метка той команды модуля, с которого надо начинать выполнение программы. Адрес этой метки и записывается в заголовок. Данный адрес определяется просто: эта метка - одно из имен, описанных в модуле, поэтому информация о метке имеется в таблице имен (ТИ), которую строит ассемблер, а в этой таблице для каждого имени указываются среди прочего имя сегмента, в котором оно описано, и смещение имени внутри сегмента. Когда ассемблер доходит до директивы END и встречает в ней метку, то по ТИ он узнает адрес этой метки, который и записывает в заголовок (например, S3:0). В заголовке остальных, не головных, модулях как-то помечается, что точки входа нет.^ Сегмент стека. Как известно, если при описании сегмента стека в его директиве SEGMENT указан параметр STACK, тогда перед началом программы регистры SS и SP должны быть автоматически установлены на этот сегмент. Естественно, надо знать, какой из сегментов является стеком. Определяется этот сегмент просто: когда ассемблер встречает директиву SEGMENT с параметром STACK, то имя этого сегмента (например, S1) он заносит в заголовок ОМ. ^ Таблица общих имен (ТОБ). Общим называется имя, которое указано в директиве PUBLIC (например, PUBLIC B). Содержательно - это имя, описанное в данном модуле, но доступное для всех остальных модулей программы. При объединении модулей в единую программу в тех модулях, где это имя используется, надо будет заменить его на его адрес внутри данного модуля. Ясно, что ассемблер, транслирующий модули по отдельности, не может сделать эту замену. Он ее и не делает, однако для будущего запоминает информацию о всех общих именах модуля и их адресах внутри модуля. Эта информация и образует ТОБ. Построить такую таблицу просто. Во-первых, все общие имена описаны в модуле, поэтому информация о них имеется в ТИ. Во-вторых, общие - это те имена, которые перечислены в директиве PUBLIC. Поэтому, встречая (на 2-м проходе) директиву PUBLIC, ассемблер для каждого из указанного здесь имени извлекает информацию из ТИ и заносит ее в ТОБ. Например, для следующего модуля (слева) будет создана такая ТОБ (справа): PUBLIC B^ EXTRN X:WORD, P:FAR S2 SEGMENT DATA общее имя его адрес A DW X ---------------------- B DW SEG X, ? B S2:2 C DD P ... ... Таблица вхождений внешних имен (ТВН). Внешним называется имя, указанное в директиве EXTRN (например, EXTRN X:BYTE, P:FAR). Содержательно - это имя, которое используется в данном модуле, но описано в другом модуле. Ясно, что, транслируя данный модуль независимо от других, ассемблер не знает адреса внешних имен, поэтому не может заменить их на адреса. Такую замену внешнего имени на адрес можно будет сделать только позже, на этапе объединения модулей в единую программу, когда будет известна информация о всех модулях. Пока же ассемблер в соответствующую ячейку объектного модуля записывает 0, но фиксирует, что позже в эту ячейку надо будет записать адрес внешнего имени. Такая информация о каждом вхождении в модуль каждого внешнего имени и запоминается в ТВН. Например, для указанного выше модуля будет создана такая ТВН: внеш.имя адрес вхождения тип вхождения ------------------------------------------ X S2:0 ofs X S2:2 seg P S2:6 segofs ... Здесь "адрес вхождения" - это адрес той ячейки текущего модуля, в которую надо будет затем вставить адрес внешнего имени, указанного в первой колонке. Однако только этой информации мало. Дело в том, что в разных случаях под "адресом внешнего имени" понимаются разные вещи. Например, в директиве DW X (или в команде MOV AL,X) имя X надо заменять на смещение (ofs) этого имени, а в директиве DW SEG X (или в команде MOV AX,SEG X) - на начальный адрес (без последнего 0) того сегмента, где имя описано (на seg). Что касается директивы DD P (или команды CALL P), то имя P должно заменяться на полный адрес (на адресную пару seg:ofs). На какую именно часть своего полного адреса должно заменяться внешне


Не сдавайте скачаную работу преподавателю!
Данный реферат Вы можете использовать для подготовки курсовых проектов.

Поделись с друзьями, за репост + 100 мильонов к студенческой карме :

Пишем реферат самостоятельно:
! Как писать рефераты
Практические рекомендации по написанию студенческих рефератов.
! План реферата Краткий список разделов, отражающий структура и порядок работы над будующим рефератом.
! Введение реферата Вводная часть работы, в которой отражается цель и обозначается список задач.
! Заключение реферата В заключении подводятся итоги, описывается была ли достигнута поставленная цель, каковы результаты.
! Оформление рефератов Методические рекомендации по грамотному оформлению работы по ГОСТ.

Читайте также:
Виды рефератов Какими бывают рефераты по своему назначению и структуре.