|
MFC로 프로그래밍을 하신 분이라면 아래와 같은 메모리릭 메세지를 자주 봤을 것이다.
Detected memory leaks!그런데, MFC뿐만 아니라 일반적인 프로그램도 다 이런 메모리릭 기능을 켤 수 있다. 의외로 MFC가 아니면 안 된다고 생각하시는 분이 많다. 그렇지 않다. 또, new만 지원이 되고 malloc으로 한 것은 이 기능을 사용할 수 없다고 생각하시는 분도 있는데 역시 아니다. malloc, new 모두 가능하다. 그리고 생각보다 메모리릭을 디텍트 하는 기능은 만들기도 매우 간단하다. 알고보면 아무것도 아니다. 먼저, Visual Studio에서 사용하는 MS C Runtime 라이브러리에서 malloc, new는 모두 Win32 API HeapAlloc을 호출하는 것으로 구현이 되어있다. 아시다시피 new/delete는 C++ operator 형태로 되어있을 뿐, 실제 작동은 malloc과 다른 점이 하나도 없다. 리눅스의 libc는 malloc에 직접적인 구현이 다 들어가있지만 MSVC는 HeapAlloc으로 연결해놓았을 뿐이다. 물론, HeapAlloc/HeapFree의 동작방식도 malloc 동작 방식과 거의 차이가 없다. 다만 Windows XP SP2 이후 부터는 잦은 heap overflow attack을 막기 위해 약간의 보안 기능이 들어갔다 (그러나 워낙 적은 비트를 가지고 해야하기 때문에, 낮은 엔트로피로 brute-force로 공격하면 역시 뚫릴 수 밖에 없다). 그럼 차근차근 MSVC가 제공하는 메모리릭 검출 기능에 대해서 알아보자. 일단, MFC로 만든 프로그램에서 디버그 모드에서 아래 두 줄을 넣어보고 프로그램 종료 시 디버그 창의 메세지를 확인해보자. memset(malloc(7), 0xFF, 7);먼저, malloc으로 7바이트를 할당하고 0xFF로 초기화를, new로 4바이트 만큼 잡고 0x11로 초기화를 하였다. 그러면 프로그램 종료시 메모리 릭을 탐지하는데, new로 할당한 4바이트는 어느 파일에서 어느 소스 라인에서 할당이 되었는지 친절히 알려준다. 그래서 이 라인을 더블 클릭하면 그 위치로 가준다. 반면, malloc으로 잡은 7바이트는 소스 정보 없이 그냥 메모리 릭만 보고 하고 있다. MFC가 만들어주는 cpp 소스 앞 부분에는 아래와 같은 정의가 있다. #ifdef _DEBUG바로 이것으로 인해 new로 인한 메모리릭을 감지할 수 있다. 그렇다면 DEBUG_NEW는 어떻게 정의되어있을까. 아래 소스는 MFC 소스 중 일부인 afx.h에서 가져왔음. // Memory tracking allocation보다시피 new에 할당 크기인 nSize이외에 THIS_FILE, __LINE__이라는 두 매크로가 부가적으로 들어감을 알 수 있다. 이것때문에 특정 소스파일의 라인에서 메모리릭이 일어났음을 친절히 알려줄 수 있다. void* __cdecl operator new(size_t nSize, LPCSTR lpszFileName, int nLine)iwongu님 지적 감사합니다. 이 부분 잘못되었습니다. 클래스 객체를 배열로 잡을 때, 그 수 만큼 생성자나 파괴자를 호출해야 합니다. 따라서 몇 개나 할당했느냐를 맨 앞에다가 저장을 하고 있습니다. 제가 혼동한 이유는 이 작업을 new[] 연산자 자체에서 해주는 것으로 생각했고, new와 new[] 차이가 없길래 그렇게 하지 않다고 생각한 것입니다. new[] 자체는 new와 다를 바가 없지만 컴파일러가 4바이트 만큼 공간을 더 할당하고 그 공간에 개수를 채워넣는 부분을 disassembly로 파악할 수 있었습니다. Pseudo code로 대략 컴파일러가 만들어 놓은 어셈코드를 의역하면 다음과 같습니다. ;; Rect* pRect = new Rect[nCount]; 이제 직접 디버거를 이용해서 new 연산을 계속 step-in을 해보자. 그러면 바로 위에 보여준 소스 코드를 지나 아래 소스 코드로 도달한다. (복잡한 각종 매크로 부분은 삭제했음) void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine)보다시피 디버깅 모드가 아닐 때는 일반 new로 불러주고 그렇지 않을 때는 _malloc_dbg라는 디버깅 모드 malloc를 호출함을 알 수 있다. 여기서 또 다시 step-in을 계속하면 몇몇 함수를 거쳐 결국 MFC가 아닌 일반 C 런타임 함수인 _heap_alloc_dbg에 도달함을 알 수 있다. 따라서, 메모리 릭 감지 기능은 MFC 기능이 아닌 일반적인 MSVC의 기능이다. 아래는 dbgheap.c 소스 중 일부 (간략히 축약했음): extern "C" void * __cdecl _heap_alloc_dbg(복잡해 보이지만 따라보면 무척 간단하다. 실제 필요한 데이터 크기에다 _CrtMemBlockHeader라는 크기와 NoMansLandSize를 더한 크기 만큼을 할당한다. 그런뒤 _heap_alloc_base를 호출하는데 이 함수는 정말 HeapAlloc만 호출한다. 그런 뒤에, 이제 메모리 릭을 찾기 위한 메모리 할당 정보를 리스트 형태로 쭉 연결한다. 보다시피 각 블락마다 할당 크기, 소스 파일 위치, 소스 라인 등을 기록한다. 실제 디버거로 살펴보면 아래 그림과 같다: ![]() 이렇게 모든 메모리 할당에 대해 별도의 부가 정보를 기억한다. 그렇다면 free할 때 이 정보를 삭제할 것이고, 프로세스 종료시 남아있는 리스트의 내용을 보여주면 메모리 릭을 알려 줄 수 있을 것이다. 정말 간단하지 않은가? 실제 내용을 덤프하는 함수 역시 dbgheap.c에 구현이 되어있다. 이 함수는 CRT 내부에서 프로세스 종료시 호출이 된다. 보다시피 친숙한 "Dumping objects"를 출력하는 부분을 볼 수 있다. static void __cdecl _CrtMemDumpAllObjectsSince_stat( 지금까지 MFC가, 아니 MSVC가 어떻게 메모리 릭을 찾는지 알아보았다. 그러면 맨 처음 예에서 보았듯이 제대로 찾지 못하였던 malloc로 인한 메모리릭 소스 위치를 확인하려면 어떻게 해야할까? #define DEBUG_MALLOC(size) _malloc_dbg(size, _NORMAL_BLOCK, __FILE__, __LINE__)이렇게 malloc을 _malloc_dbg로 대체하면 된다. 그리고 MFC가 아닌 일반 Win32 프로젝트로 작업할 때, new/malloc으로 인한 메모리 릭을 잡기 위한 방법을 직접 예제 코드를 통해 알아보자: #include < tchar.h >대략 이런 방식으로 crtdbg.h를 추가하고 new 연산자를 다른 것으로 대체하면 된다. 약간의 혼동이 있을 수가 있는데, crtdbg를 include하기 전에 #define _CRTDBG_MAP_ALLOC를 해줄 경우 malloc을 별도로 DEBUG_MALLOC 등으로 대체할 필요는 없다. 컴파일러가 malloc이 중복 정의되어있다고 경고를 띄우니 잘 골라서 하면 된다. 그리고 이것만 가지고는 메모리 릭을 확인할 수 없고 반드시 _CrtSetDbgFlag를 이용해서 플래그를 고쳐야만 한다. 그러면 이제 Win32 프로그램에서도 메모리 릭을 확인할 수 있다. 마지막으로 heap overflow를 체크하기 위해 0xbaadf00d라는 재밌는 값을 심어놓는 경우도 있다. 이런 것을 통상 Canary value라고 하는데, canary는 새 이름으로 위험을 미리 감지하는 동물이란 뜻으로 사용되었다. 그래서 메모리를 free할 때, 이 부분의 값이 바뀌어져있으면 heap corruption 에러가 뜬다. ![]() 이 글과 관련있는 글을 자동검색한 결과입니다 [?]
최근 등록된 덧글
베스킨라빈스 바닐라도 괜..
by dhunter at 07/04 이 글을 보고 온라인 알고리.. by 김정은 at 07/04 리눅스 커널도 바닐라가 있죠.. by Corund at 07/03 궁금증이 이제야 풀리네요... by 유겸애비 at 07/03 아무래도 mpeg 코덱 특성 .. by object at 07/02 그런 건 아닙니다. 논문 중에.. by object at 07/02 최근에 LCD TV를 구입해서.. by kirrie at 07/02 Supreme Commander의 .. by daybreaker at 07/02 최근 등록된 트랙백
메뉴릿
|