본문 바로가기
IT

linux에서 메모리 오류 문제 분석하기 - glibc malloc check, valgrind

by developer's warehouse 2024. 3. 5.

mariadb를 python에서 테스트하기 위해서 pyodbc를 이용해서 tpcc 테스트를 진행중입니다. 그런데, mariadb odbc 드라이버에서 메모리 할당 오류가 발생합니다. 실제 다양한 프로그램을 테스트하다보면 메모리 관련 오류를 만날 수 있습니다. 이 경우에 할 수 있는 두 가지 방법을 알아보도록 하겠습니다.

linux에서 메모리 오류 문제 분석하기 - glibc malloc check, valgrind 썸네일

1. glibc malloc_check_ 환경변수

glibc 라이브러리에서 메모리 오류를 확인하기 위해 설정할 수 있는 주요 환경 변수는 MALLOC_CHECK_입니다. 이 환경 변수는 메모리 할당 오류를 감지하고 처리하는 방법을 제어합니다.

malloc check 설명


MALLOC_CHECK_ 환경 변수의 값에 따라 다음과 같은 동작이 수행됩니다.

MALLOC_CHECK_=0: 메모리 관리 함수는 오류를 허용하고 경고를 주지 않습니다. 이 값이 기본값입니다.
MALLOC_CHECK_=1: 메모리 관리 함수는 문제가 발견되면 표준 오류에 경고 메시지를 출력합니다.
MALLOC_CHECK_=2: 메모리 관리 함수는 문제가 발견되면 abort()를 호출합니다.

MALLOC_CHECK_=3 설정은 메모리 오류가 발생하면 오류 메시지를 출력하고 abort()를 호출하면서 backtrace도 보여줍니다.

 

malloc_check_ 테스트 코드

아래 코드는 100바이트의 메모리를 할당하고, 할당된 메모리 범위를 넘어서 값을 쓰는 것을 시도합니다. 이는 메모리 오류를 일으키며, MALLOC_CHECK_ 환경 변수를 사용하면 이 오류를 감지할 수 있습니다.

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *ptr = malloc(100);
    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

    /* Intentionally write beyond the allocated memory */
    ptr[100] = 'a';

    free(ptr);
    return 0;
}

 

malloc_check_ 테스트 결과

위의 소스를 갖는 파일을 malloccheck.c로 저장후 다음과 같이 테스트를 해보았습니다.

MALLOC_CHECK_가 설정되어있지 않은 경우 아무 오류없이 프로그램이 정상 수행되고 종료됩니다.

하지만, MALLOC_CHECK_가 1로 설정되면 포인터 접근에대한 에러를 보여주고, 2인 경우 abort되어 core가 dump됩니다.

3인 경우에는 오류가 출력되고 backtrace와 함께 core dump가 발생합니다.

% gcc -o mcheck malloccheck.c

% ./mcheck

% env |grep MALLOC

% export MALLOC_CHECK_=1

% ./mcheck
*** Error in `./mcheck': free(): invalid pointer: 0x0000000002079010 ***

% export MALLOC_CHECK_=2

% ./mcheck
중지됨 (core dumped)

% export MALLOC_CHECK_=3

% ./mcheck
*** Error in `./mcheck': free(): invalid pointer: 0x000000000180d010 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x7f566)[0x7f8496d06566]
./mcheck[0x4005d7]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7f8496ca8c05]
./mcheck[0x4004f9]
======= Memory map: ========
00400000-00401000 r-xp 00000000 103:01 4380423494                        /lswhh/tmp/mcheck
00600000-00601000 r--p 00000000 103:01 4380423494                        /lswhh/tmp/mcheck
00601000-00602000 rw-p 00001000 103:01 4380423494                        /lswhh/tmp/mcheck
0180d000-0182e000 rw-p 00000000 00:00 0                                  [heap]
7f8496a71000-7f8496a86000 r-xp 00000000 fd:00 206522296                  /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f8496a86000-7f8496c85000 ---p 00015000 fd:00 206522296                  /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f8496c85000-7f8496c86000 r--p 00014000 fd:00 206522296                  /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f8496c86000-7f8496c87000 rw-p 00015000 fd:00 206522296                  /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f8496c87000-7f8496e3f000 r-xp 00000000 fd:00 201347848                  /usr/lib64/libc-2.17.so
7f8496e3f000-7f849703f000 ---p 001b8000 fd:00 201347848                  /usr/lib64/libc-2.17.so
7f849703f000-7f8497043000 r--p 001b8000 fd:00 201347848                  /usr/lib64/libc-2.17.so
7f8497043000-7f8497045000 rw-p 001bc000 fd:00 201347848                  /usr/lib64/libc-2.17.so
7f8497045000-7f849704a000 rw-p 00000000 00:00 0
7f849704a000-7f849706b000 r-xp 00000000 fd:00 206927864                  /usr/lib64/ld-2.17.so
7f849724a000-7f849724d000 rw-p 00000000 00:00 0
7f8497269000-7f849726b000 rw-p 00000000 00:00 0
7f849726b000-7f849726c000 r--p 00021000 fd:00 206927864                  /usr/lib64/ld-2.17.so
7f849726c000-7f849726d000 rw-p 00022000 fd:00 206927864                  /usr/lib64/ld-2.17.so
7f849726d000-7f849726e000 rw-p 00000000 00:00 0
7ffcdf04a000-7ffcdf06c000 rw-p 00000000 00:00 0                          [stack]
7ffcdf11a000-7ffcdf11c000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
중지됨 (core dumped)

 

 

 

glibc는 glibc.malloc.check, glibc.malloc.top_pad, glibc.malloc.perturb, glibc.malloc.mmap_threshold 등의 메모리 할당 튜닝 가능 변수를 제공합니다.

 

2. valgrind

Valgrind는 메모리 디버깅, 메모리 누수 탐지, 프로파일링 등을 수행할 수 있는 프로그래밍 도구입니다. Valgrind를 사용하면 메모리 누수를 쉽게 찾아낼 수 있습니다.

Valgrind를 사용하여 메모리 누수를 찾는 방법은 다음과 같습니다:

Valgrind 설치

  • Valgrind는 리눅스에서 기본적으로 제공되지만, 설치되어 있지 않은 경우에는 패키지 관리자를 통해 설치할 수 있습니다. 예를 들어, Ubuntu에서는 다음 명령을 사용하여 Valgrind를 설치할 수 있습니다.
sudo apt-get -y install valgrind

#아래와 같이 실행하면 설치가 완료됩니다.
lswhh@lswhh-KVM:~$ sudo apt-get -y install valgrind
[sudo] password for lswhh:
패키지 목록을 읽는 중입니다... 완료
의존성 트리를 만드는 중입니다... 완료
상태 정보를 읽는 중입니다... 완료
다음의 추가 패키지가 설치될 것입니다 :
  libc6-i386
제안하는 패키지:
  valgrind-dbg valgrind-mpi kcachegrind alleyoop valkyrie
다음 새 패키지를 설치할 것입니다:
  libc6-i386 valgrind
0개 업그레이드, 2개 새로 설치, 0개 제거 및 91개 업그레이드 안 함.
16.9 M바이트 아카이브를 받아야 합니다.
이 작업 후 91.8 M바이트의 디스크 공간을 더 사용하게 됩니다.
받기:1 http://kr.archive.ubuntu.com/ubuntu jammy-updates/main amd64 libc6-i386 amd64 2.35-0ubuntu3.6 [2,837 kB]
받기:2 http://kr.archive.ubuntu.com/ubuntu jammy/main amd64 valgrind amd64 1:3.18.1-1ubuntu2 [14.1 MB]
내려받기 16.9 M바이트, 소요시간 7초 (2,373 k바이트/초)
Selecting previously unselected package libc6-i386.
(데이터베이스 읽는중 ...현재 237780개의 파일과 디렉터리가 설치되어 있습니다.)
Preparing to unpack .../libc6-i386_2.35-0ubuntu3.6_amd64.deb ...
Unpacking libc6-i386 (2.35-0ubuntu3.6) ...
설치된 패키지 libc6:i386 (2.35-0ubuntu3.6)의 파일로 대체됩니다...
Selecting previously unselected package valgrind.
Preparing to unpack .../valgrind_1%3a3.18.1-1ubuntu2_amd64.deb ...
Unpacking valgrind (1:3.18.1-1ubuntu2) ...
libc6-i386 (2.35-0ubuntu3.6) 설정하는 중입니다 ...
valgrind (1:3.18.1-1ubuntu2) 설정하는 중입니다 ...
Processing triggers for man-db (2.10.2-1) ...
Processing triggers for libc-bin (2.35-0ubuntu3.6) ...
lswhh@lswhh-KVM:~$ which valgrind
/usr/bin/valgrind

예제코드

  • 아래 예제 코드는 100 byte를 할당 후 해제하지 않아 leak이 발생하는 코드입니다.
% cat valcheck.c
#include <stdio.h>
#include <stdlib.h>

int main() {
    char *ptr = malloc(100);
    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    /*ptr is leak*/
    return 0;
}

프로그램 컴파일

  • Valgrind는 디버깅 정보가 없어도 사용할 수 있습니다. 하지만, 프로그램을 -g 옵션으로 컴파일하여 디버깅 정보를 포함하면 더 많은 정보를 출력해 줍니다. 예를 들어, gcc -g -o vcheck valcheck.c 명령을 사용하여 프로그램을 컴파일할 수 있습니다.

 

Valgrind 실행

  • Valgrind를 실행하여 메모리 누수를 찾습니다. 다음과 같은 명령을 사용하여 Valgrind를 실행할 수 있습니다:
valgrind --leak-check=full --error-limit=no ./vcheck

이 명령은 my_program을 실행하고 메모리 누수를 찾습니다 --leak-check=full 옵션은 모든 개별 누수를 보여주고, --error-limit=no 옵션은 모든 오류를 보여줍니다.

결과 분석

  • Valgrind는 메모리 누수와 관련된 정보를 출력합니다. 이 정보를 분석하여 메모리 누수를 찾을 수 있습니다.
  • 아래의 결과를 보면 "definitely lost: 100 bytes in 1 blocks" 100byte 블록 1개가 누수된 것을 확인할 수 있습니다.
% gcc -o vcheck valcheck.c
lswhh: ~/tmp
% valgrind --leak-check=full --error-limit=no ./vcheck
==122798== Memcheck, a memory error detector
==122798== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==122798== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==122798== Command: ./vcheck
==122798==
==122798==
==122798== HEAP SUMMARY:
==122798==     in use at exit: 100 bytes in 1 blocks
==122798==   total heap usage: 1 allocs, 0 frees, 100 bytes allocated
==122798==
==122798== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==122798==    at 0x4C2B067: malloc (vg_replace_malloc.c:380)
==122798==    by 0x400553: main (in /lswhh/tmp/vcheck)
==122798==
==122798== LEAK SUMMARY:
==122798==    definitely lost: 100 bytes in 1 blocks
==122798==    indirectly lost: 0 bytes in 0 blocks
==122798==      possibly lost: 0 bytes in 0 blocks
==122798==    still reachable: 0 bytes in 0 blocks
==122798==         suppressed: 0 bytes in 0 blocks
==122798==
==122798== For lists of detected and suppressed errors, rerun with: -s
==122798== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

 

Valgrind는 프로그램의 메모리 오류를 찾는데 사용되는 매우 훌융한 도구이며, 메모리 누수 외에도 다양한 종류의 프로그램 오류를 찾는 데 사용할 수 있습니다.

 
facebook twitter kakaoTalk kakaostory naver band shareLink