Linux Performance
이번 글에서는 gdb를 이용해 메모리 덤프를 생성하는 방법을 살펴보겠습니다. 사실 많이 활용되지는 않지만, 메모리 관련된 이슈가 있을 때 사용하면 꽤 많은 도움을 받을 수 있기 때문에 알고 계시면 언젠가(?)는 도움이 될 겁니다. ^^
여러 가지 경우가 있겠지만 메모리 덤프를 가장 필요로 하는 순간은 역시 프로세스의 메모리 릭이 의심될 때 입니다. 프로세스는 Linux로부터 사용할 메모리 영역을 요청하게 되는데요, 이 때 요청하고 사용한 메모리 영역을 제대로 반환하지 않아서 계속 쌓이게 되면 메모리 릭이 발생하게 됩니다. 그리고 이런 메모리 릭 현상은 주로 Heap 영역에서 발생합니다.
Heap 영역 : 프로세스가 데이터의 저장, 가공을 하기 위해 사용하는 메모리 공간으로 malloc과 같은 시스템 콜을 통해 커널로부터 할당받은 메모리 영역
애플리케이션에서 메모리 릭이 의심될 경우, 우선 gdb를 이용해서 메모리 덤프를 만든 후 어떤 데이터들이 해제되지 않고 쌓이고 있는지를 확인하면 어느 부분의 소스 코드에서 문제가 발생하는지를 파악하는데 도움이 됩니다.
그럼 간단하게 메모리 릭을 발생시키는 프로그램을 하나 만들어서 테스트해 보겠습니다.
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#define MEGABYTE 1024*1024
int main() {
struct timeval tv;
char *current_data;
while (1) {
gettimeofday(&tv, NULL);
current_data = (char *) malloc(MEGABYTE);
sprintf(current_data, "%d", tv.tv_usec);
printf("current_data = %s\n", current_data);
sleep(1);
}
exit(0);
}
malloc을 통해 1MB 의 메모리 영역을 할당받고 특정 값을 해당 영역에 기록하는 코드입니다. free가 없기 때문에 메모리 릭이 발생하게 됩니다. 실행시키고 난 후 pmap 명령을 사용하면 계속해서 메모리 영역이 늘어나는 것을 볼 수 있습니다.
그럼, gdb를 이용해서 메모리 덤프를 떠 보겠습니다. 덤프 뜰 메모리 주소를 확인해 보겠습니다.
cat /proc/<pid>/smaps을 통해 확인해 보겠습니다. smaps를 살펴보다 보면 아래와 같이 비정상적으로 크기가 커진 Heap 영역을 볼 수 있습니다.
바로 저 부분이 gdb를 이용해서 메모리 덤프를 뜰 영역입니다. gdb -p <pid> 명령으로 접속한 후에 아래와 같이 명령을 입력합니다.
dump memory /root/memory_dump 0x7fbbc689a000 0x7fbbc77ac000
gdb를 빠져 나와서 /root/에 가보면 memory_dump라는 파일이 생성된 것을 볼 수 있습니다. strings 명령으로 어떤 내용들이 써져 있는지 확인해 보겠습니다.
malloc으로 할당받고 sprintf로 해당 메모리 영역에 기록한 값들이 지워지지 않고 계속 append 형식으로 붙어 있는 것을 볼 수 있습니다. (제 환경에서는 맨 처음 tv_usec 값을 찍은 게 1206 이었습니다.)
즉, free를 통해서 명시적으로 해제하지 않았기 때문에 할당된 메모리 영역은 계속 늘어나고 기록된 값들도 지워지지 않고 유지되는 것을 볼 수 있습니다.
실제 애플리케이션에서도 gdb를 이용해서 메모리 덤프를 뜨게 되면 어떤 데이터가 남아서 메모리 릭을 만들어 내는지 살펴볼 수 있습니다.
실제 모 회사에서 만든 하드웨어 모니터링용 툴이 메모리 릭을 발생하는 이슈가 있었습니다. 특정 메모리 영역이 비정상적으로 컸습니다.
대상 영역을 gdb로 덤프를 떠서 확인해 보자 아래와 같은 내용이 확인되었습니다.
INFO | jvm 1 | 2014/12/11 01:30:34 | 01:30:34 INFO[WrapperListener_start_runner]execute command: "./libs/native/MegaCli64" -AdpAllInfo -aALLINFO | jvm 1 | 2014/12/11 01:30:34 | 01:30:34 INFO[WrapperListener_start_runner]retry count: 3 and isSuccess: trueINFO | jvm 1 | 2014/12/11 01:30:34 | 01:30:34 INFO[WrapperListener_start_runner]Status Exit Code :OKINFO | jvm 1 | 2014/12/11 01:30:34 | 01:30:34 INFO[WrapperListener_start_runner]Status Summary :Exit Code: 0x00INFO | jvm 1 | 2014/12/11 01:30:34 | 01:30:34 INFO[WrapperListener_start_runner]execute command: "./libs/native/MegaCli64" -AdpBbuCmd -aALL
날짜를 보시면 아시겠지만 2014년 12월 11일부터의 로그가 계속 남아 있었습니다. 즉, 해당 프로세스가 로그 파일에 로그를 기록하는 과정에서 메모리 할당을 하고 완료된 후 제대로 반환하지 않아서 메모리 릭이 발생한 것을 확인할 수 있었습니다.
gdb를 통해 메모리 덤프를 남긴다고 해서 모든 문제를 해결할 수 있는 것은 아니지만, 메모리 릭이 발생할 경우 어떤 로직에 의해서 릭이 발생하게 되는지를 빠르게 판단할 수 있어서 많은 도움이 됩니다.
남아 있는 값들을 확인해 보면 로직을 추정할 수 있기 때문입니다.
strace 만큼은 아니지만 gdb도 알아 두면 다양한 디버깅에 도움을 받을 수 있습니다.
감사합니다.