боремся с нехваткой ресурсов
От: Valery A. Boronin Россия linkedin.com/in/boronin
Дата: 02.07.06 13:54
Оценка: 11 (4)
#Имя: FAQ.asm.insufficient.resources
VAB>PS убедился на примере душераздирающего зрелища Щвейцария — Украина. Ох и прет же братьям-славянам. Но дальше — ce finita! C такой игрой на мундиале даже находиться все же стыдно. Впрочем Италия скоро объяснит что такое finita! прогнозирую победу в два мячика. минимум.
с этой игрой угадать было легко. 3-0 и достойная компенсация от букмекера

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

ок, делюсь — только тут ничего сверхестественного не будет, сразу предупреждаю

какие ресурсы нам обычно нужны в своих процедурах?
— стек под локальные переменные
— память под динамические переменные

невыделение памяти через ExAllocatePool в ситуации если кто-то хочет очень большой кусок памяти а его нет (к примеру) можно лечить:
— разбиением большого запроса в серию мелких (а-ля classpnp делает для диска)
— всегда иметь под рукой хотя бы пару страниц выделенных заранее и использовать их когда совсем тяжелая ситуация
— выделять (на старте) память из спец пула, память в котором "всегда есть"

NonPagedPoolMustSucceed
This value is for internal use only, and is only allowed during system startup. Otherwise, drivers must not specify this value, because a "must succeed" request crashes the system if the requested memory size is unavailable.



это не секрет, об этом сразу вероятно все подумали
Автор: Злость
Дата: 28.06.06
и без этого текста.

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

Bug Check 0x7F: UNEXPECTED_KERNEL_MODE_TRAP + the first parameter is 0x8 (a "double fault")
вот что чаще всего (хотя может быть много чего вместо) мы наблюдаем — и при этом user видит что "ОС предполагает" что виноват именно наш драйвер, заказчики нервничают и ругаются соотв. не на их пяток-другой фильтров в стеке, которым по архитектурным причинам еще со времен НТ4 сложно вместе ужиться при любом раскладе. А ругаются на то, что наш фильтр оказался тем перышком, что ломает спину их верблюда

так вот, что можно сделать здесь?

— определить что стека мало и соотв. это дело обработать
— минимизировать использование стека

собственно последний вариант я бы и хотел осветить. Итак, как можно минимизировать использование стека?

я уже вижу как тянутся руки и всплывает решение — выделять память под локальные переменные динамически, не класть всякие структуры на стеке:
NTSTATUS DispatchHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    NTSTATUS            status;
    NTSTATUS            WaitStatus;
    KPROCESSOR_MODE     PreviousMode;
    PCM_KEY_BODY        MasterKeyBody;
    PCM_KEY_BODY        SlaveKeyBody;
    PKEVENT             UserEvent=NULL;
    PCM_POST_BLOCK      MasterPostBlock;
    PCM_POST_BLOCK      SlavePostBlock;
    KIRQL               OldIrql;
    HANDLE              SlaveKeyHandle;
    POST_BLOCK_TYPE     PostType = PostSynchronous;
    ...
    еще 40 строк с локальными переменными включая WCHAR MyPath[_MAX_PATH] и т.п. :) 
    ...


а делать примерно так: все локальные переменные выше образуют структуру PARAMETER_BLOCK
затем в DispatchHandler скажем происходит выделение памяти через ExAllocatePool и дальше вся работа через указатель. можно для удобства продублировать свои dispatch handlers в стиле
NTSTATUS RealDispatchHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp, PARAMETER_BLOCK* pb);

и писать реальные обработчики в соотв. терминах — все только через указатель

в этом случае мы тратим весьма мало стека — в идеале нам (нашей процедуре которая как мы помним вызывала падение) нужно лишь место под переменную типа PARAMETER_BLOCK*. Это уже серьезно уменьшает шанс на то что падение будет у нас, но он по-прежнему есть.

на заметку: можно кстати __try/__finally в DispatchHandler пользовать — за счет небольшой траты стека гарантируем отсутствие утечек памяти. Иногда это бывает важнее


И вот тут начинаются "маленькие футбольные хитрости" — надо избавиться от траты sizeof PARAMETER_BLOCK* стека под нашу структуру. Как? да очень просто — конечно же сложить в регистр. Способов тут много — от ручного ассемблера, до всяких register ключевых слов в копиляторах (надо обязательно проверять в какой код потом это все скомпилируется). Я не буду на таких деталях останавливаться — предпочитаю донести идею.

Хорошо, теперь у нас невозможно переполнение стека внутри нашей процедуры, время бить в ладоши и радоваться? Не совсем.

Ведь как правило сама ф-я имеет параметры, типа того что мы видим в RealDispatchHandler — я спецально там "забыл" PDEVICE_OBJECT DeviceObject, PIRP Irp. Отлично, это тоже решается легко — переносим все входящие параметры также внутрь структуры PARAMETER_BLOCK. Так лучше, теперь все отлично?

Опять нет — все равно сама ф-я
NTSTATUS RealDispatchHandler(PARAMETER_BLOCK* pb);    // PDEVICE_OBJECT DeviceObject, PIRP Irp теперь живут внутри
по-прежнему имеет параметр и соотв адрес возврата — т.е. мы все еще можем упасть в очень редком случае.

дальше уже стоит вспомнить ассемблер, вспомнить о преймуществах FASTCALL перед CDECL/STDCALL (неспроста кстати the calling convention mode for the 64-bit compiler contained in Microsoft Windows Server 2003 DDK is limited to just the FASTCALL type) и порадовать себя небольшим программистским этюдом — добиться вызова RealDispatchHandler с параметрами в регистрах + проконтролировать что стек совсем не используется, избавиться от call RealDispatchHandler — оставить только jmp где нужно. Но это уже имеет не столько практический смысл, сколько "для души" конечно же.

Комбинация всего вышеперечисленного (мы же помним что выделение памяти под PARAMETER_BLOCK может быть неудачно — вспоминаем первую часть этого сообщения) позволяет с уверенностью заявить — мы сделали все что могли и надеяться что если падения в определенных условиях конечно же будут, то уже скорее всего не у нас — крэш дамп уедет к другим ребятам и они будут тратить время на разборки с дампом и с заказчиком. А мы пойдем в это время вперед. Что и требовалось

PS полезная статья по теме: Don't Blow Your Stack -- Clever Ways to Save Stack Space расскажет как позвать IoGetRemainingStackSize и соотв. в опасных ситуациях пытаться уйти от проблемы нехватки стека через использование work items и спец. потоков.
... << RSDN@Home 1.2.0 alpha rev. 648>>
Valery A. Boronin, RSDN Team, linkedin.com\in\boronin
R&D Mgmt & Security. AppSec & SDL. Data Protection and Systems Programming. FDE, DLP, Incident Management. Windows Filesystems and Drivers.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.