Дамп

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

Изучение выдачи хорошо начинать с начала и производить последовательно. Код завершения распечатывается на первой странице или вместе с выдачей JCL. Затем следует выдача ассемблера. Сразу же посмотрите, нет ли синтаксических ошибок или предупреждений. Далее следует информация, выдаваемая редактором связей. Затем следует выдача самой программы (заметим, что ее может не быть вообще).

Описание: C:\Users\Екатерина\Desktop\Безымянный.png

Рис. 12.1. Распечатка информации управления заданием для отлаживаемой программы.

Рис. 12.2 Листинг ассемблера отлаживаемой программы

23

-13

Рис. 12.3. Выход отлаживаемой программы.

В некоторых случаях эта выдача облегчает поиск ошибок, особенно если она выглядит странно на внешний вид. Но ни в коем случае не полагайтесь на выдачу при определении места в программе, вызвавшего аварийное завершение. Может случиться (это зависит от конкретного способа организации вывода), что не все требуемые результаты распечатаны. Наконец, мы дошли до дампа. На рис. 12.1— 12.4 приведены примеры программы, требующей отладки. Необходимо помнить, что формат выдачи JCL, ассемблера, редактора связей и дампа во многом зависит от конкретно используемой вычислительной установки. Но проблема заключается не столько в том, где искать, сколько в том, что искать. В большинстве случаев полезную информацию, даже если она находится среди массы всякой другой, легко выделить. Ваш преподаватель или специалисты вычислительного центра сообщат вам о возможностях отладки в конкретной системе.

 

С программой, использованной в качестве примера, мы уже встречались в гл. 6. Программа AVEDIF предназначена для вычисления среднего арифметического нескольких чисел и разностей между каждым числом и полученным средним. Но уже из выдачи JCL (рис. 12.1) видно, что программа должным образом не работает. Выделенное сообщение

COMPLETION CODE— SYSTEM = 0С6 USER = 0 0 0 0

свидетельствует о том, что в какой-то команде мы допустили ошибку при задании операнда.

Рис. 12.2 содержит выдачу ассемблера, соответствующую отлаживаемой программе. Здесь нет сообщений об ошибках, поэтому бегло

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

Может быть, вы уже нашли ошибку? Если это даже так, то предположим, что ошибка еще не найдена и используем для ее поиска дамп.

Весь вывод, который получился при работе программы, представлен на рис. 12.3. На рис. 12.4 представлены две страницы дампа. В целях экономии места здесь изображен не весь дамп, а только наиболее часто используемые его сегменты. Остальные части дампа представляют собой специальные таблицы, содержащие полезную для системных программистов информацию, и распечатку содержимого памяти. Снова обращаем ваше внимание на то, что формат дампа во многом зависит от конкретной вычислительной системы. Здесь приведены только части, присутствующие во всех дампах.

Нашей целью является использование информации дампа для определения места прекращения выполнения программы и источника ошибки. Эта работа обычно начинается с рассмотрения содержимого PSW.

 

Рис. 12.4a. Первая страница дампа.

Рис. 12.4б. Первая страница части дампа, представляющей содержимое регистров и памяти.

 

Первая строка первой страницы дампа (рис. 12.4а) идентифицирует программу, во второй повторно указывается код завершения. Третья же строка имеет вид

PSW AT ENTRY ТО ABEND FFA5000D 9006А8СС

Эти 16 шестнадцатеричных цифр представляют собой содержимое PSW на момент остановки выполнения программы.

Что полезного можно извлечь из этой информации? Посмотрим внимательнее на второе слово. В первом полубайте содержится код длины команды и признак результата (см. рис. 11.1). В двоичном виде содержимое этой части выглядит так:

Итак, ILC=2, т. е. выполнявшаяся команда имела длину 4 байта, а признак результата — единица, из чего следует, что последняя команда, влияющая на СС, дала отрицательный результат.

Счетчик команд содержит адрес команды, которая выполнялась бы следующей при нормальном продолжении работы. Счетчик команд в PSW занимает 3 байта, поэтому

(PC) = 06А8СС = адрес следующей команды

Поскольку команда, выполнение которой привело к прерыванию, имела длину 4 байта, то ее адрес можно определить, вычитая 4 из содержимого PC:

(PC) = 6А8СС = адрес следующей команды

 = адрес выполнявшейся команды

Таким образом, PSW содержит всю необходимую информацию для определения адреса команды, при выполнении которой был зафиксирован особый случай.

Дамп регистров и памяти содержит информацию о содержимом памяти и регистров на момент прекращения выполнения программы. В нашем случае первая ‘страница дампа памяти изображена на рис. 12.4 б. Первые четыре строки этой страницы представляют содержимое регистров. Далее следует дамп памяти, т. е. шестнадцатеричная распечатка содержимого памяти. Числа в первой колонке каждой строки представляют адреса первых байтов областей, содержимое которых распечатано в этих строках. Адреса в соседних строках отличаются друг от друга на 201e, из чего следует, что в каждой строке записано содержимое 201e = 3210 байтов. Например, адрес в первом столбце третьей строки равен 6А840. Слово, хранящееся по этому адресу, имеет содержимое FFFFFFF3. Каждая строка распечатки содержимого памяти подразделяется на две части. Адрес первого байта второй части больше адреса первой части на 1016. По адресу 6А910 расположено слово, содержащее 00000009. В каждой части выделено четыре полных слова.

Справа от дампа памяти располагается информация, которая была бы получена при распечатке содержимого каждой строки в коде EBCDIC. Заметим, что каждому байту, не содержащему код какого- либо символа печати, в распечатке соответствует точка.

Теперь, зная PC и ILC, мы в состоянии определить машинную команду, вызвавшую аварийное завершение программы. 4-байтовое поле (длина команды равна четырем байтам), начинающееся по адресу 6А8С8, содержит

58A9D0D8

что и представляет собой машинный код искомой команды. Соответствующая команда языка ассемблера выглядит так:

L 10,X'D8'(9,13)

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

 

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

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

Адресом точки входа (Entry Point Address — ЕРА) загрузочного модуля называется адрес первой выполняемой после загрузки команды. В данном случае для нас не существенно различие между адресом точки входа и адресом загрузки, т. е. адресом, по которому происходит загрузка первого байта модуля. В более сложных случаях эти адреса не совпадают, но описываемая ниже процедура может быть легко модифицирована применительно к ним.

Где именно следует искать адрес точки входа — зависит от операционной системы и типа дампа. На рис. 12.4 изображен дамп Системы 360, так называемый SYSTEM UTILITY DUMP. В нем значение ЕРА располагается в первой строке таблицы CDE (Contents Directory Entries).В этой таблице содержится описание содержимого всех областей памяти, запрашиваемых данным шагом задания. Нашему загрузочному модулю система присвоила имя ** GO. Адрес точки входа загрузочного модуля всегда помещается в первую строку CDE, которую следует искать в конце первой страницы дампа. Итак, в соответствующем месте первой строки мы находим предложение

ЕРА 06А810

Это и есть искомый адрес точки входа.

Графически рассматриваемая ситуация изображена на рис. 12.5. Адрес команды, при выполнении которой имел место особый случай, равен значению счетчика команд за вычетом длины команды. В данном случае ЕРА совпадает с адресом первого байта загрузочного модуля. Если адресацию вести, начиная с байта 0, то, очевидно

(PC)—2(ILC)=EPA+LOC

Итак, значение счетчика размещения, соответствующее команде исходной программы, при выполнении которой был зафиксирован особый случай, равно

LOC=(PC)—2(ILC) — ЕРА

Возвращаясь к нашему примеру, имеем

6 А8С8 = (PC) — 2( ILC)

Рис. 12.5. Взаимосвязь содержимого счетчика команд, адреса точки входа и счетчика размещения.

Из рис. 12.2 видно, что предложение, которому соответствует значение счетчика размещения В8, имеет вид

DIFLOOP L 10,NMBRS(9)

Именно эта команда загрузки вызвала особый случай.

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

 следующей команды

Команда, предшествующая той команде, значение счетчика размещения для которой равно ВС, и является искомой.

Описанная выше процедура поиска команды, при выполнении которой обнаружена ошибка, справедлива всегда, за исключением случая, когда искомая команда является командой перехода. Например, при попытке передачи управления по нечетному адресу фиксируется особый случай спецификации, 0С6. При этом PSW содержит адрес той команды, на которую мы пытались произвести переход, и не позволяет определить, где же находится сама команда перехода.

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

 

Как только искомая команда найдена, возникает вопрос, почему же она не выполняется должным образом. Не существует никакого общего алгоритма нахождения ответа на этот вопрос, однако несколько общих соображений, облегчающих его поиск, мы попытаемся привести. 0С1 свидетельствует о том, что была произведена попытка выполнения команды с неверно заданным кодом операции. Это могло произойти в результате присутствия внутри тела программы некоторых числовых или текстовых данных, что в свою очередь может объясняться либо наличием команд типа резервирования памяти, расположенных внутри программы, либо записью информации в неподходящем формате внутри программы уже на этапе ее выполнения.

Если справедливо первое предположение, то для устранения ошибки следует либо вывести команды DS и DC за пределы реально выполняемой программы, либо организовать обход зарезервированной области с использованием команд перехода. Причиной записи данных в тело программы во время выполнения является ошибка адресации. Код 0С1 может быть выдан еще и в том случае, если расположенные за пределами выполняемой части программы данные выбираются как команды. (Не забыли ли вы написать команду EOJ или ее эквивалент?) Наконец, как уже было отмечено, мог произойти переход на команду с неверно заданным кодом операции. Это опять свидетельствует об ошибочной адресации.

Ошибки адресации, вообще, являются наиболее часто встречающимися. Они приводят к выдаче кодов завершения 0С4, 0С5, 0С6 (или, как мы только что видели, 0С1). Поиск признаков ошибки сводится к рассмотрению содержимого регистров и значений смещений, использованных при вычислении адресов операндов команд работы с памятью. Это конкретно означает проверку содержимого базового и индексного регистров, если они были использованы, и соответствующего значения смещения для операндов команды.

Возвращаясь к нашему примеру, мы видим, что допущена ошибка в адресации, о чем свидетельствует код завершения 0С6. Найденная нами машинная команда, выполнение которой привело к аварийному завершению, указывает смещение D8; в качестве индексного и базового регистров использованы соответственно регистры 9 и 13. Адрес расположения операнда в памяти равен

D8+(9)+(13)

Для определения содержимого регистров используем информацию третьей строки (REGS 8—15) на рис. 12.46.

Находим, что на момент остановки

(9) = 00000001

(13) = 0006А828

Итак, адрес операнда вычисляется следующим образом:

D8 = смещение

+1 = (9)

6A828 = (13)

6A901 = действительный адрес

Вы можете сказать: «Ну и что?» Давайте подумаем. Код завершения, равный 0С6, говорит о том, что была совершена ошибка при задании операнда. Кроме того, известно, что команда, выполнение которой привело к фиксации ошибки, является командой LOAD и что адрес ее операнда равен 6А901. Какие действия выполняются по команде LOAD? Производится пересылка содержимого полного слова, расположенного по адресу, указанному в команде, в регистр общего назначения. Но адрес полного слова должен быть кратен четырем... Вот, в чем дело! Полученный нами код завершения 0С6 говорит о том, что мы использовали команду, работающую с полным словом, адрес которого не был кратен четырем.

Почему же это произошло? Значение смещения, как и содержимое базового регистра, делится на 4. Значение индекса равно 1. Значит, в индекс-регистр было загружено неверное значение.

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

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

АДРЕС=ЕРА+ЕОС

Определим, например, содержимое NMBRS на момент остановки все той же программы, приведенной на рис. 12.2. В данном случае значение ЕРА равно 6В810, а адрес NMBRS относительно начала программы (значение счетчика размещения) — F0. Итак, необходимо определить содержимое полного слова, имеющего адрес

6А810+ F0=6А900

Дамп, изображенный на рис. 12.46, обеспечивает нас необходимой информацией. Находим:

(6А900)=0000000А

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

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

 

Определив, почему искомая команда не выполняется должным образом, следует перейти к поиску ошибки в логике программы, следствием которой и является возникновение особого случая. В нашем примере регистр 9, который был использован в качестве индексного, содержал нечетное число, поэтому при выполнении операции загрузки полного слова был зафиксирован особый случай спецификации.

Как же получилось, что в регистр 9 попала 1? Ответы на вопросы такого типа обычно найти достаточно сложно. В процессе получения ответа обычно используется вся находящаяся в нашем распоряжении информация: листинг, полученные результаты, дамп и наши знания о программной логике.

В рассматриваемом случае задача сводится к определению того, какие действия были произведены над содержимым регистра 9 непосредственно перед выполнением команды LOAD. Мы знаем, что регистр 9 был использован для хранения индекса текущего слова при обработке массива NMBRS в цикле DIFLOOP. Непосредственно перед началом цикла в регистр 9 было загружено значение 0. Мы производим выборку первого числа, вычитаем из него значение среднего арифметического, находящееся в регистре 7, выводим результат на печать, увеличиваем (9) на значение данного слова, выполняем проверку на завершение и возвращаемся к выполнению команды DIFLOOP. Стоп! А как же нами выполнен шаг увеличения значения индекса? Разве при обработке массива полных слов приращение индекса не должно быть равно 4? Вот в чем ошибка! Ясно, что мы ошиблись в определении длины полного слова. (Никогда не следует полагаться на информацию комментариев — вычисления выполняются так, как это указано в команде, а не в комментарии!) Для устранения ошибки предложение 56 в программе необходимо заменить на следующее:

А 9,=F'4'

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

1. Определение кода завершения.

2. Поиск и изучение сообщений об ошибках в выдачах ассемблера и редактора связей.

3. Определение команды, вызвавшей прекращение выполнения программы, по содержимому PSW и значению ЕРА.

4. Ответ на вопрос, почему найденная команда не выполняется должным образом (при этом используются дампы регистров и памяти).

5. Поиск определяющей ошибки в логике программы с использованием всей находящейся в распоряжении информации.

До сих пор нас интересовали исключительно ошибки, приводящие к программным прерываниям. Существует также большое количество ошибок, приводящих к прекращению выполнения программы по причине, отличной от попытки незаконного выполнения команды. Такие ошибки возникают вследствие неправильного использования системы. Например, при зацикливании (бесконечном выполнении последовательности команд какого-нибудь цикла) операционная система не позволяет производить эти операции действительно бесконечно. Выполнение программы прекращается по истечении некоторого отведенного для нее времени, выдается дамп, если он был запрошен, и соответствующий код завершения (в системе OS—322). Подобным образом программа может попытаться .распечатать слишком длинную выдачу, считать больше карт, чем было предусмотрено или использовать слишком большой объем памяти. При остановке выполнения программы вследствие одной из подобных причин необходимо сначала определить, что означает выданный код завершения. Для этого предназначено уже упоминавшеесяруководство «Сообщения и коды». В большинстве случаев описанная выше процедура позволяет определить, какая команда программы выполнялась в момент, когда системой была зафиксирована ошибка.{toc_noshowall}

 
Статьи раздела