在上一章 命名管道IPC通信 中,我们学习了如何为驱动程序安装一部"电话",让外部的控制软件可以实时地向它发送命令。在这个过程中,我们反复看到了一个名为 vddlog
的函数,它似乎在驱动程序的关键时刻记录着各种信息。
这些信息最终去了哪里?这个日志系统是如何工作的?为什么它对于一个驱动程序的健康运行至关重要?本章将带你深入 MttVDD
的"神经中枢"------日志系统,并了解其背后的专业技术:WPP Tracing。
飞机的"黑匣子"
想象一下,你正在开发一个极其复杂的软件,比如一架飞机的飞行控制系统。当飞机试飞时,如果出现了一点小故障,你该如何知道问题出在哪里?你不可能在飞机飞到一半时暂停它,然后连接调试器一步步检查代码。
你需要的是一个"黑匣子"。这个黑匣子会不间断地记录下飞行过程中的所有关键数据:高度、速度、引擎转速、操作指令等等。当飞机降落后,工程师就可以通过分析这些日志,精确地回溯故障发生的全过程。
驱动程序日志系统就是我们 MttVDD
的"黑匣子"。由于驱动程序运行在操作系统的核心地带,我们无法像调试普通桌面程序那样轻松地设置断点或打印信息。日志系统是我们观察驱动内部世界、诊断问题的几乎唯一窗口。
MttVDD
中的 vddlog
函数,就是我们在代码中安放的"传感器",它负责将运行状态、警告信息和错误报告记录到这个"黑匣子"里。
什么是 WPP Tracing?
在专业的 Windows 驱动开发中,最常用、最高效的日志技术叫做 WPP Tracing (Windows Software Trace Preprocessor)。
你可能会想:"记录日志不就是往文件里写字符串吗?这有什么难的?"
问题在于性能。驱动程序是对性能极其敏感的代码。如果每一次记录日志都需要执行一次缓慢的文件写入操作,那么在驱动满负荷运行时,大量的日志记录本身就会成为性能瓶瓶颈,导致画面卡顿甚至系统崩溃。
WPP Tracing 用一种极其聪明的方式解决了这个问题:
- 预处理 :它不是一个普通的函数库,而是一个预处理器 。在你的代码被真正编译之前,WPP 会扫描你的代码,找到所有日志记录调用(例如
Trace(...)
),然后把它们替换成一段高度优化的、极速的二进制事件记录代码。 - 轻量级事件:它记录的不是长长的字符串,而是非常紧凑的二进制事件。这就像发电报,只发送关键的编码信息,而不是长篇大论的信件。
- 事后解码 :这些二进制日志(通常保存在
.etl
文件中)在平时是无法直接阅读的。你需要使用专门的工具(如TraceView
)和驱动程序的调试信息文件(.pdb
)来将它们"解码"回人类可读的文本。
这个过程就像是,你在代码里写的是"引擎温度过高",WPP 预处理器把它变成了"信号 #123",驱动运行时就飞快地记录下这个信号。事后,你用解码器一对照,就知道"信号 #123"代表"引擎温度过高"。因为记录一个数字远比记录一长串字符快得多,所以 WPP 对性能的影响微乎其微。
WPP 在 MttVDD
中的配置
虽然 MttVDD
为了方便初学者,主要使用了一个更简单的自定义文件日志函数 vddlog
,但它完整地保留了 WPP Tracing 的标准配置结构。这为未来进行更专业的性能分析打下了基础。
让我们看看 Trace.h
文件,了解一下 WPP 是如何配置的。
1. 注册唯一的"无线电频道"
首先,我们需要为我们的驱动日志定义一个全球唯一标识符(GUID)。这就像是为我们的"黑匣子"申请一个独一无二的无线电频率,确保它的信号不会和系统中其他成千上万个程序的日志信号混淆。
c++
// 文件: Trace.h
#define WPP_CONTROL_GUIDS \
WPP_DEFINE_CONTROL_GUID( \
MyDriver1TraceGuid, (b254994f,46e6,4718,80a0,0a3aa50d6ce4), \
\
WPP_DEFINE_BIT(MYDRIVER_ALL_INFO) \
/* ... 其他日志级别定义 ... */ \
)
MyDriver1TraceGuid
就是这个唯一的"频率名称",后面的那一长串数字就是它的"频率值"。
2. 定义"电报"的格式
接下来,我们需要告诉 WPP 我们的日志函数长什么样。这就像是和 WPP 约定好我们的"电报"格式。
c++
// 文件: Trace.h
//
// begin_wpp config
// FUNC Trace{FLAG=MYDRIVER_ALL_INFO}(LEVEL, MSG, ...);
// end_wpp
//
这短短几行注释有着神奇的魔力。它告诉 WPP 预处理器:
- 寻找一个名为
Trace
的函数。 - 这个函数接受一个
LEVEL
(日志级别)、一个MSG
(格式化字符串)和可变参数...
(就像printf
一样)。 - 当 WPP 找到这样的调用时,就施展魔法,把它转换成高效的二进制事件。
有了这个配置,我们理论上就可以在代码的任何地方像这样调用 WPP 来记录日志了:
c++
// 这是一个 WPP 的标准用法示例
// Trace(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "创建了 %d 个显示器", numVirtualDisplays);
这个调用会被转换成一个非常小的二进制包,里面包含了格式字符串的标识符和 numVirtualDisplays
的值,然后被快速地写入到内存缓冲区中。
MttVDD
的简化实践:vddlog
函数
正如前面所说,为了简单和直观,MttVDD
在日常开发中主要使用了一个自定义的、基于文本文件的日志函数------vddlog
。它不像 WPP 那样追求极致性能,但它的优点是极其方便:日志内容是纯文本,可以直接用记事本打开查看,无需任何特殊工具。
vddlog
函数的实现位于 Driver.cpp
中,让我们来看看它的工作流程。
vddlog
代码解析
vddlog
的代码逻辑清晰地反映了上图的流程。这是一个简化版的实现:
cpp
// 文件: Driver.cpp
void vddlog(const char* type, const char* message) {
// 1. 检查日志功能是否已在配置中启用
if (!logsEnabled) {
return;
}
// 2. 准备日志文件路径和时间戳
wstring logsDir = confpath + L"\\Logs";
// ... (创建目录的代码) ...
// ... (获取当前时间并格式化成字符串 ss 的代码) ...
// 3. 将类型字符(如 'i')转换成完整的单词(如 "INFO")
string logType;
// ... (switch-case 语句来设置 logType) ...
// 4. 如果是 "DEBUG" 级别,还需要检查 debugLogs 开关
if (logType == "DEBUG" && !debugLogs) {
return;
}
// 5. 打开日志文件并追加内容
// ... (fopen_s 打开文件) ...
fprintf(logFile, "[%s] [%s] %s\n", ss.str().c_str(), logType.c_str(), message);
fclose(logFile);
// 6. 如果启用了管道日志,并且有客户端连接,则通过管道发送
if (sendLogsThroughPipe && g_pipeHandle != INVALID_HANDLE_VALUE) {
// ... (通过 WriteFile 将日志消息发送到命名管道) ...
}
}
这个函数完美地结合了我们在前面章节学到的知识:
- 它的行为由 配置与设置管理 系统中的
logsEnabled
和debugLogs
全局变量控制。 - 它能通过 命名管道IPC通信 将日志实时发送给配套的控制软件,让开发者可以在图形界面上实时监控驱动状态。
如何查看日志?
使用 vddlog
的最大好处就是查看日志非常简单。
- 确保日志已启用 :打开
C:\VirtualDisplayDriver\vdd_settings.xml
文件,确保<logging>
标签的值是true
。 - 找到日志文件 :日志文件位于驱动安装目录下的
Logs
文件夹中,通常是C:\VirtualDisplayDriver\Logs
。 - 打开查看 :文件会根据日期命名,例如
log_2023-10-27.txt
。你可以用任何文本编辑器(如记事本、VS Code)打开它。
你会看到类似这样的内容:
ini
[2023-10-27 10:30:00] [INFO] Driver Starting
[2023-10-27 10:30:00] [INFO] IDDCX Version: 0x10400
[2023-10-27 10:30:01] [DEBUG] Adapter Caps Initialized: ...
[2023-10-27 10:30:01] [INFO] Creating Monitor: 1
[2023-10-27 10:30:05] [INFO] Unasigning Swapchain. Processing will be stopped.
[2023-10-27 10:30:10] [PIPE] Client Connected
每一行都清晰地记录了时间、日志级别和具体信息,让排查问题变得一目了然。
总结
在本章中,我们深入了解了驱动程序不可或缺的"黑匣子"------日志系统。我们学到了:
- 日志为何重要:它是我们在不中断驱动运行的情况下,观察其内部状态和诊断问题的关键工具。
- WPP Tracing 是什么:一种专业、高性能的 Windows 标准日志技术,它通过预处理器将日志调用转换为轻量级的二进制事件,对性能影响极小。
MttVDD
的实践 (vddlog
):项目采用了一个更简单直观的自定义文件日志系统,它将易于阅读的文本日志写入文件中,非常适合学习和常规调试。- 日志系统的联动 :
vddlog
的行为受到配置文件、全局开关的控制,并且能与命名管道系统联动,将日志实时传输到外部应用。
至此,我们已经完成了 MttVDD
核心功能的全部探索之旅。
你已经掌握了构建一个现代虚拟显示驱动程序所需的所有核心概念。现在,最好的学习方式就是亲自动手去探索和实验。尝试修改配置文件来创建不同能力的显示器,阅读代码来理解每个回调函数的具体作用,甚至可以尝试为你自己的应用场景添加新的命名管道命令。