У меня уже работает своя система обработки исключений: в начале проги через SetUnhandledExceptionFilter ставится обработчик, которому виндовс даёт один параметр в стеке - указатель на указатели EXCEPTION_RECORD и CONTEXT В этом одном обработчике я делаю всё что мне нужно (нахожу по моему механизму обработчик сопоставленный с адресом аварийной инструкции и вызываю его). Всё хорошо, но... сбил меня с понталыку l_inc - говорит лучше вместо SetUnhandledExceptionFilter в стек загрузить адрес моего обработчика, затем push [FS:0], затем mov [FS:0],esp. Вот я и заморочился с этим... Всётки хочется как можно меньше обращаться к API - будет надёжнее и предсказуемее. С наскоку ничего не получилось. Щас я в новом обработчике ничего не делаю, только вывожу сообщение и возвращаю различные значения, пытаюсь выяснить что как срабатывает. Раньше (это я выудил у Рихтера) - при нуле - закрытие проги со стандартным виндовым сообщением, 1 - закрыть и не показывать стандартное сообщение, -1 - перезагрузить контекст и продолжить повторив аварийную инструкцию. В новом и 0 и -1 - продолжается повтор аварийной инструкции, при 1 - виндовое сообщение и закрытие. Тоесь явно всё по-другому. Вникать во всю систему SEH мне не хочется и это ни к чему, тем более она толком не документирована, у Matt Pietrek - одни лишь догадки, нелогично и непонятно. А мне надо-то всего лишь знать - где обработчику установленному через FS:0 передаются EXCEPTION_RECORD и CONTEXT, что возвращать, и на сколько очищать стек при возврате. Подскажите, кто знает.
gas Кто-кто Вас с толку сбил? В общем, здесь пример использования SEH под fasm. Там, конечно, основная идея не в SEH, но кода всё равно немного. У Matt Pietrek отлично и достаточно подробно всё расписано. В примере выше по ссылке всё указано. Обратите внимание на конвенцию вызова, параметры exRecord, exContext и возвращаемое значение процедуры exHandler.
gas Забудьте про финальные обработчики, они использовались в младших версиях системы, когда отсутствовала векторная обработка исключений. У питреков, рихтеров и прочих чудоавторов этих доков описание архисложное, на самом деле механизмы совершенно прозрачные и в них ничего сложного нет. Регистрируете SEH. Возвращаете соответствующие значения из колбека: Код (Text): EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) ( IN struct _EXCEPTION_RECORD *ExceptionRecord, IN PVOID EstablisherFrame, IN OUT struct _CONTEXT *ContextRecord, IN OUT PVOID DispatcherContext ); Что тут может быть не понятного ?
Всё, разобрался. Ответ на мой вопрос звучит так: В стеке четыре параметра, интересующие меня EXCEPTION_RECORD и CONTEXT указаны первым и третим Возвращать: 0 - продолжить 1 - пусть виндовс делает что хочет (остальные 2 и 3 мне, при единственном обработчике, не нужны)
gas Не совсем. Вы можете сделать вложенные обработчики. Скажем, один внешний, который устанавливается в самом начале исполнения потока, и вынимается в конце (ф-ия main, например), а потом в некоторых местах/процедурах ставить вложенные. Так вот при возврате 1 (ExceptionContinueSearch) управление перейдёт к следующему (внешнему) обработчику, т.к. внутренний (вернувший 1) не хочет обрабатывать исключение. Вариант "пусть виндовс делает что хочет" произойдёт в случае, если Ваш собственный самый внешний обработчик отказался обрабатывать ислкючение (вернул ExceptionContinueSearch). В этом случае управление будет передано действительно самому внешнему юзермодному обработчику, который располагается в ntdll (на системах, начиная с висты) или kernel32 (на системах xp/2003 и ранее).
l_inc я имел ввиду - при моей системе с принципиально одним единственным обработчиком - если я верну 1 - значит "пилите шура, я пас".
Если один единственный, значит критических мест у вас не много. В VX для защиты кода от крешей используется немного иная технология, причина использование - невозможность обработки кода, не описанного в загрузчике(эта проблема решается, но не проще, чем некоторое подобие самостоятельной реализации сех: STPT). Используются вызовы стороннего кода, защищённого сех - весьма логичное и очень эффективное решение, так как в ядре не может быть не стабильности в этих механизмах. Например вызываем любой сервис требующий ссылку в аргумента - если сервис отработает успешно, значит ссылка валидна, например: Код (Text): NtResumeThread(NtCurrentThread, pDWORD) test eax,eax jz Writable cmp eax,STATUS_ACCESS_VIOLATION je NoAccess ; #AV в ядре, без последствий.. Так можно проверять атрибуты буфера(R/W), большие буфера(передавать строки и структуры) и пр. Это более оптимально, чем однократное использование сех.
klzlk, я при одном обработчике могу контролировать неограниченное число конкретных адресов инструкций могущих вызвать исключение, для каждого - свой обработчик, но не устанавливаемый через SEH, а сопоставленный с этим адресом в специальном списке, создаваемом при компиляции. У меня на FASM создание списка критических адресов и их обработчиков выглядит так try ;макрос заносящий текущий адрес в список div ecx ;инструкция могущая вызвать и исключение except ;макрос заносящий адрес нижеследующего обработчика в список ... конечный обработчик (правка регистров) ... end_except ;макрос просто ставящий скрытую метку для перепрыгивания обработчика ;продолжение нормального хода Таких try-except я могу понаставить мильон, но никаких установок сех-обработчиков. Он ставится только один в самом начале, а удаляется в самом конце. При исключениях по спискам находит кого надо вызвать. Наверно это можно назвать векторной обработкой исключения. Список адресов обработчиков - это есть таблица векторов. Я придумал сам, нигде не подглядывал (я вообще читать не люблю)
gas Возможно проще сохранять идентификатор процедуры в TLS ? Например загружать ссылку на сех в тлс или сех-фрейм, это будет оптимальнее, чем массив адресов инструкций.
klzlk Я не зняю что такое TLS (любитель, да ещё книжек не читаю) И не представляю что может быть оптимальнее. На каждую критическую инструкцию экзешник увеличивается всего на 10 байт - 8 в списке и 2 на перепрыг обработчика (сам обработчик не считаю - т.к. речь о том как оптимальнее до него добраться). А сколько кода на установку-снятие SEH-обработчика, который жрёт такты и при нормальном прохождении? Искать адрес в списке из сотни двордов - наносекунды. Если список большой - можно организовать поиск последовательным приближением. По-любому в сравнении что там делает виндовс прежде чем вызвать мой обработчик - капля в море. В сех-фреймах вообще не вижу никакого смысла. Смысл есть только в защите конкретной инструкции. Вообще, когда я почитал у Рихтера об этой SEH, у меня возникло ощущение что там в микрософте у когото что-то переклинило...
gas Какой смысл в обработке каждой инструкции, тоесть что делает сех для например div, вызвавшей фолт ? TLS - буфер связанный с потоком, как например стек или TEB. Установить можно общий сех, вы это сделали. Сех фрейм локален для треда, стек это тлс. Можно не формировать весь сех-пролог и эпилог, а только загружать в сех-фрейм адрес инструкции вызвавшей фолт. Безопасное место можно не использовать, если есть LDE(можно использовать системный лде шима, это если нужно вернуть управление на следующую инструкцию, взведя флаги ошибки например), либо взять текущий стековый фрейм и выполнить возврат из процедуры(тогда появятся проблемы с определением числа аргументов, удаляемых из стека). Например таким образом: Код (Text): %SEH_PROLOG ... mov eax,dword ptr fs:[0] ; Xcpt list mov dword ptr [eax + 4],offset SEH1 ; EXCEPTION_REGISTRATION_RECORD.Handler mov dword ptr [eax + 2*4],offset SafeSEH1 ; Опционально, можно заюзать LDE(XcptIp) в сех. ... Call Sub1 ... %SEH_EPILOG Sub1: mov eax,dword ptr fs:[0] mov dword ptr [eax + 4],offset SEH2
h0t а зачем всё это? - раскрутки и прочее? Объясните на пальцах? Если процедура выскочила куда-то не туда и не с того места - это надо лечить в коде, а не пытаться исправлять налету. Если исключение ожидаемое - известен конкретный адрес кода - что может быть естественнее при исключении сразу вызвать сопоставленный в списке к этому адресу адрес обработчика? Зачем какой-то маразм с установкой очередного обработчика перед этим кодом и снятие после?
klzlk не, не хочу грузить себе мозги тем в чём не вижу реальной потребности. То что мне надо - я добился, зачем мне лезть в в дебри? Ну разве что, в порядке общего развития, поинтересуюсь немножко, если расскажете пример (без сильно-специфичных терминов, на моём уровне) где моя система окажется недееспособной? Какую ситуацию я не смогу разрешить?
например у Вас есть такой код на С++ Код (Text): __try { ex_fun(); goto label; } __finally(...) { } label: что-бы подобный код корректо обрабатывался (finally проробатывал) это нужно.
У меня такого кода не будет... C++ не знаю и знать не хочу (разве что хелп по API из С++билдера посмотреть) Ну ладно... я так понял - внешняя функция тоже может глюкануть, может иметь свой обработчик, если он откажется исправлять ситуацию - вызовется мой. Это есть раскрутка. Тут я согласен - если в одном потоке работает код разных разработчиков - без такой системы не обойтись. И я этого механизма не нарушаю, да и не могу при всём желании - я ж не запрещу внешним функциям грузить [FS:0]. А всё что касается моего личного кода - какая бы глубина вложений процедур ни была, мне достаточно одного обработчика (вернее одной точки входа, а дальше разрулить по списку адресов) Нет никакого смысла в том чтобы обработчик исключения мог отказаться от обработки и перекладывать это на вышестоящего. Если виноват вышестоящий - повторить инструкцию с заведомо безопасными значениями, а ему вернуть "шеф, операция провалилась". Вот и всё. Не надо раскруток, нужен только список желающих обрабатывать свои исключения, который создаётся компилятором, а во время исполнения не тратится ни единого такта на регистрацию обработчика. Это ж надо додуматься! - регистрироваться, а потом снимать регистрацию при КАЖДОМ проходе критичного участка... Почему не сделать это один раз при компиляции. Что мешало по такому пути пойти микрософту? Вот я и говорю - у кого-то где-то переклинило...
я это к тому привел, что finally должен отработать В ЛЮБОМ СЛУЧАЕ, даже если ошибок не было т.е.goto НЕ должен сработать. Из-за подобных ситуации обработчики исключений имеют подобную структуру + из-за некоторых парадигм программирования. если исключение может возникнуть, и быть корректно обработано, то я это делю если нет пускаю дальше в надежде что кто-то обработает. В некоторых случаях лучше ставить один обработчик исключений чем множество проверок различных условий. P.S. такой механизм придуман не Microsoft а родился из исключений в языках программирования.
Вот такая парадигма - имхо, порочна. Перекладывание на плечи другого - верный путь к закрытию проги. Если на моём участке исключение - именно я должен избежать закрытия, кто бы ни был в этом виноват. А вызывавшему передать в чём он не прав. Надо ставить обработчик на конкретную инструкцию могущую вызвать исключение, и тогда ему проще простого определить в чём причина. При моём подходе к этой инструкции добавляется лишь один короткий jmp на перепрыг обработчика. А если обработчики компилировать где-то в сторонке, то даже джампов не надо, защищаемый код остаётся как буд-то он вовсе и не защищаемый.
это уже философия, а закрытие программы не самый плохой путь развития событий при не предусмотренном исключении. обработчик верхнего уровня может корректно обработать исключение! На эту тему есть куча работ и это другой вопрос. Дело в том что разработчики ОС ОБЯЗАНЫ (если хотят что-бы это кто-то использовал кроме их самих) реализовывать общие механизмы!
gas Значит вы достигли своего предела.. Вобщем считаю заключать все инструкции в сех глупо и бессмысленно. Если например возникнет фолт на тойже Div, что тут хэндлить ? Чтото пошло не так - гдето появился инвалидный параметр. В любом случае откатать это будет не возможно, так причина исключения не в валидности исполнения инструкции процессором, а во входных данных. Обычно при таких фолтах возвращают из процедуры статус(причину исключения). Да и каждую инструкцию не понятно зачем обёртывать в сех - получится куча копий одного хэндлера. А в сех многократно оборачиваются места, для последующего освобождения ресурсов(например какойто ресурс захвачен, далее при возникновении ошибки он должен быть освобождён, тоесть просто по цепочке стековых фреймов нельзя выполнить возврат без отката состояний. В NT подход к идентификации ошибок фактически совершенный - все процедуры Bp-based/stdcall и сама структурная обработка исключений это механизм очень гибкий.