본문 바로가기
IT/linux

valgrind, linux memory leak detection

by 어느해겨울 2023. 3. 21.

valgrind

valgrind 는 DBI(Dynamic Binary Instrumentation) 도구이기 때문에 gdb attach 처럼 실행 중인 프로세스를 분석할 수 없다.

DBI는 바이너리를 런타임(실행되고 있는 동안) 코드를 삽입하여 동작 분석한다.
코드를 삽입하여 동작을 분석하는 행위를 instrumentation 이라고 한다.
이는 valgrind 에 입력으로 들어가는 실행 코드를 실행 시간에 직접 가공하는 것을 의미한다.

 

valgrind를 통한 분석

본인이 사용하는 valgrind 옵션과 사용법을 알아본다. 

$ valgrind --leak-resolution=high --leak-check=yes --show-reachable=yes --track-origins=yes --log-file="/tmp/valgrind.XXXX" -v ~/bin/XXXX
 
--leak-resolution=high  : <low|med|high> [default: high], 결과의 표시 방식 결정, leak/memory check의 결과를 병합하지 않고 얼마나 상세하게 나타낼 것인지 나타낸다.
--leak-check=yes        : <no|summary|yes|full> [default: summary], 프로그램을 종료할 때 얼마나 많은 leak 이 발생했는지 정도를 나타낸다.
--show-reachable=yes    : reachable leak 을 결과에 나타낸다.
--track-origins=yes     : <yes|no> [default: no], Memcheck가 초기화되지 않은 값의 출처를 추적하는지 여부를 나타낸다.
--log-file=""           : 결과를 대상 경로의 파일로 출력한다.
-v                      : verbose, 로드된 공유 객체, 비정상적인 동작에 대한 경고 등 추가 정보 제공, 옵션을 반복하면 상세 수준이 높아진다.

그 외 옵션 목록과 설명은 valgrind 매뉴얼 페이지를 참고하자. 일반적인 옵션부터 메모리 체크, 디버깅 등 많은 옵션들을 제공한다.
https://valgrind.org/docs/manual/manual.html

 

결과를 살펴보기 위해 Memory leak detection 에 대한 설명은 다음과 같다.

     Pointer chain            AAA Leak Case   BBB Leak Case
     -------------            -------------   -------------
(1)  RRR ------------> BBB                    DR
(2)  RRR ---> AAA ---> BBB    DR              IR
(3)  RRR               BBB                    DL
(4)  RRR      AAA ---> BBB    DL              IL
(5)  RRR ------?-----> BBB                    (y)DR, (n)DL
(6)  RRR ---> AAA -?-> BBB    DR              (y)IR, (n)DL
(7)  RRR -?-> AAA ---> BBB    (y)DR, (n)DL    (y)IR, (n)IL
(8)  RRR -?-> AAA -?-> BBB    (y)DR, (n)DL    (y,y)IR, (n,y)IL, (_,n)DL
(9)  RRR      AAA -?-> BBB    DL              (y)IL, (n)DL

Pointer chain legend:
- RRR: a root set node or DR block
- AAA, BBB: heap blocks
- --->: a start-pointer
- -?->: an interior-pointer

Leak Case legend:
- DR: Directly reachable
- IR: Indirectly reachable
- DL: Directly lost
- IL: Indirectly lost
- (y)XY: it's XY if the interior-pointer is a real pointer
- (n)XY: it's XY if the interior-pointer is not a real pointer
- (_)XY: it's XY in either case


"Still reachable"
This covers cases 1 and 2 (for the BBB blocks) above. A start-pointer or chain of start-pointers to the block is found. Since the block is still pointed at, the programmer could, at least in principle, have freed it before program exit. "Still reachable" blocks are very common and arguably not a problem. So, by default, Memcheck won't report such blocks individually.

"Definitely lost"
This covers case 3 (for the BBB blocks) above. This means that no pointer to the block can be found. The block is classified as "lost", because the programmer could not possibly have freed it at program exit, since no pointer to it exists. This is likely a symptom of having lost the pointer at some earlier point in the program. Such cases should be fixed by the programmer.

"Indirectly lost"
This covers cases 4 and 9 (for the BBB blocks) above. This means that the block is lost, not because there are no pointers to it, but rather because all the blocks that point to it are themselves lost. For example, if you have a binary tree and the root node is lost, all its children nodes will be indirectly lost. Because the problem will disappear if the definitely lost block that caused the indirect leak is fixed, Memcheck won't report such blocks individually by default.

"Possibly lost"
This covers cases 5--8 (for the BBB blocks) above. This means that a chain of one or more pointers to the block has been found, but at least one of the pointers is an interior-pointer. This could just be a random value in memory that happens to point into a block, and so you shouldn't consider this ok unless you know you have interior-pointers.

Pointer chain legend, 메모리 누수와 관련된 포인터 체인을 표시

Pointer chain은 메모리 블록 간의 포인터 관계를 내고 Valgrind 출력에서 "->"로 표시된다. 
예를 들어, "0x12345678 -> 0x87654321"은 0x12345678 주소의 메모리 블록이 0x87654321 주소의 메모리 블록을 가리키는 포인터를 갖고 있음을 나타낸다.

Pointer chain legend를 사용하면 메모리 누수와 관련된 포인터 체인을 쉽게 파악할 수 있다.

 

Leak Case legend, 메모리 누수와 관련된 정보를 제공

Leak Case legend는 Valgrind 출력에서 "definitely lost", "indirectly lost", "possibly lost" 등과 함께 사용된다.
예를 들어, "definitely lost: 24 bytes in 1 blocks"는 1개의 메모리 블록에서 24바이트의 메모리가 누수되었음을 나타낸다.

Leak Case legend를 사용하면 어떤 유형의 누수가 발생했는지, 얼마나 많은 메모리가 누수되었는지 등의 정보를 파악할 수 있다.

 

Still reachable, 프로그램이 종료되어도 여전히 할당된 메모리가 있음을 의미
이러한 메모리는 여전히 프로그램에 의해 접근이 가능한 상태를 나타낸다.

Still reachable 오류는 보통 프로그램이 종료되기 전에 모든 메모리를 해제하지 않은 경우 발생하고, 이것은 누수라기 보다는 할당되어 있는 메모리를 정리하지 않은 것으로 판단한다

Definitely lost, 프로그램이 종료될 때 접근할 수 없는 메모리를 의미
이러한 메모리는 누수이며 프로그램에서 더 이상 접근하지 않는 메모리 블록이나 포인터를 가리키는 메모리가 있을 때 발생하고, 이런 경우는 메모리가 해제되지 않아 메모리 사용량이 계속 증가하는 문제를 일으키므로 반드시 수정이 필요하다.

Indirectly lost, 해제된 포인터를 가리키는 포인터가 있음을 의미
Indirectly lost 오류는 포인터가 가리키는 메모리 블록이 해제되었으나 이를 가리키는 포인터가 여전히 존재하는 경우 발생한다.

이러한 포인터는 더 이상 유효하지 않으므로 프로그램에서 이후에 누수를 발생시킬 수 있다.

Possibly lost, 할당된 메모리 블록이 해제되지 않았을 가능성이 있음을 의미
해당 경우에는 프로그램에서 메모리 누수가 발생할 가능성이 높다.

프로그램에서 메모리를 할당하고 이를 가리키는 포인터를 사용한 후 해당 포인터를 NULL로 초기화하지 않은 경우 possibly lost 오류가 발생할 수 있다. 이 역시 수정해주는 것이 좋다.

댓글