메모리 누수 감지(memory leak detection)란, [포인터를 가지고 있지 않은 할당된 메모리 블록을 감지]하는 것을 의미한다. 어플리케이션에서 메모리를 할당하면, 할당된 블록의 위치를 어플리케이션에서 포인터로써 가지고 있게 되는데, 할당된 블록을 해지하지 않았음에도 불구하고 이 포인터가 사라진 경우를 메모리 누수가 발생한것으로 간주한다.
솔라리스에서 메모리 관리는 매우 효율적으로 이루어진다. 솔라리스 전체의 메모리 사용 양을 알려면 아래와 같이 한다.
User/Anon: 익명 영역. 프로세스에서 동적으로 할당한 메모리(malloc 에 의해 할당된 영역)는 vnode 1)에 매칭되어있는데, 프로세스에 할당되어있지만 vnode에 매칭되어있지 않는 메모리 영역을 의미한다. 주로 프로세스의 힙, 스택 영역이다
Exec and libs: 실행 코드 및 공유 라이브러리
Page cache: 페이지의 캐시. 파일 시스템(ZFS제외)의 캐시. swap 영역(/tmp, /var/run 등) 에 큰 일반 파일이 있는 경우 이 값이 비정상적으로 커진다.
Free (cachelist): 여유 영역이지만 캐시(주로 디스크 캐시)에 의해 사용되는 영역. vnode에 매칭은 되어있음. 필요한 경우 언제든 freelist 로 돌아갈 수 있다.
Free: 여유 영역
1) vnode: 솔라리스 커널에서 사용하는 일종의 파일 시스템. FS에 독립적이다. 구조는 sys/vnode.h 설명되어 있음.
여유 메모리 양
솔라리스에서 여유 메모리 크기는 Free(cachelist) + Free(freelist) 이지만, ZFS File Data 영역도 메모리가 부족할시 여유 메모리로 반환된다.
libumem을 이용한 메모리 누수 감지
사용자 영역 slab 할당자인 libumem을 이용해 메모리 누수를 감지하는 방법이 있다. (원래 libumem 의 기능은 malloc, free 의 대체다) DTrace를 이용하는 방법도 있지만 이게 제일 간편하지 않을까 생각한다. (libumem을 프리로딩 해야하는 문제는 있지만) libumem 은 메모리 할당/해제와 관련된 문제를 찾는제 도움을 주며, 메모리 누수와 관련된 내용은 libumem의 기능중 일부임을 미리 밝혀둔다.
솔라리스 9 u3이상부터 가능하다. libumem이 이때부터 포함되었다.
예제1
memoryleak_leak.c
(152 바이트)
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
void * m;
m = malloc(10);
m = malloc(30);
m = malloc(50);
sleep(10);
}
위 파일을 컴파일해서 위에서 말한대로 코어를 얻고 mdb로 분석해보자. STEP 1에서는 메모리 누수가 발생했다고 나오지만 실제로는 아니다. 32bit 시스템에서 처음 할당한 [10킬로바이트]의 메모리 주소 m = a * 4 + b가 되며 어쨌든 주소를 알기 때문에 할당 해제가 가능하다.
바꿔말하면 할당된 메모리 주소를 가리키는 포인터가 변형(값이 없어지거나 바뀌거나 다른 곳으로 이동되거나)되면 메모리 누수로 판단한다.
STEP 2에서는 이를 계산해 할당 해제했으며, umem의 리포트에도 메모리가 제대로 해제되었음을 알 수 있다. 이로써 umem가 메모리 누수를 찾는 원리를 유추해볼 수 있다.
DTrace를 이용한 메모리 누수 감지
솔라리스 10에 추가된 DTrace를 이용해 메모리 누수 감지를 하는 방법에 대해 설명한다.
본 글의 일부는 아래의 페이지를 참고했다.
http://docs.sun.com/source/820-4221/
http://blogs.sun.com/sanjeevb/entry/dtrace_to_identify_memory_leaks
해제된 메모리를 다시 접근할때 Use After Free 라는 용어를 사용한다. 해제된 메모리를 가리키고 있는 포인터를 허상 포인터(Dangling Pointer, Wild Pointers)라고 한다. 프로그램에 따라 오류가 날 수도 있고, 보안상 문제가 될 수도 있다.
buf2R1, buf2R2 가 같은 곳을 가리키고 있다. 하지만 프로그램은 오류가 나지 않기 때문에 허상 포인터가 있다는 것을 프로그래머가 인지하기 힘들다. 이 때문에 여러 보안 업체에서 이슈로 다루는 것이다.
몇몇 디버거들은 허상 포인터를 알아내기 위한 탐지 방법을 제공하기도 한다. 아래에 소개할 Valgrind 도 --keep-stacktrace옵션을 사용하면 탐지해낸다.
Valgrind
valgrind(1)은 솔라리스/리눅스/Android/OSX 용 동적 프로그램 분석 툴로, 메모리 누수, 메모리 분석, 프로파일링에 사용한다. 위에서 나온 툴 보다 쉽고 강력한 기능을 제공해준다. Valgrind 에 대한 자세한 사항은 윈디하나의 솔라나라: Valgrind를 읽어보자.
root@wll ~ # gcc -o leak -g memoryleak_leak.c
root@wll ~ # valgrind --leak-check=full ./leak
==4123== Memcheck, a memory error detector
==4123== Copyright (C) 2002-2012, and GNU GPL#39;d, by Julian Seward et al.
==4123== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==4123== Command: ./leak
==4123==
==4123==
==4123== HEAP SUMMARY:
==4123== in use at exit: 90 bytes in 3 blocks
==4123== total heap usage: 3 allocs, 0 frees, 90 bytes allocated
==4123==
==4123== 10 bytes in 1 blocks are definitely lost in loss record 1 of 3
==4123== at 0x4A069EE: malloc (vg_replace_malloc.c:270)
==4123== by 0x40051C: main (memoryleak_leak.c:6)
==4123==
==4123== 30 bytes in 1 blocks are definitely lost in loss record 2 of 3
==4123== at 0x4A069EE: malloc (vg_replace_malloc.c:270)
==4123== by 0x40052A: main (memoryleak_leak.c:7)
==4123==
==4123== 50 bytes in 1 blocks are definitely lost in loss record 3 of 3
==4123== at 0x4A069EE: malloc (vg_replace_malloc.c:270)
==4123== by 0x400538: main (memoryleak_leak.c:8)
==4123==
==4123== LEAK SUMMARY:
==4123== definitely lost: 90 bytes in 3 blocks
==4123== indirectly lost: 0 bytes in 0 blocks
==4123== possibly lost: 0 bytes in 0 blocks
==4123== still reachable: 0 bytes in 0 blocks
==4123== suppressed: 0 bytes in 0 blocks
==4123==
==4123== For counts of detected and suppressed errors, rerun with: -v
==4123== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 6 from 6)
상용 메모리 누수 감지 소프트웨어
IBM Rational Purify
메모리 누수 감지하는 소프트웨어는 Rational® Purify® 가 가장 유명하다. 메모리 누수뿐만 아니라, 메모리 접근 오류까지 잡아준다. 상용 소프트웨어다.