文件系统开发之特殊文件links篇(上)

链接(links)是文件系统最常用的一类特殊文件,一般分为两类:

  • 硬链接(hardlink): 硬链接和普通文件(或目录)实际并没有差别,只是它和另外的文件共用一个文件数据体,反过来说就是,同一个文件体可以有多个名字,而且这些名字可以在卷内的任意目录中。类似于同一座房子,却有着不同的门牌号。 硬链接必须在同一卷上,就像门牌号必须附着于房子实体上一样。 Linux文件系统Ext3允许针对目录和文件的硬链接的创建,但Windows文件系统NTFS只允许硬链接的创建于文件之上。造成此差别的原因在于文件系统的实现上的不同,Linux Ext3文件系统对dentry(文件名)和inode(文件数据)的管理是分开的,但对NTFS却不是,Fcb是dentry和inode的合体,二者分不开,这部分以后再详述。
  • 软链接(softlink),同时又有符号链接(symbolic link, symlink)之称。符号链接不同于硬链接,它是一类特殊文件,就像是“邮政信箱”,它只是个指向一个门牌号的别名而已,门牌号可以代表一座房子的实体,但“邮政信箱”并不能。 软链接是可以跨卷的,即软链接自身位置和其目标位置,差个108000里也没关系。 Windows NTFS对符号链接的支持是从Vista才开始的,就是说XP系统并不支持。

对于Windows系统,还有另外两种比较特殊的“链接”:

  • Junction: Junction其实是一种符号链接,只是在Win2k或XP系统上符号链接并没有实现的情况下的一个临时替代品。Junction只支持目录链接,不支持文件。Junction是可以跨卷的,因为它本质上符号链接。
  • Reparse Point:Reparse Point最早的作为Mount Point(卷加载点)来引入的,但其功能却相当强大,比如上面提到的Symlink/Junction的实现其实都是基本Reparse Point的,再者NTFS的高级特性如Hierarchical Storage,也是通过Reparse Point来实现的。 对Linux Ext3来说,任意子目录都可以是Mount Point。对Windows文件系统NTFS来说,虽然任意子目录也可以作Mount Point,但一旦成为Mount Point,其子目录便转换为了Reparse Point属性,也正是这个实现上的差别导致了Windows系统在链接的操作上出现了不少怪现象。

后面会进一步针对Linux Ext3及Windows NTFS内部对链接的实现进行介绍和分析。

Windows的快捷方式(Shortcut)并不在此文的讨论之列,因为它是Shell层(即Windows Explorer)的行为,并非文件系统本身的特性。

蓝屏,谁之过?

Bug总能在你意想不到的地方给你个措手不及,只是它所带来并不是惊喜,而是Blue Screen Of Death !

既如此,只能兵来将挡。

先介绍一下程序的大体流程:

NTSTATUS
XXXProcessDirents(…)
{    
    do {
        KeEnterCriticalRegion();
        ExAcquireResourceSharedLite(&fcb->Resource, TRUE);

        /* access several members of fcb structure */
        ExReleaseResourceLite(&fcb->Resource);
        KeLeaveCriticalRegion();

         XXXXProcessDirent(…);

    } while (list_is_not_empty(….));

    return status;
}

NTSTATUS
XXXXProcessDirent(…)
{
    HANDLE handle = NULL;
    XXXX_FILE_HEADE fileHead;
    ……

    /* open file */
    status = ZwCreateFile(&handle, GENERIC_READ, &oa, &iosb, NULL, 0,
                          FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                          FILE_OPEN, 0, NULL, 0);

    /* read file header*/
    status = ZwReadFile(handle, ioevent, NULL, NULL, &iosb, (PVOID)&fileHead,
                        sizeof(XXXX_FILE_HEADE), &offset, NULL);

    /* check whether file is interesting to us */
    if (status == STATUS_SUCCESS && iosb.Information == sizeof(……)) {
        /* it’s my taste, haha */
    }

    /* close file, not interested in it any more */

    if (handle){
        ZwClose(handle);
    }

    return status;
}

过程比较简单,XXXProcessDirents()会循环调用XXXProcessDirent(),直至列表中所有项全检查完毕。

下面再来看windbg分析吧:

1: kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

IRQL_NOT_LESS_OR_EQUAL (a)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If a kernel debugger is available get the stack backtrace.
Arguments:
Arg1: 0abc9867, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000001, bitfield :
bit 0 : value 0 = read operation, 1 = write operation
bit 3 : value 0 = not an execute operation, 1 = execute operation (only on chips which support this level of status)
Arg4: 806e7a2a, address which referenced memory

Debugging Details:
------------------

WRITE_ADDRESS:  0abc9867

CURRENT_IRQL:  2

FAULTING_IP:
hal!KeAcquireInStackQueuedSpinLock+3a
806e7a2a 8902            mov     dword ptr [edx],eax

DEFAULT_BUCKET_ID:  DRIVER_FAULT

BUGCHECK_STR:  0xA

PROCESS_NAME:  System

TRAP_FRAME:  b9019bbc -- (.trap 0xffffffffb9019bbc)
ErrCode = 00000002
eax=b9019c40 ebx=00000000 ecx=c0000211 edx=0abc9867 esi=c0000128 edi=8842d268
eip=806e7a2a esp=b9019c30 ebp=b9019c68 iopl=0         nv up ei ng nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010286
hal!KeAcquireInStackQueuedSpinLock+0x3a:
806e7a2a 8902            mov     dword ptr [edx],eax  ds:0023:0abc9867=????????
Resetting default scope

LAST_CONTROL_TRANSFER:  from 806e7a2a to 80544768

STACK_TEXT:
b9019bbc 806e7a2a badb0d00 0abc9867 804f4e77 nt!KiTrap0E+0x238
b9019c68 806e7ef2 00000000 00000000 b9019c80 hal!KeAcquireInStackQueuedSpinLock+0x3a
b9019c68 b9019d24 00000000 00000000 b9019c80 hal!HalpApcInterrupt+0xc6
WARNING: Frame IP not in any known module. Following frames may be wrong.
b9019cf0 80535873 00000000 8896fb20 00000000 0xb9019d24
b9019d10 b79d87ff ba668a30 8859b7e8 00000440 nt!ExReleaseResourceLite+0x8d
b9019d2c b79d8a5c 8a3ff2f0 00000003 ba6685f0 XXXXX!XXXProcessDirents+0xef
b9019d88 b79e163a e2f6b170 00000001 00000001 XXXXX!XXXKernelQueryDirectory+0x20c
b9019ddc 8054616e b79e1530 88a8ae00 00000000 nt!PspSystemThreadStartup+0x34
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

问题出在系统函数ExReleaseResourceLite()及KeAcquireInStackQueuedSpinLock()上,且程序要写的地址为0abc9867 ,明显不对,所以此处可做栈损坏推断。

第一嫌疑要考虑的是,XXXProcessDirents()中有锁保护的部分,此部分是果真是最容易造成栈损坏buffer复制操作。但经过仔细检查及测试,便排除了此部分出错的可能。

在排除第一嫌疑后,就没有明显目标了。只好再接着看windbg log:

貌似KeAcquireInStackQueuedSpinLock()要写的地址是LockHandle的LockQueue->Next,而LockHandle一般都在从当前堆栈分配的,由此可肯定之前对于栈损坏的推断。可问题是,是谁导致的栈损坏。

Stack中有hal!HalpApcInterrupt()调用记录,它是处理APC的软中断。hal!HalpApcInterrupt()会一般会调用nt!KiDeliverApc()来处理线程的APC队列。但当ExReleaseResourceLite()调用的时候,线程还处于临界区内(Critical Section),此时User mode APC及Kernel mode normal APC都会被禁止的,但Kernel mode special APC不会。

Kernel Special APC最常见的情况便是由IoCompleteRequest()添加的:在APC Level中调用IopCompleteRequest()以处理Irp的Stage 2的清理工作。

至此,问题终于有些眉目了。分析代码中唯一有可能导致APC添加的地方就在函数XXXXProcessDirent()中的ZwReadFile()调用,而且fileHead正是于堆栈中分配的。

想到此处,此bug的根据原因便付出水面:

XXXXProcessDirent()没有处理ZwReadFile()返回STATUS_PENDING的情况,此情形下,XXXXProcessDirent()退出并继续执行,而之前的ZwReadFile()的IRP完成操作也在同时进行(还没有完成),并且此完成操作所要写的fileHead地址,正是早已被回收并加以重用的当前栈。

搞清楚之后,便在调用ZwReadFile()后,特别针对STATUS_PENING的情况来调用ZwWaitForSingleObject()以确保读操作全部完成后,再进行下一步操作。

到此,问题解决!

一个蓝屏的问题,竟然如此之绕,不禁让我想起刘震云的《一句顶一万句》,只是这能顶一万句的一句到底是哪句呢?

<下一步打算写写APC相关的东西,操作系统将APC隐藏得太深,总让人捉摸不定!>

钓鱼岛是我们的

dyisland

上面并不是我的原创,却同样表达了我的意思。印象中是在苏花公路上骑行时,看到一骑机车环岛的台湾哥们,在机车后面特意挂了一个牌子,上面就是这样写的,可惜我怎么也找不到当时拍的照片了。他骑得虽是机车,但因着拍照的关系,我们轮流着一前一后共同骑了很长一段路,中间也曾短暂交谈。虽萍水相逢,却都有着中国心。

今天在google.com.hk上试着搜索钓鱼岛,竟然提示“敏感字”。什么玩意嘛,这个是倒底是哪门子的敏感?!

记得8月中在承德避暑山庄游玩时,同样有个牌子,上面写的是晚清政府,可所有人看了均相视而笑:

chengde

真的,还是假的,马上就见分晓了,我且拭目以待。

危机免疫的危机

用自动饮水机加水时,总有着一种紧张感,考虑到水可能溢出的后果,所以都是耐着性子等杯子加满才会离开;偶尔中间走开的话,也总是急匆匆地走,急慌慌地回。

身边总是被各种各样类似的 “小危机”围绕着,无处不在,无时无刻!

好在小危机总是确定的,我们完全可以预见什么时间它会带来什么样的后果。所以,大可不必如此着急,短时间走开的话,亦可从容行事。

危机让人警觉,给人压力,也给人动力和潜能。有危机感不是坏事,可如果连生活的点滴中都要受它支配的话,那真得要“悲哀”了。

城市节奏的飞转,压力的与日剧增,已让生活变得支离破碎,再加上身边貌似火烧眉毛的琐屑小事,更让生活失去了仅存的乐趣。

不仅如此,更大的问题是,一直绷紧的神经会对真正的危机产生免疫,以至漠视了真正危机的存在。

我们总是一厢情愿地认为,“大危机”离我们总是很远,但对于它什么时间会发生以及要造成什么后果,则毫无概念。也正是它的不确定性,却又助长了我们毫无根据的乐观精神,赌徒般的盲目乐观。

真正的危机就在眼前,只是我们对其一无所知!

Win7调试模式问题一例

BootMgr的引入,虽然增加了操作及维护的复杂度,但相应带来的好处也不少,就比如我曾做过的一次操作:将Win7系统从第一分区移动至第三分区。这样的操作在XP时代,是不太可能的,因为这就意味着盘符的改变,系统内部配置都是以盘符来确定的,而Win7始终将系统盘加载至C:上。

将Win7移动后,修改BootMgr记录中的device及osdevice项后,Win7系统便能正常启动,相应命令如下:

bcdedit /set {uuid} device=partitioin=X:
bcdedit /set {uuid} osdevice=partitioin=X:

然后将debug模式打开:

bcdedit /debug {uuid} on

然后再设置dbgsettings为1394,而不是默认的com串口:

C:\>bcdedit /dbgsettings
An error occurred while attempting to access the boot configuration data.
The system cannot find the file specified.

系统中竟没有dbgsettings,那就自己创建好了:

C:\>bcdedit /create {dbgsettings}
The entry {dbgsettings} was successfully created.
C:\>bcdedit /dbgsettings
There are no debugger settings present.
The operation completed successfully.
C:\>bcdedit /dbgsettings 1394 channel:11
The operation completed successfully.
C:\>bcdedit /dbgsettings
debugtype               1394
channel                 11
The operation completed successfully.

设置好生重启系统,但wndbg死活就是连不上。但同样的硬件,用XP启动windbg确实能正常,所以排除了线材、硬件问题,还是要从Win7系统着手。

经过详细对比正常系统的BootMgr设置后,发现是dbgsettings虽设好了,但并没有关联到Win7启动项上。那就做一下关联试试:

C:\>bcdedit /create {globalsettings}
The entry {globalsettings} was successfully created.
C:\>bcdedit /set {globalsettings}  inherit {dbgsettings}
The operation completed successfully.
C:\>bcdedit /set {current} inherit {globalsettings}
The operation completed successfully.

重启系统后,windbg便能正常连接了。

挺简单的事,非要搞得这么复杂!难怪简单的苹果产品却赢得了如此多用户的芳心!?

银行—身边的收费专家

9月1号莫名其妙收到工行的一条扣费短信,名曰还息还贷。数额虽不多,但我觉得毫无道理,因为我的信用卡设置的是自动还款,怎么还会有利息之说呢?!

打电话过去一问,方知是因为8月初在台北的一次提现导致的,这才明白提现并非正常透支,除手绪费外,还要支付利息,除非当天就还款。看来是收费真有理,而我这个用户太无知!

说起提现这事,真是让我气不打一处来。当时在鼎泰豐吃的晚饭,结账的时候竟不能刷卡,堂堂的所谓高档餐馆,只能以现金结账。这是在台湾的最后一晚,身上现金是预备给第二天的打车用的。难道是鼎泰豐也怕银行收费?但无论如何,也不能为了自己,将不方便留给客户!?完完全全的银行做派嘛!

记得《财经郞眼》有一期里就说国有大银行,养着一大群高学历高智商的人,这些人的工作就是研究要收什么费,以及怎么收,名目如此繁多的收费项目全出自他们之手。

除工行外,中国银行也差不多,也曾被它黑过一次:我的卡包丢了,只能挂失,中国银行只提供永久挂失,即销卡,工行倒时可以暂时挂失。后来,我朋友在他车上发现了我的卡包,但中国银行的信用卡只能重新办卡。重新办嘛就意味着宰你一把!

银行,收费很在行,我们身边的收费专家,大家要小心!