Э. Таненбаум - Архитектура компьютера (1127755), страница 203
Текст из файла (страница 203)
Путем трассировки можно определить, что этот адрес — Ох0011. Первая часть этой программы призвана подготовить вызов функции чесщц); для этого 5Р сохраняется в ВР, а затем адреса чес2 и чес1 вводятся в стек, что обеспечивает функции чесщц! возможность доступа к ним. Далее, в строке 8 длина вектора в байтах загружается в ЕХ. После смещения этого результата на 1 бит вправо (в строке 9) значение СХ выражает число слов в векторе, которое помещается в стек в строке 10.
Вызов чесщц! выполняется в строке 11. Стоит, опять же, отметить, что аргументы подпрограмм помещаются в стек в обратном порядке, и делается это в целях соответствия принятому в языке С порядку вызова. Так, средствами С функцию чеовц! можно вызвать следующим образом: 798 Приложение В. Программирование на языке ассемблера | !зо Р!тг-!в =ос!г:гэ е4 г! 4 о о з о мо!.......,гаезт !з! ! 22 АОО -2(ВР),АХ Арр о!,г ! СОР!Ь Рис. В.7. Содержимое окна трассера для программы чесргоп.з при достижении строки 28, но до начала цикла Первая команда в подпрограмме — Р05Н.
Она выполняется для указателя базы (ВР) в строке 22. Значение ВР сохраняется в связи с тем, что этот регистр понадобится для адресации аргументов и локальных переменных данной подпрограммы. Далее, в строке 23 в регистр ВР копируется указатель стека; таким образом, новое значение указателя базы задает прежнее значение указателя стека. После этого все готово к загрузке аргументов в регистры и резервированию пространства под локальную переменную. В следующих трех строках аргументы по одному извлекаются из стека и размещаются в регистре. Как вы помните, стек оптимизирован для хранения слов, значит, адреса должны быть четными.
Адрес возврата следует сразу за прежним указателем базы, а потому обращение к нему производится как к 2(ВР). Следующим идет аргумент соопг — 4(ВР). Он загружается в регистр СХ в строке 24. В строках 25 и 26 векторы чес1 и чес2 загружаются в регистры 51 и 01, соответственно. Для сохранения промежуточного результата данной подпрограмме нужна одна локальная переменная с исходным значением О. В связи с этим в строке 27 в стек вводится значение О. Состояние процессора непосредственно перед первым проходом цикла, начинающемся в строке 28, показано на рис.
В.7. В узком окне в середине верхней части (справа от регистров) изображена область стека. На его дне находится адрес чес2 (Ох0022); далее, в порядке восхождения, следуют адрес чес1 (Ох0018) и третий аргумент, выражающий число элементов в каждом векторе (Ох0005).
Затем указывается адрес возврата (Ох0011). Цифра 1 слева от этого адреса свидетельствует о том, что он является адресом возврата, отстоящим на один уровень от основной программы. В окне под регистрами также показана цифра 1, но на этот раз она выражает символический адрес. Выше адреса возврата в стеке следуют старое значение ВР (Ох71сО) и нуль, помещаемый в стек в строке 27. Стрелка, указывающая на это значение, отражает положение указателя стека (регистра 5Р). В окне справа от секции стека показан фрагмент текста программы; стрелка здесь указывает на следующую команду в порядке выполнения.
Примеры 799 Теперь рассмотрим цикл, начинающийся в строке 28. Команда ЫЮ5 через регистр 51 загружает слово памяти из сегмента данных в АХ. Так как флаг направления установлен, команда Ь005 выполняется в автоинкрементном режиме, а значит, после ее завершения регистр 51 указывает на следующий элемент чес1. Чтобы представить этот механизм графически, запустите команду трассера С88 чеспгьс При появлении окна трассера введите следующую команду; !неспщ~)Ь Затем нажмите клавишу возврата каретки, установив тем самым контрольную точку в строке, содержащей команду ЬО05.
(Далее по тексту мы не будем напоминать о том, что после всех команд необходимо нажимать клавишу возврата каретки.) Введите команду В результате выполнения этой команды трассер будет выполнять команды до контрольной точки. В данном случае он остановится в строке, содержащей команду ЬО05.
В строке 29 значение АХ умножается на исходный операнд. Слово в памяти, связанное с командой МОЬ, выбирается из сегмента данных с помощью регистра 01 методом косвенной регистровой адресации. Неявным (не указанным в команде) целевым адресом команды МОЬ является комбинация регистров ОХ: АХ. В строке 30 результат прибавляется к локальной переменной, расположенной в стеке по адресу . 2(ВР). Так как команда МОЬ не выполняет автоматическое приращение своего операнда, это действие явно выполняется в строке 31.
После этого регистр 01 указывает на следующий элемент чес2. Текущий этап работы программы завершается командой ЬООР. Относительно значения регистра СХ выполняется отрицательное приращение, и если после этого оно остается положительным, программа переходит к локальной метке 1 в строке 28. Локальная метка 1Ь означает поиск ближайшей метки 1 в обратном направлении от текущей позиции. После завершения цикла подпрограмма выталкивает возвращаемое значение из стека в регистр АХ (строка 33), восстанавливает значение ВР (строка 34) и возвращается к вызывающей программе (строка 35).
После вызова выполнение основной программы возобновляется с помощью команды мОЧ (переход к строке 12). эта команда открывает последовательность из пяти команд, направленную на вывод результата. Системный вызов рг)пЬГ построен по модели функции рг1пЬГ стандартной библиотеки программирования языка С. В строках 13-15 в стек помещаются 3 аргумента: целочисленное значение, которое предполагается вывести, адрес форматной строки (ргвс) и код функции рг1 псг (127). Форматная строка ргат содержит символ Ы, указывающий на то, что целочисленная переменная, необходимая для форматирования, является аргументом вызова рг)пЬА В строке 17 стек очищается. Так как начало программы находится в строке 5, где указатель стека был сохранен в регистре указателя базы, для очистки стека с тем же успехом можно запустить команду МЯЧ 5Р.ВР 800 Приложение В.
Программирование на языке ассемблера Преимущество такого решения состоит в том, что программисту нс приходится следить за стеком. Применительно к основной программе это не слишком важно, но при работе с подпрограммами этот подход позволяет избавляться от ненужных данных, таких как устаревшие локальные переменные. Подпрограмму уесвцТ можно включать в другие программы.
Если имя исходного файла чесргодл поместить в командой строке перед именем другого исходного файла на языке ассемблера, последний сможет обращаться к подпрограмме умножения двух векторов фиксированной длины. Во избежание дублирования предварительно рекомендуется исключить определения констант ЕХ1Т и Рк1МТЕ. Если заголовочный файл зузса1лт.й подсоединен, писать определения констант системных вызовов в других местах нет необходимости. Отладка программы вывода массива Рассмотренные в предыдущих примерах программы просты и не содержат ошибок. В данном примере мы покажем, как трассер может помочь в отладке программ с ошибками.
Наша следующая программа предназначена для вывода целочисленного массива, определенного после метки уес1. В ее первоначальной версии есть 3 ошибки. Для их выявления мы привлечем ассемблер и трассер, но сначала обсудим код. Так как системные вызовы, а значит, и константы, с помощью которых эти вызовы можно различать по номерам, нужны любой программе, мы выделили определения констант с этими номерами в отдельный заголовочный файл, который включается в код в первой строке: 41лс1исе ". !ауаса1лг 'л" Помимо прочего, в этом файле определены константы для следующих дескрипторов файлов: 5101И = 0 5Т000Т = 1 510ЕРР = 2 Они открываются в начале процесса, а в заголовке содержатся метки, указывающие на секпии текста и данных. Этот файл имеет смысл включать в заголовок всех исходных ассемблерных файлов, поскольку имеющиеся в нем определения весьма востребованы.
Если исходный код рассредоточен по нескольким файлам, ассемблер включает в него только одну версию заголовочного файла, за счет чего ситуации многократного определения констант удается избежать. Программа аггау1лт приведена в листинге ВА. Код не прокомментирован, так как мы предполагаем, что к настоящему моменту читатель уже в достаточной степени знаком с набором команд. В строке 4 адрес пустого стека помещается в регистр указателя базы — так предусматривается возможность очистки стека путем копирования указателя базы в указатель стека, что и выполняется в строке 10. В предыдущем примере (в строках 5 — 9) мы уже рассматривали ситуацию вычисления и введения в стек аргументов перед вызовом.
В строках 22 — 25 регистры загружаются в подпрограмму. Примеры 801 Листинг В.4. Программа аггаургЖ перед отладкой 77!пс!обе ",.7ауаса1пг.П" ! 14 ! 15 ! 16 .5ЕСТ .ВАТА чес1: НОВО 3,4,7, 11,3 Тгаатетг: А5С12 "Жа" Тгваткар. ! 17 .А5С12 "Тпе аггау сопбаапа " ! 18 Ггеатнпт: А5С12 " ЖО" ! 19 В строках 27-30 кода показано, как вывести символьную строку, а в строках 31 — 34 системный вызов ргтпЖЕ выполняется применительно уже к целочисленному значению. Адрес символьной строки вводится в стек в строке 27, а в строке 33 в стек вводится целочисленное значение. В обоих случаях адресом форматной строки выступает первый аргумент команды РН1ИТГ.
В строках 37-39 отдельный символ выводится при помощи системного вызова раЖС1таг. Теперь попробуем ассемблировать и запустить программу. Для етого введем команду аа88 аггаургт.а .5ЕСТ ТЕХТ чесребгг: МОЧ ВР,5Р Р05Н нес1 МОХ СХ.Тгвататг-нес1 5НК СХ Р05Н СХ САСЕ чесрю пт МОН 5Р,ВР Р05НО Р05Н ЕХП 5У5 .5ЕСТ .ТЕХТ несрг1пт: Р05Н ВР МОН ВР,ВР МОЧ СХ.4(ВР) МОН ВХ,б(ВР7 МОХ 51.0 Р05Н Ггеатнор Р05Н Тгеататг Р05Н РВ1ИТГ 5Н5 МОХ -4(ВР7,тгшаы пЬ 1; МОХ 01,!ВХ1!511 МОУ -2!ВР1,01 515 1ИС 51 ЕООР 1Ь Р05Н Пп' Р05Н РОТСМАН 5Н5 МОХ 5Р,ВР НЕТ ! 2 ! 3 ! 4 ! 5 ! б ! 8 9 10 ! 11 ! 12 ! 13 ' 20 ' 21 ' 22 ! 23 ' 24 ! 25 ! 26 ! 27 ! 28 1 29 ! 30 ! 31 ! 32 ! 33 ! 34 ! 35 ! 36 ! 37 ! 38 ! 39 ! 40 41 802 Приложение В.