Обработка критических ошибок на Ассемблере
Обработка критических ошибок на Ассемблере
Как гласит один из законов Мерфи для программистов, в каждой программе есть ошибки. Какими бы суровыми ни были тесты, с их помощью невозможно доказать полное отсутствие ошибок. Всегда остается вероятность возникновения ситуации, не охваченной тестовой средой. В таких случаях для разработчиков и тестеров важно получить подробную информацию об ошибке, чтобы попытаться воспроизвести ее и устранить. Делается это различными способами, в том числе и при помощи обработчиков критических ошибок.
Есть разные подходы к обработке ошибок: подавление или игнорирование, корректировка "на лету" по возможности, фатальное завершение работы. Лично я придерживаюсь мнения, что при возникновении любой ошибки, даже самой незначительной, приложение должно сохранять информацию об этом куда-нибудь в лог, сообщать пользователю и завершать работу. Естественно, разработчик должен быть немедленно уведомлен о произошедшем, ему предоставляется сохраненный лог с подробнейшим описанием ситуации, приведшей к падению приложения. После этого в код добавляются все необходимые проверки, исключающие причину и повторное появление ошибки.
Одним из методов перехвата возникших ошибок является SEH (Structured Exception Handling). При возникновении критической ситуации, например, обращении к памяти по неправильному адресу, переполнении стека, делении на ноль и тому подобное будет задействована цепочка обработчиков SEH. Каждый из обработчиков поочередно может выполнить нужные действия и прервать дальнейшую обработку или проигнорировать ошибку и передать ее следующему обработчику. Для каждого потока в процессе должны быть назначены свои обработчики SEH.
Есть вариант внедрения своего обработчика напрямую в цепочку SEH, но мне такой вариант не нравится, он больше подходит для какой-нибудь антиотладки или запутывания алгоритма работы. Для нормальных приложений, на мой взгляд, больше подходит штатная функция SetUnhandledExceptionFilter. Но для начала придется самостоятельно описать недостающие структуры для FASM.
Code (Assembler) : Убрать нумерацию
- EXCEPTION_MAXIMUM_PARAMETERS = 15
- SIZE_OF_80387_REGISTERS = 80
- MAXIMUM_SUPPORTED_EXTENSION = 512
- EXCEPTION_CONTINUE_EXECUTION = -1
- struct FLOATING_SAVE_AREA
- ControlWord dd ?
- StatusWord dd ?
- TagWord dd ?
- ErrorOffset dd ?
- ErrorSelector dd ?
- DataOffset dd ?
- DataSelector dd ?
- RegisterArea rb SIZE_OF_80387_REGISTERS
- Cr0NpxState dd ?
- ends
- struct CONTEXT
- ContextFlags dd ?
- iDr0 dd ?
- iDr1 dd ?
- iDr2 dd ?
- iDr3 dd ?
- iDr6 dd ?
- iDr7 dd ?
- FloatSave FLOATING_SAVE_AREA
- regGs dd ?
- regFs dd ?
- regEs dd ?
- regDs dd ?
- regEdi dd ?
- regEsi dd ?
- regEbx dd ?
- regEdx dd ?
- regEcx dd ?
- regEax dd ?
- regEbp dd ?
- regEip dd ?
- regCs dd ?
- regFlag dd ?
- regEsp dd ?
- regSs dd ?
- ExtendedRegisters rb MAXIMUM_SUPPORTED_EXTENSION
- ends
- struct EXCEPTION_RECORD
- ExceptionCode dd ?
- ExceptionFlags dd ?
- pExceptionRecord dd ?
- ExceptionAddress dd ?
- NumberParameters dd ?
- ExceptionInformation rd EXCEPTION_MAXIMUM_PARAMETERS
- ends
- struct EXCEPTION_POINTERS
- pExceptionRecord dd ?
- pContextRecord dd ?
- ends
Code (Assembler) : Убрать нумерацию
- ; Установить обработчик ошибок
- invoke SetUnhandledExceptionFilter,ExceptionFilter
Code (Assembler) : Убрать нумерацию
- ; Добавить наш обработчик в цепочку
- push ExceptionFilter
- push dword [fs:0]
- mov [fs:0],esp
- ; Критичный участок кода
- ...
- ...
- ; Убрать наш обработчик
- pop dword[fs:0]
- add esp, 4
Code (Assembler) : Убрать нумерацию
- ;--------------------------------------------------
- ; Обработчик критических ошибок
- ;--------------------------------------------------
- proc ExceptionFilter lpExcept:DWORD
- locals
- szFile rb MAX_PATH
- szBuffer rb 500h
- endl
- mov eax,[lpExcept]
- ; EDI -> CONTEXT
- mov edi,[eax+EXCEPTION_POINTERS.pContextRecord]
- push [edi+CONTEXT.regEdi]
- push [edi+CONTEXT.regEsi]
- push [edi+CONTEXT.regEbp]
- push [edi+CONTEXT.regEsp]
- push [edi+CONTEXT.regEdx]
- push [edi+CONTEXT.regEcx]
- push [edi+CONTEXT.regEbx]
- push [edi+CONTEXT.regEax]
- ; EDI -> EXCEPTION_RECORD
- mov edi,[eax+EXCEPTION_POINTERS.pExceptionRecord]
- ; ReadWrite
- mov eax,[edi+EXCEPTION_RECORD.ExceptionInformation]
- cmp eax,2
- jb @f
- mov eax,2
- @@:
- push [.szOperation+eax*4]
- ; NumberParameters
- push [edi+EXCEPTION_RECORD.NumberParameters]
- ; Continuable
- mov eax,[edi+EXCEPTION_RECORD.ExceptionFlags]
- push [.szLogical+eax*4]
- ; Type
- mov esi,.szType
- @@:
- lodsd
- or eax,eax
- jz @f
- cmp eax,[edi+EXCEPTION_RECORD.ExceptionCode]
- je @f
- lodsd
- jmp @b
- @@:
- push dword [esi]
- ; ExceptionCode
- push [edi+EXCEPTION_RECORD.ExceptionCode]
- ; ExceptionAddress
- push [edi+EXCEPTION_RECORD.ExceptionAddress]
- ; Сформировать текст исключения
- lea ebx,[szBuffer]
- invoke wsprintf,ebx,.szMask
- add esp,40h
- ; Сформировать имя файла для логирования ошибок
- lea ebx,[szFile]
- invoke GetModuleHandle,NULL
- invoke GetModuleFileName,eax,ebx,MAX_PATH
- invoke lstrcat,ebx,.szTail
- ; Попытаться создать файл
- invoke CreateFile,ebx,GENERIC_WRITE,FILE_SHARE_READ,\
- NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL
- cmp eax,-1
- je @f
- mov ebx,eax
- ; Дописать текст в конец файла лога
- invoke SetFilePointer,ebx,0,0,FILE_END
- ; Указатель на сформированный текст исключения
- lea esi,[szBuffer]
- invoke lstrlen,esi
- invoke _lwrite,ebx,esi,eax
- invoke CloseHandle,ebx
- @@:
- ; Сообщение пользователю о возникновении ошибки
- invoke MessageBox,0,esi,.szTitle,\
- MB_OK+MB_ICONHAND+MB_APPLMODAL+MB_TOPMOST
- invoke ExitProcess,0
- .szTail db '_errors.log',0
- .szLogical dd .szFalse,.szTrue
- .szFalse db 'false',0
- .szTrue db 'true',0
- .szOperation dd .szRead,.szWrite,.szOther
- .szRead db 'read',0
- .szWrite db 'write',0
- .szOther db 'other',0
- .szType dd 0xC0000005,.stat1
- dd 0xC000008C,.stat2
- dd 0x80000003,.stat3
- dd 0x80000002,.stat4
- dd 0xC000008D,.stat5
- dd 0xC000008E,.stat6
- dd 0xC000008F,.stat7
- dd 0xC0000090,.stat8
- dd 0xC0000091,.stat9
- dd 0xC0000092,.stat10
- dd 0xC0000093,.stat11
- dd 0x80000001,.stat12
- dd 0xC000001D,.stat13
- dd 0xC0000006,.stat14
- dd 0xC0000094,.stat15
- dd 0xC0000095,.stat16
- dd 0xC0000026,.stat17
- dd 0xC0000008,.stat18
- dd 0xC0000025,.stat19
- dd 0xC0000096,.stat20
- dd 0x80000004,.stat21
- dd 0xC00000FD,.stat22
- dd 0x80000029,.stat23
- dd 0,.sUnk
- .stat1 db 'ACCESS VIOLATION',0
- .stat2 db 'ARRAY BOUNDS EXCEEDED',0
- .stat3 db 'BREAKPOINT',0
- .stat4 db 'DATATYPE MISALIGNMENT',0
- .stat5 db 'FLOAT DENORMAL OPERAND',0
- .stat6 db 'FLOAT DIVIDE BY ZERO',0
- .stat7 db 'FLOAT INEXACT RESULT',0
- .stat8 db 'FLOAT INVALID OPERATION',0
- .stat9 db 'FLOAT OVERFLOW',0
- .stat10 db 'FLOAT STACK CHECK',0
- .stat11 db 'FLOAT UNDERFLOW',0
- .stat12 db 'GUARD PAGE VIOLATION',0
- .stat13 db 'ILLEGAL INSTRUCTION',0
- .stat14 db 'IN PAGE ERROR',0
- .stat15 db 'INTEGER DIVIDE BY ZERO',0
- .stat16 db 'INTEGER OVERFLOW',0
- .stat17 db 'INVALID DISPOSITION',0
- .stat18 db 'INVALID HANDLE',0
- .stat19 db 'NONCONTINUABLE EXCEPTION',0
- .stat20 db 'PRIVILEGED_INSTRUCTION',0
- .stat21 db 'SINGLE STEP',0
- .stat22 db 'STACK OVERFLOW',0
- .stat23 db 'UNWIND CONSOLIDATE',0
- .sUnk db 'UNKNOWN',0
- .szTitle db 'Critical error',0
- .szMask db 'Exception addr: %08Xh',13,10
- db 'Exception code: %08Xh = %s',13,10,13,10
- db 'Information:',13,10
- db 'Continuable = %s, NumberParameters = %u, ReadWrite = %s'
- db 13,10,13,10
- db 'Registers:',13,10
- db 'EAX=%08Xh, EBX=%08Xh, ECX=%08Xh, EDX=%08Xh',13,10
- db 'ESP=%08Xh, EBP=%08Xh, ESI=%08Xh, EDI=%08Xh',13,10,13,10
- db 0
- endp
Code (Assembler) : Убрать нумерацию
- ;--------------------------------------------------
- ; Обработчик критических ошибок (UNICODE)
- ;--------------------------------------------------
- proc ExceptionFilter lpExcept:DWORD
- locals
- szFile rw MAX_PATH
- szBuffer rw 500h
- endl
- mov eax,[lpExcept]
- ; EDI -> CONTEXT
- mov edi,[eax+EXCEPTION_POINTERS.pContextRecord]
- push [edi+CONTEXT.regEdi]
- push [edi+CONTEXT.regEsi]
- push [edi+CONTEXT.regEbp]
- push [edi+CONTEXT.regEsp]
- push [edi+CONTEXT.regEdx]
- push [edi+CONTEXT.regEcx]
- push [edi+CONTEXT.regEbx]
- push [edi+CONTEXT.regEax]
- ; EDI -> EXCEPTION_RECORD
- mov edi,[eax+EXCEPTION_POINTERS.pExceptionRecord]
- ; ReadWrite
- mov eax,[edi+EXCEPTION_RECORD.ExceptionInformation]
- cmp eax,2
- jb @f
- mov eax,2
- @@:
- push [.szOperation+eax*4]
- ; NumberParameters
- push [edi+EXCEPTION_RECORD.NumberParameters]
- ; Continuable
- mov eax,[edi+EXCEPTION_RECORD.ExceptionFlags]
- push [.szLogical+eax*4]
- ; Type
- mov esi,.szType
- @@:
- lodsd
- or eax,eax
- jz @f
- cmp eax,[edi+EXCEPTION_RECORD.ExceptionCode]
- je @f
- lodsd
- jmp @b
- @@:
- push dword [esi]
- ; ExceptionCode
- push [edi+EXCEPTION_RECORD.ExceptionCode]
- ; ExceptionAddress
- push [edi+EXCEPTION_RECORD.ExceptionAddress]
- ; Сформировать текст исключения
- lea ebx,[szBuffer]
- invoke wsprintf,ebx,.szMask
- add esp,40h
- ; Сформировать имя файла для логирования ошибок
- lea ebx,[szFile]
- invoke GetModuleHandle,NULL
- invoke GetModuleFileName,eax,ebx,MAX_PATH
- invoke lstrcat,ebx,.szTail
- ; Попытаться создать файл
- invoke CreateFile,ebx,GENERIC_WRITE,FILE_SHARE_READ,\
- NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL
- cmp eax,-1
- je @f
- mov ebx,eax
- ; Дописать текст в конец файла лога
- invoke SetFilePointer,ebx,0,0,FILE_END
- or eax,eax
- jnz .no_bom
- invoke _lwrite,ebx,.szBOM,2
- .no_bom:
- ; Указатель на сформированный текст исключения
- lea esi,[szBuffer]
- invoke lstrlen,esi
- shl eax,1
- invoke _lwrite,ebx,esi,eax
- invoke CloseHandle,ebx
- @@:
- ; Сообщение пользователю о возникновении ошибки
- invoke MessageBox,0,esi,.szTitle,\
- MB_OK+MB_ICONHAND+MB_APPLMODAL+MB_TOPMOST
- invoke ExitProcess,0
- .szTail du '_errors.log',0
- .szLogical dd .szFalse,.szTrue
- .szFalse du 'false',0
- .szTrue du 'true',0
- .szOperation dd .szRead,.szWrite,.szOther
- .szRead du 'read',0
- .szWrite du 'write',0
- .szOther du 'other',0
- .szBOM db 0xFF,0xFE
- .szType dd 0xC0000005,.stat1
- dd 0xC000008C,.stat2
- dd 0x80000003,.stat3
- dd 0x80000002,.stat4
- dd 0xC000008D,.stat5
- dd 0xC000008E,.stat6
- dd 0xC000008F,.stat7
- dd 0xC0000090,.stat8
- dd 0xC0000091,.stat9
- dd 0xC0000092,.stat10
- dd 0xC0000093,.stat11
- dd 0x80000001,.stat12
- dd 0xC000001D,.stat13
- dd 0xC0000006,.stat14
- dd 0xC0000094,.stat15
- dd 0xC0000095,.stat16
- dd 0xC0000026,.stat17
- dd 0xC0000008,.stat18
- dd 0xC0000025,.stat19
- dd 0xC0000096,.stat20
- dd 0x80000004,.stat21
- dd 0xC00000FD,.stat22
- dd 0x80000029,.stat23
- dd 0,.sUnk
- .stat1 du 'ACCESS VIOLATION',0
- .stat2 du 'ARRAY BOUNDS EXCEEDED',0
- .stat3 du 'BREAKPOINT',0
- .stat4 du 'DATATYPE MISALIGNMENT',0
- .stat5 du 'FLOAT DENORMAL OPERAND',0
- .stat6 du 'FLOAT DIVIDE BY ZERO',0
- .stat7 du 'FLOAT INEXACT RESULT',0
- .stat8 du 'FLOAT INVALID OPERATION',0
- .stat9 du 'FLOAT OVERFLOW',0
- .stat10 du 'FLOAT STACK CHECK',0
- .stat11 du 'FLOAT UNDERFLOW',0
- .stat12 du 'GUARD PAGE VIOLATION',0
- .stat13 du 'ILLEGAL INSTRUCTION',0
- .stat14 du 'IN PAGE ERROR',0
- .stat15 du 'INTEGER DIVIDE BY ZERO',0
- .stat16 du 'INTEGER OVERFLOW',0
- .stat17 du 'INVALID DISPOSITION',0
- .stat18 du 'INVALID HANDLE',0
- .stat19 du 'NONCONTINUABLE EXCEPTION',0
- .stat20 du 'PRIVILEGED_INSTRUCTION',0
- .stat21 du 'SINGLE STEP',0
- .stat22 du 'STACK OVERFLOW',0
- .stat23 du 'UNWIND CONSOLIDATE',0
- .sUnk du 'UNKNOWN',0
- .szTitle du 'Critical error',0
- .szMask du 'Exception addr: %08Xh',13,10
- du 'Exception code: %08Xh = %s',13,10,13,10
- du 'Information:',13,10
- du 'Continuable = %s, NumberParameters = %u, ReadWrite = %s'
- du 13,10,13,10
- du 'Registers:',13,10
- du 'EAX=%08Xh, EBX=%08Xh, ECX=%08Xh, EDX=%08Xh',13,10
- du 'ESP=%08Xh, EBP=%08Xh, ESI=%08Xh, EDI=%08Xh',13,10,13,10
- du 0
- endp
Пример перехвата ошибки
В приложении пример программы с исходным текстом, в которой и есть возможность вызвать несколько видов ошибок и реализуется их перехват при помощи обработчика из статьи.
Просмотров: 5215 | Комментариев: 6
Метки: Assembler
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(24.08.2021 в 15:13):
Добавил юникодный обработчик
ManHunter
(17.08.2021 в 12:53):
Весь VEH сводится к invoke AddVectoredExceptionHandler,1,ExceptionFilter
Остальной код остается без изменений
Остальной код остается без изменений
Petya
(10.12.2020 в 16:14):
A VEH не предвидится?
ManHunter
(09.07.2017 в 20:57):
Разобрался. Чтобы работало под x64, надо устанавливать обработчик непосредственно в начале критических мест программы и только через встраивание в цепочку seh. Один глобальный перехватчик для всего процесса, как было для x86, не прокатит. Примеры обновил, в качестве бонуса добавил пример установки обработчика через встраивание в цепочку seh.
pawel97, спасибо!
pawel97, спасибо!
ManHunter
(08.07.2017 в 23:18):
На x86 все работает. На x64 да, не срабатывает. Обязательно изучу вопрос, тогда допишу статью.
Вот, кстати, официоз: https://support.microsoft.com/...n-a-64-bit-v
Вот, кстати, официоз: https://support.microsoft.com/...n-a-64-bit-v
pawel97
(08.07.2017 в 22:35):
Что-то не работает демо пример. В оле поставил бряк на ExceptionFilter, жму кнопки - а оно не срабатывает. Пора переставлять винду? :)
Добавить комментарий
Заполните форму для добавления комментария