- PVSM.RU - https://www.pvsm.ru -
По роду своей деятельности (Windows Kernel) мне регулярно приходится разбирать дампы BSOD'ов. Не единичны случаи, когда у конечного пользователя успешно пишутся только Mini-дампы, в которых сохраняется только значение регистров процессора и стек падения. А другого средства отладки клиентской машины просто нет. Но что делать, если в стеке нет нашего драйвера, а заказчик настаивает, что падения начались после установки продукта и закончились после отключения драйвера этого продукта? В моем случае хорошим решением оказалось ведение небольшого журнала последних событий в циклическом буфере. Осталось только сохранить этот циклический буфер в дампе.
Под катом я расскажу, как из своего драйвера добавить в дамп данные. А затем извлечь их, используя pykd [1].
Начиная с Windows XP SP1 и 2003 Server система предоставляет возможность драйверам добавлять в дамп падения ядра собственные данные: Secondary Callback Data. Для того, что бы система запросила эти данные у драйвера, необходимо зарегистрировать свою callback-функцию вызовом KeRegisterBugCheckReasonCallback [2]. При регистрации нужно указать адрес функции, которая будет вызваться при падении ядра и, в нашем случае (BugCheckSecondaryDumpDataCallback [3]), предоставлять данные, которыми нужно дополнить системный дамп. Указанная callback-функция будет вызвана дважды:
Из-за того, что callback-функция вызывается в момент падения ядра операционной системы, на код этой функции накладываются серьезные ограничения: не использовать выделение памяти (все выделяется заранее), не обращаться к Paged-памяти (подкачка страниц невозможна), не использовать механизмы синхронизации (риск взаимоблокировок). Более подробные детали можно найти в статье MSDN Writing a Bug Check Callback Routine [5].
Достаточно странно, но примера использования функции KeRegisterBugCheckReasonCallback, нет в коллекции примеров к WDK [6]. Зато пример обнаружился в открытых Microsoft'ом исходниках KMDF (Kernel-Mode Driver Framework) — fxbugcheckcallback.cpp [7]:
//
// The KeRegisterBugCheckReasonCallback exists for xp sp1 and above. So
// check whether this function is defined on the current OS and register
// for the bugcheck callback only if this function is defined.
//
RtlInitUnicodeString(&funcName, L"KeRegisterBugCheckReasonCallback");
funcPtr = (PFN_KE_REGISTER_BUGCHECK_REASON_CALLBACK)
MmGetSystemRoutineAddress(&funcName);
if (NULL == funcPtr) {
goto Done;
}
//
// Initialize the callback record.
//
KeInitializeCallbackRecord(callbackRecord);
//
// Register the bugcheck callback.
//
funcPtr(callbackRecord,
FxpLibraryBugCheckCallback,
KbCallbackSecondaryDumpData,
(PUCHAR)WdfLdrType);
ASSERT(callbackRecord->CallbackRoutine != NULL);
VOID
FxpLibraryBugCheckCallback(
__in KBUGCHECK_CALLBACK_REASON Reason,
__in PKBUGCHECK_REASON_CALLBACK_RECORD /* Record */,
__inout PVOID ReasonSpecificData,
__in ULONG ReasonSpecificLength
)
/*++
Routine Description:
Global (framework-library) BugCheck callback routine for WDF
Arguments:
Reason - Must be KbCallbackSecondaryData
Record - Supplies the bugcheck record previously registered
ReasonSpecificData - Pointer to KBUGCHECK_SECONDARY_DUMP_DATA
ReasonSpecificLength - Sizeof(ReasonSpecificData)
Return Value:
None
Notes:
When a bugcheck happens the kernel bugcheck processor will make two passes
of all registered BugCheckCallbackRecord routines. The first pass, called
the "sizing pass" essentially queries all the callbacks to collect the
total size of the secondary dump data. In the second pass the actual data
is captured to the dump.
--*/
{
PKBUGCHECK_SECONDARY_DUMP_DATA dumpData;
ULONG dumpSize;
UNREFERENCED_PARAMETER(Reason);
UNREFERENCED_PARAMETER(ReasonSpecificLength);
ASSERT(ReasonSpecificLength >= sizeof(KBUGCHECK_SECONDARY_DUMP_DATA));
ASSERT(Reason == KbCallbackSecondaryDumpData);
dumpData = (PKBUGCHECK_SECONDARY_DUMP_DATA) ReasonSpecificData;
dumpSize = FxLibraryGlobals.BugCheckDriverInfoIndex *
sizeof(FX_DUMP_DRIVER_INFO_ENTRY);
//
// See if the bugcheck driver info is more than can fit in the dump
//
if (dumpData->MaximumAllowed < dumpSize) {
dumpSize = EXP_ALIGN_DOWN_ON_BOUNDARY(
dumpData->MaximumAllowed,
sizeof(FX_DUMP_DRIVER_INFO_ENTRY));
}
if (0 == dumpSize) {
goto Done;
}
//
// Ok, provide the info about the bugcheck data.
//
dumpData->OutBuffer = FxLibraryGlobals.BugCheckDriverInfo;
dumpData->OutBufferLength = dumpSize;
dumpData->Guid = WdfDumpGuid2;
Done:;
}
В качестве демонстрации, именно эти данные и будем извлекать из дампа. Данными является массив структур FX_DUMP_DRIVER_INFO_ENTRY [8], каждая структура имеет в своих полях версию и имя драйвера. Ключом к данным в дампе выступает указанный при записи GUID, в нашем случае это {F87E4A4C-C5A1-4d2f-BFF0-D5DE63A5E4C3} [9].
Для просмотра сохраненных в дампе данных есть команда отладчика .enumtag [10]. В результате выполнения команды мы увидим сырой дамп памяти. Вот пример интересующих нас данных:
1: kd> .enumtag
{65755A40-F146-43EA-8C9136B85728FD35} - 0x0 bytes
<...>
{F87E4A4C-C5A1-4D2F-BFF0D5DE63A5E4C3} - 0x508 bytes
00 00 00 00 00 00 00 00 01 00 00 00 0D 00 00 00 ................
00 00 00 00 57 64 66 30 31 30 30 30 00 00 00 00 ....Wdf01000....
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 90 AC 55 00 00 E0 FF FF ..........U.....
01 00 00 00 0B 00 00 00 00 00 00 00 61 63 70 69 ............acpi
65 78 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ex..............
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
30 81 F6 00 00 E0 FF FF 01 00 00 00 0B 00 00 00 0...............
00 00 00 00 6D 73 69 73 61 64 72 76 00 00 00 00 ....msisadrv....
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 A0 D3 EB 00 00 E0 FF FF ................
01 00 00 00 0B 00 00 00 00 00 00 00 76 64 72 76 ............vdrv
<...>
Работать с таким форматом можно, но не удобно. Microsoft предлагает написать свое расширение к отладчику [11]:
To use this data in a more practical way, it is recommended that you write your own debugger extension.
Но я являюсь одним из разработчиков проекта pykd [1]. Модуль pykd может выступать расширением отладчика, позволяющим использовать Python для автоматизации отладки. Поэтому я покажу как с его помощью извлечь и визуализировать данные. Сразу оговорюсь, что перечисление и извлечение Secondary Callback Data было добавлено в последнем (на момент написания статьи) релизе — 0.3.3.3. Поэтому, если у вас уже установлена более старая версия, нужно обновить pykd (Last Release [12]).
В качестве тестового дампа я буду использовать файл, используемый для unit-тестов pykd — win8_x64_mem.cab [13]
Собственно, весь скрипт чтения и форматирования данных:
import os
import sys
import pykd
import struct
def print_command(command):
if pykd.getDebugOptions() & pykd.debugOptions.PreferDml:
pykd.dprint( '<exec cmd="{}">{}</exec>'.format(command, command),
dml = True )
else:
pykd.dprint( command )
def parse():
buff = bytearray( pykd.loadTaggedBuffer("F87E4A4C-C5A1-4d2f-BFF0-D5DE63A5E4C3") )
entry_type = pykd.typeInfo("Wdf01000!_FX_DUMP_DRIVER_INFO_ENTRY")
_struct = struct.Struct( "<{}III".format("Q" if pykd.is64bitSystem() else "L") )
name_offset = entry_type.fieldOffset("DriverName")
name_size = entry_type.DriverName.size()
entry_size = entry_type.size()
if len(buff) % entry_size:
raise RuntimeError( "The buffer size ({}) is not a multiple of entry size ({})".format(len(buff), entry_size) )
print("[FxLibraryGlobals.BugCheckDriverInfo]")
while len(buff):
ptr, mj, mn, build = _struct.unpack_from(buff)
name = str(buff[name_offset : name_offset + name_size]).strip("")
command = "!drvobj {} 7".format(name)
print_command( command )
pykd.dprint( " " * (24 - len(name)) )
pykd.dprint( " {:12} ".format("({}.{}.{})".format(mj, mn, build)) )
if ptr:
command = "dx ((Wdf01000!{})0x{:x})".format(entry_type.FxDriverGlobals.name(), ptr)
print_command( command )
pykd.dprintln( "" )
buff = buff[entry_size:]
if __name__ == "__main__":
if len(sys.argv) == 1:
parse()
else:
for file_name in sys.argv[1:]:
print(file_name)
dump_id = pykd.loadDump(file_name)
parse()
pykd.closeDump(dump_id)
Содержимое скипта, на мой взгляд, достаточно простое (функция parse):
Исполняем скрипт в отладчике WinDbg:
Если посмотреть на содержимое скрипта после функции parse, то можно заметить, что скрипт может принимать аргумент. Скрипт kmdf_tagged.py написан так, что бы продемонстрировать работу в автономном режиме (вне отладчика), если ему указан аргумент командной строки. Каждый переданный аргумент скрипт трактует как путь в файлу дампа, загружает этот дамп и извлекает из него целевые данные. В частности, скриптом можно в пакетном режиме обработать файлы дампов:
~> for /R .dumps %i in (*.*) do @python.exe kmdf_tagged.py %i
~dumpswin8_x64_mem.cab
[FxLibraryGlobals.BugCheckDriverInfo]
!drvobj Wdf01000 7 (1.13.0)
!drvobj acpiex 7 (1.11.0) dx ((Wdf01000!_FX_DRIVER_GLOBALS*)0xffffe0000055ac90)
<...>
!drvobj PEAUTH 7 (1.7.6001) dx ((Wdf01000!_FX_DRIVER_GLOBALS*)0xffffe000022081c0)
~dumpswin8_x64_mem2.cab
[FxLibraryGlobals.BugCheckDriverInfo]
!drvobj Wdf01000 7 (1.13.0)
!drvobj acpiex 7 (1.11.0) dx ((Wdf01000!_FX_DRIVER_GLOBALS*)0xffffe0000055ac90)
<...>
!drvobj PEAUTH 7 (1.7.6001) dx ((Wdf01000!_FX_DRIVER_GLOBALS*)0xffffe000022081c0)
Надеюсь, что мой опыт (и содержимое этой статьи) будет кому-то полезным. А количество BSOD'ов, причина которых остается загадкой, будет стремиться к 0.
Автор: Aleksey R.
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/windows/274877
Ссылки в тексте:
[1] pykd: https://githomelab.ru/pykd/pykd
[2] KeRegisterBugCheckReasonCallback: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-keregisterbugcheckreasoncallback
[3] BugCheckSecondaryDumpDataCallback: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nc-wdm-kbugcheck_reason_callback_routine
[4] KBUGCHECK_SECONDARY_DUMP_DATA: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_kbugcheck_secondary_dump_data
[5] Writing a Bug Check Callback Routine: https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/writing-a-bug-check-callback-routine
[6] коллекции примеров к WDK: https://github.com/Microsoft/Windows-driver-samples
[7] fxbugcheckcallback.cpp: https://github.com/Microsoft/Windows-Driver-Frameworks/blob/b72d691fbfd505dcb3c23c4bfc37d5322dea4990/src/framework/kmdf/src/core/fxbugcheckcallback.cpp
[8] FX_DUMP_DRIVER_INFO_ENTRY: https://github.com/Microsoft/Windows-Driver-Frameworks/blob/b72d691fbfd505dcb3c23c4bfc37d5322dea4990/src/framework/kmdf/inc/private/fxbugcheck.h#L76
[9] {F87E4A4C-C5A1-4d2f-BFF0-D5DE63A5E4C3}: https://github.com/Microsoft/Windows-Driver-Frameworks/blob/b72d691fbfd505dcb3c23c4bfc37d5322dea4990/src/framework/shared/inc/private/common/fxifr.h#L65
[10] .enumtag: https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-enumtag--enumerate-secondary-callback-data-
[11] Microsoft предлагает написать свое расширение к отладчику: https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/reading-bug-check-callback-data#span-iddisplaying-secondary-callback-dataspanspan-iddisplaying-secondary-callback-dataspandisplaying-secondary-callback-data
[12] Last Release: https://githomelab.ru/pykd/pykd/wikis/Last%20Release
[13] win8_x64_mem.cab: https://githomelab.ru/kdlibcpp/kdlibcpp/blob/dev-1.0/kdlib/tests/dumps/win8_x64_mem.cab
[14] python-модуля struct: https://docs.python.org/2/library/struct.html
[15] DML: https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-markup-language-commands
[16] Источник: https://habrahabr.ru/post/350406/?utm_source=habrahabr&utm_medium=rss&utm_campaign=350406
Нажмите здесь для печати.