本文共 6949 字,大约阅读时间需要 23 分钟。
win32平台下malloc的内部实现
by flyingkisser 2007.09.10 如果您不想看那一堆确实比较XX的汇编代码,我直接给您一个结论吧: 1.malloc是如何实现的? malloc(Size)最终调用的是HeapAlloc(msvcrt!_crtheap,0,dwSize) 2.msvcrt.dll使用到的堆是如何初始化的? msvcrt的dll入口函数初始化时,调用HeapCreate(0,1000h,0)来创建一个私有堆,并放入到全局变量msvcrt!_crtheap中,供以后的malloc使用。 malloc是C语言中用于内存分配的函数,其声明是 void *malloc( size_t size ); 只有一个size参数。 在windows平台下,类似于这种C接口的函数,都统一放进了称作run-time c library的动态链接库中, 即msvcrt.dll,当然,还会有msvcrt20.dll,msvcrt40.dll等不同版本的run-time c library。 下面以msvcrt.dll导出的malloc为主要分析对象。 为了可以方便地分析这个函数,我简单写了一段程序,先装载msvcrt.dll,找到malloc的导出地址,再 调用它,在调用前加入int 3断点,方便调试器断在malloc即将调用前的位置,也方便后面的其它分析。 程序代码片断如下: -------------------------- _malloc proc local @hDll,@lpStr pushad invoke LoadLibrary,string("msvcrt.dll") mov @hDll,eax invoke GetProcAddress,eax,string("malloc") int 3 push 80 call eax add esp,4 push eax invoke GetProcAddress,@hDll,string("free") call eax add esp,4 invoke FreeLibrary,@hDll popad ret _malloc endp -------------------------- 用cdb加载这个程序,直接g运行,断在int 3处,这时运行一下k命令,得到 0:000> k ChildEBP RetAddr 0012ff2c 77bfc3c9 ntdll!RtlAllocateHeap 0012ff6c 77bfc3e7 msvcrt!_heap_alloc+0xe0 0012ff78 77bfc42e msvcrt!_nh_malloc+0x13 0012ff88 004011f5 msvcrt!malloc+0x27 WARNING: Stack unwind information not available. Following frames may be wrong. 0012ffbc 00401211 image00400000+0x11f5 0012fff0 00000000 image00400000+0x1211 可以看出malloc的内部调用结构, malloc-->_heap_alloc-->RtlAllocateHeap 即malloc最终调用的还是RtlAllocateHeap,当然,它是由ntdll导出的,但并没有相关文档。 现在问题大概清楚了,malloc是在堆上分配的内存,那么,新的问题就来了,malloc在哪个堆上分配的 内存呢? 熟悉windows堆相关的API的人都清楚,在一个进程中,堆分为默认堆和私有堆,默认堆是系统在进程 创建的时候创建的,由系统在需要进行一些内存分配操作时使用,用户没有权力去销毁默认堆。 私有堆是我们用户自己创建的,我们可以随意在私有堆上分配、释放内存,并可以随意销毁我们自己 创建的私有堆。 那么,malloc要在堆上分配内存,必须需要一个堆句柄,那么,这个堆句柄,对应的是系统默认堆 还是私有堆呢?如果是私有堆,这个私有堆是什么时候创建和初始化的呢? 我们知道,创建堆调用的API是kernel32.dll导出的HeapCreate(),而HeapCreate()又调用了ntdll导出 的RtlCreateHeap(),当然,只有HeapCreate()是有文档的。下面,我们只需要在RtlCreateHeap()下断 点就行了。 用cdb重新装载上面的小程序,用 bp ntdll!RtlCreateHeap 下断点,g执行,断下来,再用k查看当前调用堆栈 0:000> k ChildEBP RetAddr 0012eaec 7c812bff ntdll!RtlCreateHeap 0012eb10 77bfa2f9 kernel32!HeapCreate+0x55 0012eb24 77beef48 msvcrt!_heap_init+0x1b 0012eb2c 77bef010 msvcrt!_core_crt_dll_init+0x10 0012ebd0 7c9211a7 msvcrt!__CRTDLL_INIT+0x9f 0012ebf0 7c93cbab ntdll!LdrpCallInitRoutine+0x14 0012ecf8 7c936178 ntdll!LdrpRunInitializeRoutines+0x344 0012efa4 7c9362da ntdll!LdrpLoadDll+0x3e5 0012f24c 7c801bb9 ntdll!LdrLoadDll+0x230 0012f2b4 7c80ae5c kernel32!LoadLibraryExW+0x18e 0012f2c8 77f1d126 kernel32!LoadLibraryW+0x11 0012f2f0 77f13de7 GDI32!GdiInitializeLanguagePack+0x15 0012f304 77d1a02d GDI32!GdiProcessSetup+0x11d 0012f444 77d1a133 user32!ClientThreadSetup+0x33 0012f448 7c92eae3 user32!__ClientThreadSetup+0x5 0012f454 77ef67d4 ntdll!KiUserCallbackDispatcher+0x13 0012f464 77d1f774 GDI32!NtGdiInit+0xc 0012f9f0 7c9211a7 user32!_UserClientDllInitialize+0x315 0012fa10 7c93cbab ntdll!LdrpCallInitRoutine+0x14 0012fb18 7c94173e ntdll!LdrpRunInitializeRoutines+0x344 可以看出,主要的调用线路是: __CRTDLL_INIT-->_core_crt_dll_init-->_heap_init-->HeapCreate 即在msvcrt的dll入口函数的初始化工作时,调用kernel32的HeapCreate()创建一个堆。 让我们进入到_heap_init()内部看看 0:000> u msvcrt!_heap_init msvcrt!_heap_init: 77bfa2de 8bff mov edi,edi 77bfa2e0 55 push ebp 77bfa2e1 8bec mov ebp,esp 77bfa2e3 33c0 xor eax,eax 77bfa2e5 394508 cmp [ebp+0x8],eax 77bfa2e8 6a00 push 0x0 ;dwMaximumSize=0,说明堆是可增长的 77bfa2ea 0f94c0 sete al ;如果参数1为0,则al=1,否则al=0 77bfa2ed 6800100000 push 0x1000 ;dwInitialSize=1000h,4K 77bfa2f2 50 push eax ;flOptions为0或1 77bfa2f3 ff150411be77 call dword ptr [msvcrt!_imp__HeapCreate (77be1104)] HeapCreate(参数1==0 ? 1 :0,1000h,0) 77bfa2f9 85c0 test eax,eax 77bfa2fb a31824c377 mov [msvcrt!_crtheap (77c32418)],eax 77bfa300 7436 jz msvcrt!_heap_init+0x5a (77bfa338) 77bfa302 e85ffeffff call msvcrt!__heap_select (77bfa166) 77bfa307 83f803 cmp eax,0x3 77bfa30a a31c24c377 mov [msvcrt!__active_heap (77c3241c)],eax 可以看出,调用HeapCreate的参数是 HeapCreate(参数1==0 ? 1 :0,1000h,0) 上面的参数1是指_core_crt_dll_init调用_heap_init的参数1 那么参数1是0还是非0呢?再看一下_core_crt_dll_init就知道了 0:000> u _core_crt_dll_init msvcrt!_core_crt_dll_init: 77beef38 833da417c37700 cmp dword ptr [msvcrt!_core_crt_dll_init_completed (77c3 17a4)],0x0 77beef3f 751d jnz msvcrt!_core_crt_dll_init+0x26 (77beef5e) 77beef41 6a01 push 0x1 ;参数1是1 77beef43 e896b30000 call msvcrt!_heap_init (77bfa2de) 可以看出_core_crt_dll_init调用_heap_init时,参数是1。 所以,msvcrt初始化时会调用HeapCreate(0,1000h,0)来创建一个私有堆,并放入到全局变量 msvcrt!_crtheap中,供以后的malloc使用 现在,我们搞清楚了二个问题, msvcrt的dll入口函数初始化,调用eapCreate(0,1000h,0)来创建一个私有堆,并放入到全局变量 msvcrt!_crtheap中,供以后的malloc使用。 malloc()再最终调用HeapAlloc()来分配内存。那么,malloc()调用HeapAlloc()的具体参数又是 什么样的呢? 再回到第一次下断时运行k的结果 0012ff2c 77bfc3c9 ntdll!RtlAllocateHeap 0012ff6c 77bfc3e7 msvcrt!_heap_alloc+0xe0 只要进入_heap_alloc看看就行了 msvcrt!_heap_alloc+0xba: 77bfc3a3 8b7508 mov esi,[ebp+0x8] ;esi=dwSize 77bfc3a6 85f6 test esi,esi 77bfc3a8 7501 jnz msvcrt!_heap_alloc+0xc2 (77bfc3ab) 77bfc3aa 46 inc esi ;大小为0的话则赋值为1 77bfc3ab 833d1c24c37701 cmp dword ptr [msvcrt!__active_heap (77c3241c)],0x1 77bfc3b2 7406 jz msvcrt!_heap_alloc+0xd1 (77bfc3ba) 77bfc3b4 83c60f add esi,0xf ;把大小按16字节对齐 77bfc3b7 83e6f0 and esi,0xfffffff0 77bfc3ba 56 push esi ;dwSize 77bfc3bb 6a00 push 0x0 ;0 77bfc3bd ff351824c377 push dword ptr [msvcrt!_crtheap (77c32418)];msvcrt!_crtheap 77bfc3c3 ff15f410be77 call dword ptr [msvcrt!_imp__HeapAlloc (77be10f4)] HeapAlloc(msvcrt!_crtheap,0,dwSize) 77bfc3c9 e88db00000 call msvcrt!_SEH_epilog (77c0745b) 77bfc3ce c3 ret 即malloc(Size)最终调用的是HeapAlloc(msvcrt!_crtheap,0,dwSize) 让我们再来把问题及答案总结一下 1.msvcrt.dll使用到的堆是如何初始化的? msvcrt的dll入口函数初始化时,调用HeapCreate(0,1000h,0)来创建一个私有堆,并放入到全局变量 msvcrt!_crtheap中,供以后的malloc使用。 2.malloc是如何实现的? malloc(Size)最终调用的是HeapAlloc(msvcrt!_crtheap,0,dwSize) 下面再附上HeapCreate及HeapAlloc的原型。 HANDLE HeapCreate( DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize ); 有效的flOptions是 HEAP_CREATE_ENABLE_EXECUTE 0x00040000 HEAP_GENERATE_EXCEPTIONS 0x00000004 HEAP_NO_SERIALIZE 0x00000001 LPVOID HeapAlloc( HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes ); 有效的dwFlags是 HEAP_GENERATE_EXCEPTIONS 0x00000004 HEAP_NO_SERIALIZE 0x00000001 HEAP_ZERO_MEMORY 0x00000008
msvcrt.dll这个版本中的malloc比较简单,往下没有逆太深,有时看看确实没有这个必要。
关键还是RtlAllocateHeap malloc 分配指定大小的内存 void *malloc( size_t size ); if(msvcrt!_crtheap==0 && msvcrt!_core_crt_dll_init()==0) return 0; return msvcrt!_nh_malloc(dwSize,msvcrt!_newmode); msvcrt!malloc: 77bfc407 8bff mov edi,edi 77bfc409 55 push ebp 77bfc40a 8bec mov ebp,esp 77bfc40c 833d1824c37700 cmp dword ptr [msvcrt!_crtheap (77c32418)],0x0 77bfc413 750b jnz msvcrt!malloc+0x19 (77bfc420) 77bfc415 e81e2bffff call msvcrt!_core_crt_dll_init (77beef38) 77bfc41a 85c0 test eax,eax 77bfc41c 7502 jnz msvcrt!malloc+0x19 (77bfc420) 77bfc41e 5d pop ebp 77bfc41f c3 ret ;如果全局变量msvcrt!_crtheap非0,跳到此 77bfc420 ff350818c377 push dword ptr [msvcrt!_newmode (77c31808)] 77bfc426 ff7508 push dword ptr [ebp+0x8] ;参数dwSize 77bfc429 e8a6ffffff call msvcrt!_nh_malloc (77bfc3d4) _nh_malloc(dwSize,msvcrt!_newmode) 77bfc42e 59 pop ecx 77bfc42f 59 pop ecx 77bfc430 5d pop ebp 77bfc431 c3 ret
转载地址:http://dajtd.baihongyu.com/