DVM отладчик - оглавление Часть 1
(1 - 4)
Часть 2 (5 - 6.4) Часть 3 (6.5) Часть 4 (7)
дата документа: февраль. 2000 - дата последнего обновления 22.05.01 -

1 Функции DVM отладчика

DVM отладчик предназначен для отладки DVM-программ (написанных на языках Fortran-DVM и C-DVM). Для отладки DVM-программ используется следующий подход. Сначала программа отлаживается на рабочей станции как последовательная программа с использованием обычных средств отладки. Затем программа выполняется на той же рабочей станции в специальном режиме проверки DVM-директив. На третьем этапе программа выполняется на параллельном компьютере в специальном режиме сравнения промежуточных результатов выполнения с эталонными результатами (например, с результатами последовательного выполнения).

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

В общем случае можно выделить следующие четыре класса ошибок:

Ошибки первого класса выявляются при компиляции.

С ошибками второго класса справиться посредством статического анализа программ невозможно (за исключением простейших случаев, например, задание неверного типа параметра). Чтобы обнаруживать ошибки этого класса каждая функция библиотеки Lib-DVM проверяет корректность порядка выполнения DVM-указаний и передаваемых параметров. Данные проверки осуществляется динамически во время параллельного выполнения программы. Некоторые из таких проверок могут вызывать заметные накладные расходы и поэтому производятся только по специальному указанию.

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

DVM отладчик предназначен для обнаружения ошибок третьего класса и базируется на следующих двух методах.

Первый метод, метод динамического контроля DVM-указаний, позволяет проверить корректность распараллеливания программы с помощью DVM-указаний. Он основан на анализе последовательности вызовов функций Lib-DVM и обращений к переменным во время моделирования на одном процессоре параллельного выполнения программы.

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

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

1.1 Метод динамического контроля DVM-указаний

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

1.2 Типы выявляемых ошибок

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

1.3 Метод сравнения результатов выполнения

Динамический контроль предназначен, прежде всего, для проверки корректности DVM-указаний. Область контроля ограничена только DVM-программами, скомпилированными в специальном отладочном режиме. Однако, в программе возможны обращения к процедурам, написанным на обычных последовательных языках (включая ассемблер). Работа таких процедур, которая не контролируется, может быть причиной некорректного параллельного выполнения программы. И, наконец, в программе могут быть ошибки (не связанные с ее распараллеливанием), которые не проявлялись при ее последовательном выполнении, но приводят к неверному параллельному выполнению.

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

2 Состав DVM отладчика

Отладчик можно разбить на две, четко выраженные, системы: динамический контроль и сравнение результатов выполнения.

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

На базе этих двух подсистем строятся остальные системы, более высокого уровня.

Динамический контроль включает в себя следующие компоненты:

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

3 Прототипы отладочных функций

Система поддержки Lib-DVM содержит дополнительные системные вызовы, относящиеся к динамическому отладчику и отвечающие за динамический контроль и сравнение трассировки. Обращения к данным вызовам подставляются компиляторам C-DVM и Fortran-DVM при компиляции программы в специальном отладочном режиме.

Прототипы отладочных функций следующие:

long dprstv_ (long *TypePtr, AddrType *addr, long *Handle, char *Operand,
                       long OperLength)
     
TypePtr тип переменной (константы rt_INT, rt_LONG, rt_FLOAT или rt_DOUBLE). Если тип не соответствует ни одному из стандартных типов, то данное обращение будет проигнорировано для трассировки;
addr адрес переменной или элемента массива;
Handle дескриптор DVM-массива, если обращение идет к элементу массива. Иначе аргумент должен быть равным NULL;
Operand строка с именем операнда. Данная строка будет выводиться в сообщениях об ошибках и трассировке;
OperLength длина строки, переданной в Operand. Данный аргумент служит для совместимости с Fortran. При вызове из Си должен быть равен –1.

Функция отмечает начало модификации переменной или элемента массива. Вызов должен вставляться до присвоения переменной значения и до вычисления присваиваемого выражения. Данный системный вызов должен быть парным с dstv_().

long dstv_(void)

Функция отмечает завершение вычисления выражения и присвоение переменной нового значения. Функция должна вызываться после присвоения переменной нового значения и должна быть парной с вызовом dprstv_().

long dldv_(long *TypePtr, AddrType *addr, long *Handle, char *Operand,
                  long OperLength)
     
TypePtr тип переменной (константы rt_INT, rt_LONG, rt_FLOAT или rt_DOUBLE). Если тип не соответствует ни одному из стандартных типов, то данное обращение будет проигнорировано для трассировки;
addr адрес переменной или элемента массива;
Handle дескриптор DVM-массива, если обращение идет к элементу массива. Иначе аргумент должен быть равным NULL;
Operand строка с именем операнда. Данная строка будет выводиться в сообщениях об ошибках и трассировке;
OperLength длина строки, переданной в Operand. Данный аргумент служит для совместимости с Fortran. При вызове из Си должен быть равен –1.

Функция отмечает обращение к переменной или элементу массива на чтение.

long dbegpl_(long *Rank, long *No, long *Init, long *Last, long *Step)
     
Rank ранг параллельного цикла;
No номер конструкции. Должен быть уникален для всех циклов и областей задач в пределах одной программы;
Init массив размерности Rank начальных значений итерационных переменных цикла;
Last массив размерности Rank конечных значений итерационных переменных цикла;
Step массив размерности Rank значений шага итерационных переменных.

Функция отмечает начало параллельного цикла. Обращение к данному вызову должно идти до отображения параллельного цикла ( функция mappl).

long dbegsl_(long *No)
     
No номер конструкции. Должен быть уникален для всех циклов и областей задач в пределах одной программы.

Функция отмечает начало последовательного цикла. Ранг последовательного цикла всегда принимается равным 1.

long dbegtr_(long *No)
     
No номер конструкции. Должен быть уникален для всех циклов и областей задач в пределах одной программы.

Функция отмечает начало области задач. Ранг области задач всегда принимается равным 1.

long dendl_(long *No, unsigned long *Line)
     
No номер завершающейся конструкции;
Line строка, в которой произошел выход из конструкции. Служит для контроля корректности завершения параллельных циклов.

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

long diter_(AddrType *index)
     
index массив адресов итерационных переменных. Размерность массива должна совпадать с рангом исполняющейся конструкции.

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

long drmbuf_(long* ArrSrc, AddrType* RmtBuff, long* Rank, long* Index)
     
ArrSrc дескриптор исходного DVM-массива, для которого создается буфер удаленного доступа;
RmtBuff адрес скалярной переменной, если буфер создается для единичного элемента массива, или дескриптор DVM-массива, выполняющего роль буфера, если буфер создается для вырезки массива;
Rank ранг буфера удаленного доступа. Должен быть равен 0, если буфер создается в скалярной переменной;
Index массив, содержащий индексы вырезаемых измерений. Если элемент массива не равен -1, то из DVM-массива берется вырезка, соответствующая данному элементу массива, иначе берется все измерение массива.

Функция регистрирует в системе динамического контроля создание буфера удаленного доступа. Данная функция необходима для корректного контроля обращений к элементам DVM-массива через буфер удаленного доступа. Вызов должен стоять после создания буфера и инициализации его элементов.

long dskpbl_(void)

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

4 Реализация базовых модулей

4.1 Таблица

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

Для таблицы определена структура данных следующего вида:

typedef struct tag_TABLE
{    
  byte IsInit;
  s_COLLECTION cTable;
  size_t TableSize;
  size_t CurSize;
  size_t ElemSize;
  PFN_TABLE_ELEMDESTRUCTOR Destruct;
} TABLE;  
     
IsInit флаг инициализации таблицы. Используется для отложенной инициализации в целях оптимизации работы;
cTable коллекция указателей на выделенные фрагменты памяти;
TableSize число элементов, для которого будет выделена память при расширении таблицы;
CurSize число занятых элементов в текущем выделенном фрагменте памяти;
ElemSize размер в байтах одного элемента;
Destruct указатель на функцию с прототипом
void (*PFN_TABLE_ELEMDESTRUCTOR)(void *Elem)
.Данная функция будет вызвана для каждого элемента таблицы при его удалении.

Прототипы функций для работы с таблицей:

void table_Init(TABLE *tb, size_t TableSize, size_t ElemSize,                           PFN_TABLE_ELEMDESTRUCTOR Destruct)
     
tb указатель на инициализируемую таблицу;
TableSize число элементов, под которое будет выделена память при расширении таблицы;
ElemSize размер элемента таблицы;
Destuct указатель на функцию-деструктор элемента таблицы. Может быть равен NULL.

Функция инициализации таблицы. Данная функция должна быть вызвана перед началом работы с таблицей.

void table_Done( TABLE *tb )
     
tb указатель на таблицу.

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

long table_Count( TABLE *tb )
     
tb указатель на таблицу.

Функция возвращает число помещенных элементов в таблице.

void *table_At( TABLE *tb, long No )
     
tb указатель на таблицу;
No номер элемента. Нумерация элементов в таблице идет с 0. Если No выходит за пределы числа элементов, то программа аварийно завершается.

Функция возвращает указатель на элемент таблицы с номером No.

long table_Put( TABLE *tb, void *Struct )
     
tb указатель на таблицу;
Struct указатель на вставляемый элемент. В таблицу будет помещено только ElemSize байт указанной структуры.

Функция добавляет новый элемент в конец таблицы.

void *table_GetNew( TABLE *tb )
     
tb указатель на таблицу.

Функция выделяет место для нового элемента в таблице и возвращает указатель на выделенную память. Использование данной функции более оптимально, чем table_Put(), так не выполняет дополнительного копирования блока памяти.

void table_RemoveFrom( TABLE *tb, long Index )
     
tb указатель на таблицу;
Index номер элемента, после которого будет произведено удаление.

Функция удаляет из таблицы элементы, начиная с номера Index+1. Сам элемент с номером Index из таблицы не удаляется.

void table_RemoveAll( TABLE *tb )
     
tb указатель на таблицу.

Функция удаляет все элементы из таблицы. Функция освобождает всю память, выделенную для хранения элементов таблицы.

void table_Iterator(TABLE *tb, PFN_TABLEITERATION Proc,
                                 void *Param1, void *Param2)
     
tb указатель на таблицу;
Proc итерационная функция с прототипом
void (*PFN_TABLEITERATION)(void *Elem, void *Param1, void *Param2).
Функция будет вызвана для каждого элемента таблицы;
Param1 параметр, передаваемый как Param1 в функцию Proc;
Param2 параметр, передаваемый как Param2 в функцию Proc.

Функция выполняет однотипную операцию для всех элементов таблицы.

4.2 Хеш-таблица

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

При помещении новой записи в хеш-таблицу, для ее ключа вычисляется хеш-значение, которое находится в фиксированном диапазоне [0, IndexSize]. Это значение является номером элемента в индексе хеш-таблицы, куда помещается указатель на новую запись цепочки. Предыдущий указатель сохраняется во вставляемой записи.

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

Для вычисления хеш-значений используются следующие два алгоритма:

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

Рис. 1. Организация хеш-таблицы

Для работы с хеш-таблицей описаны следующие типы и структуры:

Хеш-значение:

typedef size_t HASH_VALUE

Тип данных, служащий для ассоциативного поиска. По данному типу вычисляется хеш-значение:

typedef unsigned long STORE_VALUE

Структура для хранения элемента хеш-таблицы:

typedef struct tag_HashList
{    
  long NextElem;
  STORE_VALUE Value;
  long Assign;
} HashList;  
     
NextElem номер следующего элемента цепочки. Если равен 0, то это последний элемент цепочки
Value ключ, с которым ассоциируется хранимое значение
Assign хранимое значение

Структура, описывающая хеш-таблицу:

typedef struct tag_HASH_TABLE
{    
  TABLE hTable;
  long * pIndex;
  size_t IndexSize;
  PFN_CALC_HASH_FUNC HashFunc;
  unsigned long * pElements;
  unsigned long statCompare, statPut, statFind;
} HASH_TABLE;  
     
hTable таблица для хранения элементов хеш-таблицы;
pIndex массив для указателей на начало цепочек. Хеш-значение определяет номер элемента в этом массиве;
IndexSize размер массива pIndex;
HashFunc указатель на функцию для вычисления хеш-значения по ключу;
pElements массив, каждый элемент которого хранит длину цепочки по соответствующему элементу в pIndex. Служит для хранения статистики плотности заполнения хеш-таблицы;
statCompare, statPut, statFind данные поля содержат число операций сравнения, вставки и поиска, произведенных с хеш-таблицей.

Прототипы функций для работы с хеш-таблицей:

void hash_Init(HASH_TABLE *HT, size_t IndexSize, int TableSize, PFN_CALC_HASH_FUNC Func)
     
HT указатель на хеш-таблицу;
IndexSize размер массива индексов;
TableSize величина приращения для таблицы элементов;
Func функция вычисления хеш-значения. Если параметр равен NULL, то будет применяться стандартная функция StandartHashCalc, работающая по формуле: <хеш-значение> = <ключ> % IndexSize.

Функция инициализирует хеш-таблицу. Производит инициализацию и заполнение всех необходимых структур.

void hash_Done( HASH_TABLE *HT )
     
HT указатель на хеш-таблицу.

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

long hash_Find( HASH_TABLE *HT, STORE_VALUE Val )
     
HT указатель на хеш-таблицу;
Val ключ, по которому осуществляется поиск.

Функция осуществляет поиск значения по ключу.

void hash_Insert( HASH_TABLE *HT, STORE_VALUE Val, long Assign )
     
HT указатель на хеш-таблицу;
Val ключ вставляемого элемента;
Assign число, ассоциированное с данным ключом.

Функция вставляет новый элемент в хеш-таблицу.

void hash_Change( HASH_TABLE *HT, STORE_VALUE Val, long Assign )
     
HT указатель на хеш-таблицу;
Val ключ изменяемого элемента;
Assign новое число, ассоциированное с данным ключом.

Функция изменяет ассоциированного значения для заданного ключа.

void hash_Remove( HASH_TABLE *HT, STORE_VALUE Val )
     
HT указатель на хеш-таблицу
Val ключ удаляемого элемента

Функция удаляет элемента из хеш-таблицы по заданному ключу.

void hash_Iterator(HASH_TABLE *HT, PFN_HASHITERATION Proc,
                                 void *Param)
     
HT указатель на хеш-таблицу;
Proc итерационная функция с прототипом:
void (*PFN_HASHITERATION) (long Elem, void* Param)
Функция будет вызвана для каждого элемента хеш-таблицы. Она принимает следующие аргументы:
Elem – ассоциированное значение элемента хеш-таблицы;
Param – параметр, передаваемый как Param в функцию Proc;
Param Дополнительный параметр, передаваемый в функцию Proc.

Функция выполняет однотипную операцию над всеми элементам хеш-таблицы.

void hash_RemoveAll( HASH_TABLE *HT )
     
HT указатель на хеш-таблицу.

Функция удаляет все элементы хеш-таблицы.

4.3 Таблица переменных

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

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

Рис. 2. Структура таблицы переменных

Для хранения информации о переменной используется следующая структура:

typedef struct tag_VarInfo
{    
  byte Busy;
  void * VarAddr;
  long PrevNo;
  int EnvirIndex;
  byte Type;
  SysHandle * Handle;
  void * Info;
  int Tag;
  byte IsInit;
  PFN_VARTABLE_ELEMDESTRUCTOR pfnDestructor;
} VarInfo;  
     
Busy флаг, указывающий свободна эта структура или нет. Служит для оптимизации операций с таблицей переменных;
VarAddr адрес переменной;
PrevNo номер элемента таблицы переменных, описывающий ту же переменную в другом контексте выполнения;
EnvirIndex номер уровня исполнения программы для данной переменной;
Type класс переменной;
Handle указатель на дескриптор распределенного массива. Не равен NULL, если структура описывает распределенный массив;
Info указатель на дополнительное описание переменной;
Tag флаг для хранения различных атрибутов переменной в зависимости от класса ее использования;
IsInit флаг инициализации переменной. Используется для скалярных переменных;
pfnDestructor указатель на деструктор описания переменной. Данная функция будет вызвана при удалении описания переменной из таблицы переменных. Может быть равен NULL.

Следующая структура описывает таблицу переменных:

typedef struct tag_VAR_TABLE
{    
  HASH_TABLE hIndex;
  TABLE vTable;
} VAR_TABLE;  
     
hIndex хеш-таблица для поиска информации о переменной по ее адресу;
vTable таблица для хранения описаний переменных.

Прототипы функций для работы с таблицей переменных:

void vartable_Init( VAR_TABLE *VT, int vTableSize, int hIndexSize, int hTableSize,
                                PFN_CALC_HASH_FUNC Func )
     
VT указатель на таблицу переменных;
vTableSize размер приращения таблицы vTable;
hIndexSize размер массива индексов хеш-таблицы hIndex;
hTableSize размер приращения хеш-таблицы hIndex;
Func указатель на функцию, используемую для вычисления хеш-значения по адресу переменной.

Функция инициализирует таблицу переменных.

void vartable_Done( VAR_TABLE *VT );
     
VT указатель на таблицу переменных

Деструктор таблицы переменных.

VarInfo *vartable_GetVarInfo( VAR_TABLE *VT, long NoVar )
     
VT указатель на таблицу переменных;
NoVar номер переменной в таблице.

Функция осуществляет поиск информации о переменной по ее номеру. Возвращает указатель на структуру с описанием переменной.

VarInfo *vartable_FindVar( VAR_TABLE *VT, void * Addr )
     
VT указатель на таблицу переменных;
Addr адрес переменной.

Функция осуществляет поиск информации о переменной по ее адресу. Возвращает указатель на структуру с описанием переменной или NULL, если переменная с таким адресом не зарегистрирована.

long vartable_FindNoVar( VAR_TABLE *VT, void * Addr )
     
VT указатель на таблицу переменных;
Addr адрес переменной.

Функция осуществляет поиск номера переменной в таблице по ее адресу. Возвращает номер переменной в таблице или -1, если переменная с таким адресом не зарегистрирована.

long vartable_PutVariable(VAR_TABLE* VT, void* Addr, int Env, byte Type,
                                              SysHandle* Handle, int Tag, void* Info,
                                              PFN_VARTABLE_ELEMDESTRUCTOR pfnDestructor)
     
VT указатель на таблицу переменных;
Addr адрес переменной;
Env номер уровня выполнения;
Type тип использования переменной;
Handle дескриптор распределенного массива при его регистрации;
Tag значение дополнительного атрибута переменной;
Info указатель на дополнительную информацию;
pfnDestructor указатель на деструктор описания переменной. Может быть равен NULL.

Функция регистрирует новую переменную в таблице. Возвращает номер зарегистрированной переменной.

void vartable_VariableDone( VarInfo* Var )
     
Var указатель на описание переменной.

Функция вызывается для деинициализации описания переменной. Устанавливает флаг Busy в 0 и, при необходимости, вызывает соответствующий деструктор.

void vartable_RemoveVariable( VAR_TABLE *VT, void * Addr )
     
VT указатель на таблицу переменных;
Addr адрес удаляемой переменной.

Функция удаляет описание переменной из таблицы переменных.

void vartable_RemoveAll( VAR_TABLE *VT );
     
VT указатель на таблицу переменных.

Функция удаляет описания всех переменных из таблицы переменных.

void vartable_RemoveVarOnLevel( VAR_TABLE *VT, int Level )
     
VT указатель на таблицу переменных
Level номер уровня выполнения

Функция удаляет описание всех переменных с указанным уровнем исполнения.

void vartable_Iterator( VAR_TABLE *VT, PFN_VARTABLEITERATION Func )
     
VT указатель на таблицу переменных;
Func итерационная функция с прототипом:
void (*PFN_VARTABLEITERATION)(VarInfo *).
Функции будут переданы все указатели на описания переменных в таблице переменных.

Функция выполняет однотипную операцию над всеми описаниями переменных из таблицы переменных.

void vartable_LevelIterator(VAR_TABLE* VT, int Level, PFN_VARTABLEITERATION Func)
     
VT указатель на таблицу переменных;
Level номер уровня выполнения;
Func итерационная функция с прототипом:
void (*PFN_VARTABLEITERATION)(VarInfo *).
Функции будут по очереди переданы все указатели на описания переменных в таблице переменных, имеющих указанный номер контекста выполнения.

Функция выполняет однотипную операцию над всеми описаниями переменных указанного уровня исполнения из таблицы переменных.

4.4 Модуль выдачи диагностики

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

Модуль диагностики состоит из двух блоков. Блок обработки контекста отслеживает текущий контекст выполнения программы и позволяет получить подробное описание текущего контекста для диагностики. Блок выдачи диагностики занимается выдачей диагностики и фильтрацией однотипных сообщений.

4.4.1 Обработка контекста диагностики

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

Для описания контекста программы служит структура типа CONTEXT, которая содержит такие данные как уровень вложенности цикла или области задач, номер конструкции, ранг цикла и т.д.:

typedef struct _tag_CONTEXT
{    
  byte Rank;
  byte Type;
  int No;
  byte ItersInit;
  long Iters[MAXARRAYDIM];
  s_REGULARSET Limits[MAXARRAYDIM];
} CONTEXT;  
     
Rank ранг цикла;
Type тип конструкции: область задач, параллельный или обычный цикл;
No уникальный номер конструкции;
ItersInit определяет, выполнилась ли хотя бы одна итерация цикла или задача из области задач;
Iters массив текущих значений итерационных перемененных. Для области задач содержит номер текущей выполняющейся задачи.
Limits массив начальных и конечных значений и шагов приращения итерационных переменных.

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

Для хранения последовательности структур CONTEXT в отладчике используется глобальная таблица gContext. При инициализации отладчика, в данную таблицу помещается корневой контекст с номером 0, который соответствует процедуре main() программы.

Рис. 3. Представление контекстов выполнения программы

Для работы с контекстом выполнения предназначен следующий набор функций:

void cntx_Init(void)

Функция инициализацирует глобальную таблицу gContext для работы с контекстами. В gContext помещается корневой контекст с номером 0, соответствующий основной ветви программы.

void cntx_Done(void)

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

void cntx_LevelInit(int No, byte Rank, byte Type, long* pInit, long* pLast,
                                  long* pStep)
     
No уникальный номер конструкции;
Rank ранг цикла;
Type тип конструкции: область задач, параллельный или последовательный цикл;
pInit массив начальных значений итерационных переменных цикла;
pLast массив конечных значений итерационных переменных цикла;
pStep массив шагов приращения итерационных переменных цикла.

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

void cntx_LevelDone(void)

Функция вызывается при завершении текущей исполняющейся конструкции. Удаляет из gContext текущий контекста.

void cntx_SetIters( AddrType *index , long IndexTypes[] )
     
index массив, содержащий текущие значения итерационных переменных или номер задачи. Размерность массива должна совпадать с рангом текущей конструкции;
IndexTypes массив типов индексных переменных.

Функция изменяет текущие значений итерационных переменных. Вызывается при начале новой итерации цикла или задачи.

CONTEXT *cntx_CurrentLevel(void)

Функция возвращает указатель на структуру, описывающую текущий контекст.

CONTEXT *cntx_GetLevel( long No )
     
No номер контекста.

Функция возвращает указатель на структуру, описывающую контекст с указанным порядковым номером.

long cntx_LevelCount(void)

Функция возвращает величину вложенности контекстов исполнения программы.

long cntx_GetParallelDepth(void)

Функция возвращает глубину вложенности параллельных конструкций (параллельных циклов или областей задач) программы.

int cntx_IsInitParLoop(void)

Функция возвращает значение, отличное от 0, если началось выполнения тела текущего параллельного цикла.

long cntx_GetAbsoluteParIter(void)

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

int cntx_IsParallelLevel(void)

Функция возвращает значение, отличное от нуля, если в момент вызова функции исполняется хотя бы одна параллельная конструкция.

CONTEXT* cntx_GetParallelLevel(void)

Функция возвращает описание самого вложенного выполняющегося параллельного контекста. Если параллельных контекстов нет, то возвращает NULL.

char *cntx_FormatLevelString( char* Str )
     
Str указатель на начало буфера.

Функция формирует в строке по адресу Str описание текущего контекста выполнения программы. Возвращает указатель на конец сформированной строки.

4.4.2 Функции выдачи диагностики

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

Общая структура сообщения об ошибке:

(<process number>)<context> File: <file>, Line: <line>
(<count> times)<error message>

где:

<process number> номер процессора, на котором произошла ошибка. Номер процессора выводится, только при запуске программы на нескольких процессорах.
<context> контекст, в котором произошла ошибка. Контекст может иметь одну из следующих форм:
Sequential branch – ошибка произошла в последовательной части программы
Loop( No(N1), Iter(I1,I2,…) ), …, Loop( No(Nm), Iter(I1,I2,…) ) – ошибка произошла при выполнения цикла m-степени вложенности.
<file> имя файла, где произошла ошибка.
<line> номер строки
<count> число повторений данной ошибки в данном контексте. Выводится при итоговой выдаче всех найденных ошибок.
<error message> описание произошедшей ошибки.

Структура, используемая для хранения описания ошибки:

typedef struct _tag_ERROR_RECORD
{    
  int StructNo;
  long CntxNo;
  char Context[MAX_ERR_CONTEXT];
  char Message[MAX_ERR_MESSAGE];
  char File[MAX_ERR_FILENAME];
  unsigned long Line;
  int Count;
} ERROR_RECORD;  
     
StructNo номер текущего цикла. Равен -1, если нет текущего цикла;
CntxNo номер текущего контекста исполнения. Вычисляется как cntx_LevelCount() – 1;
Context строка с описанием контекста;
Message строка с описанием ошибки;
File имя файла;
Line номер строки;
Count число ошибок данного типа в данном контексте.

Структура таблицы для накопления диагностических сообщений:

typedef struct _tag_ERRORTABLE
{    
  TABLE tErrors;
  int MaxErrors;
  int ErrCount;
} ERRORTABLE;  
     
tErrors таблица, содержащая элементы типа ERROR_RECORD;
MaxErrors максимальное число обрабатываемых ошибок;
ErrCount текущее число произошедших ошибок.

Прототипы функций для работы с диагностикой:

void error_Init(ERRORTABLE* errTable, int MaxErrors)
     
errTable указатель на инициализируемую таблицу диагностики;
MaxErrors максимально число обрабатываемых ошибок.

Функция инициализирует таблицу диагностики.

void error_Done(ERRORTABLE* errTable)
     
errTable указатель на таблицу диагностики

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

ERROR_RECORD *error_Put(ERRORTABLE* errTable, char* File,
                                                      unsigned long Line, char* Context, char* Message,
                                                      int StructNo, long CntxNo)
     
errTable указатель на таблицу диагностики;
File имя файла;
Line номер строки;
Context описание контекста;
Message сообщение об ошибке;
StructNo номер цикла;
CntxNo номер контекста исполнения.

Функция заносит в таблицу диагностики новое сообщение об ошибке.

ERROR_RECORD *error_Find(ERRORTABLE* errTable, char* File,
                                                        unsigned long Line, char* Message,
                                                        int StructNo, long CntxNo)
     
errTable указатель на таблицу диагностики;
File имя файла;
Line номер строки;
Message сообщение об ошибке;
StructNo номер цикла;
CntxNo номер контекста исполнения.

Функция осуществляет поиск сообщения с заданными параметрами в таблице диагностики.

byte error_Message(char* To, ERRORTABLE* errTable, char* File,
                                    unsigned long Line, char* Context, char* Message)
     
To имя файла, куда выводится диагностика. Если равен NULL, то диагностика выводится в поток stderr;
errTable указатель на таблицу диагностики;
File имя файла;
Line номер строки;
Context описание контекста;
Message сообщение об ошибке.

Функция помещает сообщение в таблицу диагностики и выводит его на экран, если данное сообщение отсутствует в таблице диагностики. Иначе, для сообщения только увеличивается счетчик количества сообщений.

byte error_Print(char* To, ERROR_RECORD* pErr)
     
To имя файла, куда выводится диагностика. Если равен NULL, то диагностика выводится в поток stderr;
pErr указатель на структуру, описывающую диагностику.

Функция выдает сообщение в указанный файл.

void error_PrintAll( char *To, ERRORTABLE *errTable )
     
To имя файла, куда выводится диагностика. Если равен NULL, то диагностика выводится в поток stderr;
errTable указатель на таблицу диагностики.

Функция выдает все диагностические сообщения из таблицы диагностики в указанный файл.

void error_DynControl( int code, ... )
     
code код ошибки

Функция выдает диагностику с указанным кодом для модуля динамического контроля.

void error_DynControlPrintAll(void)

Функция выдает все диагностические сообщения модуля динамического контроля.

void error_CmpTrace( char *File, unsigned long Line, int code )
     
File имя файла трассировки
Line номер строки трассировки
code код ошибки

Функция выдает диагностику с указанным кодом для модуля сравнения результатов выполнения при обработке файла трассировки.

void error_CmpTraceExt( long RecordNo, char *File, unsigned long Line,
int code, ... )
     
RecordNo номер записи в файле трассировки
File имя файла
Line номер строки
code код ошибки

Функция выдает диагностику с указанным кодом для модуля сравнения результатов выполнения.

void error_CmpTracePrintAll(void)

Функция выдает все диагностические сообщения модуля сравнения результатов выполнения.


DVM отладчик - оглавление Часть 1
(1 - 4)
Часть 2 (5 - 6.4) Часть 3 (6.5) Часть 4 (7)