通用
配置符号路径
- 目的
- 让
WinDbg能够将内存地址翻译成可读的函数名,否则调用栈全是地址,无法分析
- 让
- 命令
|
1 2 3 |
.symfix .sympath+ C:\YourApp\Symbols .reload |
识别 Dump 类型和架构
- 目的
- 确定是什么类型的
dump(full dump? mini dump?) 32位还是64位程序- 操作系统版本
- 是否是
WOW64(64位系统运行32位程序)
- 确定是什么类型的
- 命令
|
1 |
vertarget |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Windows 10 Version 19041 MP (8 procs) Free x64 ↑ ↑ ↑ ↑ | | | 架构 | | 编译类型 | 多处理器 系统版本 Product: WinNt, suite: SingleUserTS ↑ 单用户桌面系统 Edition build lab: 19041.1.amd64fre.vb_release.191206-1406 Machine Name: Debug session time: Fri Dec 06 10:30:45.000 2024 (UTC + 8:00) ↑ Dump创建时间(重要!) System Uptime: 2 days 5:23:11.234 ↑ 系统运行了多久(判断是否刚重启) Process Uptime: 0 days 1:15:32.567 ↑ 进程运行了多久(判断是否刚启动就崩溃) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 编译 Free → Release版本(优化编译,难调试) Checked → Debug版本(未优化,易调试) // 架构判断 x64 → 64位系统,64位程序 x86 → 32位系统或32位程序 WOW64 → 64位系统运行32位程序(重要!) Debug session time: Fri Dec 06 10:30:45.000 2024 System Uptime: 2 days 5:23:11 Process Uptime: 0 days 0:00:05.123 ↑ 如果只运行了5秒就崩溃 → 可能是启动初始化问题 |
- 如果看到
WOW64
|
1 |
.effmach x86 # 切换到32位模式分析 |
查看进程和模块信息
- 目的
- 进程名称、
PID - 加载了哪些
DLL - 哪些是系统
DLL,哪些是第三方DLL
- 进程名称、
- 进程命令
|
1 |
| # 竖线,列出所有进程(用户态dump通常只有1个) |
|
1 2 3 4 5 6 |
. 0 id: 1a4c create name: YourApp.exe ↑ ↑ ↑ ↑ | | | 进程名 | | 进程状态 | 进程ID(十六进制) 当前进程(.表示) |
- 模块命令
|
1 2 3 |
lm # 简略模式 lm v # 详细模式(推荐) lm vm 模块名 # 查看特定模块详情 |
|
1 2 3 4 5 6 |
start end module name 00400000 00450000 YourApp ← 你的主程序 10000000 10050000 helper ← 你的辅助DLL 5a000000 5a100000 ThirdParty ← 第三方DLL 76690000 76850000 kernel32 ← Windows系统DLL 77890000 77a40000 ntdll ← Windows核心DLL |
- 模块地址范围
|
1 2 3 4 5 6 7 8 |
// 判断崩溃地址是否在某个模块内 // 例如崩溃在 `0x00420000`,在 YourApp 的地址范围内 start end 00400000 00450000 YourApp ↑ ↑ 起始 结束 计算大小:0x450000 - 0x400000 = 0x50000 = 327,680 字节 = 320 KB |
- 时间戳的价值
|
1 2 3 |
// 确认DLL版本是否正确 Timestamp: Fri Nov 22 15:30:45 2024 |
- 查找特定模块
|
1 2 |
lm m kernel32 # 只显示kernel32相关模块 lm m Your* # 通配符查找所有Your开头的模块 |
自动分析
- 目的
- 让
WinDbg自动分析dump,给出初步诊断
- 让
- 命令
|
1 |
!analyze -v |
|
1 2 3 4 5 |
!analyze # 基本分析 !analyze -v # 详细分析(推荐) !analyze -hang # 专门分析卡住问题 !analyze -crash # 专门分析崩溃问题 !analyze -show # 显示上次分析结果 |
!analyze -v的输出
|
1 2 3 4 5 6 7 8 9 10 11 |
// 第1部分:Dump 基本信息 DUMP_CLASS: 2 ↑ 0 = 内核dump 1 = 完整用户态dump 2 = 迷你dump(minidump) DUMP_QUALIFIER: 0 ↑ dump的额外限定信息 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 第2部分:异常信息(崩溃时) FAULTING_IP: YourApp!BadFunction+42 00420123 8b08 mov ecx,dword ptr [eax] ↑ ↑ 地址 出错的汇编指令 EXCEPTION_RECORD: ffffffffffffffff -- (.exr 0xffffffffffffffff) EXCEPTION_CODE: c0000005 ↑ 异常代码(Access Violation = 访问违例) EXCEPTION_PARAMETER1: 00000000 ↑ 0 = 读取失败 1 = 写入失败 8 = DEP违例 EXCEPTION_PARAMETER2: 00000000 ↑ 导致访问违例的地址(这里是NULL) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 第3部分:错误分类 PROCESS_NAME: YourApp.exe ↑ 出错的进程名 ERROR_CODE: (NTSTATUS) 0xc0000005 - Access Violation ↑ 错误码的含义 EXCEPTION_CODE_STR: ACCESS_VIOLATION ↑ 错误码的文本描述 READ_ADDRESS: 00000000 ↑ 试图访问的地址(NULL指针) FAULTING_THREAD: 00001234 ↑ 出错的线程ID |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// 第4部分:调用栈(最重要!) STACK_TEXT: 00 0019f8e0 00420456 YourApp!BadFunction+0x42 01 0019f910 004205a3 YourApp!ProcessData+0x56 02 0019f950 00420800 YourApp!MainLoop+0xa3 03 0019f9a0 00401234 YourApp!main+0x100 04 0019ffc0 76695d49 kernel32!BaseThreadInitThunk+0x19 STACK_COMMAND: ~0s ; kb ↑ WinDbg建议你执行的命令 // 如何阅读调用栈 00 0019f8e0 00420456 YourApp!BadFunction+0x42 ↑ ↑ ↑ ↑ ↑ | | | | 偏移量 | | | 函数名 | | 返回地址(下一帧的指令地址) | 栈帧地址(EBP寄存器值) 帧序号(从0开始,0是当前帧) 调用关系(从下往上读): main 调用了 MainLoop MainLoop 调用了 ProcessData ProcessData 调用了 BadFunction BadFunction 在 +0x42 位置崩溃 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 第5部分:问题归类 FAILURE_BUCKET_ID: NULL_POINTER_READ_c0000005_YourApp.exe!BadFunction ↑ ↑ ↑ 问题类型 异常码 出错位置 BUCKET_ID: APPLICATION_FAULT_NULL_POINTER_READ_INVALID_POINTER_READ ↑ 更详细的分类 WATSON_BUCKET: 1234 ↑ 微软错误报告分类ID // FAILURE_BUCKET_ID 的价值 // 可以用这个ID在Google搜索类似问题 // 可以在微软知识库搜索 // 可以对比多个dump,看是否同一问题 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 第6部分:责任归属 ODULE_NAME: YourApp ↑ 出错的模块 IMAGE_NAME: YourApp.exe ↑ 出错的可执行文件 FOLLOWUP_NAME: YourApp ↑ 建议跟进的责任方 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 第7部分:诊断建议 ANALYSIS_VERSION: 10.0.22621.1 amd64fre PRIMARY_PROBLEM_CLASS: APPLICATION_FAULT ↑ 主要问题类型 DEFAULT_BUCKET_ID: NULL_POINTER_READ FAILURE_ID_HASH_STRING: null_pointer_read_{app-version} FAILURE_ID_HASH: {12345678-1234-1234-1234-123456789abc} Followup: YourApp ↑ 你应该联系谁来修复 |
- 根据
!analyze输出判断问题类型
|
1 2 3 4 5 6 |
// 场景1:崩溃(Crash) EXCEPTION_CODE: c0000005 EXCEPTION_RECORD: 有效地址 FAULTING_IP: 有具体地址 → 这是崩溃,继续看异常代码和调用栈 |
|
1 2 3 4 5 6 |
// 场景2:卡死(Hang) No exception information WAIT_CHAIN_ANALYSIS: 显示等待链 BLOCKING_THREAD: 显示阻塞线程 → 这是hang,需要分析等待原因 |
查看所有线程
- 目的
- 了解程序有多少线程,每个线程在做什么
- 列出线程概览
Suspend: 0- 线程可以运行Suspend: 1- 线程被挂起(可能正常,也可能异常)Unfrozen- 线程可调度Frozen- 线程被冻结(调试器命令导致)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
~ # 列出线程概览 // 示例 0 Id: 1a4c.1b3c Suspend: 0 Teb: 00981000 Unfrozen "MainThread" ↑ ↑ ↑ ↑ ↑ ↑ | | | | | 线程名称(如果有) | | | | 线程状态 | | | 线程环境块地址 | | 挂起计数(0=运行中) | 进程ID.线程ID(十六进制) 线程索引 . 1 Id: 1a4c.2d4e Suspend: 0 Teb: 00984000 Unfrozen ↑ . = 当前线程 2 Id: 1a4c.3a5f Suspend: 1 Teb: 00987000 Frozen ↑ ↑ 挂起计数>0 Frozen=已冻结 |
- 查看所有线程的调用栈
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
~*k # 所有线程,简略栈 ~*kb # 所有线程,带参数的栈 ~*kv # 所有线程,详细栈 // 示例 0 Id: 1a4c.1b3c Suspend: 0 Teb: 00981000 Unfrozen # ChildEBP RetAddr 00 0019f8e0 00420456 YourApp!BadFunction+0x42 01 0019f910 004205a3 YourApp!ProcessData+0x56 02 0019f950 00420800 YourApp!MainLoop+0xa3 1 Id: 1a4c.2d4e Suspend: 0 Teb: 00984000 Unfrozen # ChildEBP RetAddr 00 0029fa20 76a89068 ntdll!NtWaitForSingleObject+0xc 01 0029fa90 76a88fd2 KERNELBASE!WaitForSingleObjectEx+0x88 02 0029faa4 00430123 YourApp!WorkerThread+0x23 |
- 快速扫描所有线程
- 看每个线程在做什么:
NtWaitForSingleObject- 等待(正常)Sleep- 休眠(正常)- 如果都在等待,可能死锁
- 如果某线程栈很深,可能递归太深
- 切换线程
|
1 2 3 |
~0s # 切换到线程0 ~1s # 切换到线程1 ~*s # 切换到所有线程(不常用) |
|
1 2 3 4 5 |
// 切换后可以执行 k # 查看当前线程的调用栈 r # 查看当前线程的寄存器 dv # 查看当前线程的局部变量 |
查看寄存器和内存
- 目的
- 查看崩溃时的
CPU状态和内存内容
- 查看崩溃时的
- 查看寄存器
|
1 2 3 4 5 6 7 8 9 10 11 12 |
r # 查看所有寄存器 r eax # 查看特定寄存器(32位) r rax # 查看特定寄存器(64位) // 示例 eax=00000000 ebx=0019f910 ecx=00420000 edx=0040abcd esi=00000001 edi=0019f950 ↑ NULL指针! eip=00420123 esp=0019f8e0 ebp=0019f910 ↑ 指令指针 ↑ 栈指针 ↑ 栈帧指针 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202 |
|
1 2 3 4 5 6 7 8 9 |
eip/rip 当前执行到哪条指令(崩溃地址) esp/rsp 栈指针(栈顶) ebp/rbp 栈帧指针 eax/rax 通常存放返回值或函数参数 // 常见问题特征 eax=00000000 # 如果崩溃在 mov ecx,[eax],说明NULL指针访问 eip=00000000 # 指令指针是0,说明跳转到了NULL地址 esp=00000000 # 栈指针是0,栈已损坏 |
- 查看内存
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
db 地址 # Display Bytes(16进制字节) dw 地址 # Display Words(16进制WORD,2字节) dd 地址 # Display DWORDs(16进制DWORD,4字节) dq 地址 # Display QWORDs(16进制QWORD,8字节,64位) da 地址 # Display ASCII字符串 du 地址 # Display Unicode字符串 dd 0019f8e0 0019f8e0 00420456 0040abcd 00000001 0019f950 ↑ ↑ ↑ ↑ 地址处的4个DWORD值 du 00420000 00420000 "Hello World" |
其他
symfix- 自动连接到微软的公共符号服务器
- 可以下载
Windows系统文件(kernel32.dll, ntdll.dll等)的符号 - 默认储存在
C:\Symbols</li> </ol> </li> <li>sympath+ 路径- 添加你自己程序的符号文件路径
+号:表示追加,不覆盖已有路径
1.sympath+ C:\YourApp\Symbolsreload- 重新加载所有模块的符号
12.reload /f # 强制重新加载.reload YourApp.exe # 只重新加载指定模块- 如何判断符号加载成功
1lm # 列出所有模块输出示例:
1234start end module name00400000 00450000 YourApp C:\Symbols\YourApp.pdb ← 有pdb路径=成功76690000 76850000 kernel32 (pdb symbols) ← 有"pdb symbols"=成功10000000 10050000 BadModule (deferred) ← "deferred"=未加载- 常见异常代码
123456c0000005 → Access Violation(访问违例)- 最常见的崩溃c0000094 → Integer Division by Zero(除零错误)c000001d → Illegal Instruction(非法指令)c00000fd → Stack Overflow(栈溢出)80000003 → Breakpoint(断点,通常不是错误)c000008e → Float Division by Zero(浮点除零)崩溃(
Crash)专项分析异常信息
- 目的
- 详细了解崩溃的原因和上下文
- 查看异常记录
ExceptionAddress- 崩溃发生的精确地址ExceptionCode- 异常类型Parameter[0]- 操作类型(读/写)Parameter[1]- 访问的地址(0表示NULL)
123456789.exr -1 # 查看最后一个异常记录.exr 地址 # 查看指定异常记录ExceptionAddress: 00420123 (YourApp!BadFunction+0x00000042)ExceptionCode: c0000005 (Access violation)ExceptionFlags: 00000000NumberParameters: 2Parameter[0]: 00000000 ← 0=读取,1=写入Parameter[1]: 00000000 ← 尝试访问的地址- 查看上下文
- 执行后,寄存器会切换到崩溃瞬间的状态,然后可以:
- 查看崩溃时的准确调用栈
- 查看崩溃时的寄存器
1.cxr -1 # 查看崩溃时的CPU上下文分析崩溃点的代码
- 目的
- 理解崩溃指令在做什么
- 反汇编崩溃点
123u 地址 # 反汇编指定地址u eip # 反汇编当前指令指针ub eip # 反汇编指令指针之前的代码1234567891011121314151617u 00420123YourApp!BadFunction+0x3c:00420123 8b08 mov ecx,dword ptr [eax] ← 崩溃在这里!↑从eax指向的地址读取数据00420125 894dfc mov dword ptr [ebp-4],ecx00420128 8b45fc mov eax,dword ptr [ebp-4]// 如何分析mov ecx,dword ptr [eax]↑ ↑| 从eax指向的内存读取4字节存入ecx寄存器如果 eax=0,则访问地址0 → NULL指针如果 eax=0xcccccccc,则未初始化变量 → 使用了未初始化的指针如果 eax=0x12345678,则可能野指针 → 指针已被释放但仍在使用- 反汇编整个函数
123456789101112uf 函数名 # Unassemble Functionuf YourApp!BadFunctionYourApp!BadFunction:00420100 push ebp00420101 mov ebp,esp00420103 sub esp,800420106 mov eax,dword ptr [ebp+8] ← 获取第一个参数00420109 test eax,eax ← 检查是否NULL0042010b je YourApp!BadFunction+0x50 ← 如果NULL则跳转...00420123 mov ecx,dword ptr [eax] ← 崩溃点!说明前面的NULL检查没生检查函数参数和局部变量
- 目的
- 查看崩溃函数的输入和内部状态
- 查看局部变量
12345678910111213dv # Display Variablesdv /V # 详细模式(显示地址和类型)dv /i # 显示变量类型dvpData = 0x00000000 ← NULL指针!nCount = 0n42 ← 0n表示十进制szName = 0x0040abcd "Test"dv /V@ ebp-04 pData = 0x00000000↑变量在栈上的位置- 查看函数参数
1234dv /i /t # 包含类型信息@ebp+08 struct MyData * pData = 0x00000000@ebp+0c int nCount = 0n42- 手动查看栈内容
123456dd ebp L4 # 查看栈帧指针附近dd ebp0019f910 0019f950 004205a3 00000000 0000002a↑ ↑ ↑ ↑上一帧ebp 返回地址 参数1 参数2常见崩溃模式识别
NULL指针访问
123456EXCEPTION_CODE: c0000005READ_ADDRESS: 00000000反汇编:mov eax, dword ptr [ecx] # ecx = 0诊断:某个指针没有正确初始化- 野指针(已释放的内存)
1234567EXCEPTION_CODE: c0000005READ_ADDRESS: 0xdddddddd (或 0xfeeefeee, 0xcdcdcdcd)0xdddddddd → 被free()释放的内存(Debug模式)0xcdcdcdcd → 未初始化的栈内存(Debug模式)0xfeeefeee → 被HeapFree释放的内存诊断:使用了已释放的对象(Use-After-Free)- 栈溢出
123456789XCEPTION_CODE: c00000fd (Stack Overflow)调用栈:00 Function A01 Function B02 Function A03 Function B...99 Function A ← 递归太深诊断:无限递归或栈上分配了巨大的数组- 除零错误
1234EXCEPTION_CODE: c0000094 (Integer Divide by Zero)反汇编:idiv ecx # ecx = 0诊断:除数为0卡死(
Hang)专项识别阻塞线程
- 目的
- 找出是哪个线程卡住了,或线程间是否死锁
- 运行
hang分析
1!analyze -v -hang12345678BLOCKING_THREAD: 3↑线程3导致了阻塞WAIT_CHAIN_ANALYSIS:Thread 0 → Waiting on Mutex A → Owned by Thread 3Thread 3 → Waiting on Mutex B → Owned by Thread 0↑ 死锁!两个线程互相等待- 查看线程
CPU使用- 某线程时间特别长→ 可能死循环,切换到该线程查看
- 所有线程时间都很短→ 不是
CPU问题,可能是在等待I/O或锁
1234!runaway # 查看用户态时间!runaway 1 # 查看内核态时间!runaway 2 # 查看创建后的总时间!runaway 7 # 显示所有信息12345User Mode TimeThread Time3:1a4c 0 days 0:05:30.123 ← 线程3占用5分30秒0:2b5d 0 days 0:00:00.0451:3c6e 0 days 0:00:00.023分析等待原因
- 目的
- 查看线程在等待什么资源
- 查看线程调用栈(重点)
1~*kb # 查看所有线程栈典型等待模式
- 模式1:等待同步对象
123400 ntdll!NtWaitForSingleObject+0xc01 KERNELBASE!WaitForSingleObjectEx+0x8802 YourApp!WorkerThread+0x42→ 线程在等待Event/Mutex/Semaphore123!handle # 查看所有句柄k = kb # 查看详细栈,获取等待的句柄值!handle 句柄值 f # 查看句柄详情- 模式2:等待文件
I/O- 线程在读文件(可能网络文件导致慢)
12300 ntdll!NtReadFile+0xc01 KERNELBASE!ReadFile+0x6002 YourApp!LoadConfig+0x23123456!handle 0 f File # 列出所有文件句柄Handle 1234Type FileName \Server\Share\config.txt↑ 网络路径!可能网络问题- 模式3:等待网络
123400 ntdll!NtDeviceIoControlFile+0xc01 ws2_32!recv+0x4502 YourApp!DownloadData+0x67→ 线程在等待网络数据- 模式4:等待消息(
UI线程)
123400 win32u!NtUserGetMessage+0xc01 user32!GetMessageW+0x3002 YourApp!WinMain+0x123→ UI线程在消息循环(可能正常)检查死锁
- 目的
- 查找线程间的循环依赖
- 查看所有锁
123!locks # 查看Critical Sections!cs -l # 列出所有临界区!cs -l -o # 只显示被占用的1234567891011CritSec YourApp+0x45678 at 0040abcdWaiterWoken NoLockCount 2 ← 2个线程在等待RecursionCount 1OwningThread 1a4c ← 线程1a4c持有EntryCount 3ContentionCount 5*** Locked ← 当前被锁定WaitingThreads:2b5d ← 线程2b5d在等待3c6e ← 线程3c6e在等待- 检查特定临界区
12!cs -s # 显示统计信息!cs 地址 # 查看特定临界区分析线程池
- 目的
- 查看线程池状态,是否线程池耗尽
- 命令
Idle Thread Count = 0→ 线程池饱和,所有线程在忙Current = Max→ 达到上限,无法创建新线程
1!threadpool # 查看线程池状态12345678Worker Thread Pool:Current Thread Count: 8Max Thread Count: 512Idle Thread Count: 0 ← 没有空闲线程!I/O Thread Pool:Current Thread Count: 4Max Thread Count: 1000Idle Thread Count: 4内存泄漏专项
概述
- 需要两个
dump对比,或使用内存分析扩展
查看堆内存状态
- 目的
- 了解内存使用情况,识别泄漏
- 堆概览
Committed:实际占用的物理内存Reserved:保留的虚拟地址空间- 如果某个堆特别大,可能是泄漏源
1!heap -s # Heap Summary12345678Heap Address Size Committed ReservedDefault Heap 00150000 00100000 00200000Heap 1 00250000 00050000 00100000Heap 2 00350000 00A00000 01000000 ← 这个堆占用10MB!Total: Size Committed Reserved00B50000 00AA0000 01300000↑ ↑ ↑总计11.3MB 10.6MB提交 19MB保留- 查看堆详情
12!heap -a 堆地址 # 显示堆的所有分配!heap -stat # 统计信息12345Handle allocations:Count Size12345 00001000 ← 12345个分配,每个4KB67890 00000020 ← 67890个分配,每个32字节如果某个大小的分配数量异常多,可能是泄漏点检查句柄泄漏
- 目的
- 查看是否有句柄(文件、线程、Event等)没有关闭
- 统计句柄
- 正常程序:几十到几百个句柄
- 如果有几千个句柄,可能泄漏
- 重点关注
Event、Mutex、File类型
123!handle # 列出所有句柄!handle 0 f # 详细列出所有句柄!handle 0 f File # 只列出文件句柄12345Handle 1234Type EventHandle 1238Type Event... (共5000个Event句柄) ← 异常多!可能泄漏- 按类型统计
1!handle 0 4 # 按类型统计123456Handle count by type:Type CountEvent 5234 ← 异常多!File 23Thread 12Mutex 8查找内存分配源头
- 目的
- 找到哪段代码分配了大量内存但没释放
- 需要
UMDH或LeakDiag
12!heap -p -a 地址 # 查看某个地址的分配调用栈!heap -l # 检测泄漏(需要特殊配置)- 专业的工具
UMDH- 对比两个dump找泄漏Application Verifier- 运行时检测泄漏WinDbg Preview的Time Travel Debugging
性能问题专项
CPU热点分析- 目的
- 找出哪个函数占用
CPU最多
- 找出哪个函数占用
- 命令
1!runaway 7 # 查看所有线程的CPU时间1234// 找到最耗时的线程后~3s # 切换到该线程kb # 查看在做什么实战技巧
保存分析日志
123.logopen C:\Logs\analysis.txt[执行所有分析命令].logclose对比两个
Dump123456789# 在第一个dump中!heap -s > C:\dump1_heap.txt!handle > C:\dump1_handles.txt# 在第二个dump中!heap -s > C:\dump2_heap.txt!handle > C:\dump2_handles.txt# 对比两个文件,找增长的内存或句柄
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Windows API11/11
- ♥ Windbg:命令了解学习二08/28
- ♥ WindowsETW进程监控相关03/17
- ♥ X86_64汇编学习记述四08/09
- ♥ C++程序高级调试与优化_第一篇07/20
- ♥ Windbg:命令了解学习一08/19