Ошибки в пользовательском режиме

Тема в разделе "WASM.WIN32", создана пользователем Clerk, 10 апр 2009.

  1. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    С учётом большого количества топиков, в которых происходит гадание на том где и почему приложение падает создал этот топик. Это имеет смысл для ошибок возникающих в юзермоде. Итак матчасть.
    Когда при исполнении инструкции процессор не может её исполнить, по причине того, что некоторые внутренние проверки не прошли, например сегмент не доступен для записи он прерывает исполнение программы. Это исключительная ситуация именуется исключением. Процессор выполняет переход в зависимости от причины исключения на обработчик, определённый в одном из шлюзов дескриптороной таблицы прерываний. При этом происходит смена кольца защиты на нулевое(релоад кодового селектора), перезагрузка стека из сегмента состояния задачи на ядерный, сохранение в ядерном стеке регистра флагов EFlags, адреса инструкции вызвавшей исключение, селектора стека и указателя на стек. На стороне ядра обработчики именуются KiTrapXX, соответственно коду исключения(имена можно увидеть тут http://sasm.narod.ru/docs/pm/pm_app/app_1/app_1.htm . Ядро выполняет некоторые действия по диспетчеризации исключения, в частности вызывает ядерный отладчик если он активен, подготавливает фрейм, который содержит описание исключения и передаёт управление обратно в юзермод.
    Для любого исключения поток возвращается в юзермод на одну точку, она экспотрируется модулем ntdll и носит имя KiUserExceptionDispatcher. Проще говоря при возникновении исключения поток начинает исполнять диспетчер исключений. Далее можно забыть про SEH и остальное.
    При входе в диспетчер исключений в стеке находится структура, которая полностью описывает где и почиму произошло исключение. Регистр Esp указывает на структуру:
    Код (Text):
    1. EXCEPTION_POINTERS struct
    2.     ExceptionRecord PEXCEPTION_RECORD ?
    3.     ContextRecord   PCONTEXT ?
    4. EXCEPTION_POINTERS ends
    Первое поле содержит указатель на описание исключения, это структура EXCEPTION_RECORD:
    Код (Text):
    1. EXCEPTION_RECORD struxt
    2.     ExceptionCode       NTSTATUS    ?   ; 0000h
    3.     ExceptionFlags      DWORD       ?   ; 0004h
    4.     ExceptionRecord     PVOID       ?   ; 0008h
    5.     ExceptionAddress    PVOID       ?   ; 000Ch
    6.     NumberParameters    DWORD       ?   ; 0010h
    7.     ExceptionInformation    DWORD       EXCEPTION_MAXIMUM_PARAMETERS dup(?) ; 0014h
    8. EXCEPTION_RECORD ends
    Второй параметр структуры EXCEPTION_POINTERS это ContextRecord - указатель на полный контекст потока(содержание всех его регистров) на момент возникновения исключения. Благодаря этой структуре поток может вернуться на инструкцию, которая вызвала исключение, полностью восстановив состояние процессора.
    Структура CONTEXT содержащая полное состояние процессора на момент возникновения исключения:
    Код (Text):
    1. CONTEXT struct
    2.     ContextFlags        DWORD       ?   ; 0000h
    3. ;CONTEXT_DEBUG_REGISTERS
    4.     regDr0          DWORD       ?   ; 0004h
    5.     regDr1          DWORD       ?   ; 0008h
    6.     regDr2          DWORD       ?   ; 000Ch
    7.     regDr3          DWORD       ?   ; 0010h
    8.     regDr6          DWORD       ?   ; 0014h
    9.     regDr7          DWORD       ?   ; 0018h
    10. ;CONTEXT_FLOATING_POINT
    11.     FloatSave       FLOATING_SAVE_AREA <>   ; 001Ch
    12. ;CONTEXT_SEGMENTS
    13.     regSegGs        DWORD       ?   ; 008Ch
    14.     regSegFs        DWORD       ?   ; 0090h
    15.     regSegEs        DWORD       ?   ; 0094h
    16.     regSegDs        DWORD       ?   ; 0098h
    17. ;CONTEXT_INTEGER
    18.     regEdi          DWORD       ?   ; 009Ch
    19.     regEsi          DWORD       ?   ; 00A0h
    20.     regEbx          DWORD       ?   ; 00A4h
    21.     regEdx          DWORD       ?   ; 00A8h
    22.     regEcx          DWORD       ?   ; 00ACh
    23.     regEax          DWORD       ?   ; 00B0h
    24. ;CONTEXT_CONTROL
    25.     regEbp          DWORD       ?   ; 00B4h
    26.     regEip          DWORD       ?   ; 00B8h
    27.     regSegCs        DWORD       ?   ; 00BCh
    28.     regEFlags       DWORD       ?   ; 00C0h
    29.     regEsp          DWORD       ?   ; 00C4h
    30.     regSegSs        DWORD       ?   ; 00C8h
    31. ;CONTEXT_EXTENDED_REGISTERS
    32.     ExtendedRegisters   BYTE        MAXIMUM_SUPPORTED_EXTENSION dup(?)  ; 00CCh
    33. CONTEXT ends
    Поле ContextFlags это набор флагов, определяющих какие группы регистров включены в эту структуру, соответственно константы CONTEXT_XX. При входе в диспетчер исключений сохраняется полный контекст потока, тоесть все поля валидны.
    Структура EXCEPTION_RECORD содержит причину исключения. Поле ExceptionCode содержит код исключения, эти коды определены в файле из сурцов винды ntstatus.h
    Поле ExceptionFlags содержит флажки, например может ли исключение быть обработано, обычно это поле можно игнорировать. ExceptionAddress содержит адрес инструкции, которая вызвала исключение. NumberParameters это число параметров следующих далее, они содержат более подробную информацию об исключении. Наиболее распространённое исключение - это исключение по доступу к памяти, имеет код STATUS_ACCESS_VIOLATION. Для этого исключения содержится дополнительная информация - адреc к которому произошло обращение.
    Имея всю эту информацию мы можем полностью идентифицировать исключение. Практически необходимо только два поля структуры EXCEPTION_RECORD - это адрес инструкции вызвавшей исключение и его код. Остальное можно извлечь из структуры контекст(все регистры там).
    Практический пример. На экспортируемую модулем ntdll процедуру KiUserExceptionDispatcher ставится точка останова. Когда возникает исключение произойдёт останов при входе в диспетчер исключений. Тут мы и выполняем анализ стекового фрейма. Допустим исполняется инструкция mov dword ptr ds:[0],eax
    Эта инструкция вызывает исключение и поток возвращается на диспетчер исключений:
    [​IMG]
    Отсюда мы находим инструкцию, которая вызвала исключение:
    [​IMG]
    Далее выполняется анализ кода, где находится эта инструкция. Определяется почему и как оказался не валидный адрес. Обычно удобно использовать стековые фреймы, который формируются при входе в процедуры для выделения памяти под локальные переменные. Используется тот факт, что при входе в диспетчер исключений не изменяется регистр Ebp, который эти фреймы и адресует. Тоесть мы смотрим что находится в стеке по адресу, на который указывает регистр Ebp - нормально там цепочка фреймов, где каждый элемент содержит адрес возврата из процедуры и указатель на следующий фрейм. Тоесть по адресу (Ebp + 4) находится указатель на адрес возврата из процедуры, в которой произошло исключение. Это позволяет быстро перейти к нужной процедуре.
    В большинстве случаев этого будет достаточно для определения исключения. В более сложных случаях необходимо рассмотреть текущий контекст потока при входе в диспетчер исключений, но не буду описывать это тут.
    Добавля что удобно использовать смещения от начала структур для определения необходимых регистров. Также в случае если селекторы отличны от нормальных, тоесть используется LDT необходимо учитывать базы сегментов.

    Довольно чато распространённая ситуация - при возникновении ошибки выводится окно с её описанием. Для идентификации причины возникновения данной ошибки, а она не всегда вызывается исключениями, следует выполнить следующее. Мессагу боксит сервис NtRaiseHardError, он вызывается ntdll так и kernel32, весьма часто при ошибках в загрузчике. Следует установить точку останова на данный сервис и рассмотреть цепочку фреймов, определив причину вызова данного сообщения.

    Надеюсь это краткое описание многим поможет.
     
  2. RET

    RET Well-Known Member

    Публикаций:
    17
    Регистрация:
    5 янв 2008
    Сообщения:
    789
    Адрес:
    Jabber: darksys@sj.ms
    Хорошая статья.
    Вот хидер (не мой но пользуюсь часто + кое что подправил) с двумя ф-циями для Си с реализацией вывода ошибки в виде строки (как это умеет Syser), а не в виде кода NTSTATUS. Может для отладки кому пригодится.http://depositfiles.com/files/mommloixs(в этом же архиве таблица NTSTATUS->DOS_ERROR->WINDOWS ERROR MESSAGE)
     
  3. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    RET
    Есть функция RtlNtStatusToDosErrorNoTeb. Для преобразования NTSTATUS кода ошибки в её имя и наоборот много либ есть, у Бартона в блоге вроде видел, да и сам писал когдато. Вот в аттаче(не пикод, но переделать минутное дело). Да и основные коды исключений следует знать, там их не много.
     
  4. RET

    RET Well-Known Member

    Публикаций:
    17
    Регистрация:
    5 янв 2008
    Сообщения:
    789
    Адрес:
    Jabber: darksys@sj.ms
    ULONG WINAPI RtlNtStatusToDosError(NTSTATUS) и
    ULONG WINAPI RtlNtStatusToDosErrorNoTeb(NTSTATUS) возвращают DOS-код, а не символьное имя...Кстати, а чем они отличаются друг от друга (я лично не знаю)?
    В твоем примере тоже используется выборка (см. NtStatusTable.inc и размер приложения). В нативе и ядре нет прямой функции выдающей символьное имя ошибки, но есть цепочка, позволяющая получить то что мы называем "сообщение об ошибке виндовс":
    RtlNtStatusToDosError -> RtlFindMessage -> RtlFormatMessage ...
     
  5. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    RET
    Я про DOS-код и говорил. Разница что в теб не пишет первая. Имя не нужно, я этим никогда не пользуюсь. Достаточно открыть ntstatus.h и найти описание ошибки, там и имя будет. Топик этот чтобы показать как выполнить анализ исключения и только.
     
  6. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    Clerk
    Интересная инфа. Может ещё напишешь как возобновить работу с меcта креша?
     
  7. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Booster
    Просто там всё. Восстановить состояние процессора, подкорректировать как нужно контекст в стеке и выполнить NtContinue, ну или отдать управление далее диспетчеру исключений, он сам состояние восстановит. Для того примера - можно селектор перезагрузить в Ds, вариантов множество, смотря какая ошибка и как её обработать нужно.
     
  8. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    Clerk
    Только я не совсем понимаю всё это. У меня когда отладчик ловит исключение, то он показывает инструкцию на которой произошёл креш, и в стеке нету никаких EXCEPTION_POINTERS.
     
  9. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Booster
    Дебуггер тут не при чём.
     
  10. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    Booster
    Если поставить точку останова на KiUserExceptionDispatcher, то можно ее поймать как раз в тот момент, когда она получает управление. Тогда можно увидеть структуру.
     
  11. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    Mika0x65
    Это всё конечно хорошо, но как это поможет мне в поиске ошибки?
     
  12. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    Booster
    Ну, для поиска ошибки обычно достаточно адреса падения. А после декомпилция (если написано не на ассемблере) и поиск причины.

    Вообще, я думал интерес больше академический :).
     
  13. Booster

    Booster New Member

    Публикаций:
    0
    Регистрация:
    26 ноя 2004
    Сообщения:
    4.860
    Mika0x65
    Не ну конечно интересно знать как всё устроено под капотом, но всё равно не понимаю при чём здесь поиск места падения, если отладчики это место прекрасно показывают без всяких плясок с бубном.
     
  14. Freeman

    Freeman New Member

    Публикаций:
    0
    Регистрация:
    10 фев 2005
    Сообщения:
    1.385
    Адрес:
    Ukraine
    допустим у тя есть прога, которую ты даешь пользователям. и вот вдруг у когото она начинает падать. ты ж не будешь пользователю говорить "установите отладчек, жмите ф9, посморите циферке с букавкаме". а в прогу ты вшил простенький код, который инфу об ошибке пишет в файл. пользователю тогда достаточно прислать тебе файл(ну или прога сама пришлет)
     
  15. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Booster
    Причём тут отладчик, в большинстве случаев останов на диспетчер исключений единственный вариант нормально исполнить анализ. Я всегда так исключения в юзермоде разбираю.
     
  16. RET

    RET Well-Known Member

    Публикаций:
    17
    Регистрация:
    5 янв 2008
    Сообщения:
    789
    Адрес:
    Jabber: darksys@sj.ms
    Без отладчика, при работе с сторонними приложениями не обойтись, но процесс можно НА НАЧАЛЬНОЙ стадии в прикладном режиме автоматизировать заинжектив свою либу, перехватывающую в ntdll KiUserExceptionDispatcher, и выводящую содержимое отладочной структуы _EXCEPTION_POINTERS и иже сней. (Это думаю будет полезно, если потом проанализировав все в отладчике, дописать в эту библиотеку код изменяющий все в лушую сторону, как говорит Clerk
    http://wasm.ru/forum/viewtopic.php?pid=309914#p309914) И бывшее когда-то нерабочее чужое приложение станет работать, например не работает после обновления винды. Я сам сталкивался с таким после установки "переделанного" DirectX10 на XP, некоторые программы перестали работать на стадии загрузки, пришлось все править вручную).
    P>S>Может Clerk еще доходчиво напишет как работает Syser Debug (в плане ядерных точек останова. Например, постановка точки останова на какую либо API -вызывает останов на этом адресе во всех процессах. Это махинация с регистрами процессора или что?).
     
  17. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Диспетчерезиция исключения это системный механизм, как и прочие он имеет свою модель вызова. Не имеет смысла рассматривать этот механизм как запредельный для приложения. Вызов диспетчера исключений следует рассматривать как просто передачу управления на диспетчер, не более. Это главная точка, с которой начинает выполняться SEH и пр. Как например диспетчеризация APC рассматривается как передачу управления на какойто код, также и для исключений. Простой пример - юзермодные дебаггеры сбрасывают отладочные регистры, код выполняющий манипуляции с ними продебажить средствами отладчика не получится. Отладочный порт это кривой функционал, не многие задачи с его помощью решаются. Скока раз приходилось зацикливать диспетчер исключений, ибо юзермодный дебуггер был бесполезен. В случае ядерного отладчика - описанное единственный нормальный способ просмотреть инфу об исключении. На ISR брякаться или анализ TRAP_FRAME для исключений, вызванных в третим кольце защиты это пустая трата времени, делается всё куда проще и быстрее. В аттаче элитный док с вирустека, описывающий модель вызова, может кому пригодится. Да и вообще не следует надеятся на помощь дебаггера, это не на столько гибкий инструмент, чтобы решать подобнае задачи.
    RET
    Сисер ISR перехватывает для #BP, тем самым когда возникает исключение он получает управление.
     
  18. Y_Mur

    Y_Mur Active Member

    Публикаций:
    0
    Регистрация:
    6 сен 2006
    Сообщения:
    2.494
    Clerk
    А VEH тоже работает через KiUserExceptionDispatcher?
    А как у этого механизма с мультивиндовостью? Например если на его основе сделать свою версию SEH - будет работать в Висте, семёрке, 9х?
     
  19. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Конечено, всюду одинаково.
     
  20. dendi

    dendi New Member

    Публикаций:
    0
    Регистрация:
    3 сен 2007
    Сообщения:
    233
    в 7 в начале появилось CLD только, а так всё такое-же.