dll导出中函数中使用CString类型的参数引发的ESP出错

分类: C、VC/MFC J、PLC 551人阅读 评论(2) 收藏 举报

今天下午花了很久去调试这个bug,因为自己一个dll函数参数的疏忽,导致浪费很多的时间去调试。

 

这个参数直接使用了CString类型,后面会造成两次析构而引发ESP出错,直接崩掉...

 

1

 

回来的时候,抽空分析了一下,大致过程是这样的,先写个简单的伪代码来调试吧:

 

Dll源码:

  1. //  
  2. // MyDll.h   
  3. #include <afx.h>   
  4. #ifndef MY_API   
  5. #define MY_API _declspec(dllimport)  
  6. #endif   
  7. CString g_strTitle = "";  
  8. MY_API void    MySetTitle(CString strTitle);  
  9. MY_API CString MyGetTitle();  

 

  1. //  
  2. // MyDll.cpp   
  3. #include "MyDll.h"   
  4. void MySetTitle(CString strTitle)  
  5. {  
  6.     g_strTitle = strTitle;  
  7. }  
  8. CString MyGetTitle()  
  9. {  
  10.     return g_strTitle;  
  11. }  

 

主工程为对话框,添加两个按钮,消息响应如下:

  1. void CTestDlg::OnBtnDefault()   
  2. {  
  3.     // TODO: Add your control notification handler code here  
  4.     CString str = "Hello World !";  
  5. }  
  6. void CTestDlg::OnBtnTest()   
  7. {  
  8.     // TODO: Add your control notification handler code here  
  9.     CString str = "Hello World !";  
  10.     MySetTitle(str);  
  11.     str = MyGetTitle();  
  12. }  

 

点击测试,再关闭队话框,即出现常见的对话框(如果在调试时,将会出现上图结果):

Microsoft Visual C++ Debug Library:

Debug Error:
Program: ...
Module:
file: i386/chkesp.c
Line: xx

The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

(Press Retry to Debug the Application)

 

于是载入IDA,查看两个函数反汇编代码:

  1. ; Attributes: bp-based frame  
  2. protectedvoid __thiscall CTestDlg::OnBtnDefault(void)  
  3. ?OnBtnDefault@CTestDlg@@IAEXXZ proc near  
  4. var_48= byte ptr -48h  
  5. var_8= byte ptr -8  
  6. var_4= dword ptr -4  
  7. push    ebp  
  8. mov     ebp, esp  
  9. sub     esp, 48h  
  10. push    ebx  
  11. push    esi  
  12. push    edi  
  13. push    ecx  
  14. lea     edi, [ebp+var_48]  
  15. mov     ecx, 12h  
  16. mov     eax, 0CCCCCCCCh  
  17. rep stosd  
  18. pop     ecx  
  19. mov     [ebp+var_4], ecx  
  20. push    offset aHelloWorld ; "Hello World !"  
  21. lea     ecx, [ebp+var_8]  
  22. call    ??0CString@@QAE@PBD@Z ; CString::CString(char const *)  ; 构造函数  
  23. lea     ecx, [ebp+var_8]  
  24. call    ??1CString@@QAE@XZ ; CString::~CString(void)        ; 调用析构函数,这里是正常的  
  25. pop     edi  
  26. pop     esi  
  27. pop     ebx  
  28. add     esp, 48h  
  29. cmp     ebp, esp  
  30. call    __chkesp  
  31. mov     esp, ebp  
  32. pop     ebp  
  33. retn  
  34. ?OnBtnDefault@CTestDlg@@IAEXXZ endp  

 

  1. ; Attributes: bp-based frame  
  2. protectedvoid __thiscall CTestDlg::OnBtnTest(void)  
  3. ?OnBtnTest@CTestDlg@@IAEXXZ proc near  
  4. var_68= byte ptr -68h  
  5. var_28= dword ptr -28h  
  6. var_24= dword ptr -24h  
  7. var_20= dword ptr -20h  
  8. var_1C= byte ptr -1Ch  
  9. var_18= dword ptr -18h  
  10. var_14= byte ptr -14h  
  11. var_10= dword ptr -10h  
  12. var_C= dword ptr -0Ch  
  13. var_4= dword ptr -4  
  14. push    ebp  
  15. mov     ebp, esp  
  16. push    0FFFFFFFFh  
  17. push    offset __ehhandler$?OnBtnTest@CTestDlg@@IAEXXZ  
  18. mov     eax, large fs:0  
  19. push    eax  
  20. mov     large fs:0, esp  
  21. sub     esp, 5Ch  
  22. push    ebx  
  23. push    esi  
  24. push    edi  
  25. push    ecx  
  26. lea     edi, [ebp+var_68]  
  27. mov     ecx, 17h  
  28. mov     eax, 0CCCCCCCCh  
  29. rep stosd  
  30. pop     ecx  
  31. mov     [ebp+var_10], ecx  
  32. push    offset aHelloWorld ; "Hello World !"  
  33. lea     ecx, [ebp+var_14]  
  34. call    ??0CString@@QAE@PBD@Z ; CString::CString(char const *)  
  35. mov     [ebp+var_4], 0  
  36. push    ecx  
  37. mov     ecx, esp  
  38. mov     [ebp+var_18], esp  
  39. lea     eax, [ebp+var_14]  
  40. push    eax  
  41. call    ??0CString@@QAE@ABV0@@Z ; CString::CString(CString const &) ; 构造函数  
  42. mov     [ebp+var_20], eax  
  43. call    ds:__imp_?MySetTitle@@YAXVCString@@@Z ; MySetTitle(CString)  
  44. add     esp, 4  
  45. mov     esi, esp  
  46. lea     ecx, [ebp+var_1C]  
  47. push    ecx  
  48. call    ds:__imp_?MyGetTitle@@YA?AVCString@@XZ ; MyGetTitle(void)  
  49. add     esp, 4  
  50. cmp     esi, esp  
  51. call    __chkesp  
  52. mov     [ebp+var_24], eax  
  53. mov     edx, [ebp+var_24]  
  54. mov     [ebp+var_28], edx  
  55. mov     byte ptr [ebp+var_4], 1  
  56. mov     eax, [ebp+var_28]  
  57. push    eax  
  58. lea     ecx, [ebp+var_14]  
  59. call    ??4CString@@QAEABV0@ABV0@@Z ; CString::operator=(CString const &); 这里拷贝构造函数,所以才引起两次析构造  
  60. mov     byte ptr [ebp+var_4], 0  
  61. lea     ecx, [ebp+var_1C]  
  62. call    ??1CString@@QAE@XZ ; CString::~CString(void)             ; 第一次调用析构函数  
  63. mov     [ebp+var_4], 0FFFFFFFFh  
  64. lea     ecx, [ebp+var_14]  
  65. call    ??1CString@@QAE@XZ ; CString::~CString(void)             ; 第二次调用析构函数  
  66. mov     ecx, [ebp+var_C]  
  67. mov     large fs:0, ecx  
  68. pop     edi  
  69. pop     esi  
  70. pop     ebx  
  71. add     esp, 68h  
  72. cmp     ebp, esp  
  73. call    __chkesp  
  74. mov     esp, ebp  
  75. pop     ebp  
  76. retn  
  77. ?OnBtnTest@CTestDlg@@IAEXXZ endp  
 

 

于是,这个bug就立刻展现眼前,原来是CString拷贝构造函数引起两次析构函数,而造成ESP出错!于是改这个bug也很简单了,在参数前加个&即可。感谢法师,加一个&符号即可修正之.....

 

突然,才发现MS SDK的MFC类库函数也很少用到这个类型的参数,只是有很少的一部分也只是加上&引用使用。

 

所以,建议大家在Dll中尽量不要直接使用CString作为参数,可以直接使用LPCSTR替代吧!

 

配套源码:http://download.csdn.net/source/2372247

 

另外,送一个相关的超级传送门...

 

点击此处!!!

 

查看评论
2楼 wocow32010-06-12 01:31发表 [回复] [引用][举报]
跟析构函数没关系,问题出在跨模块边界使用堆
ESP出错一般是调用方式不一致引起的

dll函数中这句 g_strTitle = strTitle;
第一次调用没问题,因为g_strTitle初始化为空,赋值时后g_strTitle的数据是在exe中分配堆内存;
第二次调用出错, 因为g_strTitle有数据赋值前先要析构,dll堆中是不能释放exe的堆内存的,因此出错
1楼 ohyeah5212010-05-22 19:15发表 [回复] [引用][举报]
学习了。[e01]
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值