leaks
•
malloc으로 프로세스에게 할당한 가상 메모리 공간 중, 더 이상 지시(referenced)되지 않는 영역을 찾는 macOS 운영체제의 커맨드 라인 도구.
•
메모리 누수가 발생한 영역의 주소, 누수된 영역의 바이트 단위 크기, 누수된 영역이 담고 있는 데이터를 표시합니다.
기본적인 leaks 사용법
•
C언어의 경우, stdlib.h의 system 함수와 atexit 함수를 사용하여, 프로그램이 종료되면 leaks 를 실행하도록 설정할 수 있습니다. 보다 자세한 사용법은 아래의 팔만코딩경을 참고!
유용한 leaks 옵션: MallocStackLogging
준비물:
•
프로그램을 실행할 터미널 창
•
프로그램 실행 결과를 출력할 별개의 터미널 창
다음과 같은 간단한 프로그램이 있습니다.
// main.c
#include <string.h>
#include <stdio.h>
char *get_sentence(void)
{
char *string;
char sentence[] = "Omae wa mou shindeiru...";
string = malloc(sizeof(char) * 44);
strcpy(string, sentence);
return (string);
}
int main(void)
{
char *str_ptr;
str_ptr = get_sentence();
printf("%s\n", str_ptr);
printf("NANI?!\n");
printf("=====================\n");
// 이런! free를 하지 않고 프로그램을 끝낸다.
return (0);
}
C
복사
gcc로 프로그램을 컴파일하고 실행하면, 별 다른 에러 없이 잘 작동하기에, 겉보기에는 별 다른 문제가 없는 것처럼 보입니다.
Omae wa mou shindeiru...
NANI?!
=====================
Shell
복사
하지만 leaks 명령어로 체크를 해보면… 프로그램의 메모리 누수를 확인할 수 있습니다.
#include <stdlib.h> // system, atexit 함수를 사용하기 위해 추가.
#include <string.h>
#include <stdio.h>
// 프로그램 바깥 터미널에서 leaks 명령어를 수행한다
// (컴파일 후 프로그램의 이름은 기본 a.out으로 설정).
void check_leak(void)
{
system("leaks a.out");
}
char *get_sentence(void)
{
char *string;
char sentence[] = "Omae wa mou shindeiru...";
string = malloc(sizeof(char) * 44);
strcpy(string, sentence);
return (string);
}
int main(void)
{
char *str_ptr;
str_ptr = get_sentence();
printf("%s\n", str_ptr);
printf("NANI?!\n");
printf("=====================\n");
// 프로그램이 종료되면 아래 함수를 호출한다.
atexit(check_leak);
return (0);
}
C
복사
Omae wa mou shindeiru...
NANI?!
=====================
Process: a.out [4414]
Path: /Volumes/VOLUME/*/a.out
Load Address: 0x10aa13000
Identifier: a.out
Version: ???
Code Type: X86-64
Parent Process: zsh [1999]
Date/Time: 2022-08-29 15:07:06.699 +0900
Launch Time: 2022-08-29 15:07:06.322 +0900
OS Version: Mac OS X 10.15.7 (19H2026)
Report Version: 7
Analysis Tool: /Applications/Xcode.app/Contents/Developer/usr/bin/leaks
Analysis Tool Version: Xcode 12.3 (12C33)
Physical footprint: 316K
Physical footprint (peak): 316K
----
leaks Report Version: 4.0
Process 4414: 157 nodes malloced for 12 KB
Process 4414: 1 leak for 48 total leaked bytes.
1 (48 bytes) ROOT LEAK: 0x7fe26f405820 [48] length: 24 "Omae wa mou shindeiru..."
Shell
복사
malloc 으로 할당한 메모리를 해제해주지 않아 메모리 누수가 발생했군요. 조금 더 깔끔하게 정보를 출력하기 위해 --list 옵션을 추가해봅시다.
// main.c:5
void check_leak(void)
{
system("leaks --list -- a.out");
}
C
복사
...
leaks Report Version: 3.0
Process 5069: 157 nodes malloced for 12 KB
Process 5069: 1 leak for 48 total leaked bytes.
Leak: 0x7fabd1405820 size=48 zone: DefaultMallocZone_0x10bfa6000 length: 24 "Omae wa mou shindeiru..."
Shell
복사
메모리 누수가 발생하였고, 누수된 영역에 대한 정보를 알 수 있게 되었지만, 디버깅에서 가장 중요한 요소인, 어디에서 메모리 누수가 발생했는지 확인하기 어렵습니다. 위 정보만으로는 메모리 누수가 발생한 지점을 명확하게 파악하기가 어렵네요.
하지만, MallocStackLogging 환경변수를 사용하면, 마법같은 일이 발생합니다. 프로그램 실행 결과를 출력할 별개의 터미널 창에 아래의 명령어를 입력해주세요.
export MallocStackLogging=1
Shell
복사
해당 환경변수가 설정되면, malloc 으로 메모리 할당을 하는 함수를 기억하고 로그를 남깁니다. 환경변수를 설정하였다면, 다시 leaks 로 프로그램을 실행해봅시다. 엄청난 양의 출력이 나오지만… 스크롤 압박에 굴하지 않고, 우리에게 필요한 리포트 부분을 확인해보면…
...
leaks Report Version: 3.0
Process 6077: 161 nodes malloced for 12 KB
Process 6077: 1 leak for 48 total leaked bytes.
Leak: 0x7f8739405ad0 size=48 zone: DefaultMallocZone_0x104701000 length: 24 "Omae wa mou shindeiru..."
Call stack: 0x7fff69e91cc9 (libdyld.dylib) start | 0x1046f2e84 (a.out) main | 0x1046f2e1a (a.out) get_sentence | 0x7fff6a047cf5 (libsystem_malloc.dylib) malloc | 0x7fff6a047d9e (libsystem_malloc.dylib) malloc_zone_malloc
Shell
복사
아하, get_sentence에서 할당한 문자열이 제대로 해제되지 않아서 발생한 문제였군요!
TIP : gcc 컴파일에 -g 옵션을 주고 컴파일하였다면, 보다 자세한 정보를 얻을 수 있습니다.
> gcc -g main.c
> ./a.out
...
Leak: 0x7fadf0405ad0 size=48 zone: DefaultMallocZone_0x107533000 length: 24 "Omae wa mou shindeiru..."
Call stack: 0x7fff69e91cc9 (libdyld.dylib) start | 0x107524e84 (a.out) main main.c:24 | 0x107524e1a (a.out) get_sentence main.c:15 | 0x7fff6a047cf5 (libsystem_malloc.dylib) malloc | 0x7fff6a047d9e (libsystem_malloc.dylib) malloc_zone_malloc
Shell
복사
main 함수의 24번째 줄에서 실행된, get_sentence 함수의 15번째 줄에서 malloc으로 할당한 문자열이 프로그램이 종료되어도 해제되지 않았군요. 그렇다면 프로그램이 끝나기 전에, 해당 문자열을 담은 포인터를 해제해줍시다.
// main.c:20
int main(void)
{
char *str_ptr;
str_ptr = get_sentence();
printf("%s\n", str_ptr);
free(str_ptr);
printf("NANI?!\n");
printf("=====================\n");
atexit(check_leak);
return (0);
}
C
복사
...
leaks Report Version: 3.0
Process 7254: 160 nodes malloced for 12 KB
Process 7254: 0 leaks for 0 total leaked bytes.
C
복사
메모리 누수를 잡았습니다!
맨 처음 준비물에서 프로그램 실행 결과를 출력할 별개의 터미널 창이 필요하다고 하였는데, 위 환경변수를 설정한 분들이라면 이유를 짐작하실 수 있을 것 같습니다. 매번 명령어를 칠 때마다 로깅이 수행되는 것 같더군요…
변수의 값을 0으로 다시 설정해도 별다른 변화가 없어서, 저는 그냥 다른 창에서 위와 같은 디버깅을 진행하였습니다.
긴 글 읽으시느라 고생 많으셨습니다!! (좋아요 구독 알람설정)
참고자료: