Search
Duplicate
🐛

leaks 1.2배 더 유용하게 사용하기

간단소개
leaks 명령어, 솔직히 사용하시는데 조금 불편하셨죠?
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
잡지식
디버거
Scrap
태그
9 more properties

leaks

malloc으로 프로세스에게 할당한 가상 메모리 공간 중, 더 이상 지시(referenced)되지 않는 영역을 찾는 macOS 운영체제의 커맨드 라인 도구.
메모리 누수가 발생한 영역의 주소, 누수된 영역의 바이트 단위 크기, 누수된 영역이 담고 있는 데이터를 표시합니다.

기본적인 leaks 사용법

C언어의 경우, stdlib.hsystem 함수와 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으로 다시 설정해도 별다른 변화가 없어서, 저는 그냥 다른 창에서 위와 같은 디버깅을 진행하였습니다.
긴 글 읽으시느라 고생 많으셨습니다!! (좋아요 구독 알람설정)
참고자료: