崩溃,高 CPU 占用,死锁,内存泄漏。 感觉最难处理的就是堆破坏,因为不知道被哪个线程写坏了的。 本文收集了几个例子。
bp BaseAddr+OffsetAddr
下断点;bl
查看当前断点。windbg 分析堆溢出 《windows 高级调试》第 6.2.2 节,参考书中的方法进行。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Image File ExecutionOptions
条件断点: windbg 调试 test.exe 下条件断点当 CreateFileA 的第三个参数以 FILE_SHARE_READ(1)时断下,否则不断。
bp kernel32!CreateFileA ".if (poi(esp+c)=1){}.else{gc;}"
# Bcdedit
bcdedit -set testsigning on
bcdedit /dbgsettings serial baudrate:115200 debugport:2
bcdedit /copy {current} /d DebugEntry
bcdedit /displayorder {current} {GUID}
bcdedit /debug {GUID} ON
# vmware 增加串口:\\.\pipe\com_2
VirtualKD-2.7 帮你一键搭建 windbg+vm 的双机调试环境。
1. iArray[i] == 2
2. (strArray[i])._Bx._Buf[0x00000000] == 'b' && (strArray[i])._Bx._Buf[0x00000001] == '2'
3. *((bstrArray[i]).m_str + 0) == 'b' && *((bstrArray[i]).m_str + 1) == '2'
4. (*(strIter)._Myptr)._Bx._Buf[0x00000000] == 'c' && (*(strIter)._Myptr)._Bx._Buf[0x00000001] == '3'
5. *((*(bstrIter)._Myptr).m_str +0) == 'b' && *((*(bstrIter)._Myptr).m_str + 1) == '2'
6. (*(*(pointVecIter)._Myptr)).i == 2
7. (((strMapIter)._Ptr->_Myval).first)._Bx._Buf[0x00000000] == 'c' && (((strMapIter)._Ptr->_Myval).first)._Bx._Buf[0x00000001] == '3'
8. (((strMapIter)._Ptr->_Myval).second)._Bx._Buf[0x00000000] == 'b' && (((strMapIter)._Ptr->_Myval).second)._Bx._Buf[0x00000001] == '2'
9. *((((bstrMapIter)._Ptr->_Myval).first).m_str + 0) == 'c' && *((((bstrMapIter)._Ptr->_Myval).first).m_str + 1) == '3'
10. *((((bstrMapIter)._Ptr->_Myval).second).m_str + 0) == 'b' && *((((bstrMapIter)._Ptr->_Myval).first).m_str + 1) == '2'
11. (((pointMapIter)._Ptr->_Myval).first)._Bx._Buf[0x00000000] == 'b' && (((pointMapIter)._Ptr->_Myval).first)._Bx._Buf[0x00000001] == '2'
12. (*(((pointMapIter)._Ptr->_Myval).second)).i == 3
Windows 栈溢出原理 进程使用的内存可以分成 4 个部分
寄存器与函数栈帧
在 Windows 中,一个线程的栈空间的默认大小是 1MB,对于 MFC UI 主线程,一般其栈空间的大小均为 1MB,当 UI 主线程的数据较大时就可能会造成栈溢出,从而导致程序出现异常。
int a = 0; // 全局初始化区
char *p1; // 全局未初始化区
int main()
{
int b; // 栈
char s[] = "abc"; // 栈
char *p2; // 栈
char *p3 = "123456"; // 123456\0 在常量区,p3 在栈上。
static int c = 0; // 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); // 分配得来得 10 和 20 字节的区域就在堆区。
strcpy(p1, "123456"); // 123456\0 放在常量区,编译器可能会将它与 p3 所指向的"123456"优化成一个地方。
return 0;
}
push ebp
mov ebp, esp
在一个单例类的析构内去调用另一个单例
http://blog.rdev.kingsoft.net/?p=3359 29:36 Windbg 调试技巧-张韬.pdf 12/43 一个单例类调用了另外一个单例类的函数,这个先被析构,另外一个后被析构,单例居然为空,就崩溃了。
.load wow64exts
!sw
!cs # 扩展显示一个或多个临界区 (criticalsection) 或者整个临界区树
!cs Address # 指定要显示的临界区地址。如果省略该参数,调试器显示当前进程中所有临界区。
!cs -s # 如果可能的话,显示每个临界区的初始堆栈回溯。
!cs -l # 仅显示锁定的临界区。
!locks
~*kb
IDA 反汇编 ntdll!_LdrpInitialize:
线程在调用 DllMain 之前,要先获取锁,等 DllMain 执行完再解开这个锁。这样不同线程加载 DLL 就可以实现序列化操作。《Windows 核心编程》
The DllMain entry-point function. This function is called by the loader when it loads or unloads a DLL. The loader serializes calls to DllMain so that only a single DllMain function is run at a time . 微软官方文档《Best Practices for Creating DLLs》
https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
The loader lock. This is a process-wide synchronization primitive that the loader uses to ensure serialized loading of DLLs. Any function that must read or modify the per-process library-loader data structures must acquire this lock before performing such an operation. The loader lock is recursive, which means that it can be acquired again by the same thread. 《Best Practices for Creating DLLs》
http://www.microsoft.com/whdc/devtools/debugging/default.mspx https://developer.microsoft.com/en-us/windows/hardware/windows-driver-kit