|
Рассмотренные в предыдущих примерах программы просты и не содержат ошибок. В данном примере мы покажем, как трассер может помочь в отладке программ с ошибками. Наша следующая программа предназначена для вывода целочисленного массива, определенного после метки vecl. В ее первоначальной версии есть 3 ошибки. Для их выявления мы привлечем ассемблер и трассер, но сначала обсудим код.
Так как системные вызовы, а значит, и константы, с помощью которых эти вызовы можно различать по номерам, нужны любой программе, мы выделили определения констант с этими номерами в отдельный заголовочный файл, который включается в код в первой строке:
#include "../syscalnr.h"
Помимо прочего, в этом файле определены константы для следующих дескрипторов файлов:
STDIN = О STD0UT = 1 STDERR = 2
Они открываются в начале процесса, а в заголовке содержатся метки, указывающие на секции текста и данных. Этот файл имеет смысл включать в заголовок всех исходных ассемблерных файлов, поскольку имеющиеся в нем определения весьма востребованы. Если исходный код рассредоточен по нескольким файлам, ассемблер включает в него только одну версию заголовочного файла, за счет чего ситуации многократного определения констант удается избежать.
Программа arrayprt приведена в листинге В.4. Код не прокомментирован, так как мы предполагаем, что к настоящему моменту читатель уже в достаточной степени знаком с набором команд. В строке 4 адрес пустого стека помещается в регистр указателя базы - так предусматривается возможность очистки стека путем копирования указателя базы в указатель стека, что и выполняется в строке 10. В предыдущем примере (в строках 5-9) мы уже рассматривали ситуацию вычисления и введения в стек аргументов перед вызовом. В строках 22-25 регистры загружаются в подпрограмму.
Листинг В,4- Программа arrayprt перед отладкой
|
finclude "../syscalnr.h"
|
! 1
|
|
.SECT .TEXT
|
! 2
|
|
vecpstrt:
|
! 3
|
|
MOV BP.SP
|
! 4
|
|
PUSH vecl
|
! 5
|
|
MOV CX,frmatstr-vecl
|
! 6
|
|
SHR CX
|
! 7
|
|
PUSH CX
|
! 8
|
|
CALL vecprint
|
! 9
|
|
MOV SP.BP
|
! 10
|
|
PUSHO
|
! 11
|
|
PUSH EXIT
|
! 12
|
|
SYS
|
! 13
|
|
.SECT .DATA
|
! 14
|
|
vecl: .WORD 3,4.7,11,3
|
! 15
|
|
frmatstr: .ASCIZ "%s"
|
! 16
|
|
frmatkop:
|
! 17
|
|
.ASCIZ "The array contains "
|
! 18
|
|
frmatint: .ASCIZ " %6"
|
! 19
|
|
.SECT .TEXT
|
! 20
|
|
vecprint:
|
! 21
|
|
PUSH BP
|
! 22
|
|
MOV BP.SP
|
! 23
|
|
MOV CX,4(BP)
|
! 24
|
|
MOV BX,6(BP)
|
! 25
|
|
MOV SI.O
|
! 26
|
|
PUSH frmatkop
|
! 27
|
|
PUSH frmatstr
|
! 28
|
|
PUSH PRINTF
|
! 29
|
|
SYS
|
! 30
|
|
MOV -4(BP),frmatint
|
! 31
|
|
1: MOV DI.(BX)CSI)
|
! 32
|
|
MOV -2(BP),DI
|
! 33
|
|
SYS
|
! 34
|
|
INC SI
|
! 35
|
|
LOOP lb
|
! 36
|
|
PUSH '\n'
|
! 37
|
|
PUSH PUTCHAR
|
! 38
|
|
SYS
|
! 39
|
|
MOV SP.BP
|
! 40
|
|
RET
|
! 41
|
В строках 27-30 кода показано, как вывести символьную строку, а в строках 31-34 системный вызов printf выполняется применительно уже к целочисленному значению. Адрес символьной строки вводится в стек в строке 27, а в строке 33 в стек вводится целочисленное значение. В обоих случаях адресом форматной строки выступает первый аргумент команды PRINTF. В строках 37-39 отдельный символ выводится при помощи системного вызова putchar.
Теперь попробуем ассемблировать и запустить программу. Для этого введем команду
as88 arrayprt.s
В результате появляется сообщение об ошибке операнда в строке 28 файла arrayprt.$. Этот файл генерируется ассемблером путем объединения включаемых файлов с исходным файлом; именно результирующий файл обрабатывается ассемблером. В сообщении об ошибке имеется в виду строка 28 именно этого объединенного файла. Изучение строки 28 файла arrayprt.s ничего не даст - нумерация строк в двух файлах не совпадает из-за включения в файл arrayprt.$ строк заголовочного файла. Строка 28 файла arrayprt.$ соответствует строке 7 arrayprt.s, так как включаемый заголовочный файл syscalnr.h содержит 21 строку.
В UNIX для поиска строки 28 в файле arrayprt.$ достаточно ввести команду
head -28 arrayprt.s
Эта команда выводит первые 28 строк объединенного файла. Соответственно, ошибку нужно искать в нижней строке листинга. Аналогичного эффекта можно добиться, просмотрев объединенный файл в текстовом редакторе. Таким образом, мы локализуем ошибку в строке 7 исходной программы, которая содержит команду SHR. Путем изучения табл. В.2 проблема легко обнаруживается: мы забыли указать величину смещения. Строка 7 после исправления должна выглядеть следующим образом:
SHR СХ.1
Важно отметить, что ошибку нужно исправлять в файле arrayprt.s, а не в объединенном файле arrayprt.$, так как последний автоматически обновляется при каждом запуске ассемблера.
Следующая попытка ассемблировать исходный код, по идее, должна пройти успешно. Затем запускаем трассер командой
t88 arrayprt
В ходе трассировки замечаем, что выходные данные не согласуются с вектором, находящимся в сегменте данных. Вектор содержит значения 3, 4, 7, 11 и 3, в то время как на выходе последовательность начинается с 3, 1024,... Очевидно, что-то не так.
Чтобы найти ошибку, трассер можно запустить заново и шаг за шагом отслеживать состояние машины вплоть до появления неверного значения. Значения, которые требуется вывести, хранятся в памяти в строках 32 и 33. Строка вывода неверного значения - весьма удачное место для начала поисков. При втором проходе цикла становится заметно, что численное значение SI является нечетным, чего не должно быть по определению, так как индексирование производится по словам, а не по байтам. Таким образом, проблема локализуется в строке 35. Значение SI в ней приращивается на единицу, в то время как правильный шаг приращения - 2. Чтобы исправить ошибку, строку нужно изменить следующим образом:
ADD SI,2
После исправления выводимый список чисел не вызывает нареканий.
Тем не менее нас поджидает еще одна ошибка. После завершения вызова vecprint и возврата значения трассер отмечает ошибку в указателе стека. Очевидное решение - проверить, совпадает ли значение, вводимое в стек при вызове vecprint, со значением, находящимся на вершине стека при выполнении команды RET в строке 41. Как выясняется, они не совпадают. Таким образом, строку 40 следует заменить двумя новыми строками:
ADD SP.10 POP BP
Первая команда удаляет 5 слов, помещенных в стек в ходе вызова vecprint; таким образом открывается доступ к значению BP, сохраненному в строке 22. Путем выталкивания этого значения из стека мы восстанавливаем значение регистра BP, имевшее место перед вызовом, и получаем правильный адрес возврата. Теперь программа завершается корректно. Не секрет, что отладка кода на языке ассемблера - скорее искусство, чем наука, однако не стоит пренебрегать помощью трассера, который значительно упрощает процесс.
⇐Вызов регистров команд и указателя || Оглавление || Обработка символьных строк и строковые команды⇒
|