суббота, 31 марта 2012 г.

Карта распределения Page Fault's при старте приложения

В Windows используется концепция demand paging, означающая, что физическая память выделяется только при обращении к виртуальному адресу, но никак не при выделении виртуальных адресов.

На уровне ОС это реализуется через дерево VAD, привязанное к процессу(EPROCESS->VadRoot) и описывающее диапазоны и другие атрибуты этой выделенной ранее через сервисные функции памяти. При обращении к памяти срабатывает обработчик страничных фолтов(KiTrap0E), который через VAD ищет соответствующий диапазон, и в случае успеха(для private memory это наличие атрибута закоммиченой памяти) из free/standby/zeroed списков выделяется физическая страница и заполняется PDE/PTE(проставляются атрибуты, valid, pfn и т.д.).

Стало интересно, какие странички подгружаются первыми при старте приложения.
Логично было бы предположить, что это страничка с apc диспатчером и другие страницы ntdll.dll, однако это не так.

Оказалось, что первая страничка это KUSER_SHARED_DATA, к которой идет обращение при создании секции для ехе процесса:

MiMapViewOfImageSection()
{
...
    if ( ControlArea->Segment->u2.ImageInformation->ImageContainsCode &&
         ((ControlArea->Segment->u2.ImageInformation->Machine < USER_SHARED_DATA->ImageNumberLow) ||
        (ControlArea->Segment->u2.ImageInformation->Machine > USER_SHARED_DATA->ImageNumberHigh) )
...
}

Затем при создании и инициализации PEB будет второй #PF:

MmCreatePeb()
{
...
    Status = MiCreatePebOrTeb (TargetProcess, sizeof(PEB), (PVOID)&PebBase);
...
    try
    {
        PebBase->InheritedAddressSpace = InitialPeb->InheritedAddressSpace;
...
}

Следом идет создание TEB для первичного потока, при её инициализации случится еще один #PF:

MmCreateTeb()
{
...
    Status = MiCreatePebOrTeb (TargetProcess, TebSize, (PVOID) &TebBase);
...
    try
    {
        TebBase->NtTib.ExceptionList = EXCEPTION_CHAIN_END; // только на x86
...
}

Перед тем, как запустить юзермодную APC стартующую поток, ядро копирует контекст в юзермодный стек, что приводит к #PF:

KiInitializeUserApc()
{
...
    RtlCopyMemory ((PULONG)(UserStack + sizeof(KAPC_RECORD)), &ContextFrame, sizeof(CONTEXT));
...
    TrapFrame->Eip = (ULONG)KeUserApcDispatcher;
...
}

И только потом, при переходе в user mode, при выполнении KiUserApcDispatcher подгрузится страничка ntdll.dll.
Хотя подгрузится это не совсем подходящее слово, т.к. подгрузилась с диска эта dll еще при старте ОС, а в последующих случаях, т.е. при старте новых процессов все её странички разрешались ввиде soft fault's, т.е. из памяти, без обращений к диску.

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

Необходимость может наступить, например, при достижении лимита(данные для х86):

ULONG PspMinimumWorkingSet = 20;
ULONG PspMaximumWorkingSet = 45;

Soft fault's практически бесплатны в плане временных затрат( мне думается самое затратное там - это переключение контекста при срабатывании обработчика страничного фолта ), и в дальнейшем при необходимости, ранее выкинутые странички будут вновь добавлены в рабочий набор процесса.

воскресенье, 11 марта 2012 г.

Определение id некоторых сервисных функций без таблиц

Многие программы на х86 перехватывают функции через патч SDT, индексы этих функций меняются в зависимости от версии ОС.

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

Рассмотрим любую Zw функцию, экпортируемую ядром, например ZwDuplicateToken:

nt!ZwDuplicateToken:
804fe30c b845000000      mov     eax,45h
804fe311 8d542404          lea     edx,[esp+4]
804fe315 9c                    pushfd
804fe316 6a08                 push    8
804fe318 e874f10300       call    nt!KiSystemService (8053d491)
804fe31d c21800             ret     18h

Можно заметить, что id можно получить отсюда, например таким кодом:

offsetToId = (PUCHAR)ZwDuplicateToken;
id = *(PULONG)(offsetToId + 1);

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