找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 1733|回复: 3

分析Win10 ISO安装到iscsi时DRIVER_IRQL_NOT_LESS_OR_EQUAL蓝屏问题

[复制链接]
发表于 2023-8-21 12:09:32 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 ba1ba2ba3ba4 于 2023-8-21 12:09 编辑

先说"Win10 ISO安装到iscsi"是怎么操作的:

1.配好iscsi server + dhcp + tftp + ipxe
2.创建一个虚拟机, 不要硬盘, 挂一个Windows ISO
2.启动虚拟机, 指定从网络启动, 注意别直接从ISO启动
3.从网络加载IPXE后, CTRL+B进入ipxe shell, 执行sanhook命令, ipxe就会在低内存区域保存iscsi盘信息;
4.sanhook成功后, 执行exit退出ipxe shell, 再按任意键, 就会从iso启动
5.如果一切正常, ISO里面的内核在初始化时, 会从低内存区域识别iscsi盘信息并尝试连接, 然后windows安装程序就能显示出磁盘, 后续就是正常安装过程了

在有光驱并且也不追求自动化批量部署的时候, 无论是硬iscsi还是软iscsi, 这都是个很标准的操作方式, 但是这两天用这个方式安装Win10 22H2时遇到奇葩蓝屏.

如图:

setup.png
bsod.png

在走到"准备要安装的文件"这一步时必然蓝屏.


本着先怀疑自己再怀疑他人的原则, 先做了如下操作:
1. vmware换vbox
2. 配合各版本ipxe的wimboot, 在物理机上操作
3. 换不同版本的Win10 ISO
4. 检查iscsi server软坚硬是否有异常, 换不同的iscsi server及ipxe版本
5. 先本地装好系统, 然后做成IMG放到iscsi server上, 跳过iso安装过程

以上操作全都会出现一样的蓝屏. 最后找了个Win7 ISO, 在各种条件下都是一次成功.


以上操作虽然没能解决问题, 但是基本确定了问题范围: Win10.

尝试Google一下, 还真搜到一个类似的问题, 解决方法是通过挂盘修改注册表, 关闭PageFile.
然而操作之后发现依旧蓝屏. 而且看原帖也确实有人说行, 有人说不行.

那么看起来还是需要分析一下, ISO安装时蓝屏这个场景似乎不太好拿到dump文件, 所以首先对ISO开启调试设置(解压ISO -> bcdedit /store xxxxx\boot\bcd /debug on -> 重新打包ISO), 然后给虚拟机添加一个com口, 设置好windbg双机调试, 再继续先前的操作.

复现后看Windbg:
  1. *** Fatal System Error: 0x000000d1
  2.                        (0xFFFFF803467B59D0,0x0000000000000002,0x0000000000000008,0xFFFFF803467B59D0)

  3. Break instruction exception - code 80000003 (first chance)

  4. A fatal system error has occurred.
  5. Debugger entered on first try; Bugcheck callbacks have not been invoked.

  6. A fatal system error has occurred.

  7. For analysis of this file, run !analyze -v
  8. nt!DbgBreakPointWithStatus:
  9. fffff803`3e003770 cc              int     3
复制代码


解释蓝屏代码和参数0x000000d1 (0xFFFFF803467B59D0,0x0000000000000002,0x0000000000000008,0xFFFFF803467B59D0):
1. 经典代码0x000000d1, 表示IRQL太高没法处理PageFault;
2. 参数中第一个0xFFFFF803467B59D0为发生PageFault的地址;
3. 0x0000000000000002表示当时的IRQL为DISPATCH_LEVEL;
4. 0x0000000000000008表示当时是Execute操作, 不是读写;
5. 最后一个0xFFFFF803467B59D0表示是触发这次PageFault的RIP.


组合起来:
当RIP指向0xFFFFF803467B59D0准备执行时, 对应代码页不在物理内存, 发生PageFault, 但此时IRQL又是DispatchLevel, 没法做磁盘读写操作, 因此Windows选择蓝屏.


看一下栈:
  1. 0: kd> k
  2. # Child-SP          RetAddr               Call Site
  3. 00 fffff203`25c35a78 fffff803`3e115392     nt!DbgBreakPointWithStatus
  4. 01 fffff203`25c35a80 fffff803`3e114976     nt!KiBugCheckDebugBreak+0x12
  5. 02 fffff203`25c35ae0 fffff803`3dffa2d7     nt!KeBugCheck2+0x946
  6. 03 fffff203`25c361f0 fffff803`3e00e229     nt!KeBugCheckEx+0x107
  7. 04 fffff203`25c36230 fffff803`3e009de3     nt!KiBugCheckDispatch+0x69
  8. 05 fffff203`25c36370 fffff803`467b59d0     nt!KiPageFault+0x463
  9. 06 fffff203`25c36508 fffff803`46930271     0xfffff803`467b59d0
  10. 07 fffff203`25c36510 fffff803`46923aa5     dump_NETIO!WfpReleaseFastWriteLock+0x55
  11. 08 fffff203`25c36540 fffff803`46944aac     dump_NETIO!KfdSetVisibleFilterState+0x51
  12. 09 fffff203`25c36570 fffff803`469a94cc     dump_NETIO!KfdApplyBoottimePolicy+0x58
  13. 0a fffff203`25c365d0 fffff803`3e20f25b     dump_NETIO!KfdApplyBoottimePolicyCallback+0x4c
  14. 0b fffff203`25c36600 fffff803`3e20ef6e     nt!RtlpCallQueryRegistryRoutine+0x13f
  15. 0c fffff203`25c36670 fffff803`3e2f6aae     nt!RtlpQueryRegistryValues+0x31a
  16. 0d fffff203`25c36750 fffff803`469a9363     nt!RtlQueryRegistryValuesEx+0xe
  17. 0e fffff203`25c36790 fffff803`469a92c2     dump_NETIO!KfdReadAndApplyBoottimePolicy+0x4f
  18. .........其他略............
复制代码


栈里有个小问题:
nt!KiPageFault+0x463上层是一个无符号且无模块的地址: 0xfffff803`467b59d0, 也正是蓝屏参数里的地址信息(触发以及发生PF的地址).
为啥说这里存在小问题, 因为就算微软没提供符号或者是第三方模块, 按道理windbg也应该显示为模块名+偏移而不是直接显示一个绝对地址.

这种现象常见的三个原因:
1. 栈本身就是坏的, 现在肯定不属于这种情况, 蓝屏栈相当完整和正确, 可以一直回溯到用户态;
2. Windbg开的时机比较晚, 而我确定是先开的windbg再启动的虚拟机, 也不属于这种情况;
3. 以前搞安全时候经常会收集到RCE导致的栈中出现无模块RIP的dump, 然后本次使用的ISO确实都是在网上找的种子, 还真不能保证一定是干净的.

于是检查上层调用处的代码, 也就是dump_NETIO!WfpReleaseFastWriteLock+0x55的上一行:


  1. <pre>WfpReleaseFastWriteLock+48                                     loc_1C0010264:                          ; CODE XREF: WfpReleaseFastWriteLock+18↑j
  2. WfpReleaseFastWriteLock+48                                                                             ; WfpReleaseFastWriteLock+42↑j
  3. WfpReleaseFastWriteLock+48   028 48 8B 0F                      mov     rcx, [rdi]                      ; Lock
  4. WfpReleaseFastWriteLock+4B   028 48 8B D3                      mov     rdx, rbx                        ; LockState
  5. WfpReleaseFastWriteLock+4E   028 48 FF 15 47 3E 07 00          call    cs:__imp_NdisReleaseRWLock
  6. WfpReleaseFastWriteLock+4E
  7. WfpReleaseFastWriteLock+55   028 0F 1F 44 00 00                nop     dword ptr [rax+rax+00h]
  8. WfpReleaseFastWriteLock+5A   028 48 8B 5C 24 30                mov     rbx, [rsp+28h+arg_0]
  9. WfpReleaseFastWriteLock+5F   028 48 83 C4 20                   add     rsp, 20h
  10. WfpReleaseFastWriteLock+63   008 5F                            pop     rdi
  11. WfpReleaseFastWriteLock+64   000 C3                            retn
  12. </pre>
复制代码



这里可以看出发生PageFault的地址理论上应该是IAT NdisReleaseRWLock. 但因为Windbg显示不了模块, 暂时也不能确定这是不是被HOOK.
尝试手动遍历一下PsLoadedModuleList, 发现其实这个地址是在dump_ndis.sys模块内, 那看起来还是因为某些原因导致windbg没能识别出模块.

因此试试重新打开windbg, 再.reload /f重新加载符号再k, 发现windbg已经能够正确显示为dump_NDIS!NdisReleaseRWLock, 那么shellcode的可能性就可以小小的排除了:


  1. 0: kd> k
  2. # Child-SP          RetAddr               Call Site
  3. 00 fffff203`25c35a78 fffff803`3e115392     nt!DbgBreakPointWithStatus
  4. 01 fffff203`25c35a80 fffff803`3e114976     nt!KiBugCheckDebugBreak+0x12
  5. 02 fffff203`25c35ae0 fffff803`3dffa2d7     nt!KeBugCheck2+0x946
  6. 03 fffff203`25c361f0 fffff803`3e00e229     nt!KeBugCheckEx+0x107
  7. 04 fffff203`25c36230 fffff803`3e009de3     nt!KiBugCheckDispatch+0x69
  8. 05 fffff203`25c36370 fffff803`467b59d0     nt!KiPageFault+0x463
  9. 06 fffff203`25c36508 fffff803`46930271     dump_NDIS!NdisReleaseRWLock
  10. 07 fffff203`25c36510 fffff803`46923aa5     dump_NETIO!WfpReleaseFastWriteLock+0x55
  11. ..............................
复制代码


注意netio和ndis这两个模块前面都带个dump_前缀, 是因为windows dump功能初始化机制就是这样, 不是异常现象. 这里的dump_ndis.sys和dump_netio.sys在磁盘上的文件其实就是原本的ndis.sys和netio.sys, 只是内核在初始化dump功能时会加上dump_前缀, 把磁盘驱动相关的模块换个名字在内存中再次加载一份, 跟linux的kdump会重新加载一份干净的内核类似. 只有当发生蓝屏时才会走dump_前缀的模块, 目的是为了实现当系统蓝屏时, 就算是磁盘驱动自身炸了, 系统也能将DUMP数据写入磁盘(实际上iscsi启动场景这样做是没用的).

正常来说物理机应该是加载物理磁盘驱动, 但当前是iscsi启动, 对应的虚拟磁盘驱动msiscsi.sys依赖netio和ndis, 所以系统认为需要重新加载dump_ndis.sys和dump_netio.sys.

后文提到netio.sys和ndis.sys时, 如果没有特意说明, 都是指新加载的dump_ndis和dump_netio, 而不是系统正常运行时使用的ndis和netio.

到这里再重复一下:
当RIP指向NdisReleaseRWLock准备执行时, 对应代码页不在物理内存发生缺页异常然后高IRQL蓝屏.

接下来看看NdisReleaseRWLock的PTE:


  1. 0: kd> !pte @cr2
  2.                                            VA fffff803467b59d0
  3. PXE at FFFFFF7FBFDFEF80    PPE at FFFFFF7FBFDF0068    PDE at FFFFFF7FBE00D198    PTE at FFFFFF7C01A33DA8
  4. contains 000000002FC09063  contains 000000002FD16063  contains 0A0000011FFA4863  contains 950D315840B80400
  5. pfn 2fc09     ---DA--KWEV  pfn 2fd16     ---DA--KWEV  pfn 11ffa4    ---DA--KWEV  not valid
复制代码


最后一级not valid, 确实不在物理内存, 但这里就有点说不通:
NdisReleaseRWLock是个导出接口, 本身就可以跑在DispatchLevel, 而且它在Text段,不是Page或Init, 那么当内核加载dump_ndis模块后,只要没有外部干预(比如调用一些MM接口强行让内核把对应段变成paged), 那NdisReleaseRWLock所在的页应当是一直在物理内存才对, 不可能发生PF.

是Win的bug? 暂时还不能这么想, 比如有没有可能是因为iscsi不稳定, IO出问题而导致模块加载时出了一些异常?
这个推测与当前场景看起来确实有一定相关性(从网络iscsi启动, 并且还不是什么特别稳定的环境), 但是早就检查过iscsi server没有任何异常, 甚至换过多个不同的iscsi server.


那么还是继续看这个栈, 从最早的用户态线程起点开始看(可以跳过, 下面有TLDR版):


  1. 0: kd> k
  2. # Child-SP          RetAddr               Call Site
  3. 00 fffff406`82900a78 fffff804`66315392     nt!DbgBreakPointWithStatus
  4. 01 fffff406`82900a80 fffff804`66314976     nt!KiBugCheckDebugBreak+0x12
  5. 02 fffff406`82900ae0 fffff804`661fa2d7     nt!KeBugCheck2+0x946
  6. 03 fffff406`829011f0 fffff804`6620e229     nt!KeBugCheckEx+0x107
  7. 04 fffff406`82901230 fffff804`66209de3     nt!KiBugCheckDispatch+0x69
  8. 05 fffff406`82901370 fffff804`6f8b59d0     nt!KiPageFault+0x463
  9. 06 fffff406`82901508 fffff804`6fa30271     dump_NDIS!NdisReleaseRWLock
  10. 07 fffff406`82901510 fffff804`6fa23aa5     dump_NETIO!WfpReleaseFastWriteLock+0x55
  11. 08 fffff406`82901540 fffff804`6fa44aac     dump_NETIO!KfdSetVisibleFilterState+0x51
  12. 09 fffff406`82901570 fffff804`6faa94cc     dump_NETIO!KfdApplyBoottimePolicy+0x58
  13. 0a fffff406`829015d0 fffff804`6640f25b     dump_NETIO!KfdApplyBoottimePolicyCallback+0x4c
  14. 0b fffff406`82901600 fffff804`6640ef6e     nt!RtlpCallQueryRegistryRoutine+0x13f
  15. 0c fffff406`82901670 fffff804`664f6aae     nt!RtlpQueryRegistryValues+0x31a
  16. 0d fffff406`82901750 fffff804`6faa9363     nt!RtlQueryRegistryValuesEx+0xe
  17. 0e fffff406`82901790 fffff804`6faa92c2     dump_NETIO!KfdReadAndApplyBoottimePolicy+0x4f
  18. 0f fffff406`82901840 fffff804`6faa93ce     dump_NETIO!KfdProcessBoottimePolicy+0x5e
  19. 10 fffff406`82901880 fffff804`6faa9413     dump_NETIO!KfdStartModuleEx+0x3e
  20. 11 fffff406`829018b0 fffff804`6faa907b     dump_NETIO!KfdStartModule+0x23
  21. 12 fffff406`829018e0 fffff804`6fab52fb     dump_NETIO!RtlInvokeStartRoutines+0x3b
  22. 13 fffff406`82901920 fffff804`6659962e     dump_NETIO!DllInitialize+0x9b
  23. 14 fffff406`82901950 fffff804`66599473     nt!MmCallDllInitialize+0x16e
  24. 15 fffff406`829019b0 fffff804`66551acc     nt!MiLoadImportDll+0x63
  25. 16 fffff406`82901a00 fffff804`66551704     nt!MiResolveImageReferences+0x214
  26. 17 fffff406`82901b10 fffff804`6655080c     nt!MiResolveImageImports+0x94
  27. 18 fffff406`82901b80 fffff804`66599454     nt!MmLoadSystemImageEx+0x690
  28. 19 fffff406`82901d20 fffff804`66551acc     nt!MiLoadImportDll+0x44
  29. 1a fffff406`82901d70 fffff804`66551704     nt!MiResolveImageReferences+0x214
  30. 1b fffff406`82901e80 fffff804`6655080c     nt!MiResolveImageImports+0x94
  31. 1c fffff406`82901ef0 fffff804`66599454     nt!MmLoadSystemImageEx+0x690
  32. 1d fffff406`82902090 fffff804`66551acc     nt!MiLoadImportDll+0x44
  33. 1e fffff406`829020e0 fffff804`66551704     nt!MiResolveImageReferences+0x214
  34. 1f fffff406`829021f0 fffff804`6655080c     nt!MiResolveImageImports+0x94
  35. 20 fffff406`82902260 fffff804`66586871     nt!MmLoadSystemImageEx+0x690
  36. 21 fffff406`82902400 fffff80b`83695cd8     nt!IopLoadCrashdmpImage+0x21
  37. 22 fffff406`82902440 fffff80b`8369513d     crashdmp!LoadPortDriver+0x488
  38. 23 fffff406`82902690 fffff80b`83694d57     crashdmp!CrashdmpLoadDumpStack+0x17d
  39. 24 fffff406`82902750 fffff804`665ab1da     crashdmp!CrashdmpInitialize+0x3c7
  40. 25 fffff406`82902870 fffff804`665ab0e6     nt!IopInitializeCrashDump+0xb2
  41. 26 fffff406`829028f0 fffff804`665aa63a     nt!IoInitializeCrashDump+0x52
  42. 27 fffff406`82902930 fffff804`665a9f4d     nt!MiCreatePagingFile+0x6de
  43. 28 fffff406`82902ac0 fffff804`6620d9f5     nt!NtCreatePagingFile+0x2d
  44. 29 fffff406`82902b00 00007ffd`280ce754     nt!KiSystemServiceCopyEnd+0x25
  45. 2a 000000ed`af8fd688 00007ffd`220b4874     ntdll!NtCreatePagingFile+0x14
  46. 2b 000000ed`af8fd690 00007ffd`229e62f5     WinSetup!CallBack_CreatePageFile+0x444
  47. 2c 000000ed`af8fd970 00007ffd`229e487f     WDSCORE!CallSubscribers+0x145
  48. 2d 000000ed`af8fda70 00007ffd`229ec39e     WDSCORE!SeqExecute+0x35f
  49. 2e 000000ed`af8fdb10 00007ffd`229ecc27     WDSCORE!WdsExecuteWorkQueue+0x62e
  50. 2f 000000ed`af8fdff0 00007ffd`220552c7     WDSCORE!WdsExecuteWorkQueueEx+0x27
  51. 30 000000ed`af8fe030 00007ff6`8db4799d     WinSetup!InstallWindows+0x33f7
  52. 31 000000ed`af8fed10 00007ff6`8db32bb6     setup!RunSetup+0x4d
  53. 32 000000ed`af8fed50 00007ff6`8db53c99     setup!wWinMain+0x966
  54. 33 000000ed`af8ffd10 00007ffd`26457614     setup!__wmainCRTStartup+0x1c9
  55. 34 000000ed`af8ffdd0 00007ffd`280826a1     KERNEL32!BaseThreadInitThunk+0x14
  56. 35 000000ed`af8ffe00 00000000`00000000     ntdll!RtlUserThreadStart+0x21
复制代码

可以得到以下关键信息:

1. Windows安装进程Setup.exe调用NtCreatePagingFile创建分页文件;
2. 内核在创建分页文件时需要初始化dump机制, 即以dump_为前缀, 将原本的msiscsi.sys重新加载一份到内存;
3. 加载器在加载dump_msiscsi.sys过程中发现依赖dump_netio.sys, 于是继续加载dump_netio.sys;
4. dump_netio.sys是个内核DLL, 有DLLInitialize(类似DLLMain, 后面直接叫netio!DLLMain了), 于是内核决定调用netio!DLLMain (这里很关键, 当看到DLLInitialize时, 就隐约有了一种不好的预感)
5. netio!DLLMain需要做一些WFP BootFilter相关的初始化, 最终调用到了NdisReleaseRWLock, 发生PF -> 蓝屏.

在第4步, 看到DLLInitialize就比较怀疑可能是DLL依赖导致的问题(用户态DLLMain问题过于经典, 内核里一样会存在类似问题).

从导入表初步看一下依赖关系, 只写关键的:
1. msiscsi.sys依赖tdi/netio;
2. tdi依赖ndis;
3. netio依赖ndis;
4. ndis依赖netio;


netio.sys和ndis.sys互相依赖, 这其实也不是一个大问题, 因为windows pe loader本身就可以处理两个DLL互相依赖的情况, 只是跟用户态DLLMain一样, 在DLLMain里乱搞就容易踩坑.
会不会出问题最关键要看加载器是按什么顺序加载依赖模块, 以及DLLMain干了什么.

先看内核加载模块的逻辑, 也就是MMLoadSystemImage的实现, 跳过细节, 直接说关键点:
1. 创建文件Section/ControlArea/ProtoPTE
2. 各种初始化(比如DLL依赖,插入PsLoadedModuleList)
3. 还有一个关键步骤是把不能分页的段, 比如text段全部加载进物理内存, 在Win10上是由MiHandleDriverNonPagedSections处理(这里也正是问题的关键)
4. 看看是否有HotPatch, 如果有则加载并处理, 无则跳过(这个也比较有意思, 不过与本文无瓜, 先不管它)
5. 如果所有环节没出错, 调用MiDriverLoadSucceeded表示模块加载完成, 这里也会调用ImageNotify回调, 最后通过DBGLoadImageSymbols通知调试器有模块加载了
6. 调用DLLMain

当前问题中, netio和ndis互相依赖, netio的dllmain调用ndis的函数, 结合上面的模块加载逻辑, 什么情况才会导致0xD1蓝屏?
一种可能:

如果netio的dllmain被调用时, 内核对ndis的加载还没有走到第3步MiHandleDriverNonPagedSections, 也就是ndis的text段还没有被加载到物理内存, 那就有可能会出现高IRQL时PageFault蓝屏;
并且因为对NDIS的加载还没有完成, 那么Windbg就不会得到模块加载通知, 继而在蓝屏第一现场从内核中断到调试器时, Windbg就不能根据栈中的RetRIP找到具体模块, 最后k命令看栈就会出现最开始说的小问题: 直接显示一个无模块的绝对地址.
而执行.reload后又能显示对应模块, 则是因为.reload会主动遍历PsLoadedModuleList, 所以又能找到dump_ndis.sys.

这个推测好像完全讲得通, 但目前还没有证据.

想要实锤, 需要观察当时相关模块的加载处理顺序, 具体方式是在MMLoadSystemImage函数内的关键环节下断点, 配合简单windbg脚本打印出全过程日志:


  1. bp nt!MmLoadSystemImageEx ".printf "MMLoadSystemImageEx: Names(%msu - %msu - %msu)\r\n",@rcx,@rdx,@r8;.echo "\n";g;"       #模块加载的起点
  2. bp nt!MiConstructLoaderEntry ".printf "MiConstructLoaderEntry: LDR(%p) Path:%msu\n",@rcx,@rdx;.echo "\n";g;"               #这一步执行完, 在PsLoadedModuleList里就能看到对应模块了;
  3. bp nt!MiResolveImageImports ".printf "MiResolveImageImports: LDR(%p) Name(%msu)\n",@rcx,@r9;.echo "\n";g;"                 #这里开始处理IAT
  4. bp nt!MiResolveImageReferences ".printf "MiResolveImageReferences: LDR(%p) Name(%msu)\n",@rcx,@rdx;.echo "\n";g;"  
  5. bp nt!MiSnapThunk ".printf "MiSnapThunk: SrcBase(%p) Base(%p) pThunk(%ma)\n",@rcx,@rdx,poi(@r8)+@rdx+0x2;.echo "\n";g;"    #这里是具体的IAT填充过程,可以观察从啥模块导入啥函数到啥模块;
  6. bp nt!MiLoadImportDll ".printf "MiLoadImportDll: Name:(%msu - %msu)\n",@rcx,@rdx;.echo "\n";g;"                            #处理依赖的依赖
  7. bp nt!MiHandleDriverNonPagedSections ".printf "MiHandleDriverNonPagedSections: LDR(%p)\n",@rcx;.echo "\n";g;"              #将不能分页的段加载进物理内存
  8. bp nt!MmCallDllInitialize ".printf "MmCallDllInitialize: LDR(%p)\n",@rcx;.echo "\n";g;"                                    #调用DLLMain
  9. bp nt!MiDriverLoadSucceeded ".printf "MiDriverLoadSucceeded: LDR(%p) Name:(%msu)\n",@rcx,@r9;.echo "\n";g;"                #模块加载完成

  10. (以上并不是一次顺序执行完就结束了, 因为要处理依赖的依赖, 所以会有一定程度的递归)
复制代码


使用这种方式打断点日志特别慢, 一共跑了有20分钟, 毕竟是com口调试,而且windbg还要解析执行断点后的脚本, 这个脚本引擎可能也不是十分高效.
这里就凸显出linux ftrace kprobe的优越性了, 所以喷windows, 要喷对点, 不能跟风盲目喷.
不过说实话win的这个联机调试思路还是好的, 只是历史过于悠久, 没有与时俱进, 即使用VirtualKD或者NetDebug, 会快一点, 但相比linux kprobe, 依旧慢很多.

最后整理好的断点日志(省略了不重要的MiSnapThunk), 可以不看, 下面有简化归纳:

  1. ###1. 开始加载dump_msiscsi.sys
  2. MMLoadSystemImageEx: Names(\SystemRoot\System32\drivers\msiscsi.sys - dump_ - <Win32 error 0n30>)              
  3. MiConstructLoaderEntry: LDR(ffffa48771a57cc0) Path:dump_msiscsi.sys
  4. ###2. 处理dump_msiscsi.sys导入表
  5. MiResolveImageImports: LDR(ffffa487719fea70) Name(dump_msiscsi.sys)
  6. MiResolveImageReferences: LDR(ffffa487719fea70) Name(dump_msiscsi.sys)
  7.     ##3. 发现msiscsi.sys依赖tdi.sys, 因此加载dump_tdi.sys
  8.     MiSnapThunk: load dump_tdi.sys
  9.         MiLoadImportDll: Name:(\SystemRoot\System32\drivers\TDI.SYS - dump_)
  10.         MMLoadSystemImageEx: Names(\SystemRoot\System32\drivers\TDI.SYS - dump_ - <Win32 error 0n30>)  
  11.         MiConstructLoaderEntry: LDR(ffffa48771a57530) Path:dump_TDI.SYS
  12.         MiResolveImageImports: LDR(ffffa48771d9ada0) Name(dump_TDI.SYS)
  13.         MiResolveImageReferences: LDR(ffffa48771d9ada0) Name(dump_TDI.SYS)
  14.             #4. 发现tdi.sys依赖ndis.sys, 因此加载ndis.sys
  15.             MiSnapThunk: load dump_ndis.sys
  16.                 MiLoadImportDll: Name:(\SystemRoot\System32\drivers\NDIS.SYS - dump_)
  17.                 MMLoadSystemImageEx: Names(\SystemRoot\System32\drivers\NDIS.SYS - dump_ - <Win32 error 0n30>)
  18.                 # 这里执行后PsLoadedModuleList里就有了dump_ndis.sys. 虽然此时dump_ndis.sys模块整体还没加载完成,但如果此时有其他模块依赖ndis.sys
  19.                 # 加载器还是可以从PsLoadedModuleList获取ndis.sys的eat并正常填充对应模块的IAT的
  20.                 MiConstructLoaderEntry: LDR(ffffa48771a56da0) Path:dump_NDIS.SYS
  21.                 # 开始处理dump_ndis.sys的依赖
  22.                 MiResolveImageImports: LDR(ffffa4876b4bfd70) Name(dump_NDIS.SYS)
  23.                 MiResolveImageReferences: LDR(ffffa4876b4bfd70) Name(dump_NDIS.SYS)
  24.                     ###5. 发现ndis.sys依赖netio.sys, 因此加载netio.sys
  25.                     MiSnapThunk: load dump_netio.sys
  26.                         MiLoadImportDll: Name:(\SystemRoot\System32\drivers\NETIO.SYS - dump_)
  27.                         MMLoadSystemImageEx: Names(\SystemRoot\System32\drivers\NETIO.SYS - dump_ - <Win32 error 0n30>)  
  28.                         MiConstructLoaderEntry: LDR(ffffa48771a57cc0) Path:dump_NETIO.SYS
  29.                         MiResolveImageImports: LDR(dump_netio) Name(dump_NETIO.SYS)
  30.                         MiResolveImageReferences: LDR(dump_netio) Name(dump_NETIO.SYS)
  31.                             ##6. 发现netio.sys依赖ndis.sys, 此时dump_ndis.sys已经进了PsLoadedModuleList, 因此这里不需要再从头加载dump_ndis.sys
  32.                             ##而是找到dump_ndis.sys的base+eat, 填充netio.sys的iat
  33.                             MiSanpThunk: import from dump_ndis.sys                         
  34.                         ###到这里, netio.sys的所有IAT处理完成, 使netio.sys的text/data等段加载到物理内存常驻
  35.                         MiHandleDriverNonPagedSections: LDR(dump_netio)
  36.                         MiDriverLoadSucceeded: LDR(dump_netio) Name:(\SystemRoot\System32\drivers\dump_NETIO.SYS)
  37.                         ##7. 调用netio!DLLMain
  38.                         MmCallDllInitialize: LDR(dump_netio)
  39.                                 ##netio!DLLMain调用NDISReleaseRWLock
  40.                                 ##看看前面的模块依赖处理过程, 内核加载器对dump_NDSI.SYS的加载, 刚刚执行到从netio.sys导入API的阶段(对应上面#4和#5之间)
  41.                                 ##还没有执行到MiHandleDriverNonPagedSections, 那么NDIS.SYS的text/data此时也就没有常驻物理内存
  42.                                 ##这个时候netio!DLLMain去调用ndis.sys!NDISReleaseRWLock, 自然会发生pagefault, 而此时IRQL又是DPCLEVEL, 系统没法处理这次PF, 只能选择蓝屏
复制代码



以上是手动整理的加载过程全过程日志, 其实我本地排版是很好的, 但是dz这个编辑器太难用, 估计排版应该是乱了, 简单描述一下:

1. 调用MMLoadSystemImage加载dump_msiscsi.sys
2. 处理dump_msiscsi.sys导入表
3. 发现msiscsi.sys依赖tdi.sys, 因此加载dump_tdi.sys
4. 发现tdi.sys依赖ndis.sys, 加载dump_ndis.sys
5. 发现ndis.sys依赖netio.sys, 加载dump_netio.sys
6. 发现netio.sys依赖ndis.sys, 此时dump_ndis.sys在第4步已经进了PsLoadedModuleList, 因此这里不需要再从头加载dump_ndis.sys, 而是直接从已加载的dump_ndis.sys的Base+EAT, 填充netio.sys的IAT;
7. netio.sys的所有依赖处理完毕, 调用netio!DLLMain
8. netio!DLLMain调用dump_ndis!NDISReleaseRWLock

蓝屏在第8步发生, 因为这个时候netio.sys的IAT确实处理完成, 仅看netio自身, 确实已经具备了执行条件, 但是它所依赖的NDIS还处于填充IAT阶段, 根本就没走到MiHandleDriverNonPagedSections这一步, 那么dump_ndis.sys的text段此时就没有被完全加载进物理内存. 而这个时候又是DPC_LEVEl, 发生PF必然蓝屏.


按照以上分析, 可以知道当netio.sys和ndis.sys互相依赖,并且netio!DLLMain会调用NDIS接口, 那么在处理DLL依赖时:
1.如果先加载NDIS.SYS, 然后再加载NDIS.SYS的依赖项NETIO.SYS, netio!DLLMain就会引发PageFault蓝屏;
2.如果先加载Netio.sys, 然后再加载Netio.sys的依赖项NDIS.SYS, Netio!DLLMain就不会引发PageFault蓝屏;

如果理解了上面的分析, 那么应该会有一个疑问:
为了dump初始化而加载的dump_ndis和dump_netio, 和系统正常运行时所使用的ndis和netio是完全相同的文件, 那为什么在系统正常启动, 加载正常的ndis和netio时不会遇到这个问题? 按道理文件一样, IAT一样,依赖顺序处理也一样, 都是先加载ndis, netio作为依赖库后加载但先执行DLLInitialize, 理论上也会遇到上面的PageFault蓝屏问题才对.

原因:
系统的正常ndis.sys在Services注册表中Start值是0(BootStart),而所有BootStart驱动,都是在引导阶段由winload.exe/.efi去加载,直接读到并常驻物理内存,并不走MMLoadSystemImage这套流程, 在winload.exe加载完必要组件, 跳到内核的KiSystemStartup入口时, ndis的text段已经全部在物理内存了, 所以netio!DLLInitialize调用NDIS模块的函数就不会发生PageFault.

我觉得应该就是这个原因. 但在分析三方软件问题时, 静态也好,动态调试也好, 整体来说还是一个偏黑盒的方式, 大多数时候只对关键位置调试跟踪, 一般不会对整个二进制文件的所有位置逐一分析, 稍微偏大的软件, 本身分散的模块化构成, 逻辑错综复杂, 在有限的时间和人力成本条件下, 很难对整个二进制文件做全面覆盖, 即使借助自动化也很难100%.  最后就可能发生在A环境和B环境结论不一致的情况. 一个比较有用且比人肉分析省时间的做法是多做验证, 在不同的场景下去验证结论.

比如上面说的"正常加载时, NDIS.sys是boot start,由WinLoad.exe加载, 所以不会蓝屏", 这个结论真的对吗?

很难保证100%, 所以发帖前我实际验证了一次:
在一台正常启动的本地盘机器上, 将NDIS.SYS的Start由0(BootStart)改为3(DemandStart)(避免被Winload.exe加载), 然后重启机器观察是否能复现出来, 预期应当能复现.

结果是会蓝屏, 但蓝屏原因并不是之前所说的DLL依赖产生的PageFault, 具体什么原因不重要, 最关键是当时Netio!DllMain已经成功执行,并没有产生相同的PF蓝屏.

这跟前面的结论相悖.

具体问题具体分析, 这个现象实际是因为SYS不仅仅只是正常的带有DriverEntry的内核模块, 同时也可以是动态库, 如果将NDIS.SYS启动方式改为3, 但因为TCPIP.SYS也导入了NDIS, 而TCPIP.SYS的Start是0, 那么WinLoad.exe在加载TCPIP.SYS时, 就会忽略NDIS.SYS的启动方式, 继续在引导阶段就把NDIS.SYS作为依赖项加载到物理内存, 所以就没法复现出netio!DLLMain PF蓝屏问题;

既然如此, 如果把TCPIP.SYS也改为3, 是不是就能复现了:

实际还是不能复现, 虽然最后也会蓝屏, 但蓝屏原因同样不是Netio!DLLMain调用NDIS接口触发PF导致.

继续用前面提到的几个断点去抓日志看加载流程:

  1. #加载tdx.sys
  2. MMLoadSystemImageEx: Names(\SystemRoot\system32\DRIVERS\tdx.sys - <Win32 error 0n30> - <Win32 error 0n30>)  
  3.         MiConstructLoaderEntry: LDR(ffff9b8ac55361c0) Path:tdx.sys
  4.         MiResolveImageImports: LDR(ffff9b8ac4e34570) Name(tdx.sys)
  5.         MiResolveImageReferences: LDR(ffff9b8ac4e34570) Name(tdx.sys)
  6.                 #tdx.sys依赖netio.sys
  7.                 MiLoadImportDll: Name:(\SystemRoot\system32\DRIVERS\NETIO.SYS - <Win32 error 0n30>)
  8.                 MMLoadSystemImageEx: Names(\SystemRoot\system32\DRIVERS\NETIO.SYS - <Win32 error 0n30> - <Win32 error 0n30>)  
  9.                 MiConstructLoaderEntry: LDR(ffff9b8ac55370e0) Path:NETIO.SYS
  10.                 MiResolveImageImports: LDR(ffff9b8ac6e1b520) Name(NETIO.SYS)
  11.                 MiResolveImageReferences: LDR(ffff9b8ac6e1b520) Name(NETIO.SYS)
  12.                         #netio.sys依赖ndis.sys
  13.                         MiLoadImportDll: Name:(\SystemRoot\system32\DRIVERS\NDIS.SYS - <Win32 error 0n30>)
  14.                         #加载ndis.sys
  15.                         MMLoadSystemImageEx: Names(\SystemRoot\system32\DRIVERS\NDIS.SYS - <Win32 error 0n30> - <Win32 error 0n30>)  
  16.                         MiConstructLoaderEntry: LDR(ffff9b8ac5536ed0) Path:NDIS.SYS
  17.                         #填充ndis iat
  18.                         MiResolveImageImports: LDR(ffff9b8ac4a84790) Name(NDIS.SYS)
  19.                         MiResolveImageReferences: LDR(ffff9b8ac4a84790) Name(NDIS.SYS)
  20.                         #iat 填充完成
  21.                         MiHandleDriverNonPagedSections: LDR(ffff9b8ac4a84790)
  22.                         #ndis.sys加载完成, 此时ndis text/data段已经全部在物理内存
  23.                         MiDriverLoadSucceeded: LDR(ffff9b8ac4a84790) Name:(\SystemRoot\system32\DRIVERS\NDIS.SYS)
  24.                         MmCallDllInitialize: LDR(ffff9b8ac4a84790)
  25.                 #netio.sys加载成功
  26.                 MiHandleDriverNonPagedSections: LDR(ffff9b8ac6e1b520)
  27.                 MiDriverLoadSucceeded: LDR(ffff9b8ac6e1b520) Name:(\SystemRoot\system32\DRIVERS\NETIO.SYS)
  28.                 #netio!DLLMain
  29.                 MmCallDllInitialize: LDR(ffff9b8ac6e1b520)
复制代码



加载顺序是这样的:

tdx.sys -> netio.sys -> ndis.sys -> netio!DLLMain

加载TDX.sys时发现依赖netio.sys, 因此加载netio.sys, 而netio.sys又依赖NDIS.SYS, 因此加载NDIS.SYS, 并且顺利完成, 执行到了MiHandleDriverNonPagedSections, 最后MiDriverLoadSucceeded, 那么这种情况下NDIS.SYS text段就已经全部加载进了内存, 最后回到netio.sys, 执行netio!DLLMain, 此时自然就不会发生PageFault蓝屏问题. 这种就是netio先加载, ndis后加载, 理论就是没问题.

虽然没有复现出预期的PageFault现象, 但通过对这个场景相关现象的分析, 其实佐证了前面netio/ndis之间DLL依赖处理顺序的结论. 并不是一件坏事.

同时也知道了要怎么才能在系统正常加载netio/ndis时也复现出PF蓝屏:
将所有可能间接导入netio/ndis的模块全部配置为3, 将ndis.sys配置为1, 这样ndis.sys不会因为被其他模块依赖而被间接加载, 同时也可以确保netio.sys是作为NDIS.SYS的依赖项后加载, 以及确保netio.sys!DLLMain先执行.

如此操作后顺利复现:

  1. MMLoadSystemImageEx: Names(\SystemRoot\system32\drivers\ndis.sys - <Win32 error 0n30> - <Win32 error 0n30>)  
  2. MiConstructLoaderEntry: LDR(ffffad0f46dac2f0) Path:ndis.sys
  3. MiResolveImageImports: LDR(ffffad0f46884790) Name(ndis.sys)
  4. MiResolveImageReferences: LDR(ffffad0f46884790) Name(ndis.sys)
  5. MiLoadImportDll: Name:(\SystemRoot\system32\drivers\NETIO.SYS - <Win32 error 0n30>)
  6. MMLoadSystemImageEx: Names(\SystemRoot\system32\drivers\NETIO.SYS - <Win32 error 0n30> - <Win32 error 0n30>)  
  7. MiConstructLoaderEntry: LDR(ffffad0f46dac0e0) Path:NETIO.SYS
  8. MiResolveImageImports: LDR(ffffad0f48c1b8a0) Name(NETIO.SYS)
  9. MiResolveImageReferences: LDR(ffffad0f48c1b8a0) Name(NETIO.SYS)
  10. MiHandleDriverNonPagedSections: LDR(ffffad0f48c1b8a0)
  11. MiDriverLoadSucceeded: LDR(ffffad0f48c1b8a0) Name:(\SystemRoot\system32\drivers\NETIO.SYS)
  12. MmCallDllInitialize: LDR(ffffad0f48c1b8a0)
  13. KDTARGET: Refreshing KD connection

  14. *** Fatal System Error: 0x000000d1
  15.                        (0xFFFFF807553E59D0,0x0000000000000002,0x0000000000000008,0xFFFFF807553E59D0)

  16. Break instruction exception - code 80000003 (first chance)

  17. A fatal system error has occurred.
  18. Debugger entered on first try; Bugcheck callbacks have not been invoked.

  19. A fatal system error has occurred.

  20. For analysis of this file, run !analyze -v
  21. nt!DbgBreakPointWithStatus:
  22. fffff807`4f403770 cc              int     3
  23. 0: kd> k
  24. # Child-SP          RetAddr               Call Site
  25. 00 ffffe809`e2205658 fffff807`4f515392     nt!DbgBreakPointWithStatus
  26. 01 ffffe809`e2205660 fffff807`4f514976     nt!KiBugCheckDebugBreak+0x12
  27. 02 ffffe809`e22056c0 fffff807`4f3fa2d7     nt!KeBugCheck2+0x946
  28. 03 ffffe809`e2205dd0 fffff807`4f40e229     nt!KeBugCheckEx+0x107
  29. 04 ffffe809`e2205e10 fffff807`4f409de3     nt!KiBugCheckDispatch+0x69
  30. 05 ffffe809`e2205f50 fffff807`553e59d0     nt!KiPageFault+0x463
  31. 06 ffffe809`e22060e8 fffff807`55560276     0xfffff807`553e59d0
  32. 07 ffffe809`e22060f0 fffff807`55553aa5     NETIO!WfpReleaseFastWriteLock+0x5a
  33. 08 ffffe809`e2206120 fffff807`55574aac     NETIO!KfdSetVisibleFilterState+0x51
  34. 09 ffffe809`e2206150 fffff807`555d94cc     NETIO!KfdApplyBoottimePolicy+0x58
  35. 0a ffffe809`e22061b0 fffff807`4f60f25b     NETIO!KfdApplyBoottimePolicyCallback+0x4c
  36. 0b ffffe809`e22061e0 fffff807`4f60ef6e     nt!RtlpCallQueryRegistryRoutine+0x13f
  37. 0c ffffe809`e2206250 fffff807`4f6f6aae     nt!RtlpQueryRegistryValues+0x31a
  38. 0d ffffe809`e2206330 fffff807`555d9368     nt!RtlQueryRegistryValuesEx+0xe
  39. 0e ffffe809`e2206370 fffff807`555d92c2     NETIO!KfdReadAndApplyBoottimePolicy+0x54
  40. 0f ffffe809`e2206420 fffff807`555d93ce     NETIO!KfdProcessBoottimePolicy+0x5e
  41. 10 ffffe809`e2206460 fffff807`555d9413     NETIO!KfdStartModuleEx+0x3e
  42. 11 ffffe809`e2206490 fffff807`555d907b     NETIO!KfdStartModule+0x23
  43. 12 ffffe809`e22064c0 fffff807`555e52fb     NETIO!RtlInvokeStartRoutines+0x3b
  44. 13 ffffe809`e2206500 fffff807`4f79962e     NETIO!DllInitialize+0x9b
  45. 14 ffffe809`e2206530 fffff807`4f799473     nt!MmCallDllInitialize+0x16e
  46. 15 ffffe809`e2206590 fffff807`4f751acc     nt!MiLoadImportDll+0x63
  47. 16 ffffe809`e22065e0 fffff807`4f751704     nt!MiResolveImageReferences+0x214
  48. 17 ffffe809`e22066f0 fffff807`4f75080c     nt!MiResolveImageImports+0x94
  49. 18 ffffe809`e2206760 fffff807`4f750166     nt!MmLoadSystemImageEx+0x690
  50. 19 ffffe809`e2206900 fffff807`4f7330b4     nt!MmLoadSystemImage+0x26
  51. 1a ffffe809`e2206940 fffff807`4fa52b87     nt!IopLoadDriver+0x23c
  52. 1b ffffe809`e2206b10 fffff807`4fa6331a     nt!IopInitializeSystemDrivers+0x157
  53. 1c ffffe809`e2206bb0 fffff807`4f7a881b     nt!IoInitSystem+0x2e
  54. 1d ffffe809`e2206be0 fffff807`4f3030e5     nt!Phase1Initialization+0x3b
  55. 1e ffffe809`e2206c10 fffff807`4f402e08     nt!PspSystemThreadStartup+0x55
  56. 1f ffffe809`e2206c60 00000000`00000000     nt!KiStartSystemThread+0x28
复制代码


注意这个栈的起点是内核正常初始化(非iscsi启动, 非dump功能初始化), 因为NDIS.SYS start值为1, 同时没有其他模块依赖NDIS.SYS, 内核就正常加载NDIS.SYS, 解析导入加载NETIO.SYS, 然后在NETIO.SYS!DLLMain触发跟iscsi启动场景一模一样的PageFault蓝屏.
这种就是NDIS先加载, NETIO后加载. 必然蓝屏.

至此印证前面的所有结论.


一些花絮:

蓝屏时DLLMain在调用NDISReleaseRWLock, 说明之前必然也有调用NDISAcquireRWLock, 那在调用NDISAcquireRWLock的时候为啥没蓝屏:

因为虽然也是在netio!DLLMain的上下文, 但当时最开始的IRQL其实是passive_level, 发生PF可以正常处理. 是NDISAcquireRWLock被调用后在内部将IRQL提升为DPCLEVEL, 直到ReleaseRWLock被调用才会降回去.

通过查看AcquireRWLock的PTE可以确认AcquireRWLock当时确实是在物理内存:

  1. 0: kd> !pte dump_ndis!NdisAcquireRWLockWrite
  2.                                            VA fffff8046f8b42f0
  3. PXE at FFFFFC7E3F1F8F80    PPE at FFFFFC7E3F1F0088    PDE at FFFFFC7E3E011BE0    PTE at FFFFFC7C0237C5A0
  4. contains 000000002FC09063  contains 000000002FC0A063  contains 0A00000118278863  contains 00000001252DC021
  5. pfn 2fc09     ---DA--KWEV  pfn 2fc0a     ---DA--KWEV  pfn 118278    ---DA--KWEV  pfn 1252dc    ----A--KREV
复制代码


然后顺便看下NdisAcquireRWLockWrite和NdisReleaseRWLock各自的VA:
NdisAcquireRWLockWrite: fffff804`6f8b42f0
NdisReleaseRWLock:      fffff804`6f8b59d0

这两函数距离很紧, 但又不够紧(不在一个页), 如果它俩恰巧在1个页, 那么在netio调用NdisAcquireRWLockWrite发生PF的时候, 内核也会顺便把NdisReleaseRWLock也带进物理内存, 也就不会发生蓝屏了(当然前提是netio.sys!dllmain没有再调用其他dump_ndis.sys内的函数).
不过这样的话, 这个bug就更难被发现. 也不应该是靠这种方式去解决.


Win7为什么没问题?
观察Win7的netio.sys/ndis.sys的导入表, 他们也是互相依赖的, 并且netio!DLLMain中一样有调用NDIS接口的逻辑, 那为啥Win7上就不会蓝屏:
Win7和Win10的dump初始化机制有差异, 当Win7从iscsi网络启动时, 内核dump机制会初始化失败, 根本就不会走到加载dump_miscsi.sys这一步, 也就不会有后续的DLL互相依赖问题.
也正是因为内核dump机制初始化失败, 当Win7走原生iscsi功能从网络启动时, 系统内必然会出现一条"系统未能初始化故障转储程序"日志:

volmgr.png


作为用户, 要解决这个问题当前只能是靠一些临时规避方案, 这里直接给方案, 不谈个中细节:
1.删除Services\BFE\Parameters\Boot\Filter下的注册表; 如果注册表不存在, netio!DLLMain就不会执行到后面触发蓝屏的流程; 但是要注意这个注册表有可能会被BFE或其他组件重新创建出来, 需要禁用其他服务, 不太推荐;
2.关闭系统的蓝屏dump功能, 关闭后当内核创建分页文件的时候, 就不会去初始化dump机制, 也就不会加载dump_msiscsi.sys, 最后就不会调用到dump_netio.sys!dllmain; (关闭dump功能不影响分页文件)

至于禁用分页文件, 其实确实可以作为一个解决方案, 但是它有局限性, 只能用于一种场景:
先本地装系统, 关闭分页文件, 然后disk2vhd生成img.
这个方案不能用于直接从pxe->iso启动setup.exe然后安装到iscsi盘的场景(至少对我使用的三个ISO不行), 因为ISO里的安装程序setup.exe逻辑是直接调用NtCreatePagingFile强行创建分页文件, 完全不理会注册表中的分页文件开关.

而这应当也是在我最开始搜到类似问题, 按同样操作关闭页面文件没有效果的原因, 甚至也可能是有些人说行, 有些人说不行的原因:
说行的可能是disk2vhd这种方式部署(或者是他们用的ISO里面的setup.exe不会像我这个版本这样强行创建分页文件), 说不行的应该跟我一样是直接从ISO启动setup.exe安装;

本质不是PageFile原因, 而是在创建PageFile的时候, 如果DUMP功能是开着的, 内核就会顺便初始化DUMP, 从而触发最根源的DLL依赖加载顺序问题.
关掉PageFile, 或者禁用dump功能, 都可以规避.
然后Windows只会在启动卷上初始化DUMP功能, 比如如果在本地盘启动的系统上, 挂载一个网络iscsi盘作为数据盘, 然后把页面文件设置在这个iscsi数据盘, 这种情况下应当不会蓝屏(未验证).

再扩展一下:
在windows原生iscsi网络启动场景, 系统发生蓝屏时, 即使蓝屏dump保存功能是开启的, 也不大可能成功写入蓝屏dump信息, 因为这个场景下, 系统往磁盘写入dump数据时, 无论怎么写都得经过msiscsi.sys, 而msiscsi.sys是通过WSK Socket接口走系统的标准协议栈跟iscsi server通信, 在蓝屏时, 系统整体环境已经不正常, msiscsi.sys此时并不能稳定跟iscsi server通信. 虽然系统重新加载了一份netio和ndis, 但是还有网卡驱动以及tcpip.sys还是用的原来的, 要想蓝屏时依旧稳定收发, 整条IO链上所有模块都要配合才行, 这也是windows做的不如Linux的地方, linux是直接拉起一个新内核, 写dump时所处环境相对要更稳一些.

实际只有物理磁盘驱动, 或者自带TCPIP协议栈不依赖OS的硬iscsi, 才能实现稳定的蓝屏dump保存功能.
软iscsi如果只想保存几十到几百K的minidump还是可以的,比如通过NDIS协议驱动直接构造UDP包发出去, 有比较不错的概率可以在系统彻底死掉前把数据发出去, 但要想稳定保存上G甚至10G+的完整dump就比较困难, 大多数时候都是发着发着整个系统就彻底死掉(也不是绝对不行, 实现起来有些复杂, 又没有太大价值).


最后, 根源的DLL依赖问题得微软自己去修(如果我没漏掉什么的话). 另外此问题理论上在iscsi启动场景必现, 至少在我使用的22H2以及其他相近的几个版本上必现, 感觉微软只测了本地盘启动, 根本没测过iscsi启动场景, 略离谱.
回复

使用道具 举报

发表于 2023-8-22 11:08:19 | 显示全部楼层
我最头痛的事情之一就是分析蓝屏,哪怕是分析自己驱动的蓝屏都心烦。

楼主真乃高人也,把微软的蓝屏都给分析得这么细致。

自从阿三主政微软,WINDOWS的毛病就越来越多了,界面上甚至还有英语单词的拼写错误。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2023-8-24 00:26:36 | 显示全部楼层
美俪女神 发表于 2023-8-22 11:08
我最头痛的事情之一就是分析蓝屏,哪怕是分析自己驱动的蓝屏都心烦。

楼主真乃高人也,把微软的蓝屏都给分 ...

确实阿三之后, Win有些地方做得有点马虎.
回复 赞! 靠!

使用道具 举报

发表于 2023-9-3 20:44:22 | 显示全部楼层
之前用mdt搞过pxe启动win10 企业版rtm测试成功过,mdt这玩意可以帮你集成兼容pxe启动的驱动
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-27 13:47 , Processed in 0.036400 second(s), 26 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表