利用logger.vxd记录VXD文件输出
发表于:2007-07-14来源:作者:点击数:
标签:
北京2865信箱160分箱 冉林仓 1.引言: 在调试程序的时候,我们经常使用日志文件记录调试文件的运行结果,跟踪程序运行的流程。通过这个文件,即便在调试过程中间系统崩溃,我们也能够从生成的日志文件中发现些可用信息。有的时候,这个日志文件的作用并不亚
北京2865信箱160分箱 冉林仓
1.引言:
在调试程序的时候,我们经常使用日志文件记录调试文件的运行结果,跟踪程序运行的流程。通过这个文件,即便在调试过程中间系统崩溃,我们也能够从生成的日志文件中发现些可用信息。有的时候,这个日志文件的作用并不亚于一个调试器。
在调试用户态应用程序的时候,特别是那些与Com、外壳、钩子函数打交道的动态链接库的时候,
程序员习惯使用WritePrivateProfileString函数或者WritePrivateProfileInt函数,把程序运行的反馈信息记录到一个文本文件中;在调试wdm驱动程序的时候,编程人员也可以通过下列函数(ZwCreateFile、ZwWriteFile和ZwClose)找到替代方案。这些生成的日志文件,给程序的调试人员带来了极大的方便。然而,在编写VXD程序的时候,麻烦出现了,系统提供的VXD服务好像没有一个能够把日志信息输出到文件中,即便VToolsD提供了一些文件IO函数(比如:i_open、i_close、i_write等等),这些函数也只能在VXD初始化阶段才能被调用。一旦INIT_COMPLETE 消息被发送之后,这些函数将不能调用。文件系统钩子服务的确可以解决这个问题,但是其烦琐的编程让驱动
开发人员望而却步。这样,我们每次调试VXD程序的时候都不得不启动一个调试器来观察vxd输出结果,这对我们驱动程序编程人员可以说是非常的不便。
其实Win 95和Win 98 系统都提供了一个名叫logger.vxd的文件,顾名思义,我们可以知道这个文件是生成日志记录文件用的。遗憾的是,在微软提供的DDK文档中,对这个VXD提供的服务只字不提,本文正是在借鉴文献(1)的基础上的对这个VXD的输出服务进行说明,以便任何一个VXD的编程人员都能使用logger.vxd记录程序输出。
2.实现的原理:
A).在VXD中调用VXD
就像RING3 的DLL以一个标准的方式供EXE和DLL使用其输出函数一样,VXD中的函数也可以被其它的VXD按某种方式调用,当生成VXD时,所有可以被其它VXD调用的函数都被列在一个数组中,每个这样的函数也称作一个服务(SERVICE)。当一个VXD调用另一个VXD中的功能时,并不使用服务的名字,而是使用它在数组中的索引号,下面就是一个典型的声明:
Begin_Service_Table(LOGR0)
LOGR0_Service(LOGR0_Svc_GetVersion)
LOGR0_Service(LOGR0_Svc_PrintLog)
LOGR0_Service(LOGR0_Svc_CloseLog)
LOGR0_Service(LOGR0_Svc_OpenLog)
LOGR0_Service(LOGR0_Svc_FlushLog)
End_Service_Table
一个客户端VXD对
服务器端VXD的LOGR0_Svc_GetVersion调用将调用0号服务, LOGR0_Svc_PrintLog服务调用将使用1号服务,其它依次类推。
为了存取服务端VXD的一个输出服务函数,客户端的VXD在调用VXDCall或VXDJmp宏的时候,必须传递一个服务端VXD的设备ID标识,和它的一个输出函数服务号。宏能够把它扩展成一个INT 20H中断,其后跟了一个双字,并根据设备ID和服务号查找目标函数的地址。当目标函数找到后,INT 20H和其后的双字就被CALL DWORD PTR [目标函数]所代替,它们恰好都是6个字节。有时这种机理也被称为RING0的动态链接。
B).VXD输出信息的获得
察看VXD输出服务需要Soft ICE 工具,通过输入VXD LOGGER 可以看到有关这个VXD的信息。在这些信息中,其中最主要的信息是,它是否为一个可动态加载的VXD程序、它总共输出了几个VXD服务、它的设备ID号是什么。至于这些服务接口类型,就需要我们逆向工程进行反汇编来挖掘了,通过反汇编,我们至少从栈帧指针使用中了解参数的个数。接下来就要依靠我们平时积累的编程经验来推测参数类型了。譬如,按照惯例,VXD的第一个服务必须为GetVersion,通过反汇编,我们会发现logger.vxd也不例外。文件打开、读写、关闭函数一般情况下经常需要一些文件名、文件句柄等参数信息,事实上logger.vxd的服务函数同标准的C函数调用十分类似。这和我们的猜想是吻合的。
C).logger.vxd的输出函数
logger.vxd共有5个输出服务,这5个输出服务接口函数是:
DWORD LOGR0_GetVersion();
HANDLE LOGR0_OpenFile(const char * szFileName,DWORD BufferSize);
HANDLE LOGR0_CloseFile(HANDLE hFile);
void LOGR0_Printf(HANDLE hFile, const char *format,...);
DWORD LOGR0_Flush(HANDLE hFile);
3.logger.vxd的使用
在使用logger.vxd之前,客户端的VXD必须调用LOGR0_GetVersion()来确认logger.vxd本身是否可用.由于logger.vxd是一个动态的VXD ,不会随
windows系统的启动自动加载。当程序检测到logger.vxd尚未加载时,先尝试加载logger.vxd。加载完毕后,它本身并不需要卸载,这主要是因为,任何一个通过服务表输出ring0服务的VXD程序,只要它的一个服务被客户端VXD使用,它本身就不可卸载。
LOGR0_OpenFile将以写、添加方式打开一个记录文件,你可以为它指定一个完整路径,否则它就产生在windows目录中。这个文件将一直保持到调用LOGR0_CloseFile()为止。缓冲区的大小,你可以指定为零,logger.vxd会自动为你设定一个值,这个值在大多数情况情况下能够满足你的要求。注意你必须把记录文件的大小控制在256k之内,超过这个尺寸,logger.vxd将不再记录。
LOGR0_Printf函数类似于一个sprintf函数,只不过它把输出结果定向到一个文件,而不是字符串,它需要一个文件句柄来标识你要写的目标文件,这个句柄由LOGR0_OpenFile()返回值获得。全部记录完毕之后,你可以调用LOGR0_CloseFile()关闭文件。
LOGR0_Flush函数,强制保存在文件缓冲区的任何数据写到文件中。
为了更方便地使用注册函数,你除了可以直接使用上述函数之外,还可以使用程序封装的CLogFile类。使用时你只要派生一个CLogFile类的事例,即可调用其trace成员函数,对象释放时,析构函数会自动关闭日志文件。
Logger.vxd会自动地在你的VXD程序的记录文件中添加日志信息,你不需要为每一行写入的时间信息煞费苦心了。
logger.vxd 看起来像是基于IFSMgr_Ring0_FileIO创建,使用可安装文件系统的优势在于你不仅可以打开一个本地的记录文件,还可以打开一个
网络驱动器的一个远程文件,利用网络,把它保存到另外一个远程计算机上,这在你的本机文件由于某种原因不可存取时,特别有效。
下面是一段记录的日志文件信息:
07-09-2001 15:54:41.61 - Opened ThreadMon Log
07-09-2001 15:54:44.22 - Thread D5A4DD74 got CREATED; -710615692 threads are being monitored
07-09-2001 15:54:44.22 - Thread D6715D74 got CREATED; -697213580 threads are being monitored
07-09-2001 15:54:45.31 - Thread D6715D74 got CREATED; -697213580 threads are being monitored
07-09-2001 15:54:45.33 - Thread D6719DDC got DESTROYED; it ran for -697197092 msec
07-09-2001 15:54:45.33 - Thread C0FA9D88 got DESTROYED; it ran for -1057317496 msec
07-09-2001 15:54:47.70 - Thread D5EA9DDC got DESTROYED; it ran for -706044452 msec
07-09-2001 15:54:50.82 - Closed ThreadMon Log
4.程序使用:
A)使用函数接口
1.包含logger.vxd 服务函数文件
#include
2.声明一个静态文件柄
static HANDLE hLogFile;
3.在OnSysDynamicDeviceInit()中加入:
DWORD ver = LOGR0_GetVersion();
if (ver == 0)
return FALSE; // logger.vxd 不可用
hLogFile = LOGR0_OpenFile("文件名", NULL);
if (hLogFile == NULL)
return FALSE; //无法打开文件
LOGR0_Printf(hLogFile, "打开文件成功");
return TRUE;
4.在OnSysDynamicDeviceExit()函数过程中加入
if (hLogFile) {
LOGR0_Printf(hLogFile, "关闭记录文件\n");
LOGR0_CloseFile(hLogFile);//保存文件
}
5.在其它的消息处理函数中可以自由地使用LOGR0_Printf函数
LOGR0_Printf(hLogFile, " My Result is %d \n",Result);
B).使用CLogFile类举例
1.声明一个静态全局变量
static CLogFile *pLogFile=NULL;
2.在OnSysDynamicDeviceInit()中加入:
pLogFile=new CLogFile("Test.ini",NULL);
if(!pLogFile->m_bActive)return FALSE;//logger.vxd加载不成功时
if(pLogFile->m_hFile==NULL) return FALSE;//文件打开不成功时
pLogFile->trace ( "Opened ThreadMon Log\n");
return TRUE;
3.在OnSysDynamicDeviceExit()函数过程中加入
pLogFile->trace ("Closed ThreadMon Log\n");
delete pLogFile;//释放事例分配的内存,这时将调用析构函数,关闭打开文件
return TRUE;
4.在程序中使用pLogFile->trace 记录日志信息。
5.补充说明
本程序在VISUAL C++6 Pwin98 环境下,用VtoolsD调试通过。
原文转自:http://www.ltesting.net