Violetrays  
日历
<2010年12月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
统计
  • 随笔 - 1
  • 文章 - 0
  • 评论 - 2
  • 引用 - 0

导航

常用链接

留言簿

随笔档案

搜索

  •  

最新评论

 
  在《windows核心编程》中看到这样一段代码:
// EXE模块
VOID EXEFunc()
{
   PVOID pv 
= DLLFunc();
   free(pv);
}


// DLL模块
PVOID DLLFunc()
{
   
return (malloc(100));
}

         书中说,进程的单个地址空间由一个可执行模块和若干个DLL模块组成。这些模块中,有些可以链接到静态的LIB版本的C/C++运行期库,有些可以链接到一个动态的DLL 版本的C/C++运行期库,而有些模块(不然不是用C/C++编写的话)则根本不需要C/C++运行期库。

        比如这段代码中如果EXEDLL都链接到DLLC/C++运行期库,那么上面的代码将能够很好的运行。但是如果两个模块中的一个或者两个都链接到静态C/C++运行期库,那么对free函数的调用就会失败。 

        书上也没说明具体到底错在哪里,我开始时不明白这个错误的原因,在网上搜索了一大堆的网页,都没查出个所以然出来。为了找到原因,自己写了一个控制台EXE和一个很简单的DLL模块来调试,DLL模块采用隐式链接。对于DLL模块,链接的时候选择多线程静态的C/C++运行期库会报错,提示导出符号无法识别,对于这个错误我也没深入研究,于是采用的是DLL版本的C/C++运行期库。对EXE模块,编译链接的时候既可以选择静态C/C++运行期库,也可以选择动态的C/C++运行期库。对于选择动态C/C++运行期库,程序能够正常的运行,而选择静态C/C++运行期库则运行失败。

         调试之前要注意到两个问题:首先,EXE模块的入口函数_tmain_tWinMain是由C/C++运行期启动函数调用的,也就是tmainCRTStartup函数;DLL模块的入口函数DllMain也是由C/C++启动函数调用的,也就是_DllMainCTRStartup函数。其次,由于mallocfree操作的都是堆栈,出现错误那肯定与这两个函数的实现有关。抓住这两个问题去寻找crt的源代码

         我用的平台是VS2010的,在crt目录下发现这么几个文件:crt0.ccrtexe.ccrtdll.cdllcrt0.c。其中,__tmainCRTStartup函数存在于crt0.ccrtexe.c文件中,_DllMainCRTStartup函数存在于crtdll.cdllcrt0.c文件中。在smalheap.c文件中找到了mallocfree的定义。

         由于不知道各模块运行的时候会调用哪个启动函数,所以我在crt0.ccrtexe.c_tmainCRTStartup函数入口处都设置了断点,在crtdll.cdllcrt0.c_DllMainCRTStartup处也设置了断点。

         经过调试发现,编译链接EXE模块时,分别选择选择DLL版的C/C++运行期库和LIB版的C/C++运行期库,它们调用的启动函数不一样。选择DLLC/C++运行期库会调用crtexe.c中的__tmainCRTStartup函数,选择LIBC/C++运行期库会调用crt0.c中的__tmainCRTStartup函数。仔细对比,发现这两个文件中有些不同,在crt0.c中有个_heap_init()函数,字面意思是堆初始化。再查询_heap_init()函数的代码,_crtheap = HeapCreate( 0, BYTES_PER_PAGE, 0 ),而_crtheap就是模块中的全局变量,代表进程中要调用这个模块中的mallocfree函数要用到的堆。

         为什么选择静态的运行期库会有堆初始化,而选择动态运行期库却没有初始化?而且调试后得知,在编译链接DLL模块时选择动态的C/C++运行期库,同样不执行堆初始化操作。那么它是不是在别的地方初始化了?继续搜索文件,发现在crtlib.c文件中有个__CRTDLL_INIT()函数,其中就调用了_heap_init(),于是在__CRTDLL_INIT()函数入口处下断点,再次调试后发现,如果选择的是DLL版本的C/C++运行期库,则系统会先于其他函数调用crtlib.c文件中的__CRTDLL_INIT()函数(注意到此函数的参数,与DllMain的参数一样,首次调用时参数dwReason的值为DLL_PROCESS_ATTACH),接着调用_heap_init()初始化堆,再接着调用其他DLLC/C++运行期启动函数,最后调用EXE模块的C/C++启动函数,最后进入_tmain或者_tWinMain。

         我得出结论:编译链接的时候,不管是EXE模块还是DLL模块,如果选择的是DLL版本的C/C++运行期库,运行的时候,系统会将msvcrt*.dll映射到进程的地址空间,然后调用__CRTDLL_INIT()函数,后者会调用_heap_init()初始化一个堆栈(只有在首次被映射到进程地址空间后才调用),由全局变量_crtheap标识。如果选择的是LIB版的C/C++运行期库,_heap_init()会硬编码到所在的模块中,当运行的时候,调用本模块内的_heap_init()函数初始化一个堆栈,也由全局变量_crtheap标识,不过这个堆栈只有本模块中malloc、free等函数可以调用。

          所以,如果我们在编译链接上述的EXEDLL的时候,如果某一个模块选择了LIB版的C/C++运行期库,则会在多个模块内出现堆栈(由各模块中的全局变量_crtheap标识),用某个模块中的free函数去释放另外一个模块中malloc的堆,就会出错了。因为在free的时候会调用HeapValidate()函数:

BOOL WINAPI HeapValidate( HANDLE hHeap,  DWORD dwFlags, LPCVOID lpMem);

hHeap是本模块的堆,lpMem是其他模块堆中的地址,调用这个函数最终导致失败。

posted on 2010-11-19 12:59 紫光 阅读(2830) 评论(2)  编辑 收藏 引用
评论:
  • # re: [原创]C/C++运行期堆栈  Log Posted @ 2010-11-26 14:48
    很强大  回复  更多评论   

  • # re: [原创]C/C++运行期堆栈  jmchxy Posted @ 2010-12-01 16:47
    所以编写库的人,提供了一个分配函数,一定要提供释放函数。就像的:fopen, fclose.  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


 
Copyright © 紫光 Powered by: 博客园 模板提供:沪江博客