Реклама:

Рассмотренные в предыдущих примерах программы просты и не содержат ошибок. В данном примере мы покажем, как трассер может помочь в отладке программ с ошибками. Наша следующая программа предназначена для вывода целочисленного массива, определенного после метки 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, имевшее место перед вызовом, и получаем правильный адрес возврата. Теперь программа завершается корректно. Не секрет, что отладка кода на языке ассемблера - скорее искусство, чем наука, однако не стоит пренебрегать помощью трассера, который значительно упрощает процесс.

Вызов регистров команд и указателя || Оглавление || Обработка символьных строк и строковые команды