Проблема следующая, господа: Есть некий драйвер для w2k/XP. Он осуществляет обработку исключения 0d (general protection) в собственном коде. Реализована обработка путем замены соответствующего IntGate дескрипора в IDT на собственный, адресующий мой обработчик. (Почему именно так? - таковы условия задачи) Соответственно мой обработчик: 1. сохраняет регистры (pushad) 2. сохраняет сегментные регистры (es,ds,fs), инициализирует ds=ss, es=ss, fs=30h 3. производит проверки: error code=0; вызов из ring 0; 4. проверяет где произошло исключение - если проверки не проходят возвращает управление на сохраненный адрес оригинального обработчика (с восстановлением всех регистров соответственно) jmp dword ptr [xxxx] 5. производит некоторые действия 6. восстанавливает сегментные и прочие регистры 7. iretd Вобщем, при таком раскладе, все работает нормально. Однако в процессе обработки исключения возникает необходимость обратиться к данным расположенным в PagedPool. Но для этого необходимо, чтобы в системе были разрешены перерывания (понятно почему). Однако внутри обработчика прерывания получившего управление через IntGate прерывания по определению запрещены (IF=0). Были предприняты следующие возможные варианты: 1. разрешить прерывания - STI внутри обработчика 2. использовать TrapGate и запретить CLI перед передачей управления на оригинальный обработчик. 3. скорректировать адрес возврата iret на дополнительный код обработки, выполнить действия, передать управление ret Все эти действия приводят к тому, что в системе начинают, через некоторое время, падать UserMode процессы. Причем падают только в UserMode по разным адресам но все при обращении к памяти и потому, что у всех значение ds=0 и es=0. С моей стороны произведены следующие проверки: Регистры и флаги восстанавливаются перед передачей управления на 100%; IRQL=PASSIVE - всегда; вызов никаких API функций из обработчика не производится; после обработки исключения, управление возвращается корректно. Так вот: с точки зрения здоровой логики, подкрепленной intel-овской документацией операционная система не должна ничего знать о таких исключениях? Тогда какие будут версии происходящего? Может кто сталкивался?
Извините, что лезу в тему, про которую даже не слышал (я и про прерывания пока толком ничего не знаю :$), но меня смущает второй пункт: Ты уверен, что здесь всё в порядке? Можно взглянуть на этот отрезок кода? Я предполагаю, что стек должен быть кратен 32 битам. To All. Ещё раз извините, если в данном случае я говорю бред.
через какое? извинием за бред. Zufyxe если хочеш, могу протестить у себя. без кода, что-то никаких соображений.
Bitfry, а ты проверь, как работает сохранение в стек сегментного регистра в 16-ти и 32-разрядном режиме=) В 32-разрядном тебе в стек пихнется дворд, младшая часть которого и есть сегментный регистр. dead_body, падения происходят только (по крайней мере мы наблюдали) в тех (но не во всех) потоках, которые щас в UserMode. Потоки в ядре у нас ни разу не падали. Со временем не очень понятно, но довольно быстро, т.е. если будешь работать в системе - упадет секунд за 20 что-нибудь гарантированно. Мы попробуем собрать драйвер для теста, но гарантии, что он будет ронять потоки также мы не дадим, поскольку сами не понимаем как это сделать=) ЗЫ Ответил я, поскольку вместе ковыряемся с этой проблемой...
Мы тоже сталкнулись с такой проблемой (подкачать страницу из обработчика) - решили ее путем пропачивания юзерского кода и временной передачи управления туда. Т.е. следующее - пачится юзерский код командой mov al, byte ptr [адресс_для_подкачки] - подготавливается стек для iretd на эту команду - устанавливаю флаг TF для юзерского кода (это проще чем мутить callgate) - делаю iretd и после выполнения команды mov al... автоматически получаю управление назад, т.к. сработало трассировочное прерывание int1, из которого передаю управление себе назад. - восстанавливаю юзерский код назад так реализована команда PAGEIN в айсе Я тоже сначала пытался подкачать страницу из обработчика непосредственно (восстанавливая флаг STI и т.д.) и вроде все работало, но система иногда случано (уже после) выдавала синих слоников с сообщением что нарушены таблицы страниц. MoonShiner можете выложить код, как вы пытаетесь подкачать страницу из обработчика? Мне интересно сравнить будет
а меня смущает, то что ваш обработчик вызывает iret, т.е. вы берете на себя обработку всех исключений? Тогда и обрабатывайте все исключения. Выход такой - вешайте свой обработчик перед виндусовским и фильтруйте, то что нужно и если это не интесное вам исключение передавайте стандартному. Это не относится к вашему случаю, но примерно так: Код (Text): __declspec(naked) NewInt3Handler() { KIRQL OldIrql; OldIrql = KeGetCurrentIrql(); DbgPrint("Current IRQL %08X\n", (KIRQL) KeGetCurrentIrql()); if (KeGetCurrentIrql() == PASSIVE_LEVEL) __asm int 1; __asm { pushad int 1 popad jmp origInt3Handler; } }
Godness, проблема уже не в подкачке страниц. Разрешив прерывания (STI) в обработчике int 0d, либо осуществив возврат (IRETD) мы без проблем используем PagedPool и можем вызывать API, использующие подкачиваемые страницы. Но впоследствие, из-за этого мы получаем обнуление ds и es в юзерских тредах. А трюк с подкачкой ты придумал интересный, только int1 хватать приходится. Saint German, естественно, наш обработчик должен сам вызывать iret если, после фильтрации и проверок в шаге 4, он обнаруживает, что это наше исключение, нами и сгенеренное и сам его обработает. А вот не наши исключения, как я ранее писал мы сбрасываем джампом на предыдущий обработчик. В аттаче код, который иллюстрирует описанную ситуацию. Вкратце, что он делает: Патчит IDT для перехвата прерывания 0Dh. Создает системный поток, который вертится в цикле. Цикл представляет из себя вызов KeDelayExecutionThread на некоторое время и генерацию исключения: Код (Text): xor eax,eax dec eax trap_code: mov edx,dword ptr [eax] - здесь В обработчике мы проверяем, наше ли это исключение, т.е. произошло ли оно по адресу trap_code. Если это так, мы заносим в eax адрес валидной переменной и возвращаем управление на trap_code (iretd). Инструкция, сгенерившая исключение отрабатывает нормально, и дальше в цикле, пока мы не захотим остановить тред, поместив в переменную не 0. Вообще, после запуска этого драйвера, на живой w2k и на vmware с XP что-нибудь сразу валится с ds=es=0. Так что этот код воспроизводит проблему. 1298063930__MLODR.ASM
Да, и еще, достаточно в драйвере: Код (Text): cmp eax,offset trap_code jnz go_down sti ; ============================= убрать STI - как все работает стабильно. Хотя, еще раз повторюсь, система, теоретически, ничего не может знать об этом исключении.
test byte ptr [esp+0eh],2 jz GP_not_from_v86 mov ebx,30h mov eax,32h mov fs,bx mov ds,ax mov es,ax это начало GPF_handler-а из (например) вин2003 вы при обработке exception-а из V86 не сохраняйте и не восстанавливайте селекторы (они сами сохраняются и восстанавливаются) - иначе их обнулят при выходе, как и обещано в интеловской доке
z0mailbox, при добавлении в самое начало нашего обработчика строк: Код (Text): test byte ptr [esp+0eh],2 jnz jmp_prev ; переход на оригинальный обработчик проблема остается. Значит дело вовсе не в v86 селекторах.
значит вы все-таки попадаете в этот кусок IRETD RETURN-TO-OUTER-PRIVILGE-LEVEL: [much algo skipped] FOR each of segment register (ES, FS, GS, and DS) DO; IF segment register points to data or non-conforming code segment AND CPL > segment descriptor DPL (* stored in hidden part of segment register *) THEN (* segment register invalid *) SegmentSelector = 0; (* null segment selector *) FI; OD; То есть вам надо на выходе вместо pop ds pop es pop fs воткнуть pop eax or ax,3 mov ds,ax и.т.д. или еще проще mov ax,38h mov fs,ax mov ax,23h mov ds,ax mov es,ax а вот почему так, почему у вашего обработчика оказываются привилегированные селекторы данных я честно говоря с ходу не врубаюсь. может еще кто-то перехватывает GPF? может SEH-и дурят как-то хитро...
z0mailbox, test byte ptr [esp+0eh],2 идет ПЕРВОЙ инструкцией обработчика прерывания, а в jmp_prev стоит jmp Old_Handler. Т.е. мы вообще не трогаем сегментные регистры.
но они отчего-то же невалидные (слишком привилегированные) предлагаю сделать такое начало test byte ptr [esp+0eh],2 ; from V86? jnz V86_handler ; to Old_Handler push eax test byte ptr [esp+0ch],3 ; from user-mode? jz kernel_mode ; user-mode selectors check mov ax,ds and ax,3 cmp ax,3 je @F ; Ok db 0cch @@: mov ax,es and ax,3 cmp ax,3 je @F db 0cch @@: kernel_mode: и когда попадете в отладчик - все увидите сами и нам расскажите
Значит так. z0mailbox, большое тебе спасибо за то, что направил нас куда надо и настаивал на своем, пока мы тебя деликатно посылали=) И, вообще, всем спасибо за участие. На всякий случай расскажу о возникавших, после того, как поправили код, проблемах: 1) Занесение в ds и es 23h перед iret-ом на первый взгляд работало, но только на первый. Случайно обнаружилсь проблема с Acrobat Reader, который в коде одной из своих либ имел следующее: in ax,dx. Возникало исключение C0000096h - privileged instruction. И дальше валилось в BSOD на инструкции в оригинальном обработчике pop dword ptr [0FFDFF000h]. Проблема была устранена проверкой в начале обработчика прерывания равенства нулю ds и es и установкой в них 23h. Далее следовало их сохранение и восстановление перед iret-ом. Если же какой-то из них ненулевой - сохраняли его в стек и инициализировали в 23h. Восстанавливали перед iret-ом тот, что пришел в обработчик. 2) Изначально мы делали в обратчике следующее: Код (Text): push ss pop ds push ss pop es Однако, при таком раскладе несколько раз выскочил синяк и иногда повисал комп на загрузке. Такой же код снял эту проблему: Код (Text): push 23h pop ds push 23h pop es
- может быть и так. Но все-таки, может кто-нибудь объяснить, каким образом ОС узнает о возникновении обрабатываемого нами прерывания? И почему, в конечном итоге изменяются сегментные регистры? Господа Гуру, ткните меня пожалуиста носом в Intell-овский документ, где было бы сказано, что при обработки GPF в PM сбрасываются сегментные регистры, и что программа должна их инициализировать?