Blog. Just Blog

Обработка критических ошибок на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Обработка критических ошибок на Ассемблере
Обработка критических ошибок на Ассемблере

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

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

Одним из методов перехвата возникших ошибок является SEH (Structured Exception Handling). При возникновении критической ситуации, например, обращении к памяти по неправильному адресу, переполнении стека, делении на ноль и тому подобное будет задействована цепочка обработчиков SEH. Каждый из обработчиков поочередно может выполнить нужные действия и прервать дальнейшую обработку или проигнорировать ошибку и передать ее следующему обработчику. Для каждого потока в процессе должны быть назначены свои обработчики SEH.

Есть вариант внедрения своего обработчика напрямую в цепочку SEH, но мне такой вариант не нравится, он больше подходит для какой-нибудь антиотладки или запутывания алгоритма работы. Для нормальных приложений, на мой взгляд, больше подходит штатная функция SetUnhandledExceptionFilter. Но для начала придется самостоятельно описать недостающие структуры для FASM.
  1. EXCEPTION_MAXIMUM_PARAMETERS = 15
  2. SIZE_OF_80387_REGISTERS      = 80
  3. MAXIMUM_SUPPORTED_EXTENSION  = 512
  4. EXCEPTION_CONTINUE_EXECUTION = -1
  5.  
  6. struct FLOATING_SAVE_AREA
  7.   ControlWord          dd ?
  8.   StatusWord           dd ?
  9.   TagWord              dd ?
  10.   ErrorOffset          dd ?
  11.   ErrorSelector        dd ?
  12.   DataOffset           dd ?
  13.   DataSelector         dd ?
  14.   RegisterArea         rb SIZE_OF_80387_REGISTERS
  15.   Cr0NpxState          dd ?
  16. ends
  17.  
  18. struct CONTEXT
  19.   ContextFlags         dd ?
  20.   iDr0                 dd ?
  21.   iDr1                 dd ?
  22.   iDr2                 dd ?
  23.   iDr3                 dd ?
  24.   iDr6                 dd ?
  25.   iDr7                 dd ?
  26.   FloatSave            FLOATING_SAVE_AREA
  27.   regGs                dd ?
  28.   regFs                dd ?
  29.   regEs                dd ?
  30.   regDs                dd ?
  31.   regEdi               dd ?
  32.   regEsi               dd ?
  33.   regEbx               dd ?
  34.   regEdx               dd ?
  35.   regEcx               dd ?
  36.   regEax               dd ?
  37.   regEbp               dd ?
  38.   regEip               dd ?
  39.   regCs                dd ?
  40.   regFlag              dd ?
  41.   regEsp               dd ?
  42.   regSs                dd ?
  43.   ExtendedRegisters    rb MAXIMUM_SUPPORTED_EXTENSION
  44. ends
  45.  
  46. struct EXCEPTION_RECORD
  47.   ExceptionCode        dd ?
  48.   ExceptionFlags       dd ?
  49.   pExceptionRecord     dd ?
  50.   ExceptionAddress     dd ?
  51.   NumberParameters     dd ?
  52.   ExceptionInformation rd EXCEPTION_MAXIMUM_PARAMETERS
  53. ends
  54.  
  55. struct EXCEPTION_POINTERS
  56.   pExceptionRecord     dd ?
  57.   pContextRecord       dd ?
  58. ends
Установка обработчика ошибок делается в самом начале критических секций приложения. Ведь чем раньше он будет установлен, тем больше кода сможет охватить.
  1. ; Установить обработчик ошибок
  2. invoke  SetUnhandledExceptionFilter,ExceptionFilter
Нравится, не нравится, а, как показала практика, придется рассказать и про встраивание обработчика напрямую в цепочку. Потому что обработать исключения в 32-битной программе, запущенной под 64-битной системой, получится только так. Единый глобальный обработчик в этом случае не поможет. Реализуется встраивание следующим образом:
  1.         ; Добавить наш обработчик в цепочку
  2.         push    ExceptionFilter
  3.         push    dword [fs:0]
  4.         mov     [fs:0],esp
  5.  
  6.         ; Критичный участок кода
  7.         ...
  8.         ...
  9.  
  10.         ; Убрать наш обработчик
  11.         pop     dword[fs:0]
  12.         add     esp, 4
Теперь сам обработчик ошибок. Я постарался сделать его максимально независимым от приложения, вы можете даже описания структур внести внутрь процедуры, чтобы было удобнее подключать его в различные проекты. Ошибки записываются в файл, соответствующий имени исполняемого файла с суффиксом "_errors.log", если такой файл уже есть, то новые сообщения дописываются в конец. После записи пользователю выводится уведомление о произошедшей ошибке и приложение завершает работу.
  1. ;--------------------------------------------------
  2. ; Обработчик критических ошибок
  3. ;--------------------------------------------------
  4. proc  ExceptionFilter lpExcept:DWORD
  5.         locals
  6.             szFile   rb MAX_PATH
  7.             szBuffer rb 500h
  8.         endl
  9.  
  10.         mov     eax,[lpExcept]
  11.  
  12.         ; EDI -> CONTEXT
  13.         mov     edi,[eax+EXCEPTION_POINTERS.pContextRecord]
  14.         push    [edi+CONTEXT.regEdi]
  15.         push    [edi+CONTEXT.regEsi]
  16.         push    [edi+CONTEXT.regEbp]
  17.         push    [edi+CONTEXT.regEsp]
  18.         push    [edi+CONTEXT.regEdx]
  19.         push    [edi+CONTEXT.regEcx]
  20.         push    [edi+CONTEXT.regEbx]
  21.         push    [edi+CONTEXT.regEax]
  22.  
  23.         ; EDI -> EXCEPTION_RECORD
  24.         mov     edi,[eax+EXCEPTION_POINTERS.pExceptionRecord]
  25.  
  26.         ; ReadWrite
  27.         mov     eax,[edi+EXCEPTION_RECORD.ExceptionInformation]
  28.         cmp     eax,2
  29.         jb      @f
  30.         mov     eax,2
  31. @@:
  32.         push    [.szOperation+eax*4]
  33.  
  34.         ; NumberParameters
  35.         push    [edi+EXCEPTION_RECORD.NumberParameters]
  36.  
  37.         ; Continuable
  38.         mov     eax,[edi+EXCEPTION_RECORD.ExceptionFlags]
  39.         push    [.szLogical+eax*4]
  40.  
  41.         ; Type
  42.         mov     esi,.szType
  43. @@:
  44.         lodsd
  45.         or      eax,eax
  46.         jz      @f
  47.         cmp     eax,[edi+EXCEPTION_RECORD.ExceptionCode]
  48.         je      @f
  49.         lodsd
  50.         jmp     @b
  51. @@:
  52.         push    dword [esi]
  53.  
  54.         ; ExceptionCode
  55.         push    [edi+EXCEPTION_RECORD.ExceptionCode]
  56.  
  57.         ; ExceptionAddress
  58.         push    [edi+EXCEPTION_RECORD.ExceptionAddress]
  59.  
  60.         ; Сформировать текст исключения
  61.         lea     ebx,[szBuffer]
  62.         invoke  wsprintf,ebx,.szMask
  63.         add     esp,40h
  64.  
  65.         ; Сформировать имя файла для логирования ошибок
  66.         lea     ebx,[szFile]
  67.         invoke  GetModuleHandle,NULL
  68.         invoke  GetModuleFileName,eax,ebx,MAX_PATH
  69.         invoke  lstrcat,ebx,.szTail
  70.  
  71.         ; Попытаться создать файл
  72.         invoke  CreateFile,ebx,GENERIC_WRITE,FILE_SHARE_READ,\
  73.                 NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL
  74.         cmp     eax,-1
  75.         je      @f
  76.         mov     ebx,eax
  77.  
  78.         ; Дописать текст в конец файла лога
  79.         invoke  SetFilePointer,ebx,0,0,FILE_END
  80.         ; Указатель на сформированный текст исключения
  81.         lea     esi,[szBuffer]
  82.         invoke  lstrlen,esi
  83.         invoke  _lwrite,ebx,esi,eax
  84.         invoke  CloseHandle,ebx
  85. @@:
  86.         ; Сообщение пользователю о возникновении ошибки
  87.         invoke  MessageBox,0,esi,.szTitle,\
  88.                 MB_OK+MB_ICONHAND+MB_APPLMODAL+MB_TOPMOST
  89.  
  90.         invoke  ExitProcess,0
  91.  
  92. .szTail      db '_errors.log',0
  93.  
  94. .szLogical   dd .szFalse,.szTrue
  95. .szFalse     db 'false',0
  96. .szTrue      db 'true',0
  97.  
  98. .szOperation dd .szRead,.szWrite,.szOther
  99. .szRead      db 'read',0
  100. .szWrite     db 'write',0
  101. .szOther     db 'other',0
  102.  
  103. .szType      dd 0xC0000005,.stat1
  104.              dd 0xC000008C,.stat2
  105.              dd 0x80000003,.stat3
  106.              dd 0x80000002,.stat4
  107.              dd 0xC000008D,.stat5
  108.              dd 0xC000008E,.stat6
  109.              dd 0xC000008F,.stat7
  110.              dd 0xC0000090,.stat8
  111.              dd 0xC0000091,.stat9
  112.              dd 0xC0000092,.stat10
  113.              dd 0xC0000093,.stat11
  114.              dd 0x80000001,.stat12
  115.              dd 0xC000001D,.stat13
  116.              dd 0xC0000006,.stat14
  117.              dd 0xC0000094,.stat15
  118.              dd 0xC0000095,.stat16
  119.              dd 0xC0000026,.stat17
  120.              dd 0xC0000008,.stat18
  121.              dd 0xC0000025,.stat19
  122.              dd 0xC0000096,.stat20
  123.              dd 0x80000004,.stat21
  124.              dd 0xC00000FD,.stat22
  125.              dd 0x80000029,.stat23
  126.              dd 0,.sUnk
  127.  
  128. .stat1       db 'ACCESS VIOLATION',0
  129. .stat2       db 'ARRAY BOUNDS EXCEEDED',0
  130. .stat3       db 'BREAKPOINT',0
  131. .stat4       db 'DATATYPE MISALIGNMENT',0
  132. .stat5       db 'FLOAT DENORMAL OPERAND',0
  133. .stat6       db 'FLOAT DIVIDE BY ZERO',0
  134. .stat7       db 'FLOAT INEXACT RESULT',0
  135. .stat8       db 'FLOAT INVALID OPERATION',0
  136. .stat9       db 'FLOAT OVERFLOW',0
  137. .stat10      db 'FLOAT STACK CHECK',0
  138. .stat11      db 'FLOAT UNDERFLOW',0
  139. .stat12      db 'GUARD PAGE VIOLATION',0
  140. .stat13      db 'ILLEGAL INSTRUCTION',0
  141. .stat14      db 'IN PAGE ERROR',0
  142. .stat15      db 'INTEGER DIVIDE BY ZERO',0
  143. .stat16      db 'INTEGER OVERFLOW',0
  144. .stat17      db 'INVALID DISPOSITION',0
  145. .stat18      db 'INVALID HANDLE',0
  146. .stat19      db 'NONCONTINUABLE EXCEPTION',0
  147. .stat20      db 'PRIVILEGED_INSTRUCTION',0
  148. .stat21      db 'SINGLE STEP',0
  149. .stat22      db 'STACK OVERFLOW',0
  150. .stat23      db 'UNWIND CONSOLIDATE',0
  151. .sUnk        db 'UNKNOWN',0
  152.  
  153. .szTitle     db 'Critical error',0
  154. .szMask      db 'Exception addr: %08Xh',13,10
  155.              db 'Exception code: %08Xh = %s',13,10,13,10
  156.              db 'Information:',13,10
  157.              db 'Continuable = %s, NumberParameters = %u, ReadWrite = %s'
  158.              db 13,10,13,10
  159.              db 'Registers:',13,10
  160.              db 'EAX=%08Xh, EBX=%08Xh, ECX=%08Xh, EDX=%08Xh',13,10
  161.              db 'ESP=%08Xh, EBP=%08Xh, ESI=%08Xh, EDI=%08Xh',13,10,13,10
  162.              db 0
  163. endp
Вариант обработчика для юникодных программ. Не сильно, но отличается.
  1. ;--------------------------------------------------
  2. ; Обработчик критических ошибок (UNICODE)
  3. ;--------------------------------------------------
  4. proc  ExceptionFilter lpExcept:DWORD
  5.         locals
  6.             szFile   rw MAX_PATH
  7.             szBuffer rw 500h
  8.         endl
  9.  
  10.         mov     eax,[lpExcept]
  11.  
  12.         ; EDI -> CONTEXT
  13.         mov     edi,[eax+EXCEPTION_POINTERS.pContextRecord]
  14.         push    [edi+CONTEXT.regEdi]
  15.         push    [edi+CONTEXT.regEsi]
  16.         push    [edi+CONTEXT.regEbp]
  17.         push    [edi+CONTEXT.regEsp]
  18.         push    [edi+CONTEXT.regEdx]
  19.         push    [edi+CONTEXT.regEcx]
  20.         push    [edi+CONTEXT.regEbx]
  21.         push    [edi+CONTEXT.regEax]
  22.  
  23.         ; EDI -> EXCEPTION_RECORD
  24.         mov     edi,[eax+EXCEPTION_POINTERS.pExceptionRecord]
  25.  
  26.         ; ReadWrite
  27.         mov     eax,[edi+EXCEPTION_RECORD.ExceptionInformation]
  28.         cmp     eax,2
  29.         jb      @f
  30.         mov     eax,2
  31. @@:
  32.         push    [.szOperation+eax*4]
  33.  
  34.         ; NumberParameters
  35.         push    [edi+EXCEPTION_RECORD.NumberParameters]
  36.  
  37.         ; Continuable
  38.         mov     eax,[edi+EXCEPTION_RECORD.ExceptionFlags]
  39.         push    [.szLogical+eax*4]
  40.  
  41.         ; Type
  42.         mov     esi,.szType
  43. @@:
  44.         lodsd
  45.         or      eax,eax
  46.         jz      @f
  47.         cmp     eax,[edi+EXCEPTION_RECORD.ExceptionCode]
  48.         je      @f
  49.         lodsd
  50.         jmp     @b
  51. @@:
  52.         push    dword [esi]
  53.  
  54.         ; ExceptionCode
  55.         push    [edi+EXCEPTION_RECORD.ExceptionCode]
  56.  
  57.         ; ExceptionAddress
  58.         push    [edi+EXCEPTION_RECORD.ExceptionAddress]
  59.  
  60.         ; Сформировать текст исключения
  61.         lea     ebx,[szBuffer]
  62.         invoke  wsprintf,ebx,.szMask
  63.         add     esp,40h
  64.  
  65.         ; Сформировать имя файла для логирования ошибок
  66.         lea     ebx,[szFile]
  67.         invoke  GetModuleHandle,NULL
  68.         invoke  GetModuleFileName,eax,ebx,MAX_PATH
  69.         invoke  lstrcat,ebx,.szTail
  70.  
  71.         ; Попытаться создать файл
  72.         invoke  CreateFile,ebx,GENERIC_WRITE,FILE_SHARE_READ,\
  73.                 NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL
  74.         cmp     eax,-1
  75.         je      @f
  76.         mov     ebx,eax
  77.  
  78.         ; Дописать текст в конец файла лога
  79.         invoke  SetFilePointer,ebx,0,0,FILE_END
  80.         or      eax,eax
  81.         jnz     .no_bom
  82.         invoke  _lwrite,ebx,.szBOM,2
  83. .no_bom:
  84.         ; Указатель на сформированный текст исключения
  85.         lea     esi,[szBuffer]
  86.         invoke  lstrlen,esi
  87.         shl     eax,1
  88.         invoke  _lwrite,ebx,esi,eax
  89.         invoke  CloseHandle,ebx
  90. @@:
  91.         ; Сообщение пользователю о возникновении ошибки
  92.         invoke  MessageBox,0,esi,.szTitle,\
  93.                 MB_OK+MB_ICONHAND+MB_APPLMODAL+MB_TOPMOST
  94.  
  95.         invoke  ExitProcess,0
  96.  
  97. .szTail      du '_errors.log',0
  98.  
  99. .szLogical   dd .szFalse,.szTrue
  100. .szFalse     du 'false',0
  101. .szTrue      du 'true',0
  102.  
  103. .szOperation dd .szRead,.szWrite,.szOther
  104. .szRead      du 'read',0
  105. .szWrite     du 'write',0
  106. .szOther     du 'other',0
  107.  
  108. .szBOM       db 0xFF,0xFE
  109.  
  110. .szType      dd 0xC0000005,.stat1
  111.              dd 0xC000008C,.stat2
  112.              dd 0x80000003,.stat3
  113.              dd 0x80000002,.stat4
  114.              dd 0xC000008D,.stat5
  115.              dd 0xC000008E,.stat6
  116.              dd 0xC000008F,.stat7
  117.              dd 0xC0000090,.stat8
  118.              dd 0xC0000091,.stat9
  119.              dd 0xC0000092,.stat10
  120.              dd 0xC0000093,.stat11
  121.              dd 0x80000001,.stat12
  122.              dd 0xC000001D,.stat13
  123.              dd 0xC0000006,.stat14
  124.              dd 0xC0000094,.stat15
  125.              dd 0xC0000095,.stat16
  126.              dd 0xC0000026,.stat17
  127.              dd 0xC0000008,.stat18
  128.              dd 0xC0000025,.stat19
  129.              dd 0xC0000096,.stat20
  130.              dd 0x80000004,.stat21
  131.              dd 0xC00000FD,.stat22
  132.              dd 0x80000029,.stat23
  133.              dd 0,.sUnk
  134.  
  135. .stat1       du 'ACCESS VIOLATION',0
  136. .stat2       du 'ARRAY BOUNDS EXCEEDED',0
  137. .stat3       du 'BREAKPOINT',0
  138. .stat4       du 'DATATYPE MISALIGNMENT',0
  139. .stat5       du 'FLOAT DENORMAL OPERAND',0
  140. .stat6       du 'FLOAT DIVIDE BY ZERO',0
  141. .stat7       du 'FLOAT INEXACT RESULT',0
  142. .stat8       du 'FLOAT INVALID OPERATION',0
  143. .stat9       du 'FLOAT OVERFLOW',0
  144. .stat10      du 'FLOAT STACK CHECK',0
  145. .stat11      du 'FLOAT UNDERFLOW',0
  146. .stat12      du 'GUARD PAGE VIOLATION',0
  147. .stat13      du 'ILLEGAL INSTRUCTION',0
  148. .stat14      du 'IN PAGE ERROR',0
  149. .stat15      du 'INTEGER DIVIDE BY ZERO',0
  150. .stat16      du 'INTEGER OVERFLOW',0
  151. .stat17      du 'INVALID DISPOSITION',0
  152. .stat18      du 'INVALID HANDLE',0
  153. .stat19      du 'NONCONTINUABLE EXCEPTION',0
  154. .stat20      du 'PRIVILEGED_INSTRUCTION',0
  155. .stat21      du 'SINGLE STEP',0
  156. .stat22      du 'STACK OVERFLOW',0
  157. .stat23      du 'UNWIND CONSOLIDATE',0
  158. .sUnk        du 'UNKNOWN',0
  159.  
  160. .szTitle     du 'Critical error',0
  161. .szMask      du 'Exception addr: %08Xh',13,10
  162.              du 'Exception code: %08Xh = %s',13,10,13,10
  163.              du 'Information:',13,10
  164.              du 'Continuable = %s, NumberParameters = %u, ReadWrite = %s'
  165.              du 13,10,13,10
  166.              du 'Registers:',13,10
  167.              du 'EAX=%08Xh, EBX=%08Xh, ECX=%08Xh, EDX=%08Xh',13,10
  168.              du 'ESP=%08Xh, EBP=%08Xh, ESI=%08Xh, EDI=%08Xh',13,10,13,10
  169.              du 0
  170. endp
Если нужно, вы можете подкорректировать обработчик по своему усмотрению. Например, вместо принудительного завершения работы попробовать сохранить какие-нибудь данные, или расширить информацию для записи в лог, или еще что-то, все зависит от задачи.

Пример перехвата ошибки
Пример перехвата ошибки

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

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

SEH.Demo.zip (9,335 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 5062 | Комментариев: 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, спасибо!
ManHunter (08.07.2017 в 23:18):
На x86 все работает. На x64 да, не срабатывает. Обязательно изучу вопрос, тогда допишу статью.
Вот, кстати, официоз: https://support.microsoft.com/...n-a-64-bit-v
pawel97 (08.07.2017 в 22:35):
Что-то не работает демо пример. В оле поставил бряк на ExceptionFilter, жму кнопки - а оно не срабатывает. Пора переставлять винду? :)

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.26 сек. / MySQL: 2 (0.0077 сек.) / Память: 4.5 Mb
Наверх