В силу характера моей работы (ядро Windows) мне регулярно приходится анализировать дампы BSOD. Не единичны случаи, когда конечный пользователь успешно записывает только Мини-дампы, в которых сохраняются только значения регистров процессора и стек сбоев.
Других средств отладки клиентской машины просто нет. Но что делать, если нашего драйвера нет в стеке, а заказчик утверждает, что сбои начались после установки продукта и закончились после отключения драйвера для этого продукта? В моем случае хорошим решением оказалось хранение небольшого журнала последних событий в кольцевом буфере.
Остается только сохранить этот кольцевой буфер в дамп.
Ниже под катом я расскажу, как добавить данные в дамп из вашего драйвера.
А затем извлеките их, используя пикд .
Начиная с Windows XP SP1 и 2003 Server, система предоставляет драйверам возможность добавлять свои собственные данные в дамп сбоя ядра: Вторичные данные обратного вызова .
Чтобы система запросила эти данные у драйвера, вам необходимо зарегистрировать свою функцию обратного вызова, вызвав KeRegisterBugCheckReasonCallback .
При регистрации нужно указать адрес функции, которая будет вызываться при крахе ядра и в нашем случае ( BugCheckSecondaryDumpDataCallback ), предоставляют данные, которые необходимо дополнить дампом системы.
Указанная функция обратного вызова будет вызвана дважды:
- Система первый раз вызывает драйвер для определения размера буфера.
Уже на этом этапе ОС указывает максимальный размер входных данных ( KBUGCHECK_SECONDARY_DUMP_DATA .
MaximumAllowed), который можно сохранить в дамп.
Этот размер зависит от типа создаваемого дампа системы.
В Windows XP, когда установлена настройка записи мини-дампа, система предоставляет 4096 байт (одну страницу памяти).
- Второй раз система запрашивает данные сама.
Более подробную информацию можно найти в статье MSDN. Написание процедуры обратного вызова для проверки ошибок .
Как ни странно, но пример использования функции KeRegisterBugCheckReasonCallback , нет в сборники примеров для WDK .
Но пример был найден в KMDF (Kernel-Mode Driver Framework) от Microsoft с открытым исходным кодом.
fxbugcheckcallback.cpp : Регистрация обработчика: кусочки функции FxInitializeBugCheckDriverInfo
// // 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);
Реализация обработчика: функция FxpLibraryBugCheckCallback
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 , каждая структура имеет в своих полях версию и имя драйвера.
Ключом к данным в дампе является указанный при записи GUID, в нашем случае это {F87E4A4C-C5A1-4d2f-BFF0-D5DE63A5E4C3} .
Для просмотра данных, сохраненных в дампе, есть команда отладчика .
enumtag .
В результате выполнения команды мы увидим сырой дамп памяти.
Вот пример интересующих нас данных: 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 предлагает написать собственное расширение для отладчика :
Чтобы использовать эти данные более практично, рекомендуется написать собственное расширение отладчика.Но я один из разработчиков проекта пикд .
Модуль pykd может действовать как расширение отладчика, позволяющее использовать Python для автоматизации отладки.
Поэтому я покажу вам, как использовать его для извлечения и визуализации данных.
Сразу оговорюсь, что перечисление и получение Secondary Callback Data было добавлено в последнем (на момент написания статьи) релизе — 0.3.3.3. Поэтому, если у вас уже установлена более старая версия, вам необходимо обновить pykd ( Последний выпуск ).
В качестве тестового дампа я буду использовать файл, используемый для модульных тестов pykd — win8_x64_mem.cab Собственно, весь скрипт чтения и форматирования данных: kmdf_tagged.py 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("\0") 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)
Содержимое скипта, на мой взгляд, достаточно простое (функция разбора):
- Вызвав pykd.loadTaggedBuffer, мы читаем содержимое сохраненных данных, указывая GUID в качестве строкового аргумента в качестве ключевого аргумента.
- Используя информацию из символов отладки (создавая экземпляр объекта pykd.typeInfo), мы получаем смещение имени драйвера (name_offset), размер буфера имени драйвера (name_size) и размер одной структуры FX_DUMP_DRIVER_INFO_ENTRY (entry_size).
- Для каждой структуры FX_DUMP_DRIVER_INFO_ENTRY в вычитаемом буфере с использованием стандартного структура модуля Python распакуйте поля структуры, содержащей указатель на глобальный объект драйвера и версию.
А затем мы получаем имя драйвера, преобразуя его в строку, отбрасывая 0 символов.
И распечатайте полученные данные, используя ДМЛ , если текущая среда позволяет использовать этот язык разметки (функция print_command).
Если вы посмотрите на содержимое скрипта после функции синтаксического анализа, вы заметите, что скрипт может принимать аргумент. Скрипт kmdf_tagged.py написан для демонстрации того, как он работает в автономный режим (вне отладчика), если ему передан аргумент командной строки.
Скрипт интерпретирует каждый переданный аргумент как путь к файлу дампа, загружает этот дамп и извлекает из него целевые данные.
В частности, скрипт умеет обрабатывать файлы дампа в пакетном режиме: ~> for /R .
\dumps %i in (*.
*) do @python.exe kmdf_tagged.py %i ~\dumps\win8_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) ~\dumps\win8_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. Теги: #Windows #kernel #BSOD #pykd #KeRegisterBugCheckReasonCallback #BugCheckSecondaryDumpDataCallback #Системное программирование #отладка #разработка для Windows
-
Эрстед, Ганс Кристиан
19 Oct, 24 -
Остаться В Живых. Скада-Безопасность
19 Oct, 24