coredump原理探究 Windows 笔记

coredump原理探究 Windows 笔记

原文链接:https://blog.csdn.net/xuzhina/article/details/8247701
感谢原作者,侵删

 

 

一、环境搭建

1、Win7捕获程序dump

注册表HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/Windows Error Reporting/LocalDumps中新建几个key

 

抓dump注册表配置

抓dump注册表配置

 

2、Windbg符号表设置(Symbols Search Path)

自动下载
D:\Debug\Symbols;SRV\*D:\Debug\Symbols\*http://msdl.microsoft.com/download/symbols;D:\Debug\Dump
手动下载
https://developer.microsoft.com/en-us/windows/hardware/download-symbols

二、WinDbg命令

命令含义实例
x显示所有上下文中符合某种模式的符号x Test!m*
bp设置一个或多个软件断点;可通过
组合、地址、条件、选项来设置多种类型的断点
bp Test!main
u显示出内存里某段程序的汇编u Test!main
g开始执行指令的进程或线程;当出现下列情况,执行停止:
1、进程结束;2、执行到断点;3、某个时间导致调试器终止
 
dd显示指定范围内存单位的内容(双字dword)dd esp L 8
da显示指定内存开始的字符串da 004020dc
db单字节显示内存db esp
t执行一条指令或一行代码,并显示出所有寄存器和状态的值Trace
p执行一条指令或一行代码,并显示出所有寄存器和状态的值
当函数调用或中断发生时,也只是作为一条指令执行
Step
kbn显示指定线程的栈帧,并显示相关信息 
.frame n切换到栈帧n 
ln查找就近的符号ln 004010e0
dt -v 结构体名 地址打印结构体dt -v _HEAP_ENTRY 00730000
!heap -a查看堆信息 
!heap -x 地址查看地址所属的堆块信息!heap -x 00730000
!heap -hf 地址查看堆上所有的堆块!heap -hf 00730000

三、函数栈帧

1、栈内存布局

返回地址ret
上一栈帧地址fp
局部变量
从右往左压入参数
返回地址ret
......

2、栈溢出

往局部变量中写数据越界,导致淹没了fp和ret,函数返回时ebp、eip就会被写入非法值
此时fp/ret对的链表关系被破坏,调试器无法显示正确的函数栈

3、栈的规律

  1. esp的值不会被覆盖,永远指向栈顶
  2. fp/ret对的链表没有被完全破坏,往高地址的一些地方还是保持这种关系
  3. 从栈顶到栈底,内存地址由低到高。如果帧指针fp1的内容是fp2,fp2的内容是fp3,那么存在:esp<fp1<fp2<fp3
  4. 如果两对(fp1,ret2)、(fp2,ret2)符合条件:fp1的内容刚好是fp2,那么它们除了满足第3点外,还满足:ln ret1和ln ret2都能列出函数符号,ret2的上一条指令一定是调用ret1所在的函数

4、定位栈溢出问题的经验方法

  1. dd esp查看由esp开始的内存
  2. 找到某个内容比esp的值稍大、数值相差不是太远的内存单元,称为FP1。下一个单元称为RET1
  3. ln查看RET1的内容。如果没有显示函数符号,跳过FP1,回到第2步
  4. dd *FP1 L 2得到两个内存单元(FP2,RET2)。如果FP2的内容小于FP1的内容,跳过FP1,回到第2步
  5. ln查看RET2的内容。如果没有显示函数符号,跳过FP1,回到第2步;如果OK,跳到FP2,回到第4步

四、函数逆向

汇编代码中跳转和循环为函数的骨架,要优先寻找

五、C内存布局

1、基本类型

类型特征
charbyte ptr 挤在一起
shortword ptr 占用四字节空间
intdword ptr
longdword ptr win64采用LLP64标准,Windows系统中long和int是相同的
floatdword ptr 单精度占四字节,要配合浮点计算指令确认
doubleqword ptr 双精度占八字节,要配合浮点计算指令确认
指针lea

2、数组类型

类型特征
char基地址 + 索引值 * 1
short基地址 + 索引值 * 2 数组时会挤在一起,注意与基本类型的区别
int基地址 + 索引值 * 4
long基地址 + 索引值 * 4
float基地址 + 索引值 * 4 单精度占四字节,要配合浮点计算指令确认 
double基地址 + 索引值 * 4 双精度占八字节,要配合浮点计算指令确认 
指针基地址 + 索引值 * 4 

3、结构体

  1. 成员全是基本类型的结构体: 先把一个基地址放到某寄存器中,访问成员时在基址上加上前面所有成员的大小,每个成员与基址的偏移量不固定
  2. 复合类型构成的结构体: 同上,没有特别
  3. 结构体数组: 找到数组的首地址;根据索引找到每个元素的地址;以每个元素的地址作为结构体基址,获取成员变量的地址

六、C++内存布局

1、类的内存布局

类的成员变量排列与结构体相同

2、this指针

调用类的成员函数时,this指针放在ecx寄存器中传递,不入栈

  1. 调用函数时的汇编代码: 
  2. lea ecx,[ebp-1
  3. call Test!Test::print (00ec1030) 
  4. 被调函数开始的汇编代码: 
  5. mov dword ptr [ebp-4],ecx 
  6. mov eax,dword ptr [ebp-4
  7. push eax 

3、虚函数表及虚表指针

 

虚函数表及虚表指针

虚函数表及虚表指针

 

4、单继承

子类先调用基类的构造函数,再初始化自己的成员变量,然后设置虚表指针
子类虚函数表分布规律:

  1. 重载基类的虚函数,按照基类虚函数声明顺序排列,和子类声明顺序无关
  2. 子类独有的虚函数,按照虚函数的声明顺序排列,追加在重载虚函数的后面

5、多继承(无公共基类)

  1. 子类对象的大小等于各个基类的大小(虚表指针+成员变量)加上自身成员变量的大小
  2. 各基类在子类里的“隐含对象”是按照继承顺序来排列的,和基类的声明、定义顺序无关
  3. 每个基类都(尽可能)有自己的虚函数表;子类独有的虚函数追加到第一个虚函数表的后面;子类重载所有虚表中的同名虚函数
  4. 子类对象指针转换成基类指针,实际上是把子类对象包含的对应基类的“隐含对象”的地址赋值给基类指针
  5. 当一个虚函数在多个虚表中都出现时,实际上只会完全重载第一个虚表中的该函数。其余虚表中重载代码是通过调整this指针为子类的地址,然后跳转到子类对应函数来实现
  1. 0:000> u 012e10a8 L 5 
  2. Test![thunk]:Child::print`adjustor{12}': 
  3. 012e10a8 83e90c sub ecx,0Ch // ecx是基类“隐含对象”的地址,需调整 
  4. 012e10ab e9e0ffffff jmp Test!Child::print (012e1090) 

七、STL容器内存布局

1、vector

一个vector在栈上占三个单元(指针):
第一个_Myfirst指向vector元素的开始
第二个_Mylast指向vector元素结束的下一个位置
第三个_Myend指向vector空间结束的位置
注意:vector的begin()、end()、push_back()等成员函数的汇编,最后是:ret 4

 

vector

vector

 

2、list

  1. list有两个成员:第一个_Myhead指向链表的头部节点,第二个_Size表明链表中的节点元素个数
  2. 链表中的每个节点包含三个成员:第一个_Next指向下一个节点,第二个_Prev指向前一个节点,第三个_Myval存储节点的值
  3. list初始化时会生成一个头节点
  4. 头节点的_Next指向链表第一个节点,链表最后一个节点的_Next指向头节点
  5. 头节点的_Prev指向链表最后一个节点,链表第一个节点的_Prev指向头节点
    注意:图中_Prev指针应该都指向节点起始位置,而不是_Prev指针的位置。这里只是为了突出两个链路

 

list

list

 

3、map

  1. map有两个成员:_Myhead指向头节点,_Mysize表明map包含的元素个数
  2. 头节点的三个指针分别指向树的最左节点、树的根节点、树的最右节点
  3. 树的根节点的_Parent指向头节点
  4. 树的叶子节点的_Left、_Right指向头节点

 

enter description here

map

 

4、set

由于map、set本身的定义都没有声明任何成员变量,所有的成员变量都是从_Tree继承过来的,唯一的区别是traits的定义不一样,因此:set的特征和map类似

  1. template<class _Kty, 
  2. class _Pr = less<_Kty>, 
  3. class _Alloc = allocator<_Kty> > 
  4. class set : public _Tree<_Tset_traits<_Kty, _Pr, _Alloc, false> > 
  5. { ...... } 
  6.  
  7. template<class _Kty, 
  8. class _Ty, 
  9. class _Pr = less<_Kty>, 
  10. class _Alloc = allocator<pair<const _Kty, _Ty> > > 
  11. class map : public _Tree<_Tmap_traits<_Kty, _Ty, _Pr, _Alloc, false> > 
  12. { ...... } 

5、iterator

  1. vector的iterator只有一个成员_Ptr,取值范围:vec._Myfirst <= _Ptr < vec._Mylast
  2. list的iterator也只有一个成员_Ptr,指向list中的每个节点(头节点除外)
  3. map和set的iterator也只有一个成员_Ptr,指向map或set的节点,且iterator的遍历采用中序遍历

实际调试中,set的iterator指向节点的值在for循环中是按照0、1、2、3......、f的顺序遍历

 

set的iterator

set_iterator

 

6、string

string有三个成员:联合体_Bx,紧接着的是字符串长度_Mysize,预留空间大小_Myres

  1. 当_Mysize < _BUF_SIZE(16)时,字符串存储在_Bx的_Buf里
  2. 当_Mysize >= _BUF_SIZE(16)时,字符串存储在_Bx的_Ptr指向的内存中
  1. // TEMPLATE CLASS _String_val 
  2. template<class _Val_types> 
  3. class _String_val : public _Container_base 
  4. { // base class for basic_string to hold data 
  5. public
  6. ...... 
  7. enum { // length of internal buffer, [1, 16] 
  8. _BUF_SIZE = 16 / sizeof (value_type) < 1  
  9. ? 1 
  10. : 16 / sizeof (value_type) 
  11. }; 
  12. ...... 
  13. union _Bxty { // storage for small buffer or pointer to larger one 
  14. value_type _Buf[_BUF_SIZE]; 
  15. pointer _Ptr; 
  16. char _Alias[_BUF_SIZE]; // to permit aliasing 
  17. } _Bx; 
  18. size_type _Mysize; // current length of string 
  19. size_type _Myres; // current storage reserved for string 
  20. }; 

八、堆结构

1、NT内核堆的改造

文档中介绍的堆结构是XP环境下的。MS从Vista开始对NT内核做了较大改动,其中包括堆的改造。最直观的改造:

  1. _HEAP中采用链表方式管理_HEAP_SEGMENT,解除数组的限制
  2. _HEAP_ENTRY结构进行了编码,引入随机性,增强堆的安全性
  3. 取消空闲堆块链表的头节点数组,直接使用链表管理空闲堆块,即_HEAP中FreeLists从[128]的_LIST_ENTRY数组改为单个元素

2、Win7下堆的结构

  1. struct _HEAP_ENTRY { 
  2. SHORT Size; /* 当前块的大小。 
  3. 直接dt -v打出来的值是经过了编码的, 
  4. 实际值需要和_HEAP中的Encoding做一次异或,取最低的两个字节。 
  5. 真正的块的字节数还需要Size*8。 
  6. 这个值包含了这个堆块头结构_HEAP_ENTRY的8字节 
  7. */ 
  8. // ……省略若干字段 
  9. SHORT PreviousSize; 
  10. // ……省略若干字段 
  11. BYTE UnusedBytes; // 未使用的字节数 
  12. // ……省略若干字段 
  13. }; /* 整个_HEAP_ENTRY结构共8字节, 
  14. 后面紧接着申请出来的内存块,malloc或new返回的也是指向这个内存块的指针, 
  15. 这个指针的值一定是8的倍数, 
  16. 再后面紧接着的就是下一个堆块  
  17. */ 
  18.  
  19. struct _HEAP_SEGMENT { 
  20. _HEAP_ENTRY Entry; 
  21. UINT SegmentSignature; 
  22. UINT SegmentFlags; 
  23. _LIST_ENTRY SegmentListEntry; /* segment list的入口, 
  24. 指示当前_HEAP_SEGMENT节点在segment list中的位置, 
  25. 各_HEAP_SEGMENT通过这个字段连接 */ 
  26. _PHEAP Heap; /* 指向所属的_HEAP */ 
  27. // ……省略若干字段 
  28. _HEAP_ENTRY* FirstEntry; 
  29. _HEAP_ENTRY* LastValidEntry; 
  30. // ……省略若干字段 
  31. _LIST_ENTRY UCRSegmentList; 
  32. }; 
  33.  
  34. struct _HEAP { 
  35. _HEAP_SEGMENT Segment; /* 第一个段 */ 
  36. // ……省略若干字段 
  37. UINT EncodeFlagMask; /* 是否启用编码功能 */ 
  38. _HEAP_ENTRY Encoding; /* 编码key, 
  39. 用这个结构和每个堆块头结构_HEAP_ENTRY做异或  
  40. */ 
  41. // ……省略若干字段 
  42. _LIST_ENTRY SegmentList; /* segment list的头节点, 
  43. 分别指向第一个、最后一个_HEAP_SEGMENT的SegmentListEntry  
  44. */ 
  45. // ……省略若干字段 
  46. _LIST_ENTRY FreeLists; /* 空闲堆块链表头节点, 
  47. 分别指向第一个、最后一个_HEAP_FREE_ENTRY的FreeList, 
  48. XP中这里是[128]的_LIST_ENTRY数组  
  49. */ 
  50. // ……省略若干字段 
  51. _HEAP_TUNING_PARAMETERS TuningParameters; 
  52. }; 
  53.  
  54. /* 空闲堆块结构 */ 
  55. struct _HEAP_FREE_ENTRY { 
  56. _HEAP_ENTRY Entry; 
  57. _LIST_ENTRY FreeList; /* free list的入口, 
  58. 指示当前空闲堆块在链表中的位置 
  59. 各空闲堆块通过这个字段连接 
  60. 如果两个节点在内存中连续则合并 
  61. */ 
  62. }; 

 

heap

heap

 

3、堆块的调试

获取一个地址所属堆块的信息

  1. 0:000> !heap -x 00730000 
  2. Entry User Heap Segment Size PrevSize Unused Flags 
  3. ----------------------------------------------------------------------------- 
  4. 00730000 00730008 00730000 00730000 588 0 1 busy 
  5. 注意:这里的Size为58816进制 

获取一个堆块的大小

  1. 0:000> dt -v _HEAP_ENTRY 00730000 直接打印_HEAP_ENTRY结构 
  2. ntdll!_HEAP_ENTRY 
  3. struct _HEAP_ENTRY, 19 elements, 0x8 bytes 
  4. +0x000 Size : 0x9496 显然不对 
  5. …… 
  6. +0x007 UnusedBytes : 0x1 '' 
  7. …… 
  8. 0:000> dd 00730000+0x050 L 4 获取Encoding结构体。Encoding相对_HEAP的偏移是0x050 
  9. 00730050 47329427 000024e0 3b5c5f17 00000000 Encoding低4字节的值为47329427 
  10. 0:000> dd 00730000 L 4 打印_HEAP_ENTRY结构的值 
  11. 00730000 f7339496 010024e0 ffeeffee 00000000 打印出的值f7339496是原始值和Encoding经过异或后得到的 
  12. 0:000> ? f7339496 ^ 47329427 要求原始值只需要当前值和Encoding再异或一遍 
  13. Evaluate expression: -1342111567 = b00100b1 低地址的两字节就是原始的Size 
  14. 0:000> ? 00b1 * 8 实际堆块的字节数还要Size*8 
  15. Evaluate expression: 1416 = 00000588 

4、heap corruption问题

常见原因:

  1. free导致coredump:
    free了野指针:需要检查指针的正确性。例如:是否在!heap -hf所列范围内、是否是8的倍数等
    堆块写越界:需要检查前后堆块的Size和PreSize
  2. malloc导致coredump:
    一般是因为堆块写越界,破坏了空闲堆块的结构。!heap -hf可以找到同一个堆块即是free又是busy的状态

九、dll hell问题

dll hell常导致虚函数的漂移,本质上就是一个dll之间版本不匹配的问题。

转载于:https://www.cnblogs.com/gjf1492/p/7106917.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值