驱动学习笔记(二)(三)
驱动学习笔记(二)
1:IRP--上节学到几个对象,驱动对象,设备对象,字符串还有IRP请求,
IRP有两个基本的属性,即MajorFunction和MinorFunction,分别记录IRP的主类型和子类型。操作系统
根据MajorFunction决定将IRP分发到哪个派遣例程,然后派遣例程根据MinorFunction进行细分处理。
文件I/O的相关函数例如CreateFile、ReadFile、WriteFile、CloseHandle等分别会引发操系统产生
IRP_MJ_CREATE、IRP_MJ_READ、IRP_MJ_WRITE、IRP_MJ_CLOSE等不同的IRP,这些IRP会作被传送到驱动
程序的相应派遣例程中。
这节详细看下IRP
使用命令!strct _IRP查看IRP结构
kd> !strct _IRP
struct _IRP (sizeof=112)
+00 int16 Type--IRP类型
+02 uint16 Size--IRP大小
+04 struct _MDL *MdlAddress--内存描述符表指针,这是一个缓冲区,用来存放内核请求的
+08 uint32 Flags--文件系统驱动所用的域
+0c union __unnamed14 AssociatedIrp
+0c struct _IRP *MasterIrp
+0c int32 IrpCount
+0c void *SystemBuffer--系统缓冲区,IRP也可能用这个,取决于I/O请求方式
+10 struct _LIST_ENTRY ThreadListEntry
+10 struct _LIST_ENTRY *Flink
+14 struct _LIST_ENTRY *Blink
+18 struct _IO_STATUS_BLOCK IoStatus--I/O状态,
+18 int32 Status
+18 void *Pointer
+1c uint32 Information
+20 char RequestorMode--表示原始请求模式,分为用户模式和内核模式
+21 byte PendingReturned--如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING
+22 char StackCount
+23 char CurrentLocation--到CurrentLocation就是当前I/O堆栈单元的索引以及CurrentStackLocation
就是指向它的指针,会对驱动程序调试有一些帮助。
+24 byte Cancel
+25 byte CancelIrql
+26 char ApcEnvironment
+27 byte AllocationFlags
+28 struct _IO_STATUS_BLOCK *UserIosb
+2c struct _KEVENT *UserEvent
+30 union __unnamed15 Overlay
+30 struct __unnamed16 AsynchronousParameters
+30 function *UserApcRoutine
+34 void *UserApcContext
+30 union _LARGE_INTEGER AllocationSize
+30 uint32 LowPart
+34 int32 HighPart
+30 struct __unnamed3 u
+30 uint32 LowPart
+34 int32 HighPart
+30 int64 QuadPart
+38 function *CancelRoutine--IRP取消例程
+3c void *UserBuffer
+40 union __unnamed17 Tail
+40 struct __unnamed18 Overlay
+40 struct _KDEVICE_QUEUE_ENTRY DeviceQueueEntry
+40 struct _LIST_ENTRY DeviceListEntry
+40 struct _LIST_ENTRY *Flink
+44 struct _LIST_ENTRY *Blink
+48 uint32 SortKey
+4c byte Inserted
+40 void *DriverContext[4]
+50 struct _ETHREAD *Thread
+54 char *AuxiliaryBuffer
+58 struct _LIST_ENTRY ListEntry
+58 struct _LIST_ENTRY *Flink
+5c struct _LIST_ENTRY *Blink
+60 struct _IO_STACK_LOCATION *CurrentStackLocation
+60 uint32 PacketType
+64 struct _FILE_OBJECT *OriginalFileObject
+40 struct _KAPC Apc
+40 int16 Type
+42 int16 Size
+44 uint32 Spare0
+48 struct _KTHREAD *Thread
+4c struct _LIST_ENTRY ApcListEntry
+4c struct _LIST_ENTRY *Flink
+50 struct _LIST_ENTRY *Blink
+54 function *KernelRoutine
+58 function *RundownRoutine
+5c function *NormalRoutine
+60 void *NormalContext
+64 void *SystemArgument1
+68 void *SystemArgument2
+6c char ApcStateIndex
+6d char ApcMode
+6e byte Inserted
+40 void *CompletionKey
这里面包含的结构体非常多,很多不是很明白,DDK的帮助里面看得也是一头雾水,等到接触到了再慢慢消化。
内核中有许多数据结构阅读起来不是很方便,因此我介绍两个工具来使用,SymbolTypeViewer,和PDBexplorer。
SymbolTypeViewer使用很简单,单击file找到windows\system32\ntkrnlpa.exe,接着单击Symbolpath设定存放下载
的符号文件,点server默认用微软的链接,最后点getsymbol就可以了,然后用PDBexplorer找到刚才下载的.pdb文
件,打开即可找到结构体IRP,很方便的说,其实我在用这个PDBexplorer时候,想起来我们在配置WinDbg时,默认
也有个存放符号文件的文件夹,找到那个文件夹,在下面有各种符号文件,这样前面那个软件也就不需要了。
要想更加详细的了解每个成员,直接看他的定义好了。F:\WINDDK\7600.16385.0\ inc\ddk\wdm.h。
2:这些的请求都是被分发函数所获得的,可以只设定一个分发函数,也可以给每个请求都设置一个分发函数。
分发函数原型一般是:NTSTATUS MyDispatch(PDEVICE_OBJECT device,PIRP irp);这个函数所需要传递的就是
设备指针和IRP请求指针。到具体应用时再讲。
3:常用内核函数
Ex系列--分配内存,获取互斥体等
ExAllocatePool,分配内存
ExFreePool,释放内存
ExAcquireFastMutex获取一个快速互斥体
ExReleaseFastMutex释放一个快速互斥体
ExRaiseStatus抛出异常
Zw--Nt系列文件操作函数
ZwCreateFile--创建文件
ZwWriteFile--写文件
ZwReadFile--读取文件
ZwQueryDirectoryFile--查询目录
ZwDeviceIoControlFile--发出设备控制请求
ZwCreateKey--打开一个注册表键
ZwQueryValueKey--读取一个注册表键
Rtl系列字符串操作函数
RtlInitUnicodeString--初始化一个Unicode字符串
RtlCopyUnicode--拷贝字符串
RtlAppendUnicodeToString--追加字符串到另一个字符串
RtlStringCbPrintf--将字符打印到字符串中,相当于格式化字符串
RtlCopyMemory--拷贝内存
RtlMoveMemory--移动内存数据块
RtlZeroMemory--内存数据块清零
RtlCompareMemory--比较内存
RtlGetVersion--得到当前windows版本
Io开头的IO管理函数
IoCreateFile--打开文件,比ZwCreateFile函数更加底层
IoCreateDevice--生成一个设备对象
IoCallDriver发送请求
IoCompleteRequest--完成IRP请求
IoCopyCurrentIrpStackLocationToNext--讲当前IRP栈空间拷贝到下一个栈空间
IoSkipCurrentIrpStackLocationToNext--跳过当前IRP栈空间
IoGetCurrentIrpStackLocation--得到当前IRP栈空间。
4:多线程问题
如何判断多线程安全,主要是根据调用源来判定的,调用源说白了就是调用这个函数的函数,若调用源
是单线程,则也是单线程,遵循以下几个规则,
规则一:若函数A的所有调用源运行于同意单线程环境中,则A也只运行于单线程环境中
规则二:若A其中的一个调用源可能运行于多线程环境中,而整个调用过程中未采用自旋锁或者互斥体等同步
措施,那么其运行于多线程是不安全的
规则三:若A的所有调用路径可能运行于多线程的路径都采取了强制转化为单线程的措施,那么其多线程是安
全的,貌似跟规则二重复了。
规则四:只是用函数内部资源,不使用全局变量,静态变量等是多线程安全的。
规则五:若那些全局变量或静态变量被强制使用了同步手段规定同一时刻只能有一个线程访问,那么多线程也
是安全的。
内核代码各个函数可能运行的环境
DriverEntry,DriverUnload 单线程
各种分发函数,--多线程
完成函数--多线程
各种NDIS回调函数--多线程
5:代码的中断级概念。
Windows为CPU的运行状态定义了许多的级别,即IRQL,任一时间中,CPU总是运行在其中的某一级别,各个
级别规定了CPU哪些事可以做,哪些事不可做。级别
定义如下:
#define PASSIVE_LEVEL 0 ;级别最低,CPU在用户层,或刚进内核运行于管理层时就运行在此级别上
#define LOW_LEVEL 0 ;
#define APC_LEVEL 1 ;比PASSIVE_LEVEL略高,运行APC函数(进程与线程)时需要的级别
#define DISPATCH_LEVEL 2 ;相当于cpu运行在内核层,线程切换时级别从此下降
#define PROFILE_LEVEL 27 ;级别3以上用于硬件中断。
#define CLOCK1_LEVEL 28
#define CLOCK2_LEVEL 28
#define IPI_LEVEL 29
#define POWER_LEVEL 30
#define HIGH_LEVEL 31
1:用于页面换出,只要级别不高于APC_LEVEL,其代码都允许换出,因此在管理层的代码中每个函数前放上PAGED_CODE();说明代码所占的页面可以换出去。
2:但是光引用PAGED_CODE()不会使函数所在页面被交换出去,真正使其倒换的是编译指令#program alloc_text(),例如#program alloc_text(PAGE,NtQueryObject).
现在只了解Dispach和Passive级别两种,一般功能比较简单的函数运行在Dispatch中断级别上,到时可以查看WDK。
DriverEntry,DriverUnload--Passive
各种分发函数,--Passive
完成函数--Dispatch
各种NDIS回调函数--Dispatch
若当前CPU运行在Dispatch上,而你又想调用Passive级别上的函数,那就采用另开一个线程去专门执行这部分代码吧
