魔都的春天来了,虽然早晚还有些凉意,但是阳光已经不一样,走在路上,有时能感到温暖的春风吹来。
在如此美好的春天里,格蠹的小伙伴们也都精神抖擞,或码代码,或斩BUG,天天进步。
但在测试新版本的Nano Code时,遇到了一个古怪的BUG。经过一番跟踪,小伙伴确定是如下这句fopen识别:
fp = fopen(full_path, mode);
失败时,第一个参数代表的文件路径为:
"\\\\?\\d:\\work\\nano\\nd\\ndi\\x64\\debug\\data/ntp.cfg"
这个路径中的/ntp.cfg是动态拼接上去的,所以与前面的写法不一样。考虑到阔平台,所以使用了/,而不是\\。
但根据经验,Windows下也支持/,比如把full_path参数去掉前面的\\?\,那么就可以成功打开文件。
"d:\\work\\nano\\nd\\ndi\\x64\\debug\\data/ntp.cfg"
也就是上面这样写是可以的,但是前面加上\\?\就不行了。
那么\\?\是哪里来的呢?看一下代码,是通过微软的GetModuleFileName() API获取的。这种写法还有个堂而皇之的名字,叫Fully Qualified Path(FQP)。
如此说来,是微软的一个API返回了\\?\,但是用包含这个\\?\的路径去调用另一个API时却失败了。这让小伙伴们很难理解。
更加激发小伙伴们兴趣的是,当fopen失败时,在Watch窗口通过@err观察API错误码,居然是123。
于是,大家有点被“激怒”了,这是故意气人的吗?Windows,你在搞什么1-2-3?
这个问题看起来像是Windows的一个BUG,因为非FQP风格的路径支持/但是,FQP风格的却不支持。
但是如果遇到执拗的程序员,他或许坚持说,这不是BUG,这怎么能是BUG呢,这显然是个feature啊,我堂堂大Windows,用\来分隔路径,对于异类风格的/的分隔符号,当然是果断拒绝,给予严厉打击啦。^_^
那么,这到底是个BUG还是个feature呢?
因为芯片缺货,所以本来缓慢的软件进度现在倒比硬件的快了。既然如此,那就拿出点时间来格一下这个问题吧。
由于Windows不开源,要想理解它的内部逻辑,最好的方法就是上调试器,在用户空间跟踪一番后,发现这个路径居然传给了内核,并不是用户空间的问题。
内核空间返回的错误码是C0000033,不是123,是C0000033。其含义为STATUS_OBJECT_NAME_INVALID。
//
// MessageId: STATUS_OBJECT_NAME_INVALID
//
// MessageText:
//
// Object Name invalid.
//
#define STATUS_OBJECT_NAME_INVALID ((NTSTATUS)0xC0000033L)
追了一番,进内核了,怎么办?还要追么?再追就有点麻烦了。要内核调试。
这时一位小伙伴说,不怕,用GDK7啊。
是的,可以用GDK7,先把出问题的路径放到一个小程序里,然后在GDK7上复现问题。
确认问题依然可以在GDK7稳定复现后,准备上DCI调试。
把GDK7连到主机,设备管理器中如约出现DCI设备。
然后唤出Nano Code,选中DCI方式,以及NT内核选项,然后开始调试会话。
点击启动按钮后,可以深呼吸一下,因为启动这个DCI会话是个比较复杂的操作,要先拉起英特尔的OpenDCI进程。
等待片刻后,NDB的工具条上蓝色的中断按钮被点亮,这代表DCI服务初始化就绪,可以发起中断了。
点下Break按钮,目标机戛然而止,立刻不动了。
下一步是要搜索NT内核,要在茫茫的内存空间中,先找到NT模块,再找到关键内核地标KdVersionBlock。
Found Module Name ntkrnlmp
Found KdVersionBlock at 0xfffff80680a2a3d8
Found target VersionBlock at 0xfffff80680a2a3d8
详细过程在此跳过,感兴趣的格友可以看当年GDK7发布时的系列讲座录像。
大约2秒钟后,NDB显示出NT内核的版本号,以及关键结构体的地址:
这代表着在内存空间中,成功找到NT内核。
执行lm,可以看到NT内核的符号也几经成功加载。
lm
start end module name
fffff806`4fe00000 fffff806`508b7000 nt (pdb symbols) C:\NanoCode\sym\ntkrnlmp.pdb\DCA4AD4BEEB4746D48F84C0125019E431\ntkrnlmp.pdb
因为最近一段时间忙着GDK8,所以有段时间没有调试NT内核了,又见到它,仿佛看见一位老朋友。
“老朋友啊,抱歉又对你上调试器了,因为想知道是谁在以你的名义乱搞什么123...”
.reload后,再次lm,数百个内核模块一一陈列,是谁搞的123呢?
接下来需要一个断点。设在哪里呢?按理说应该设置在NtCreateFile,但是也可以设在它调用的子函数IopCreateFile。
设好断点后,g,很快命中。
但这次并不是出错的情况,因为用于重现错误的fopen123还没有运行。
创建文件是个很频繁的操作,所以要放掉干扰的情况。如何做呢?
可以先在GDK7上用windbg打开fopen123,然后在ntdll!NtCreateFile设置个断点,命中后让其等待。
而后在NDB中,找到fopen123进程。
[ndb]!process 0 0 fopen123.exe
!process 0 0 fopen123.exe
PROCESS ffffe28eeafd0080
SessionId: 1 Cid: 0504 Peb: 00c73000 ParentCid: 1d7c
FreezeCount 1
DirBase: 3324f002 ObjectTable: ffffaa8fb17743c0 HandleCount: 59.
Image: fopen123.exe
再设一个条件断点:
bp /p ffffe28eeafd0080 nt!IopCreateFile
然后再bl观察:
1 e fffff806`80c649a0 0001 (0001) nt!IopCreateFile
Match process data ffffe28e`eafd0080
埋伏好这个条件断点后,g恢复目标执行。
(未完待续)
(写文章很辛苦,恳请各位读者点击“在看”,也欢迎转发)
*************************************************
正心诚意,格物致知,以人文情怀审视软件,以软件技术改变人生
扫描下方二维码或者在微信中搜索“盛格塾”小程序,可以阅读更多文章和有声读物
也欢迎关注格友公众号