[OS]/Unix&Solaris

libumem 라이브러리를 이용하여 어플리케이션 내의 메모리 관리 버그를 잡아 내는 방법]

하늘을닮은호수M 2007. 10. 22. 23:37
반응형

출처 : http://sdnkorea.com/blog/95

libumem 라이브러리를 이용하여 어플리케이션 내의 메모리 관리 버그를 잡아 내는 방법

솔라리스/개발자코너 2006/03/23 10:35
들어가는 글

이 글은 솔라리스9 Update 3 부터 포함된 사용자 공간 slab 할당자인 libumem 에 대해 소개합니다. 특별히 libumem 라이브러리에 의해 제공되는 디버깅 환경에 대해 다룹니다. 이 글은 어플리케이션 개발자의 관점에서 libumem 라이브러리가 제공하는 디버깅 기능을 통해 메모리 관리의 버그를 찾고 버그를 수정하는 방법에 촛점을 맞출 것입니다.

먼저 libumem 라이브러리에 대해 설명하고 slab 할당자를 어플리케이션 메모리 관리에서 사용하는데 얻을 수 있는 장점에 대해 간단히 설명할 것입니다. 다음으로 libumem 라이브러리가 제공하는 디버깅 기능에 대해 자세히 설명하고 툴을 이용해 기능의 장점을 취하는 방법에 대해서도 다룰 것입니다. 마지막으로 libumem 라이브러리를 사용하는 몇가지 예제를 다루고 Solaris OS Modular Debugger (MDB)를 이용하여 메모리 관리상의 버그(메모리 손상 또는 누수) 를 손쉽게 잡아 내는 것에 대해 설명할 것입니다.

이 글은 원래 Sun의 Access 1 사이트에 게제 되었고 허가에 의해 재발행되었습니다.

소개

사용자 공간 slab 할당자를 만드는 것은 SunOS 5.4 [1] 에서 처음 소개 되었던 커널 공간의 slab 할당자에 영감을 받았습니다. 커널 slab 할당자는 가상 메모리(VM) 시스템을 좀 더 빠르고 효율 적이고 확장성을 높이기 위한 방법을 찾기 위한 노력의 일환으로 만들어 졌습니다. slab 할당자는 object 캐싱 전략을 사용하여 좀더 효율적이고 빠르게 메모리를 할당하는 방법을 제공합니다. object 캐싱이란 자주 할당되고 제거 되어 지는 메모리를 캐쉬하는 전략을 의미합니다. 그러므로 똑같은 자료 구조가 만들어지는 오버헤드를 감소 시켜 줍니다. 이러한 전략은 대부분의 코드에서 상당수의 변수가 재사용 되어 짐을 통해서 매우 효율적인 전략으로 증명되어 졌습니다. slab 할당자의 확장성은 각 CPU의 캐쉬를 이용함으로써 지난 몇년간 계속 발전되어져 왔습니다. 이러한 방법은 시스템이 메모리를 요구할때 좀 더 덜 복잡한 locking 스키마를 이용 할 수 있도록 해주 었고 그럼으로써 좀더 확장성이 좋은 메모리 할당자를 만들었습니다. slab 할당자가 커널 공간 상에서 잘 동작함을 확인 하고 나서는 이것을 사용자 공간으로 포팅 시켰습니다. 그렇게 해서 사용자 공간 slab 할당자 라이브러리인 libumem이 만들어 졌습니다. Solaris 9 Update 3를 시작으로 libumem 라이브러리는 솔라리스의 표준 중 일부가 되었습니다.

사용자 공간 slab 할당자는 처음 할당 되기 전에 사이즈가 결정 되는 umem 캐시의 셋들에 기반을 두고 있습니다. umem 캐시는 시스템의 메모리 slab 들에 의해 만들어 집니다. slab이란 하나 혹은 두개의 연속된 가상 메모리(VM) 페이지들이 버퍼라고 불리는 동일한 사이즈의 덩어리로 나누어 짐을 의미합니다. 버퍼는 사용자의 데이타를 저장하고 또한 환경 설정에 따라 어플리케이션 개발자들이 메모리 관리 버그를 찾을 수 있는 디버그 정보를 포함 할 수 있습니다.

slab 할당자에 대한 구조와 이론에 대한 자세한 정보는 The Slab Allocator: An Object-Caching Kernel Memory AllocatorMagazines and Vmem: Extending the Slab Allocator to Many CPUs and Arbitrary Resources, 에서 찾아 보실 수 있습니다.

디버깅 설비

이 섹션에서는 어플리케이션이 메모리 리소스를 요구 했을때 버퍼가 만들어지는 과정을 다룹니다. 추가적으로 이 버퍼의 각 부분에 값에 대한 뜻을 설명합니다. 이것은 개발자에게 libumem 라이브러리가 어떻게 어플리케이션의 메모리 트랜젝션이 적합한지 조사할 수 있는 설비를 설정하는지에 대한 방법을 이해시키기 위해 제공됩니다

Anatomy of the Buffer

버퍼는 그림 1에서 보여지는 대로 4개의 섹션으로 나누어 집니다.


메타데이타 section
사용자 데이타 섹션
레드존 섹션
디버그
메타데이타 섹션

그림 1: libumem 라이브러리에 의해 만들어 지는 버퍼의 구조

첫번째 섹션은 8바이트의 메타 데이타를 저장하는데 사용되며 이 글에서는 다루지 않을 것입니다. 두번째 섹션은 어플리케이션이 데이타를 저장하는데 사용하는 섹션입니다. 세번째 섹션인 레드존은 유저 데이타와 디버그 메타 데이타 섹션을 구분하는데 사용됩니다. 추가적으로 레드존 섹션은 어플리케이션의 메모리 요구 사이즈를 결정하는데 사용될 수 있습니다. 네번째 섹션이자 마지막 섹션은 개발자가 버퍼의 사용 기록과 상태를 결정하는데 사용하는 디버그 메타 데이터를 저장하는데 사용됩니다.

사용자 데이타 섹션

사용자 데이타 섹션은 어플리케이션 데이타를 위해 예약된 버퍼의 일부분입니다. 버퍼의 이 부분에 동작에 대해 이해하려면 slab 할당자의 블럭 생성자 기초 즉 umem caches 에 관해 이해해야 합니다. slab 할당자는umem 캐시에 바탕을 두고 있고 미리 결정된 사이즈의 버퍼로 구성된 캐시로 구성됩니다. 그러므로 어플리케이션이 시스템에 메모리를 요구할때 시스템은 요구보다 더 큰 사이즈의 유저 데이타 섹션을 가지고 있는 umem 캐쉬로 부터 메모리를 할당합니다. 유저 데이타 섹션의 사이즈는 전형적으로 그림 2에서 보는 대로 어플리케이션에 의해 요구되는 메모리의 양에 비해 클 것입니다.



어플리케이션이 사용 가능한 메모리 0xbb 어플리케이션이 사용 불가능한 메모리

그림 2:  유저 데이타 섹션의 구조

umem 캐쉬는 object의 재사용과 시스템의 메모리 단편화 현상을 줄이기 위해 미리 결정된 사이즈의 버퍼들로 구성됩니다. 그러므로 대부분의 어플리케이션에 의한 메모리 할당요구는 버퍼의 유저 데이타 섹션 공간 전체를 필요로 하지는 않습니다.

어플리케이션에 의한 메모리 요구는 유저 데이타 섹션에서 시작해서 0xbb경계 값에서 끝나게 됩니다. 0xbb 경계 값은 어플리케이션에 의한 메모리 요구의 제일 마지막 바이트에 위치하게 됩니다. 버퍼 상의 다음 섹션에서 즉 0xbb 경계값과 레즈존 섹션의 시작 사이는 어플리케이션에 의해 사용되지 않습니다. MDB의 다음과 같은 출력에서 0xbb 값은 유저 데이타 섹션의 10번째 바이트에 쓰여 있음을 확인 함으로써 정확하게 10-바이트의 어플리케이션 요구 뒤에 놓여 있음을 알 수 있습니다.

> 0x49fc0/10X    
  0x49fc0:      12      3a10bfee        baddcafe	baddcafe
	     baddbbfe   baddcafe	feedface        11a7
	      50000	a115c8ed 

주의: 이 전의 16진수 덤프는 MDB 커맨드의 출력입니다. 이 커맨드는 10개의 4바이트 16진수 값이 0x49fc0에서 시작함을 보여 줍니다. 이 출력은 전체 libumem 버퍼가 0x49fc0에서 시작함을 나타냅니다. http://docs.sun.com 에서 MDB에 대한 자세한 사항을 확인 하시기 바랍니다.

만약 어플리케이션이 요구하는 메모리의 양이 umem 캐쉬에 의해 미리 사이즈가 결정된 유저 데이타 섹션의 사이즈와 일치 한다면, the 0xbb 값은 레드존 섹션의 첫 바이트에 위치하게 될 것입니다.

0xbaddcafe 는 버퍼의 유저 데이타 섹션에 초기화 되지 않은 메모리 세그먼트에 쓰여짐을 유의하시기 바랍니다. 디버깅 설비의 이러한 기능은 libumem 라이브러리가 어플리케이션이 이전에 초기화 되지 않은 데이타를 엑세스 하는지 조사 하기 위해 사용 됩니다.

레드존 섹션

버퍼의 레드존 섹션은 8바이트의 사이즈를 가지고 있고 유저 데이타 섹션과 디버그 메타 데이타 섹션을 구분하기 위해 사용 됩니다. 경계 값 0xfeedface 은 아래와 같이 레드존 섹션의 시작을 지시합니다.

> 0x49fc0/10X    
  0x49fc0:      12      3a10bfee        baddcafe	baddcafe
	     baddbbfe   baddcafe	feedface        11a7
	      50000	a115c8ed

이전에 기술했던 대로 만약 어플리케이션이 요구하는 메모리의 양이 umem 캐쉬가 전체 유저 데이타 섹션의 사이즈를 미리 결정했던 사이즈와 같다면 0xbb 값은 레드존 섹션의 처음 바이트에 위치하게 됩니다. 그러므로 레드존은 0xfeedface 로 시작한게 아니라 0xbbedface 로 시작합니다.

레드존 경계 값은 버퍼 오버플로우가 발생했는지 조사하기 위해 사용될 수 있습니다. 추가적으로 이전의 덤프에서 레드존의 마지막 4바이트인 0x11a7 값은 어플리케이션에 의해 요구된 메모리의 양을 조사 하는데 사용될 수 있습니다. /usr/include/umem_impl.h 헤더 파일에서 확인 할 수 있듯이 이 값은 다음과 같은 메크로에 의해 인코딩 됩니다.:

#define UMEM_SIZE_ENCODING(x)         ( 251 * (x) + 1 )

x 값은 어플리케이션의 메모리 요구양을 나타내고 여기에 8바이트를 더합니다. 그러므로 이전의 덤프를 이용하여 이러한 동작이 적당한지 알아 볼 수 있습니다.

> 0x11a7=D
              4519

십진수 값인 4519 을 251로 나눈 다음 8을 빼줌으로써 어플리케이션의 10바이트의 메모리를 시스템에 요구했음을 알 수 있습니다.

디버그 메타데이타

디버그 메타데이타 섹션은 umem_bufctl_audit 구조체를 가르키는 4바이트의 포인터와 4바이트의 체크섬 총 8바이트의 버퍼로 구성되어 있습니다. umem_bufctl_audit 구조는 /usr/include/umem_impl.h 헤더 파일에서 확인할 수 있는데로 다음과 같은 내용을 포함하고 있습니다:

typedef struct umem_bufctl_audit {
    struct umem_bufctl  *bc_next;   	/* next bufctl struct */
    void            	*bc_addr;    	
/* address of buffer */    struct umem_slab    *bc_slab;   /* controlling slab */    umem_cache_t        *bc_cache;   /* controlling cache */    hrtime_t        bc_timestamp; 
/* transaction time */    thread_t        bc_thread;  
/* thread doing transaction */    struct umem_bufctl  *bc_lastlog  
/* last log entry */    void            *bc_contents; 
/* contents at last free */    int            bc_depth;    /* stack depth */    uintptr_t      bc_stack[1]; /* pc stack */ } umem_bufctl_audit_t;

특이한 점은 마지막 쓰레드가 버퍼를 할당하거나 해제한 스택 트레이스의 포인터 입니다. 디버그 메타 데이터의 그 다음 4바이트 값은 bxstat 값이라고 불리며 버퍼가 올바른 상태인지 검사 하는데 사용 될 수 있는 체크섬입니다. umem_bufctl_audit 구조체의 포인터 값과 bxstat 체크섬 값을 XOR 연산 한 값은 할당된 버퍼에 대해 반드시 0xa110c8ed, 그리고 해제된 버퍼에서 0xf4eef4ee 값을 나타내야 합니다. 이러한 경우가 아니면 버퍼에 오류가 있음을 의미합니다.

> 0x49fc0/10X    
  0x49fc0:      12      3a10bfee        baddcafe	baddcafe
	     baddbbfe   baddcafe	feedface        11a7
	      50000	a115c8ed 

> 50000^a115c8ed=K
                a110c8ed


디버깅 방법론

malloc()free()메모리 관리 메소드는 많은 어플리케이션 개발자들에 의해 사용됩니다. 어플리케이션은 malloc() free() 를 이용해서 어떤 특별한 메모리 관리 프로그래밍 인터페이스에 의존하지 않게 쓰여질 수 있습니다. 이 섹션은 libumem 라이브러리를 이용하여 어플리케이션의 메모리 트랜젝션을 디버그 하는 장점에 대해 설명할 것입니다.

라이브러리 삽입 위치와 and libumem 플래그들

만약 libumem 라이브러리가 어플리케이션이 실행될때 삽입 되었다면(LD_PRELOAD 환경 변수 셋팅에 의해) , 어플리케이션이 malloc() 또는 free() 를 호출할때 마다 libumem 라이브러리에서 사용된 malloc()free() 메소드를 사용하게 됩니다. libumem 라이브러리의 디버깅 설비의 장점을 이용하기 위해서는 어플케이션이 실행 될때 UMEM_DEBUG 와 the UMEM_LOGGING 플래그를 환경 변수에 지정해줘야 합니다. 이러한 플래그들의 가장 일반적인 값은 다음과 같습니다: UMEM_DEBUG=default 그리고 UMEM_LOGGING=transaction.  이러한 셋팅을 통해서 각 어플리케이션에 의해 수행된 모든 메모리 트랜젝션의 쓰레드 ID, 정밀한 타임 스탬프, 스택 트레이스 등을 기록합니다. 추가적으로 libumem 라이브러리는:

  • 할당되거나 제거된 메모리 버퍼에 특정한 패턴을 이용하여 메모리가 초기화 되지 않았거나 (0xbaddcafe) 사용 된 후 제거되었는지 (0xdeadbeef)를 알려 줍니다.
  • 버퍼가 할당되고 제거 된 후 유저 데이타 섹션의 무결성을 체크하여 레드존을 생성합니다.
  • umem_bufctl_audit 구조체 포인터와 bxstat 체크섬으로 이루어진 레드존 뒤에 디버그 메타데이터 섹션을 생성합니다.

다음의 예제들은 올바른 디버그 플래그를 사용하고 어플리케이션을 실행할때 libumem 라이브러리를 삽입하는 것에 대해 나타내고 있습니다.

(csh)

%(setenv UMEM_DEBUG default; setenv UMEM_LOGGING transaction;
setenv LD_PRELOAD libumem.so.1; ./a.out)

또는

(bash)

bash-2.04$UMEM_DEBUG=default UMEM_LOGGING=transaction
LD_PRELOAD=libumem.so.1 ./a.out

디버그 플래그들(UMEM_DEBUG 그리고 UMEM_LOGGING)에 대한 좀더 자세한 내용은 umem_debug(3MALLOC) 멘 페이지에서 확인하시기 바랍니다.

MDB 명령

개발자는 MDB를 이용하여 어플리케이션의 메모리 관리 트랜젝션에 대한 디버그 정보를 볼 수 있습니다. 다음의 MDB 명령어는 어플리케이션의 실행 동안 일어나는 메모리 트랜젝션의 자세한 정보를 제공해 줍니다.

::umem_status

  • 로깅 기능이 활성화 또는 비활성화 되었는지 umem 의 상태를 나타냄
> ::umem_status
Status:         ready and active
Concurrency:    1
Logs:           transaction=64k 
Message buffer:


::findleaks

  • 어플리케이션 내에서 발견된 메모리 누수의 요약을 출력
> ::findleaks
CACHE  LEAKED   BUFCTL CALLER
0003d888 1 00050000 main+0xc ---------------------------------- Total 1 buffer, 24 bytes

::umalog

  • 어플리케이션에 의해 실행된 메모리 트랜젝션의 출력과 관계된 스택 트레이스 출력
> ::umalog

T-0.000000000  addr=55fb8  umem_alloc_32
         libumem.so.1`umem_cache_alloc+0x13c
         libumem.so.1`umem_alloc+0x44
         libumem.so.1`malloc+0x2c
         main+0x18
         _start+0x108

T-0.000457800  addr=49fc0  umem_alloc_24
         libumem.so.1`umem_cache_alloc+0x13c
         libumem.so.1`umem_alloc+0x44
         libumem.so.1`malloc+0x2c
         main+0xc
         _start+0x108


::umem_cache

  • umem 캐쉬의 자세한 정보를 출력
> ::umem_cache
ADDR     NAME                      FLAG    CFLAG  BUFSIZE  BUFTOTL
0003c008 umem_magazine_1           000e 80080000        8        0
0003c1c8 umem_magazine_3           000e 80080000       16        0
0003c388 umem_magazine_7           000e 80080000       32        0
0003c548 umem_magazine_15          000e 80080000       64        0
0003c708 umem_magazine_31          000e 80080000      128        0
0003c8c8 umem_magazine_47          000e 80080000      192        0
0003ca88 umem_magazine_63          000e 80080000      256        0
0003cc48 umem_magazine_95          000e 80080000      384        0
0003ce08 umem_magazine_143         000e 80080000      576        0
0003cfc8 umem_slab_cache           000e 80080000       28      170
0003d188 umem_bufctl_cache         000e 80080000       12        0
0003d348 umem_bufctl_audit_cache   000e 80080000      100      408
0003d508 umem_alloc_8              020f 80000000        8        0
0003d6c8 umem_alloc_16             020f 80000000       16        0
0003d888 umem_alloc_24             020f 80000000       24      204
0003da48 umem_alloc_32             020f 80000000       32      170
...snip...    


[address]::umem_log

  • 어플리케이션의 umem 트랜젝션 로그 출력
> ::umem_log
CPU ADDR     BUFADDR         TIMESTAMP THREAD  
  0 0002e064 00055fb8    10475e3dd1c98 00000001
  0 0002e000 00049fc0    10475e3d62050 00000001
    0003483c 00000000                0 00000000
    000348a0 00000000                0 00000000
    00034904 00000000                0 00000000
    ... snip ...


[address]::umem_verify

  • umem 캐쉬의 무결성을 출력하여 버퍼에 오류가 있는지에 대해 조사
> ::umem_verify
Cache Name                      Addr     Cache Integrity     
umem_magazine_1                    3c008 clean
umem_magazine_3                    3c1c8 clean
umem_magazine_7                    3c388 clean
umem_magazine_15                   3c548 clean
umem_magazine_31                   3c708 clean
umem_magazine_47                   3c8c8 clean
umem_magazine_63                   3ca88 clean
umem_magazine_95                   3cc48 clean
umem_magazine_143                  3ce08 clean
umem_slab_cache                    3cfc8 clean
umem_bufctl_cache                  3d188 clean
umem_bufctl_audit_cache            3d348 clean
umem_alloc_8                       3d508 clean
umem_alloc_16                      3d6c8 clean
umem_alloc_24                      3d888 clean
umem_alloc_32                      3da48 clean
... snip ...


address$<bufctl_audit

  • /usr/include/umem_impl.h 헤더에 정의된 umem_bufctl_audit 구조체 내용을 출력
> 50000$<bufctl_audit
0x50000:        next            addr            slab
                0               49fc0           4bfb0           
0x5000c:        cache           timestamp       thread
                3d888           23764722653000  1               
0x5001c:        lastlog         contents        stackdepth
                2e000           0               5               
                libumem.so.1`umem_cache_alloc+0x13c
                libumem.so.1`umem_alloc+0x44
                libumem.so.1`malloc+0x2c
                main+4          
                _start+0x108


예제

다음과 같은 기본 예제들은 MDB와 libumem 라이브러리를 이용해서 어떻게 어플리케이션의 메모리 트랜젝션 히스토리를 검사 하는지에 대해 보여 줍니다.

전통적인 메모리 누수

어플리케이션이 메모리 누수가 되고 있는지 검사 하려면 다음과 같은 과정을 통해 소스 코드의 어떠한 부분이 메모리 누수를 일으키고 있는지 좁혀 나갈 수 있습니다.

1. libumem 라이브러리는 솔라리스9 Update 3 이상의 시스템에서만 사용할 수 있습니다.

%uname -a
SunOS fountainhead 5.9 Generic_112233-05


2. libumem 라이브러리를 삽입하여 어플리케이션을 실행 시키고 적절한 디버그 플래그들을 성정합니다.

%(setenv UMEM_DEBUG default; setenv UMEM_LOGGING transaction; \    
    setenv LD_PRELOAD libumem.so.1; ./a.out)


3. gcore (1) 커맨드를 이용하여 어플리케이션의 메모리 트랜젝션을 분석하기 위한 코어를 얻습니다.

%ps -ef | grep a.out
user1     970   714  0 10:42:42 pts/4    0:00 ./a.out


%gcore 970
gcore: core.970 dumped


4. MDB를 이용하여 지난 섹션에서 설명했듯이 메모리 누수의 코어를 분석합니다.

%mdb core.970
Loading modules: [ libumem.so.1 libc.so.1 ld.so.1 ]


> ::umem_log
CPU ADDR     BUFADDR         TIMESTAMP THREAD  
  0 0002e0c8 00055fb8     159d27e121a0 00000001
  0 0002e064 00055fb8     159d27e0fce8 00000001
  0 0002e000 00049fc0     159d27da1748 00000001
    00034904 00000000                0 00000000
    00034968 00000000                0 00000000
    ... snip ...


여기서 우리는 cpu #0, thread #1에서 3가지의 트랜젝션이 있었음을 볼 수 있습니다.

> ::umalog

T-0.000000000  addr=55fb8  umem_alloc_32
         libumem.so.1`umem_cache_free+0x4c
         libumem.so.1`process_free+0x68
         libumem.so.1`free+0x38
         main+0x18
         _start+0x108

T-0.000009400  addr=55fb8  umem_alloc_32
         libumem.so.1`umem_cache_alloc+0x13c
         libumem.so.1`umem_alloc+0x44
         libumem.so.1`malloc+0x2c
         main+0x10
         _start+0x108

T-0.000461400  addr=49fc0  umem_alloc_24
         libumem.so.1`umem_cache_alloc+0x13c
         libumem.so.1`umem_alloc+0x44
         libumem.so.1`malloc+0x2c
         main+4
         _start+0x108


3가지 트렌젝션은 24 바이트 umem 캐쉬 할당, 하나의 메모리 할당 및 32 바이트의 umem 캐쉬 해제로 이루어 져 있습니다. 주의할 점은 정밀한 타임스탬프 출력은 어플리케이션에 의해 수행된 마지막 메모리 트랜젝션의 상대적인 값임을 알아 두시기 바랍니다.

> ::findleaks
CACHE     LEAKED   BUFCTL CALLER
0003d888       1 00050000 libumem.so.1`malloc+0x0
----------------------------------------------------------------------
 Total       1 buffer, 24 bytes


이것은 24 바이트의 버퍼에 메모리 누수가 있었음을 보여 줍니다.

> 00050000$<bufctl_audit
0x50000:        next            addr            slab
                0               49fc0           4bfb0           
0x5000c:        cache           timestamp       thread
                3d888           23764722653000  1               
0x5001c:        lastlog         contents        stackdepth
                2e000           0               5               
                libumem.so.1`umem_cache_alloc+0x13c
                libumem.so.1`umem_alloc+0x44
                libumem.so.1`malloc+0x2c
                main+4          
                _start+0x108


bufctl 구조체를 덤프 함으로써 어플리케이션이 어디에서 메모리 누수를 일으켰는지에 대한 스택 트레이스를 찾을 수 있습니다. 이 구조체의 주소는 이 전의 ::findleaks 출력에서 알아 낼 수 있습니다.

> 49fc0/10X

0x49fc0:        12              3a10bfee        baddcafe        baddcafe
                baddbbfe        baddcafe        feedface        11a7
                50000           a115c8ed  


버퍼의 값에서 볼수 있듯이 버퍼에 할당한 사이즈는 10 바이트 였습니다. 이것은 레드존의 값인 0x11a7 을 251로 나누고 8을 뺌으로써 알아 낼 수 있습니다.

%cat test.c
#include <stdio .h="">
#include <stdlib .h="">
#include <unistd .h="">

void test_sleep(int);

void main(){

        int *test;

        test = malloc(10);  
// This is the memory allocation that is never freed!! test = malloc(20); free(test); test_sleep(24); } void test_sleep( int interval ){ printf("Starting to sleep for %d seconds...\n", interval); sleep(interval); printf("Stopped sleeping...\n\n"); } </unistd></stdlib></stdio>

실행파일에 코드를 확인해 봄으로써 스택 트레이스의 기능을 이용할 수 있고 어떠한 조각의 메모리가 누수 되었는지 확인 할 수 있습니다.

전통적인 메모리 오류

다음과 같은 과정을 이용하여 어플리케이션의 코어를 검사 메모리 오류 버그를 잡아 냅니다.

1. 메모리 누수 예제에서 했던 처음 두가지 과정을 따릅니다..

2. 어플리케이션이 종료 됐을 경우 코어 덤프를 분석하고 그렇지 않을 경우 위에서 했던 대로 gcore 를 사용합니다.

3. MDB 를 사용하여 이전 섹션에서 했던 대로 어플리케이션의 코어를 분석합니다.

%mdb core.1095
Loading modules: [ libumem.so.1 libc.so.1 ld.so.1 ]

> ::umem_verify
Cache Name                      Addr     Cache Integrity     
umem_magazine_1                    3c008 clean
umem_magazine_3                    3c1c8 clean
umem_magazine_7                    3c388 clean
umem_magazine_15                   3c548 clean
umem_magazine_31                   3c708 clean
umem_magazine_47                   3c8c8 clean
umem_magazine_63                   3ca88 clean
umem_magazine_95                   3cc48 clean
umem_magazine_143                  3ce08 clean
umem_slab_cache                    3cfc8 clean
umem_bufctl_cache                  3d188 clean
umem_bufctl_audit_cache            3d348 clean
umem_alloc_8                       3d508 clean
umem_alloc_16                      3d6c8 clean
umem_alloc_24                      3d888 1 corrupt buffer
... snip ...


umem_verify 커맨드를 이용하여 umem 캐쉬 중에 하나에 오류가 생겼음을 확인 했습니다.

> 3d888::umem_verify                  
Summary for cache 'umem_alloc_24'
  buffer 49fc0 (allocated) has a corrupt redzone size encoding


24 바이트 umem 캐쉬에서 어떠한 에러가 발생했는지 자세한 정보를 제공합니다.

>  49fc0/10X
0x49fc0:        18              3a10bfe8        0               1
                2               3               4               1789
                50000           a115c8ed 


버퍼의 덤프를 확인해 보면 원래 할당된 사이즈는 16바이트 였습니다. 이것은 레드존 값인 1789를 251로 나누고 8을 빼줌으로써 계산될 수 있습니다. 일단 할당된 사이즈를 알고 나면 우리는 16바이트의 0xfeedface 경계값을 찾아서 유저 데이타 섹션이 어디서 시작되는지 알 수 있습니다. 위의 버퍼 결과는 유저 섹션이 0~4로 채워져 있음을 나타냅니다. 그리고 레드존 경계 태그가 없음을 알수 있음니다(0xfeedface). 즉 4가 들어갈 공간에 레드존 값이 들어가 있어야 함을 알아 냈습니다!

>50000$<bufctl_audit      
0x50000:        next            addr            slab
                0               49fc0           4bfb0           
0x5000c:        cache           timestamp       thread
                3d888           31080154190400  1               
0x5001c:        lastlog         contents        stackdepth
                2e000           0               5               
                libumem.so.1`umem_cache_alloc+0x13c
                libumem.so.1`umem_alloc+0x44
                libumem.so.1`malloc+0x2c
                main+4          
                _start+0x108 


마지막 메모리 트랜젝션에 대한 스택 트레이스를 통해 메모리 오류가 발생한 코드가 어느 곳인지 좁혀 갈 수 있습니다.

%cat test.c
#include <stdio .h="">
#include <stdlib .h="">
#include <unistd .h="">

void test_sleep(int);

void main(){

        int *test;

        test = malloc(16);

        for(int i = 0; i <= 4; i++){     
// CORRUPTION! The for loop should only test[i] = i;
// be traversed three times instead }
// of four due to the buffer size test_sleep(24); } void test_sleep( int interval ){ printf("Starting to sleep for %d seconds...\n", interval); sleep(interval); printf("Stopped sleeping...\n\n"); } </unistd></stdlib></stdio>

코드를 보고 난 후 메모리 오류가 반복문에서의 잘못된 조건문으로 발생함을 알아 냈습니다.
참고자료

1. The Slab Allocator: An Object-Caching Kernel Memory Allocator, Jeff Bonwick 지음

2. Magazines and Vmem: Extending the Slab Allocator to Many CPUs and Arbitrary Resources, Jeff Bonwick 그리고 Jonathan Adams 지음

반응형