Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Эрни Каспер Программирование на языке Ассемблер...doc
Скачиваний:
171
Добавлен:
09.11.2019
Размер:
955 Кб
Скачать
☆

2.2.Обработка имен транслятором и компоновщиком

Перевод мнемокодов и имен регистров ассемблерной команды в ма­шинный код производится по заранее заданным таблицам с учетом способа адресации. Обработка символических имен, заданных програм­мистом, представляет более сложную задачу. Программисту нужно понимать, каким образом эти имена обрабатываются транслятором и компоновщиком. Смысл обработки имен транслятором состоит в вы­числении адресов, по которым программные объекты будут размещены в ОЗУ и ПЗУ микроконтроллера. По этим адресам определяются ма­шинные коды команд, загружаемых в ПЗУ. Транслятор и компоновщик (независимо от используемого языка программирования) решают задачу вычисления правильных адресов данных и команды в соответствии с их размещением в памяти.

Начнем ознакомление с проблемой вычисления адресов команд и данных с понятия о сегментировании памяти. В отличие от процессоров, для микроконтроллеров сегментирование связано не со способом вычис­ления исполнительного адреса, а с форматом обрабатываемой информации. Обращение к встроенному ОЗУ и к функциональным регистрам микро­контроллера может производиться как для чтения, так и для записи. Адресное пространство для данных в байтовом и битовом форматах от 00 до FF соответствует размеру байта адресации. В ОЗУ можно адресовать 128 байт (адреса от 00 до 7F) и столько же битов. Битовое пространство занимает байтовые адреса от 20 до 2F, то есть 16 байт того же самого ОЗУ. Остальные адреса используются для обращения к функциональным регистрам. Обращение к внешним ЗУ может производиться только в байто­вом формате, притом к ЗУ команд — только для чтения. Адресное про­странство внешних ЗУ ограничено двумя байтами адресации, что соответ­ствует 65536 байт (адреса от 0000 до FFFF).

В Ассемблере стандартными сегментами (они называются также сек­циями) являются сегменты CODE (предназначен для программы) и DATA (предназначен для данных). Две других секции BSECT (битовая) и RSECT (регистровая) введены для программирования обращений к ОЗУ в бито­вом и байтовом форматах соответственно. Все эти названия имеются в таблице имен приведенного примера. Любая часть транслируемого текста должна относиться к какой-либо секции, то есть находиться между открывающей и закрывающей строками директивы определения секции:

.CODE

/команды и директивы секции кода

.ENDS

.DATA

/директивы секции данных

.ENDS

.RSECT

/директивы регистровой секции

.ENDS

.BSECT

/директивы битовой секции

.ENDS

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

При желании можно задать дополнительные секции при помощи следующих директив:

имя:

.SECTION BIT ;Описывается новая битовая секция имя:

.SECTION REG ;Описывается новая регистровая секция

Эти директивы позволяют программисту задавать имена секций. Для открытия секции, определенной программистом, нужно записать имя секции в поле мнемокода. Допускается использование не более 256 секций в одном файле.

Транслятор переводит данные и команды в машинный формат, рас­полагая их в той последовательности, в какой они будут храниться в отведенном для них адресном пространстве секции. Поскольку программа состоит из нескольких секций, то вычисление адресов для записи двоичных кодов ведется для каждой секции отдельно. Начальный адрес в каждой из секций равен 0. Для использования счетчика текущего адреса в секции применяется предопределенное символическое имя $ (знак доллара) или * (звездочка). Числовое значение содержимого счетчика после трансляции строки исходного текста увеличивается на количество байтов, добавленных в адресное пространство текущего сегмента командой или директивой. Программист может использовать содержимое счетчика текущего адреса в выражениях, входящих в операнды команд или директив, и даже изменять его содержимое при помощи директивы. Запись заданного адреса в счетчик производится директивой.

ORG адрес

Синонимом ORG является ORIGIN.

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

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

Повторное использование имени в поле метки недопустимо, потому что нельзя создавать дополнительный программный объект с тем же самым именем. Исключения из правила уникальности используемых имен допускаются только в том случае, когда их повторное появление не нарушает этого правила. Это относится к директивам переопределяемых программных объектов и к локальным меткам. В таком случае при повтор­ном использовании имени ранее созданный программный объект заменяется другим.

Имя должно начинаться с буквы и состоять из букв, цифр и может включать в себя некоторые другие символы. Его длина может быть очень большой, лишь бы оператор не вышел за пределы строки редактора, но только 32 первых символа будут иметь значение для таблицы имен. Большие и маленькие буквы в имени считаются различными. При соблю­дении этих условий имя в поле метки считается нелокальным и включа­ется в таблицу имен модуля. Имя, подставленное в поле метки, называет­ся меткой (label). Метку можно начинать с любой колонки, если за именем следует двоеточие. Если двоеточие не используется, то первый символ имени должен располагаться в первой колонке.

Обычная метка наблюдаема (scope) во всем модуле. Однако при про­граммировании можно пользоваться локальными метками, видимость которых ограничена двумя ближайшими нелокальными метками (пред­шествующей и последующей). Локальные метки могут быть использова­ны многократно, поэтому они не включаются в таблицу имен. Отличи­тельным признаком локальной метки является символ $ в начале или конце имени, поэтому имя локальной метки может начинаться с цифры. Впрочем в качестве признака локальной метки можно использовать Другой символ, который указывается директивой

.LLCHAR символ

Эта директива изменяет символ-определитель локальной метки.

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

• одна часть адресов определена окончательно и не подлежит изменению,

• другая часть адресов определена с точностью до размещения модулей в программе,

• третья часть адресов неизвестна, так как они находятся в других модулях (так называемые внешние ссылки).

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

При использовании нескольких модулей большинство адресов, вы­численных транслятором, не являются окончательными и подлежит уточнению в процессе сборки. При сборке объектных модулей производится их перекомпоновка с целью объединения частей программы, содержащих одноименные секции, что позволяет вычислить все адреса. Имена секций сохраняются в объектных файлах для того, чтобы при сборке модулей объединять одноименные секции. При трансляции по умолчанию исполь­зуется относительный режим, чтобы при сборке не происходило наложения адресных пространств. В относительном режиме сборка производится так, чтобы адресное пространство очередного модуля располагалось непосредственно за адресным пространством предыдущего. Таким образом для каждой из секций размер адресного пространства программы равен сумме адресных пространств модулей, если программист не задал смещения для собираемых частей секций.

При необходимости можно обеспечить фиксированные (не изменяемые компоновщиком) адреса в пределах 0 страницы ЗУ переключением транслятора в абсолютный режим директивой

.ABSOLUTE

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

.RELATIVE

При переходе из одной секции в другую атрибуты ABSOLUTE и RELATIVE не изменяются.

В современных компьютерах большинство программ должно быть перемещаемыми, то есть работоспособными при их загрузке в произвольную часть ОЗУ. В микроконтроллерах все адреса окончательно определяются уже в процессе редактирования связей. Совершенно ясно, что транслятор может определить самостоятельно только первые два из трех перечис­ленных выше видов адреса. Притом адреса второго вида в объектном коде помечаются специальными символами, обнаружение которых используется компоновщиком для пересчета. Чтобы транслятор не выда­вал сообщения об ошибках по третьему виду адресов, программист должен включить в программу соответствующие директивы. Связи между модулями осуществляются по данным и по управлению. Эти связи осуществляются при помощи символических имен, которые в модулях, вызывающих связь, объявляются как внешние при помощи директивы

.EXTERNAL список

В списке должны быть перечислены имена, определяемые в других модулях. Регистры могут быть заданы как внешние двумя путями. Если директива используется в регистровой секции, то внешние имена опреде­ляются как регистровые. Во всех остальных секциях необходимо перед каждым именем регистровой переменной в списке вставить слово REG. Синонимами EXTERNAL являются EXTERN и XREF.

Вызываемые имена должны быть определены в соответствующих модулях как доступные при помощи директивы

.PUBLIC список

Определив имя как доступное, можно ссылаться на него из других модулей. Синонимами PUBLIC являются XDEF и GLOBAL, хотя последний мне­мокод в других диалектах Ассемблера используется для объявления и внешних, и доступных имен.

Существует еще один способ сделать имена доступными для исполь­зования другими модулями. Для этого используется директива

.GLOBALS ON

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

Эта директива не действует на локальные метки. Ее действие отменяется переходом к трансляции другого модуля или директивой

.GLOBAL OFF

Имена, использованные в разных модулях и не внесенные в соответст­вующие списки, не включаются в объектные файлы. В этом случае при компоновке редактор связей выдает сообщение об ошибке.

Использование промежуточного объектного файла при переводе исходного текста в исполняемую программу характерно для большинства современных языков программирования. Хотя получение исполняемой программы непосредственно из исходного текста в принципе возможно, по ряду причин сложилась практика двухступенчатого перехода. В случае программирования для микроконтроллеров это оправдано тем, что для разработки более или менее сложных программ удобно сначала отладить отдельные функционально законченные модули независимо друг от друга. Кроме того набор отлаженных объектных файлов, которые могут использоваться во многих программах, можно объединить при помощи программы-библиотекаря в специальный файл с расширением LIB. После отладки всех модулей их объектные файлы (включая библиотечные) собираются в один исполняемый файл для комплексной отладки про­граммы.

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

.MODULE

а в конце модуля — директиву

.ENDMOD

Вложение модулей в другие модули не допускается. Транслятор осущест­вляет перевод модулей в машинный код независимо от других модулей. Количество модулей в файле не ограничивается. Выходной файл в этом случае имеет расширение РАК. Эта пара директив предназначена для получения объектных файлов, пригодных для работы с программой-библиотекарем. Их использование позволяет транслировать всю библиотеку в целом, вместо того чтобы транслировать каждую подпрограмму отдельно, а затем собирать объектные модули в один файл. Обычно библиотеки составляются из большого числа небольших по размерам программ. Когда компоновщик не может найти глобальное имя (идентификатор) в каком-либо из компонуемых файлов, он может продолжить его поиск в библиотеках.