DDK学习笔记(转)
下面这是kruglinski关于学习驱动编写的文章。虽然是入门级文章,但是也需要对驱动有一些了解后才可以看的比较透彻。
《DDK学习笔记》1---入门
1.驱动程序的结构:
1.1、一个入口点(DriverEntry):用于创建设备对象及符号连接,以及其它初使化操作,如分配池内存等.
1.2、一个出口(DriverUnload):删除符号连接与设备对象,并释放已经分配的各种资源,如池内存等
1.3、几个DispatchHandler:用于响应Ring3程序的请求及其它驱动事件,并做相关处理
2.内存管理
2.1、分配系统池内存(ExAllocatePool):它有点像C中malloc,只不过存在分页和紧急选项
2.2、释放系统池内存(ExFreePool):它有点像C中的free
2.3、注意:Ring3中所有堆内存都是可分页的,实质上是因为Ring3程序的中断请求级非常低,当它访问分页内存时,IO管理器有机会处理页错误中断,从而调入需要的内存页,但在驱动程序中我们可以随时调整中断请求级以减少驱动程序运行的时间,这样当驱动程序的当前中断请求级高于DISPATCH_LEVEL时,IO管理器没有机会处理页错误,导致内存访问异常(此时一般出现蓝屏)
2.4、如果需要频繁分配和释放相同大小的内存,可以使用后备列表来管理系统堆内存以提高性能
3.输出输出
3.1、几种响应
IRP_MJ_DEVICE_CONTROL,响应系统的DeviceIocontrol
IRP_MJ_READ,响应系统的ReadFileEx
IRP_MJ_WRITE,响应系统的WriteFileEx
3.2、访问输入输出参数:
3.2.1. Buffered方式:使用IoGetCurrentIrpStackLocation得到调用者堆栈区域指针IrpStack(PIO_STACK_LOCATION类型)
Irp->AssociatedIrp.SystemBuffer;输出输出缓冲
IrpStack->Parameters.DeviceIoControl.InputBufferLength;输入缓冲长度
IrpStack->Parameters.DeviceIoControl.OutputBufferLength;输出缓冲长度
IrpStack->Parameters.DeviceIoControl.IoControlCode;设备控制代码
如果需要输出参数,在填写完SystemBuffer后要设置IRP的IoStatus成员的Information指示输出数据的长度.
3.2.2. Direct方式:MmGetSystemAddressForMdlSafe映射IRP->MdlAddress地址到内核空间
MmGetMdlByteCount,得到MDL大小,以字节为单位.通常应该在IRQL<=DISPATCH_LEVEL的情况下使用MDL,那么如何输出呢?和Buffered方式一样从一个地方读,处理完再写到同一个地方吗?(问题1)
3.2.3. Neither方式:这个方式比恐怖,除非确定驱动不是分层的并且运行在PASSIVE_LEVEL级,一般不使用这种方式.比如写一个简单的Dump核心数据结构的驱动,该驱动只由我们的一个程序控制,那么可以直接把用户模式的地址传给驱动使用
注意:《Programming the Microsoft Windows Driver Model》一书说到"决不(或几乎从不)直接引用用户模式的内存地址"
4.安装KMD
4.1、需要Administrator权限,调用OpenSCManager打服务管理器,用CreateService以SERVICE_KERNEL_DRIVER类型将驱动安装为服务
5.启动KMD
5.1、可以在CreateService时指定SERVICE_AUTO_START自动加载,或是指定SERVICE_DEMAND_START安装类型再调用StartService手工加载
6.停止与卸载KMD
6.1、可以使用ControlService指定SERVICE_CONTROL_STOP停止驱动,然后可以使用DeleteService卸载驱动.最后CloseServiceHandle
7.Ring3访问KMD
7.1、需要Administrator权限,CreateFile打开设备对象
7.2、DeviceIoControl,与驱动进行交互操作,可以使用各种自定义的数据结构进行输入输出.
7.3、ReadFile,读驱动数据
7.4、Writefile,写数据给驱动
7.5、CloseHandle,关闭设备对象
8.过滤/挂钩IRP请求
8.1、挂钩某个IRP处理函数:
a,调用IoGetDeviceObjectPointer返回的设备对象(PDEVICE_OBJECT)
b,由设备对象得到驱动对象PDEVICE_OBJECT->DriverObject(PDRIVER_OBJECT),这里是否应该调用ObReferenceObjectByPointer函数增加驱动对象的引用计数,以防止该驱动程序在我们的驱动程序前被卸载呢?(问题2)
c,再由驱动对象得到中断请求派遣函数表(PDRIVER_OBJECT->MajorFunction)
d,保存PDRIVER_OBJECT->MajorFunction[IRP_MJ_XXXXXXX]值(原中断请求派遣函数地址)
e,修改PDRIVER_OBJECT->MajorFunction[IRP_MJ_XXXXXXX]值(让它指向我们自定义的函数),使用锁总线前缀lock的xchg指令进行赋值操作(让代码多线程和多处理器安全)
f,结束处理同System Service Hook
8.2、过滤某个设备的IRP请求:
a,初使化IRP请求派遣函数表MajorFunction,将它们全都指向一个派遣函数DispatchAny
b,再为MajorFunction指定几个我们感兴趣的IRP请求派遣函数
c,得到设备对象指针:IoGetDeviceObjectPointer
d,将设备加到设备堆栈上:IoAttachDeviceToDeviceStack,并保存下层设备对象,以供IoCallDriver时使用.
e,在DispatchAny中将所有IRP传给下层驱动:IoSkipCurrentIrpStackLocation,IoCallDriver
f,在指定的几个请求派遣函数中对IRP进行处理,如果有必要,可以将IRP传给下层驱动.
思考:
a,知道了FileMon使用过滤型驱动原理后,发现它的确和我写的File监控驱动一样没有办法监视到USB和PGP盘的操作,因为都在程序中硬编码了卷标名.在驱动中我挂接了所有的ZwXXXFile操作,然后使用ZwQueryObject得到句柄对应的对象属性,再得到卷标,最后匹配某个盘符(符号链接)与该卷标对应,从而得到文件全路径名.
b,确定一个派遣函数在哪个驱动中:
1.使用ZwQuerySystemInformation得到Module Lists
2.使用8.1的方法得到某个设备派遣函数地址,比较函数地址是否在某个Driver Module的地址空间范围内.
c,挂接\Device\Tcp的IRP_MJ_DEVICE_CONTROL函数隐藏端口
《Subverting the Windows Kernel》一书中使用了该方法,的确比较Cool(有点像我在Ring3挂COM接口的方法,都是通过修改某个FunctionTable中的项目来实现),但是这是可以被b方法发现,如果用b方法查询某个函数地址没有存在对应的sys地址空间中,那么可以肯定rootkit也Hook了ZwQuerySystemInformation使得我们得到了不真实的Module Lists.
想到一个方法可以针C对这样的完整性检测进行攻击,首先分配一块不可分页内存,在其中写入一些花指令和时间差反跟踪代码,然后再jmp到我们的函数中,最后让\Device\Tcp的IRP_MJ_DEVICE_CONTROL的函数指向这块内存.(可行吗?问题3)
初学驱动开发,感觉对386保护模式以及DDK还需要进一步了解,否则很多操作系统核心方面的东东无法深入了解,所以说基础很重要,Rootkit或是Kernel Hacking只是这些基础技术的特殊应用.这两个星期中感觉自己学到了不少东西,得到了Eva无私帮助,无数次的问他都给我一一详细的解答,否则我没有办法进步的这么快,虽然只是刚入门但至少可以看懂《Subverting the Windows Kernel》和《Windows Internals》中的大部分内容,在这里要特别感谢Eva和那些开放核心技术资料的人,你们才是真正的Hacker!
参考资料:
《MSDN 2001》
《The Undocumented Functions Microsoft Windows NT/2000》
《Windows NT(2000) Native API Reference》
《Four-F KMD教程》,罗云彬和刘松翻译
《Programming the Microsoft Windows Driver Model》
《Undocumented Windows NT》
《Undocumented Windows 2000 Secrets》
《Subverting the Windows Kernel》
《Windows Internals,4th》
《Developing Your Own Unix-Like OS On IBM PC》
//--------------THE---END-----
《DDK学习笔记》2
同步--经常被人们所遗忘在角落的重要素.
同步真的有那么重要吗?很多人对此有疑问,我真的为此感到很不解,包括有人在多线程程序中使用全局变量,并使用非线程安全的语句来进行同步操作,虽然有了那么些同步的意识,但做法还是不那么正确,所以觉得还是应该写一下,特别是在这篇笔记中写了些关于核心态代码同步的技巧.
现在思考一下一个爆破系统中的同步,假如有这样的程序(伪码),安置线程进入爆破现场安放爆破装置然后离开,引爆线程等安置线程离开后引爆爆破装置,使用一个全局BOOL型变量表示安放是否完成是否已经撤离爆破场(不考虑Sleep等让出CPU时间的操作).
BOOL bRetreat=FALSE;
IgniteThread;引爆线程
SetterThread;安置线程
SetterThread()
{
do
{
bRetreat=FALSE;
进入现场
安放爆破装置;
撤离现场;
bRetreat=TRUE;
}
while(任务没有完成);
}
IgniteThread()
{
do
{
if(!bRetreat)
等待安置线程安放爆破装置
引爆;
}
while(任务没有完成);
妈的while怎么显示不出来,Blogchina真有点奇怪
}
这段代码看起有问题吗?引爆线程会一直等待安置线程撤离现场才引爆啊!好像没有什么问题吧!如果这是核爆炸试验,而安置原子弹的是普通人而不是机器人,下面会让你浑身冒冷汗.
在x86系统上对于
if(!bRetreat)
这样的语句,特别当bRetreat是全局变量时,非常有可能生成这样的机器指令序列:
LTEST:
mov eax,bRetreat
test eax,eax
je LWAIT
引爆
LWAIT:
jmp LTEST
问题出来了,当
mov eax,bRetreat执行完成时,事实上线程是可能被打断的,而在这时bRetreat的值可能发生变化,然引爆线程却没有机会发现这些变化,
例如:
在mov eax,bRetreat执行时bRetreat的值为TRUE,而后在test eax,eax前引爆线程被打断,安放线程将bRetreat的值变设为FALSE,随后引爆线程又Resume了,它会直接执行引爆操作,因为此时eax的值为TRUE,而此时安置线程(包括被安装线程控制的机器和人员)还没有离开现场.于是Boom.......一切灰飞烟灰.虽然可以直接使用test bRetreat,FALSE代替前两条指令,但也不是多处理器安全的.
呵呵!当然在现实生活中我们不可能都成为原子弹引爆系统或神六飞船控制系统的设计师,但这还是会引起电脑上的核爆炸---BSOD!特别是在驱动程序中.
同步实例
我们经常要在KMD中做Hook操作,然后做一些操作过滤,和日志记录.而被Hook的函数在任何时间可能会被任何进程中的线程调用,这时我们的驱动在不同进程的上下文里,同步不做好BSOD肯定是不可避免了.现在好像找到感觉了,不再那么怕在驱动使用多线程和动态内存,也不那么怕BSOD了.也许这些得益于最近对内核态的同步的深入学习.
2.1 Hook系统函数后卸载驱动时产生BSOD
通常在Hook系统函数时我们会这样做
NTSTATUS NTAPI HookedNtAPI(....)
{
NTSTATUS ns=RealNTAPI(...);
return ns;
}
当一个调用被阻塞时我们所在上下文的线程会被挂起等待函数调用完成,这时如果我们的驱动映像从内存中移除,那么当函数返回时肯定会出现一个内存访问引常引起的BSOD.因为当RealNTAPI返回时所在的空间已经不可用,原来的HookedNTAPI点用的空间已经被释放.那么应该怎么做!当然是添加引用计数,就像这个HookedNtAPI是一个COM对象一样,如果有调用被挂起就等待,直到引用计数为零时才允许退出.可以添加一个ULONG类型的全局变量nSuspendCalls来表示全局的引用计数.在每个Hook函数中调用原系统函数前增加nSuspendCalls计数,函数退时减少计数,当nSuspendCalls=0时允许卸载驱动.
ULONG nSuspendCalls=0;
....
NTSTATUS NTAPI HookedNtAPI1(....)
{
NTSTATUS ns=0;
InterlockedIncrement(&nSuspendCalls);
ns=RealNTAPI1(...);
InterlockedDecrement(&nSuspendCalls);
return ns;
}
NTSTATUS NTAPI HookedNtAPI2(....)
{
NTSTATUS ns=0;
InterlockedIncrement(&nSuspendCalls);
ns=RealNTAPI2(...);
InterlockedDecrement(&nSuspendCalls);
return ns;
}
....
而在驱动的Unload例程里我是这样做的
VOID
DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING dosDeviceName;
ULONG uWaits=100;//随便写
//如果nSuspendCalls不等于0,就会一直等待
while(InterlockedExchange(&uWaits,nSuspendCalls))
{
KeSleep(200);
}
//
// Delete the symbolic link
//
RtlInitUnicodeString(&dosDeviceName, DOS_DEVICE_NAME);
IoDeleteSymbolicLink(&dosDeviceName);
//
// Delete the device object
//
IoDeleteDevice(DriverObject->DeviceObject);
dprintf("[TdiMon] unloaded\n");
}
DriverUnload里使用多线程多处理器安全的互锁函数得到nSuspendCalls的值,如果不为0就让出CPU时间等待200毫秒,直到引用计数为0驱动退出.通常不会出现不对称的调用情况,可以认为DriverUnload里的while不会产生死循环,因为当函数钩子被解除后引用计数就不会再增加而是越来越少(当那些被挂起的函数返回后).KeSleep是怎么来的,好像没有类似Ring3里可用的Sleep函数啊!不知道有没有,反正我没有找到,于是我就写了这样一个函数,它以毫秒为单位阻塞当前线程让出CPU.
void KeSleep(ULONG uMiniseconds)
{
KTIMER ktimer;
LARGE_INTEGER liTimerout;
liTimerout.QuadPart=-(LONG)(uMiniseconds*10000);
KeInitializeTimer(&ktimer);
KeWaitForSingleObject(&ktimer,Executive,KernelMode,FALSE,&liTimerout);
}
函数内的KeWaitForSingleObject会阻塞线程直到超时,其实使用KEvent之类的也行.
2.2 临界区
如果我们要在一个列表(由LIST_ENTRY组织起来的某种数据结构)中记录日志,同样的Hook函数也会在不同的进程/线程上下文中运行,所以对于列表的操作也应该互斥,否则可能会在添加/摘除列表项时出现BSOD.这里可以使用的内核同步对象很多包括临界区对象,事件对象,互斥体,自旋锁...,需要注意的是运行于DISPATCH_LEVEL级的代码是不应该被阻塞的.
2.3 生产者 VS 消费者
这种模型通常由一个线程/进程产生数据,另一个线程/进程负责对数据进行处理,在Ring3层我对于这些同步已经非常熟(早在1年多前就开始写多线程工作代码),在Ring0层的一个应用实例是Eva给我的一个记录文件操作日志的驱动,其中Hook函数使用自旋锁对操作进行互斥并放入一个列表排队,而一个系统工作线程(消费者)同样使用这个自旋锁将列表的操作进行互斥,并将列表中的记录写入文件.在写入线程中使用一个事件对象指示停止写入操作,同时又可以将线程阻塞一秒,非常巧妙.然后进入对列表的互锁操作ExInterlockedRemoveHeadList,内核态有这么多进行同步的函数,这样的同步方法明显不同于我在Ring3所做的,通常的Ring3层生产者/消费者模型都使用一个信号量来实现,可以初使化多个空位.这可以用来控制线程数量,或是数据处理排队.
例如一个扫描程序可以由一个主调度程序来创建扫描线程,首先:
static HANDLE hSempMaxThread=CreateSemaphore(NULL,最大线程数,最大数程数,NULL);
然后在调度线程中:
void scheduler(...)
{
do
{
准备一下个IP地址及扫描参数;
WaitForSingleObject(hSempMaxThread,INFINITE);//减少信号量计数,原子操作
创建一个线程做为针对某个地址的扫描任务;
}(扫描任务没有完成);
}
在扫描线程中:
ScanTask(...)
{
取得扫描参数;
进行扫描工作;
ReleaseSemaphore(hSempMaxThread,1,NULL);//增加信号增计数,原子操作
}
程序退出时CloseHandle(hSempMaxThread);
当达到最大线程数时调度线程中的WaitForSingleObject会阻塞,直到有一个扫描任务子线程退出.当然这里应该考虑用户界面中退出应用程序的情况.
又比如一个流媒体传输程序:
static HANDLE hSempMaxQueue=CreateSemaphore(NULL,0,最大帧排队数,NULL);
void Capturer(...)
{
调用DirectShow CaptureGraphBuilder取得摄影头数据帧;
放入一个list容器,注意同步;
ReleaseSemaphore(hSempMaxQueue,1,NULL);//增加数据帧计数,原子操作
}
void FrameSender(...)
{
WaitForSingleObject(hSempMaxQueue,INFINITE);//减少数据帧计数,原子操作
从全局list容器里取像图像帧,注意同步;
发送到客户端;
}
上面的两个同步例子都比使用C代码直接操作全局变量要安全且高效,条件不满足时线程都会阻塞,而不会占用任何CPU时间.
在驱动层通常操作都需要同步,而不像Ring3程序那么随意,使用全局变量和随意的C代码进行同步的方法不可取,否则BSOD将常伴你左右,虽然内核对象也以全局变量的形式出现,但对他们的访问都使用多线程多处理器安全的原子操作函数(KeWaitForSingleObject,KeReleaseSemaphore,ExInterlockedRemoveHeadList等).
下一篇DDK笔记打算写内存操作,
努力......
我的 DDK 学习经验(转)
本文供分 3 段:
(1) 为何我要学 DDK
(2) 学习 DDK 的过程
(3)未来可能的发展.
(1) 为何我要学 DDK
我是资讯科班出身, 就读过交大计工, 清大资研. 历经 78,79 股市狂飚, 退伍后, 认为写程式没什麽前途, 也无法撑到 38 岁. 但 MBA 却是越老人脉越多, 收入也越多, 所以进资策会, 一面工作 一面准备 GMAT, 但没申请到理想的学校, 而考外贸协会的人才培训班, 在复试时也被刷下来. 最后认命乖乖学 Windows 3.1,
第一年做多媒体资料库专案, 当时连 VC++ 1.0 都还没出现. 就用 Borland C++ 3.0 以纯 SDK 方式 coding,(因不知道有 OWL 这种东西) 事后想起来真是因祸得福, 让我更了解 Window 底层的运作 (不过当时真的觉得很干).
第二年做 Video Editor 是我比较快乐的时光, VC++ 1.0 刚出来, 但市面没一本相关书籍, 连 MSDN 也一样. 怎麽办? 到处找不到资料. 最后用最笨的方法: 用 Debuger trace MFC source code! 然后搭配 Video for Window 1.1 版 SDK 来coding, 当时傻傻得, 以为可以做个 Video Editor 跟友立拼, 所以常常加班到 10 点, 但后来 Video Editor 结案后就放到储藏室, 而 Aldus 被 Adobe 并购, 友立也把 PhotoStyle 卖给 Adobe. 这件事件让我得到个教训: 套装软体不好做, 更难与国际大厂同类型产品竞争.
因有时 Video Editor 会利用 MCI Command 来控制 AVI/FLI 播放, 但觉得很奇怪, 那MCI Driver 到底在搞什麽鬼? 在好奇心驱使下, 翻 MSDN 的 MultiMedia Device Driver 来看, 才搞懂 MCI Driver 如何与底层的 Video/Audio Driver 沟通.
第三年是最黑暗的时期, Team Leader 包个 Multimedia Title 来做(还曾打算向敦煌科技包 Game 来做). 美工, 企划, 全和在一起, 而要我用 Director 3.0 的 Lingo 语言来写Title 的 Program. 我便开始消极抵抗, 同时自己偷偷用 MFC + WinG + 32 Bit Assembly 在 Win 3.1 上写个类似 Director 3.0 的 Engine. UI 及绘图引擎都完成, 而且播放速度比 Director 3.0 还快. 但卡在不知道如何设计 Script 及 Interpreter. 最后只好做罢. 然而转捩点就发生在这一年: 应台北电脑工会之邀, 开门 "Windows 影音驱动程式剖析" 课程而与童子贤先生有一面之缘(当副总的他竟跑来上我这无名小卒的课), 也保留他一张名片. 另外帮别人写 Motion Control Card 的 Dos Driver 来用在 CNC 上, 对 Driver 开始发生兴趣, 原来写 Driver 的利润颇丰.
第四年, 黑暗的日子终於过去. 叁与 IBM VisualAge for Basic 的 Visual Component 开发, 用 VisualAge for C++ 3.0 来 coding, 过了一段无任何相关资料的日子, 还好从李维那拿到 OpenClass source code, 发挥 tracing 的精神, 终於顺利结案. 这年在某电脑展中遇上任职 IBM 的大学同学, 被他讥笑还在米仓中当米虫, 让我心理不好受. 也使我思考, 只会 coding 的我未来要何去何从, 难道要过着一年换一个专案的日子吗? 然后 40 岁时会变成怎样 ?( PetShop Boy 的 " Being Borning" 在我脑中响起...)
幸好碰上一个影响我很大的同事, 他那时也刚帮人写完一个 Window 95 的 VxD, 於是向他讨教, 并学得如何收集相关资料, 练好后就接个 A/D D/A 卡的 Win31/95 Driver CASE 来做, 写完 Win95 觉得不过瘾, 便拿 NT 4.0 DDK 来看. 另外他有事没事拿 ASIS 电子周刊给我看, 当时正报导 PC97 的 specification: ACPI, AGP, OnNow等新技术, 我看完就知道大好机会来了. 凭做 A/D D/A 卡 Window Driver 的经验, 我知道随着 Win95 的普及, 这些硬体卡没有 Win95 Driver 根本很难卖到欧美. PC97 规定了 Intel 的硬体设计如何跟 Microsoft 的 Window 做结合, 而介面就是Window Driver! 加上主机板厂养的软体工程师只会写 BIOS or 8051, 对 Window Driver 根本是听都没听过, 但 PC97, 98 是必走的道路, 所以一定要找到会的人来做.而这时候我又要换另一资料库相关的专案(我最讨厌做 MIS, 成天与数据, 报表奋战, 有够无聊). 於是上网路找工作, 想到手头的童子贤名片, 就试投华硕看看. 没想到一试便上, (不过在面试时被嫌在资策会待太久, 恐染上不良习性).
进华硕后, 感觉到是Right Man in Right Place. 终於可以一展抱负, 有明确的目标, 不再虚度时光, 也不会被别人嘲笑是米虫. 更重要的是有成就感, 想想做的专案, 是要卖到全世界的产品, 而且是与一些国际大厂竞争产品上市时间. 再也不是作完往仓库一摆的东西. 再者华硕目前员工人数少, 业务扩展迅速, 加上公司高级领导阶层非常重视研发实力, 只要有能力的人, 不怕没迁机会. 不像宏基已有太多员工, 没什麽表现能力的机会, 且升迁管道太挤了.
(2) 学习 DDK 的过程
要学 DDK 首先要把 Window 的底层基础练的扎实. 但一般人对学 Window SDK 都视为畏途, 更何况是底层的东西. 从 Win31 到 Win95 变化比较多得, 我个人认为是: Multitasking, Plug&Play, Memory Addressing. 有人问我说, Memphis 都快出来, 还要花功夫看 Win95 or Win31 吗? 我认为还是要! 原因是:
a. Memphis 不是完全重新改写, 理面的一些观念还是沿用 Win95.
b. 目前市面上尚未有大师级的 Memphis 相关书籍, 如果有也只是趁火打劫类型的书, 而台湾的书商, 作者最会搞这种把戏来 A 钱. 如 Win31/95 方面已有一些大师的书籍, 如 Andrew Scrullman, Patt Metrick, Richard Jeffery, Walter O'ney, Charle Patzold 等大师. 你不去看大师级的书, 反而去看 "快快乐乐学 Memphis", "教你 21 天学会 Memphis" 这类垃圾书, 真是在浪费你的时间及金钱.
学 DDK 的第一步准备功夫是把英文阅读能力练好, 你别指望书商会出中译本. 因这类书的卖相太差, 比不上 VB, Delphi, JAVA这类较大众化的书. 再者要找到够格的译者很难. 要译好是要花相当时间, 那还到不如去写些轻松的书, 稿费也赚得多.
接下来就要练基础工夫, 如同张无忌花了 6 年时间练好九阳真经, 等到练乾昆大挪时只花数个时辰就 OK. 要如何练底层基础呢 ? 勤看书, 勤 coding 及 Trace 别人写得sample code而已.
即使是天才型的 Programmer 也是要看书, 因 Windows 不是他设计的, 必须了解Windows 才有办法下手.. 而非像写 Algorithm 方面的论文般, 自己定 assumption, Lemma, 导出 Theme, 下 conclusion 就完成. 所以一些刚入社会的研究生最好先调整自己的心态.
以往我是读一些大师级的书, 如:
"Window Programing" 的 Charles Petzold
"Undocumented Window" 系列的 Andrew Schulman,
"Win95 system Programming Secrets" 的 Matt Pietrek,
"Advanced Windows" 的 Jeffery Richter
期刊的话是:
Microsoft Systems Journal,
Doctor Dobb's Journal,
Windows Developer Journal. 这本期刊是我认为学 DDK 的人必要订阅的! 几乎过个 1, 2 期就会刊登 Window Device Driver 相关的文章, 而且理面有位 Paula 女士主持的 NT 专栏, 写的很深入, 不是市面一些标榜 NT "大剖析" 之类的书籍所能比拟. 在 Andrew Schulman 的 "Undocumented NT" 尚未问世之前, 它是我觉得最有深度的专栏.
接下来就谈与 Driver 有直接关系的资料:
Device Driver 的书籍, 我从 Win31 开始说起: "Writing Windows Device Driver and VxD", Karen Hazzen, 第 1,2 版 是最适合写Win31 Driver 的叁考书籍. 也有一本白皮的 "Writing Window Device Driver", 我认为它的叁考性很低, 因它光抄 Win31 DDK Function Description 就花了 50 ~ 60 几页, 有 A 钱之嫌.
Win95: 那当然是首推 "System Programing for Win95", Walter Oney 这本巨作. 我曾因翻Chapter 11 ~ 13 翻到书页掉落, 而重新再买一本. Walter Oney 既出, 谁与争锋. 有这本就够了! 也没有人有胆来挑战他.
WinNT: 唯一的一本: "The NT Device Driver Book", Art Baker. 很有系统的一步一步介绍如何写 NT Kernal mode driver. 先看这本书然后再看 MS 的 NT DDK on Help 会让你较容易了解. 当初没这本书时, 我刚开始看 DDK Help 是看得满头雾水. 最好搭配 "Inside Windows NT", Helen Custer 一起看, 因为 NT 底层已经导入Object Oriented 观念(WDM 是将 NT Kernel mode driver 加装 Plug&Play 及 Bus handle 等新功能), 与 Win95 的 Virtual Machine 观念 相差甚远. 这本书虽是 1992 年出版, 但有对 NT 的核心运作加以介绍, 也是一本难得的好书.
NewGroup and Web Site:
1. Win95 是 comp.os.ms-windows.programmer.vxd
2. NT 的话是 comp.os.ms-windows.programmer.nt.kernel-mode
3. Window Device Driver Web Site: http://www.albany.net/~danorton/ddk
http://www.vireo.com
发展工具:
1. 如何要让你日子好过, 一定要有套 SoftIce for Win95/NT. 千万不要用 MS 的 WinDebug, 它根本是无山晓路用. SoftIce 可让你用 source code level debuging, 对有 assembly 恐惧症的人是一大福音.(但我还是劝有心学 DDK 的人还是要摸 assembly).
2. VToolsD(for Win31/95), DriverWork (for NT/WDM) Vireo 公司出的 DDK Tool 让你完全用 C 来写 Driver, 我只能说写 Driver 时, 我已经不能没有它. 由其它还免费附 Class Library source code, 让我 Trace 的 很高兴.
领域知识 (Domain Knowledge)
写 Driver 需了解你要控制的硬体, 如 Driver 有用到 DMA/Interrupt, 建议你看一下 8259, 8237A 的资料. 在这方面, "微电脑界面技术与实作" 是本不错的叁考书. Programmer 总有一天会 coding 不动的时候. 但人老成精, 只要你的 Domain Knowledge 够强的话, 还是会有许多公司请你去当技术经理. 而这也是你能克死年轻力壮 Programmer 最大的本钱. 多多看些其他杂志, 接触一些非 Programming 方面的知识来培养你的 Domain Knowledge 吧!
最后最重要的是: 亲自动手做做做做做做做做做! 看资料是一回事, 知道是一回事, 做的出来又是另外一回事. 公司请你来不是要听你长篇大论, 或是发表多少篇论文. 是要你做个会动, 能商品化的东西. 等你做下去, 踢到铁板, 又找不到别人问, 也没有任何资料可帮助. 再加上硬体没你写的 Driver 而无法出货时, 那种滋味可是人间炼狱. 我就遇上一次, 东西都出到俄罗斯, 德国的经销商时, 结果发现 Driver 有 Bug, 无法顺利安装. 老大放话说一星期内没解决, 俄罗斯经销商就要取消订单(天呀! 几百万美金的交易), 我在想, 如果到时解不掉就准备辞职. 后来真是狗屎运, 用一个非常另类的做法(骑摩托车在半路上想到的), 解决这个 Bug, 保住订单也保住职务. 说这则例子主要是奉劝一些自视甚高的在学研究生, 不要以为顶着高学历, 就可以在社会上一帆风顺(除非窝到学校或财团法人, 政府机构内), 也鼓励一些没有傲人学历的网友, 只要有实力发挥黑手精神, 照样可以当主管来管一群硕士工程师.
(3)未来可能的发展
看看 WinTel PC98 的规格, 就知道以后会写 Driver 的人比较吃香 但目前会用 C, SDK 写程式的人不多, 更何况是写 Driver, 其实 Driver 并不难写, 只是 Learning curve 很长, 现在大多初学者怕难没耐心, 都用 VB, Delphi 这类 RAID Tool 来写程式, 都快不知道 SDK 是何物更不用说 Window 内部的运作. 除非是走 MIS 路线, 否则以后日子会很难过. 就现实面来看: 用 VB, Delphi 的人太多, 老板要找个人来取代你的职务是很容易得. 就盈馀而论, 国内做 MIS 的公司比不上做 PC 硬体, Chip set 的公司, 而且也没有股票可分,何苦去挤到经济规模小又人数众多的领域内. (但 Year 2000 对 MIS 工程师可是个大利多,只不过你还要会一些非常古老的程式语言, 而不是懂 VB, Delphi 就没事)
就我观察的结果: 目前主机板的软体工程师大都只会 BIOS, 8051. 根本都不懂 Windows Driver. 不要以为写个 softmenu 的 BIOS 就扬扬得意. 等 PC98 正式上场就有好戏看. 所以在这些公司中有有太多机会来让你表现自己的能力.
感想:
国内一些电脑作家实在是太好混日子, 可以从 VB 写到 VC, 然后还可以 "深入探讨" 3D 加速卡. 流行 VB 就出 VB 的书, 一本号称 VC++/Borland C++ 圣经的书, 竟然都没提到 MFC/OWL. 而 Windows95 一出来, 书摊又是一堆 Win95 圣经宝典之类的中文书. 等着看吧, 明年 Memphis 出来时又将旧事重演. 不只如此, 还故意标榜有博士学历或具有写作 10 几年的经验来烘抬自己的身价. 有时我看了就一肚子火. 不过这些人还不是照样继续 A 钱. 真是......, 国内一些电脑杂志的水准, 大家心理有数, 不用我多讲. 我佩服的, 大概只有侯俊杰, 不过最近一年他好像都只写 MFC 相关的文章书籍, 没写些Windows 内部运作的文章, 不知这是否跟上次 Matt Pietrek 来台有关.
总归一句话: 读者要自己小心, 少碰一些 "圣经, 宝典".