воскресенье, 31 июля 2011 г.

Driver verifier problem

Тестер проверял драйвер моего tdi фильтра под верифаером, со всеми включенными опциями, поймался bsod:

DRIVER_VERIFIER_IOMANAGER_VIOLATION (c9)
The IO manager has caught a misbehaving driver.
Arguments:
Arg1: 00000208, This IRP is about to run out of stack locations. Someone may have forwarded this
    IRP from another stack.
Arg2: ee8e99ff, The address in the driver's code where the error was detected.
Arg3: 86b5ef68, IRP address.
Arg4: 00000000

...

STACK_TEXT: 
f7675fc8 8065fe0b 0000004c 000000c9 f7675fe8 nt!KeBugCheckEx+0x1b
f7676150 80660571 f767642b 8068f090 00040000 nt!ViBugcheckHalt+0xc3
f76763f4 80660657 80693630 00000208 f7676420 nt!VfBugcheckThrowException+0xa1
f76764e4 806639ac 00000208 00000009 ee8e99ff nt!VfBugcheckThrowIoException+0xb5
f7676510 80663dce 86b5efd8 877faf00 00000002 nt!IovpExamineIrpStackForwarding+0x1dc
f7676584 80656116 001b4950 862c30e8 862df168 nt!IovpCallDriver1+0x1de
f76765ac ee8e9b20 f76765e4 ee8e99ff 861edf18 nt!IovCallDriver+0x8e

После анализа оказалось, что верифаер изменяет число stack locations, так что абсолютно корректный код:

static NTSTATUS ForwardIrp( __in PDEVICE_OBJECT deviceObject, __in PIRP irp )
{
    RT_ASSERT( irp );
    RT_ASSERT( deviceObject );   

    IoSkipCurrentIrpStackLocation( irp );

    return IoCallDriver( deviceObject, irp );   
}

Будет работать без верифаера, и будет падать при включенном верифаере.
Проблема и решение также описана тут: http://www.osronline.com/showthread.cfm?link=139736

Мораль - проверяющие тулзы это конечно хорошо, но иногда и они ошибаются.

суббота, 30 июля 2011 г.

Windows Logical Prefetcher

Logical Prefetcher появился в Windows XP, он представляет собой часть ядра и нужен для уменьшения числа head seek-ов жесткого диска, что соответственно увеличивает скорость запуска приложений или системы.

При старте процесса он отслеживает первые 10 секунд его работы(таймаут задается через реестр), затем запоминает имена файлов, смещения, а также метаданные фс к которым был произведен доступ, после чего данная информация записывается в файл в директории %SystemRoot%\Prefetch.

При повторном старте приложения, Logical Prefetcher проверяет, есть ли префетч файл в соответствующей директории, и если есть - парсит его и подгрузит все метаданные для каждой директории указанной в префетч файле.Затем идет отображение каждого файла, перечисленного в префетч файле, загрузка его и установка тех смещений, которые были запомнены ранее( и сохранены в префетч файле ).

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

Prefetcher может ускорять не только запуск приложений, но и уменьшать boot-time системы.

Настройки префетчера храняться в реестре:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters

Для его включения нужно установить EnablePrefetcher = 3 (Disabled = 0, Application = 1, BootUp = 2, Application AND BootUp = 3), хотя по умолчанию он и так включен.

Там же прописан таймаут работы префетчера при первом запуске - AppLaunchTimerPeriod.

Пример префетч файла: C:\WINDOWS\Prefetch\VMMAP.EXE-3B5AFAED.pf (вывод утилитой от Руссиновича Strings):

SCCA
VMMAP.EXE
C-p
Xa%
0pL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\NTDLL.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\KERNEL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\UNICODE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\LOCALE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SORTTBLS.NLS
\DEVICE\HARDDISKVOLUME2\INSTALLEDTOOLS\VMMAP\VMMAP.EXE
...
Структура .pf файла проста - заголовок + смещения + юникодные имена файлов.
Также для префетч файла есть несколько требований - это наличие сигнатуры в заголовке(SCCA), сам файл должен быть меньше 16мб, смещения не должны вылезать за пределы файла ( это проверяется ф-цией PfWithinBounds ).

Что касается имени префетч файла, например VMMAP.EXE-3B5AFAED.pf, то 3B5AFAED это хеш от пути, хеш подсчитывается следующей ф-цией:

#define RNDM_CONSTANT    314159269
#define RNDM_PRIME        1000000007

ULONG CcPfHashValue( PVOID Key, ULONG Len )
/*
Routine Description:
    Generic hash routine.

Arguments:
    Key - Pointer to data to calculate a hash value for.
    Len - Number of bytes pointed to by key.

Return Value:
    Hash value.
*/
{
    char *cp = Key;
    ULONG i, ConvKey=0;

    for ( i = 0; i < Len; i++ )
    {
        ConvKey = 37 * ConvKey + (unsigned int)*cp;
        cp++;
    }

    return ( abs( RNDM_CONSTANT * ConvKey ) % RNDM_PRIME );
}

Однако кроме обычных приложений есть еще и приложения которые имеют командную строку, для таких файлов хеш считается по другому, а сами файлы перечислены в реестре:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters\HostingAppList: DLLHOST.EXE,MMC.EXE,RUNDLL32.EXE

Ф-ция ответственная за проверку принадлежит или нет процесс данному списку называется CcPfIsHostingApplication.

Что касается реализации, то префетчер запускается при запуске первичного потока, цепочка вызовов выглядит так:

NtCreateThread => PspCreateThread => KeInitThread( ..., PspUserThreadStartup, ...):

VOID PspUserThreadStartup( IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext )
{
...
    if (CCPF_IS_PREFETCHER_ENABLED())
    {
        //
        // If this is the first thread we are starting up in this process, prefetch the pages likely to be used when initializing the application into the system cache.
        //

        if ((Process->Flags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
        {
            OldFlags = PS_TEST_SET_BITS(&Process->Flags, PS_PROCESS_FLAGS_LAUNCH_PREFETCHED);

            if ((OldFlags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
            {
                if (Process->SectionObject)
                {
                    //
                    // Notify cache manager of this application launch.
                    //

                    CcPfBeginAppLaunch( Process, Process->SectionObject );
                }
            }
        }
    }

    //
    // Queue the initial APC to the thread
    //

    KeRaiseIrql (APC_LEVEL, &OldIrql);

    KiInitializeUserApc( PspGetBaseExceptionFrame (Thread),
                         PspGetBaseTrapFrame (Thread),
                         PspSystemDll.LoaderInitRoutine,
                         NULL,
                         PspSystemDll.DllBase,
                         NULL);

    KeLowerIrql (PASSIVE_LEVEL);
...
}

Сам префетчинг происходит через перечисление содержимого директории ZwQueryDirectoryFile с классом FileNamesInformation, и загрузкой секций(CcPfPrefetchSections) через IoCreateFile + ZwCreateSection и метаданных (CcPfPrefetchMetadata), но тут уже идет обращение к ФС через ZwFsControlFile(..., FSCTL_FILE_PREFETCH,...).

Работа префетчера по сбору данных идет асинхронно через рабочие потоки, совершенно прозрачно для процесса.

Остался один неосвещенный вопрос - как именно префетчер узнает к какому файлу(секции) было обращение при первом запуске приложения?
Ответ - через отслеживание ошибок страниц.

Цепочка ф-ций, при страничном фолте:

_KiTrap0E => MmAccessFault => MiDispatchFault => MiCompleteProtoPteFault => CcPfLogPageFault.

VOID CcPfLogPageFault(IN PFILE_OBJECT FileObject, IN ULONGLONG FileOffset, IN ULONG Flags )
{
...
    SECTION_OBJECT_POINTERS *sectionObjectPointer = FileObject->SectionObjectPointer;
...
}

С каждым процессом связана трасса - структура данных, позволяющая вести связи между собираемыми данными. Полученный PSECTION_OBJECT_POINTERS сохраняется в этой структуре, также извлекаются и сохраняются DataSectionObject и ImageSectionObject.

Таким образом, префетчер запоминает страницы относящиеся к запускаемым файлам, соотносит их с файлами( секциями ) и после того, как таймаут трассы процесса закончится, накопленные данные будут сброшены в .pf файл, а при следующем запуске префетчер заранее загрузит необходимые файлы для процесса.

На этом кратенький, и не претендующий на истину в последней инстанции, обзор префетчера закончен.