5. Обработка критических ошибок

Операционная система MS-DOS позволяет программам устанавливать собственный обработчик критических ошибок аппаратуры. Мы уже говорили о том, что вектор 0000:0090, соответствующий прерыванию INT 24h, содержит адрес обработчика критических ошибок. Этот обработчик получает управление от операционной системы, когда драйвер какого-либо устройства обнаруживает ошибку аппаратуры.

Обратите внимание на то, что обработчик критических ошибок не вызывается при работе с диском через прерывания MS-DOS INT 25h/26h, и, тем более, при работе с диском на уровне прерывания INT 13h BIOSBIOS.

При запуске программы MS-DOS копирует адрес обработчика в префикс сегмента программы PSP, а после завершения работы программы - восстанавливает его из PSP.

Стандартный обработчик MS-DOS выводит на экран сообщение:

Abort, Retry, Ignore, Fail?

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

Когда обработчик получает управление, регистры процессора содержат информацию, необходимую для определения причины и места появления ошибки:

AH информация об ошибке:
Биты:
0 - тип операции:
0 - чтение, 1 - запись
1...2 область диска, где произошла ошибка:
00 - системные файлы;
01 - область FAT
10 - область каталога;
11 - область данных
3 - 1 - возможен выход с кодом FAIL
4 - 1 - возможен выход с кодом RETRY
5 - 1 - возможен выход с кодом IGNORE
6 зарезервирован, равен 0
7 тип устройства: 0 - диск; 1 - символьное устройство
AL номер диска (если бит 7 регистра AH равен 0)
DI код ошибки (биты 0...7, остальные биты не определены)
BP:SI адрес заголовка драйвера устройства, на котором произошла ошибка

Биты 3, 4, 5 определены только для DOS версии 3.0 и для более поздних версий.

Обработчик критических ошибок не должен пользоваться функциями MS-DOS с кодами, большими чем 0Ch (из-за того, что функции MS-DOS не реентерабельны).

Программа может вывести на экран сообщение об ошибке и запросить оператора о необходимых действиях. Ей разрешено также получить дополнительную уточняющую информацию об ошибке с помощью функции 59h прерывания INT 21h или узнать версию DOS с помощью функции 30h этого же прерывания. Дополнительная информация об устройстве, в котором произошла ошибка, может быть получена с использованием адреса заголовка драйвера устройства, который передается операционной системой при вызове обработчика в регистрах BP:SI.

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

Адрес возврата в DOS для команды IRET

IP
CS
FLAGS

Содержимое регистров программы перед вызовом INT_21h

AX, BX, CX, DX, SI, DI, BP, DS, ES

Адрес возврата в программу, вызвавшую функцию DOS

IP
CS
FLAGS

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

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

0 игнорировать ошибку;
1 повторить операцию;
2 аварийно закончить задачу, используя адрес завершения, записанный в векторе прерывания INT 23h;
3 вернуть программе управление с соответствующим кодом ошибки (этот код можно задавать только для DOS версии 3.0 и для более поздних версий).

Если вы пользуетесь операционной системой MS-DOS версии 4.0, то при открытии файлов с помощью функции 6Ch программа может заблокировать вызов обработчика критических ошибок.

Для составления программы обработки критических ошибок вы можете воспользоваться языком ассемблера или функциями стандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0 _dos_getvect(), _dos_setvect(), _chain_intr(). Однако лучше всего использовать специально предназначенные для этого (и входящие в состав стандартных библиотек указанных трансляторов) функции _harderr(), _hardresume() и _hardretn().

Функция _harderr() предназначена для установки нового обработчика критических ошибок, она имеет следующий прототип:

void _harderr(void (_far *handler)());

Параметр handler - указатель на новую функцию обработки критических ошибок.

Функции _hardresume() и _hardretn() должны быть использованы в обработчике критичеких ошибок, установленном функцией _harderr().

Функция _hardresume() возвращает управление операционной системе, она имеет прототип:

_hardresume(int result);

Парметр result может иметь следующие значения (в соответствии с необходимыми действиями):

_HARDERR_ABORT аварийно завершить программу;
_HARDERR_FAIL вернуть код ошибки;
_HARDERR_IGNORE игнорировать ошибку;
_HARDERR_RETRY повторить операцию.

Эти параметры описаны в файле dos.h.

Функция _hardretn() возвращает управление непосредственно программе, передавая ей код ошибки, определяемый параметром функции error:

void _hardretn(int error);

При этом программа получает после возврата из вызванной ей функции DOS код ошибки error. Если ошибка произошла при выполнении функции с номером, большим чем 38h, дополнительно устанавливается в 1 флаг переноса. Если номер функции был меньше указанного значения, в регистр AL записывается величина FFh.

Функция обработки критических ошибок handler имеет следующие параметры:

void _far handler(unsigned deverror, unsigned errcode,
                           unsigned _far *devhdr);

Первый параметр - код ошибки устройства. Он равен содержимому регистра AX при вызове обработчика прерывания INT 24h. Аналогично, параметр errcode соответствует содержимому регистра DI - код ошибки. Третий параметр - devhdr - это указатель на заголовок драйвера устройства (передаваемый в регистрах BP:SI).

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

// Эту программу можно запускать только из командной
// строки. При запуске из интегрированной среды QC
// или PWB возможен конфликт с используемым в этих
// средах обработчиком критических ошибок.


#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <direct.h>
#include <string.h>
#include <dos.h>
#include <bios.h>

void main(void);
void _far hhandler(unsigned deverr,
                                 unsigned doserr, unsigned _far *hdr);
void _bios_str(char *p);

void main() {

// Устанавливаем обработчик критических ошибок

         _harderr(hhandler);

// Моделируем критическую ошибку. Выполняем попытку создать
// каталог на диске А:. Если мы "забудем" вставить
// в дисковод дискету, будет вызван обработчик
// критической ошибки

         printf("\nВставьте (или не вставляйте) дискету в дисковод A:"
                   "\nи нажмите любую клавишу..."
                   "\n");

         getch();

// Создаем каталог

         if(mkdir("a:\test_ctl")) {

                  printf("\nОшибка при создании каталога" );
                  exit(-1);

         }
         else {

                  printf("\nУспешное создание каталога");

// Удаляем только что созданный каталог

                  rmdir("a:test_ctl");
                  exit(0);

         }
}


// Новый обработчик критических ошибок

void _far hhandler(unsigned deverr,
                                 unsigned doserr, unsigned _far *hdr) {

         int ch;
         static char buf[200], tmpbuf[10];

// Выводим сообщение о критической ошибке

         sprintf(buf,"\n\r"
                                 "\n\rКод ошибки устройтсва: %04.4X"
                                 "\n\rКод ошибки DOS:        %d"
                                 "\n\r\n\r"
                                 "\n\rВыполняемые действия:"
                                 "\n\r  0 - повторить"
                                 "\n\r  1 - отменить"
                                 "\n\r  2 - завершить"
                                 "\n\r----> ?",
                                 deverr,
                                 doserr);

         _bios_str(buf);

// Вводим ответ с клавиатуры

         ch = _bios_keybrd(_KEYBRD_READ) & 0x00ff;
         _bios_str("\n\r");

         switch(ch) {

                  case '0':       // Пытаемся повторить операцию
                  default:

                                _hardresume(_HARDERR_RETRY);

                  case '2':       // Завершаем работу программы

                                _hardresume(_HARDERR_ABORT);

                  case '1':       // Возврат в DOS с кодом ошибки

                                _hardretn(doserr);

         }
}

// Программа для вывода строки символов на экран
// с помощью функции BIOS 0Eh

void _bios_str(char *ptr) {

         union REGS inregs, outregs;
         char *start = ptr;

         inregs.h.ah = 0x0e;
         for(; *ptr; ptr++) {

                  inregs.h.al = *ptr;
                  int86(0x10, &inregs, &outregs);

         }
}