.NET程序启动就报错如何截获初期化时的问题json(最新推荐)
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一:背景</a></li><ul class="second_class_ul"><li><a href="#_lab2_0_0">1. 讲故事</a></li></ul><li><a href="#_label1">二:WinDbg 到底遇到了什么</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_1">1. 一个小案例</a></li><li><a href="#_lab2_1_2">2. 如何用 windbg 观察文件内容</a></li><li><a href="#_lab2_1_3">3. 有没有快捷的方式</a></li></ul><li><a href="#_label2">三:总结</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>一:背景</h2><p class="maodian"><a name="_lab2_0_0"></a></p><h3>1. 讲故事</h3>
<p>前几天训练营里的一位朋友在复习课件的时候,程序一跑就报错,截图如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202505/202505150856521.jpg" /></p>
<p>从给出的错误信息看大概是因为json格式无效导致的,在早期的训练营里曾经也有一例这样的报错,最后定位下来是公司的电脑安全软件导致的,一旦有非托管调试器,安全软件就会加密 runtimeconfig.json,最后导致程序无法正常被调试执行。</p>
<p>此时相信有很多人想搞清楚,windbg 在那个时刻到底读到了什么脏东西?到底经历了怎样的惊魂时刻?</p>
<p class="maodian"><a name="_label1"></a></p><h2>二:WinDbg 到底遇到了什么</h2>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>1. 一个小案例</h3>
<p>为了方便演示,写一个简单的 <code>hello world</code> 程序,代码如下:</p>
<div class="jb51code"><pre class="brush:csharp;">internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("hello world!");
Debugger.Break();
Console.ReadLine();
}
}</pre></div>
<p>接下来将程序编译之后观察 <code>runtimeconfig.json</code> 内容,截图如下。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202505/202505150856522.png" /></p>
<p class="maodian"><a name="_lab2_1_2"></a></p><h3>2. 如何用 windbg 观察文件内容</h3>
<p>熟悉 win32 api 的朋友都知道,C# 的 ReadFile 底层会调用 win32 的 ReadFile 方法,方法签名如下:</p>
<div class="jb51code"><pre class="brush:csharp;">BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);</pre></div>
<p>方法的 lpBuffer 参数存放的就是读取到的文件内容。</p>
<p>这里还有一个问题就是 ReadFile 是在 dotnet 进程被初始化的时候读取到内存的,在这个初始化过程中会有一系列的加载,那到底在哪个时刻埋点呢?这时候可以借助 procmon 工具,观察 <code>runtimeconfig.json</code> 的加载时机,截图如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202505/202505150856523.png" /></p>
<p>上面的卦中 <code>runtimeconfig.json</code> 的读取是发生在 <code>hostfxr.dll</code> 加载之后,有了这些信息,思路就有了。</p>
<p>sxe ld hostfxr 获取插入点。bp KERNELBASE!ReadFile 观察第二个参数。</p>
<div class="jb51code"><pre class="brush:plain;">0:000> sxe ld hostfxr
0:000> g
ModLoad: 00007ffc`29b60000 00007ffc`29b8f000 C:\windows\System32\IMM32.DLL
ModLoad: 00007ffc`03660000 00007ffc`036ba000 C:\Program Files\dotnet\host\fxr\9.0.2\hostfxr.dll
ntdll!NtMapViewOfSection+0x14:
00007ffc`2b1ad9f4 c3 ret
0:000> bp KERNELBASE!ReadFile
0:000> g
Breakpoint 1 hit
KERNELBASE!ReadFile:
00007ffc`28822670 48895c2410 mov qword ptr ,rbx ss:0000006c`c8f7de88=00000259c2eb0000
0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=000000000000012c
rdx=00000259c2ec8ee0 rsi=0000000000000003 rdi=0000000000000000
rip=00007ffc28822670 rsp=0000006cc8f7de78 rbp=0000000000001000
r8=0000000000001000r9=0000006cc8f7df38 r10=00000259c2ec8ee0
r11=0000006cc8f7db38 r12=000000000000001b r13=0000000000001000
r14=0000000000000003 r15=00000259c2ec8ee0
iopl=0 nv up ei pl zr na po nc
cs=0033ss=002bds=002bes=002bfs=0053gs=002b efl=00000246
KERNELBASE!ReadFile:
00007ffc`28822670 48895c2410 mov qword ptr ,rbx ss:0000006c`c8f7de88=00000259c2eb0000
0:000> pt
KERNELBASE!ReadFile+0xad:
00007ffc`2882271d c3 ret
0:000> dp 0000006cc8f7df38 L1
0000006c`c8f7df3800000000`0000010c
0:000> db 00000259c2ec8ee0 L?10c
00000259`c2ec8ee07b 0d 0a 20 20 22 72 75-6e 74 69 6d 65 4f 70 74{.."runtimeOpt
00000259`c2ec8ef069 6f 6e 73 22 3a 20 7b-0d 0a 20 20 20 20 22 74ions": {.. "t
00000259`c2ec8f0066 6d 22 3a 20 22 6e 65-74 38 2e 30 22 2c 0d 0afm": "net8.0",..
00000259`c2ec8f1020 20 20 20 22 66 72 61-6d 65 77 6f 72 6b 22 3a "framework":
00000259`c2ec8f2020 7b 0d 0a 20 20 20 20-20 20 22 6e 61 6d 65 22 {.. "name"
00000259`c2ec8f303a 20 22 4d 69 63 72 6f-73 6f 66 74 2e 4e 45 54: "Microsoft.NET
00000259`c2ec8f4043 6f 72 65 2e 41 70 70-22 2c 0d 0a 20 20 20 20Core.App",..
00000259`c2ec8f5020 20 22 76 65 72 73 69-6f 6e 22 3a 20 22 38 2e "version": "8.
00000259`c2ec8f6030 2e 30 22 0d 0a 20 20-20 20 7d 2c 0d 0a 20 200.0".. },..
00000259`c2ec8f7020 20 22 63 6f 6e 66 69-67 50 72 6f 70 65 72 74 "configPropert
00000259`c2ec8f8069 65 73 22 3a 20 7b 0d-0a 20 20 20 20 20 20 22ies": {.. "
00000259`c2ec8f9053 79 73 74 65 6d 2e 52-75 6e 74 69 6d 65 2e 53System.Runtime.S
00000259`c2ec8fa065 72 69 61 6c 69 7a 61-74 69 6f 6e 2e 45 6e 61erialization.Ena
00000259`c2ec8fb062 6c 65 55 6e 73 61 66-65 42 69 6e 61 72 79 46bleUnsafeBinaryF
00000259`c2ec8fc06f 72 6d 61 74 74 65 72-53 65 72 69 61 6c 69 7aormatterSerializ
00000259`c2ec8fd061 74 69 6f 6e 22 3a 20-66 61 6c 73 65 0d 0a 20ation": false..
00000259`c2ec8fe020 20 20 7d 0d 0a 20 20-7d 0d 0a 7d }..}..}
0:000> .writemem D:\testdump\1.txt 00000259c2ec8ee0 L?0x10c
Writing 10c bytes.</pre></div>
<p>从上面的输出中果然看到了 <code>runtimeconfig.json</code> 中的内容,最后打开导出的文件,没毛病。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202505/202505150856524.png" /></p>
<p class="maodian"><a name="_lab2_1_3"></a></p><h3>3. 有没有快捷的方式</h3>
<p>刚才的操作确实能够完成,但还是不爽,因为介入了第三方工具,所以能不能完全通过 windbg 显示打开的 <code>文件路径</code> 和对应的 <code>文件内容</code> 呢?当然是可以的,只需关注如下两个方法 <code>KERNELBASE!CreateFileW</code> 和 <code>KERNELBASE!ReadFile</code> 即可,签名如下:</p>
<div class="jb51code"><pre class="brush:plain;">HANDLE CreateFileW(
LPCWSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);</pre></div>
<p>在 CreateFileW 中我们提取 <code>lpFileName</code> 和返回的 <code>handle</code> 句柄,在 ReadFile 中提取 <code>hFile</code>句柄 和 <code>lpBuffer</code> 文件内容,参考脚本如下:</p>
<div class="jb51code"><pre class="brush:plain;">sxe ld hostfxr;g
bp KERNELBASE!CreateFileW+0x6a " .echo WriteFile----------------------------------; du /c100 @rbx; r @rax; gc"
bp KERNELBASE!ReadFile+0x73 ".echo ReadFile----------------------------------; r @rsi; da poi(@rsp+0x28); gc " ;</pre></div>
<p>稍微说一下 <code>KERNELBASE!CreateFileW+0x6a</code> 是方法的ret处,可以通过 <code>uf KERNELBASE!CreateFileW</code> 计算得到,如下输出所示:</p>
<div class="jb51code"><pre class="brush:plain;">0:000> uf KERNELBASE!CreateFileW
KERNELBASE!CreateFileW:
00007ffc`33341410 4883ec58 sub rsp,58h
00007ffc`33341414 448b942488000000 mov r10d,dword ptr
...
00007ffc`3334147a c3 ret
0:000> ? 00007ffc`3334147a - 00007ffc`33341410
Evaluate expression: 106 = 00000000`0000006a</pre></div>
<p>这里的 <code>KERNELBASE!ReadFile+0x73</code> 是内部函数 <code>ntdll!NtReadFile</code> 返回的RIP处。</p>
<p>最后用 windbg 执行如下:</p>
<div class="jb51code"><pre class="brush:plain;">(3770.3164): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffc`35a407a0 cc int 3
0:000> sxe ld hostfxr;g
ModLoad: 00007ffc`34250000 00007ffc`3427f000 C:\windows\System32\IMM32.DLL
ModLoad: 00007ffc`2deb0000 00007ffc`2df0a000 C:\Program Files\dotnet\host\fxr\9.0.2\hostfxr.dll
ntdll!NtMapViewOfSection+0x14:
00007ffc`35a0d9f4 c3 ret
0:000> bp KERNELBASE!ReadFile+0x73 ".echo ReadFile----------------------------------; r @rsi; da poi(@rsp+0x28); gc " ;
0:000> bp KERNELBASE!CreateFileW+0x6a " .echo WriteFile----------------------------------; du /c100 @rbx; r @rax; gc"
0:000> g
WriteFile----------------------------------
0000020d`1c77ace0"D:\skyfly\20.20250116\src\Example\Example_20_1_1\bin\Debug\net8.0\Example_20_1_1.runtimeconfig.json"
rax=0000000000000128
ReadFile----------------------------------
rsi=0000000000000128
0000020d`1c778ee0"{.."runtimeOptions": {.. "t"
0000020d`1c778f00"fm": "net8.0",.. "framework":"
0000020d`1c778f20" {.. "name": "Microsoft.NET"
0000020d`1c778f40"Core.App",.. "version": "8."
0000020d`1c778f60"0.0".. },.. "configPropert"
0000020d`1c778f80"ies": {.. "System.Runtime.S"
0000020d`1c778fa0"erialization.EnableUnsafeBinaryF"
0000020d`1c778fc0"ormatterSerialization": false.. "
0000020d`1c778fe0" }..}..}"
ReadFile----------------------------------
...</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>三:总结</h2>
<p>有了windbg之后,很多东西都会豁然开朗,而不再像以前那样人云亦云,高级调试这门<code>救火技术</code> 应该是高级程序员必须的进阶之路。</p>
頁:
[1]