Languages/C++2010/07/13 10:59

간혹가다 메모리 관련 문제를 만났는데 쓸만한 디버깅 툴이 없을 때가 있다. 가령 valgrind 같은 툴은 최신 버전의 Mac OS X에서는 컴파일이 되질 않는다. 이런 문제를 만났을 때에는 해결 방법이 묘연하다. 동적으로 할당된 메모리가 중복 해제되지는 않는지 정도만 알 수 있으면 좋겠는데, 그런 문제도 gdb로는 추적하기가 좀 난감하다.

그런 경우, 다음과 같은 매크로를 만들어 두면 도움이 될 수도 있다. debug.h 코드를 보자.

#ifdef _DEBUG_MEMORY

#define NEW(TYPE,...)  (new(__FILE__, __LINE__) TYPE(__VA_ARGS__))
#define NEW_ARR(TYPE,SIZE) (new(__FILE__, __LINE__) TYPE[SIZE])
#define DEL(OBJ) do {           \
     printf("DLLOC %s %d %p\n", __FILE__,__LINE__, OBJ); \
     delete OBJ;           \
} while ( false )
#define DEL_ARR(OBJ) do {        \
     printf("DLLOC %s %d %p\n", __FILE__,__LINE__, OBJ); \
     delete[] OBJ;          \
} while ( false )

#endif

이 메크로가 정상적으로 동작하기 위해서는 다음과 같은 overloading된 global new 오퍼레이터들이 필요하다.

// memory.h
#ifdef _DEBUG_MEMORY
void* operator new(size_t sz, const char* file, int line) throw (std::bad_alloc);
void* operator new[](size_t sz, const char* file, int line) throw (std::bad_alloc);
#endif

//memory.cpp
#include <stdio.h>
#include "memory.h"

void* operator new(size_t sz, const char* file, int line) throw (bad_alloc)
{
   void* p = ::operator new(sz);
   printf("ALLOC %s %d %p\n", file, line, p);
   return p;
}


void* operator new[](size_t sz, const char* file, int line) throw (bad_alloc)
{
   void* p = ::operator new[](sz);
   printf("ALLOC %s %d %p\n", file, line, p);
   return p;
}

이 코드들을 섞어서 돌리면 컴파일시 _DEBUG_MEMORY 메크로가 정의되어 있을 때 ALLOC, DLLOC 등이 포함된 디버그 아웃풋이 화면에 쏟아진다. 여러분이 작성한 코드의 실행파일이 a.out이라고 할 때, 그 디버그 아웃풋을 다음과 같이 파일에 저장하자.

a.out > a.out.e

이 파일에 포함된 디버그 아웃풋 가운데 LLOC이 포함되어 있는 것만 따로 a.out.memory에 저장한다.

grep LLOC a.out.e > a.out.memory

이 파일을 분석하기 위한 ruby 스크립트를 다음과 같이 작성한다. (파일 이름을 인자로 받을 수도 있어야 하는데 귀찮아서.. ㅋㅋ) 

#!/usr/bin/ruby

class AllocInfo

 # list of dealloc entries

 def initialize(args)
  @file, @line, @address = args[:file], args[:line], args[:address]
  @deallocer = []
 end

 def mark_dealloc(entry)
  @deallocer << entry
  return @deallocer.length
 end

 def is_deallocated?
  return ( @deallocer.length > 0 )
 end

 attr_reader :file, :line, :address, :deallocer
end


#
# main
#

alloc_list = []
alloc_hash = {}

File.open( "a.out.memory" ) do |f|
 f.each_line do |l|

  array = l.split()

  if array[0] == "ALLOC"

   alloc_info = AllocInfo.new(
    :file => array[1],
    :line => array[2],
    :address => array[3]
   )
   alloc_list << alloc_info
   alloc_hash[ array[3] ] = alloc_info

  else

   entry = alloc_hash.fetch( array[3], nil );

   if entry == nil && array[3].to_i != 0

    printf "unallocated memory is freed by %s %s %s \n",
      array[1], array[2], array[3]

   elsif entry != nil

    len = entry.mark_dealloc( alloc_info )
    if len > 1
     printf "MULTIPLE DEALLOCATION===================="
     entry.deallocer.each do |d|
      printf "%s %s %s", array[1], array[2], array[3]
     end
     printf "MULTIPLE DEALLOCATION--------------------"
    end

   end
  end
 end

 alloc_list.each do |e|
  unless e.is_deallocated?
   printf "not deallocated: %s %s %s\n", e.file, e.line, e.address
  end
 end
end

이렇게 하면 할당되었는 데 해제되지 않은 메모리나, 두 번 이상 해제된 메모리에 관한 정보를 화면에 찍어준다. 쓸만한 메모리 디버깅 툴이 없을 때 써 볼 만 하다.

'Languages > C++' 카테고리의 다른 글

MSVCP100D.dll  (0) 2011/02/17
[C/C++] 쓸만한 메모리 디버깅 툴이 없을 떄  (0) 2010/07/13
cgdb : 텍스트 기반의 gdb 인터페이스  (0) 2007/12/18
VI와 ctags  (0) 2007/12/18
empty container의 반환  (0) 2007/12/14
메모리 할당과 초기화는 다르다  (2) 2007/12/14


Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/280 관련글 쓰기

  1. 공중곡예사의 생각  삭제

    2010/07/13 12:33TRACKBACK FROM bjlee72's me2DAY

    [C/C++] 쓸만한 메모리 디버깅 툴이 없을 떄

댓글을 달아 주세요

Extremely Agile/TDD2008/02/17 11:28
프로그래밍을 하다가 만나게 되는 문제들 중 상당수가 (특히 C/C++ 프로그래머의 경우) 메모리 문제입니다. 메모리 문제는 찾아내기도 어렵고, 교정하기도 어렵습니다. 잘못된 메모리 사용이 실제 어떤 형태의 현상으로 드러나게 될지를 단언할 수가 없는 탓입니다. 특히 Java같은 언어는 메모리를 할당하는 과정은 프로그래머가 통제할 수 있지만, 메모리를 반환하는 과정은 통제할 수 없기 때문에, 메모리 문제를 발견하기도 어렵고 교정하기는 더더욱 어렵습니다. 그래서 Java 프로그래밍을 하는 와중에 험한 꼴을 당하지 않으려면 Effective Java같은 책을 잘 읽어야 합니다. X-)

C/C++ 프로그래밍 언어의 경우에는 메모리를 조작하는 데 있어 프로그래머가 갖는 자유도가 꽤 큽니다. 그래서 메모리를 엉뚱하게 조작하는 실수를 저지를 확률이 굉장히 높은 편입니다. 그래서 일찍부터 많은 사람들이 'Unix 계열 운영체제에서 C나 C++로 프로그래밍하는 사람들을 위한' 메모리 관련 문제 탐지 기법들을 내놓았습니다. 이런 기법들은 '잘못된 메모리 참조가 발생할 경우 그 사실을 보고해 주는' 형태를 띠고 있으며, 워낙 그런 기법에 대한 수요가 컸기 때문에 그 일부는 이미 운영체제에 붙박이로 제공되고 있기도 합니다.

가령 Linux 같은 경우는 bash 상에서 MALLOC_CHECK_ 환경변수의 값을 1로 만들면 heap curruption이 발생했을 경우 진단 메시지가 화면에 출력되고, 2로 만들면 그 즉시 실행중이던 프로그램이 종료됩니다. 디버깅을 하다보면 heap curruption이 발생하는 시점과 프로그램이 SIGSEGV를 받는 시점이 달라서 디버깅하기 곤란할 때가 있는데, 그런 경우에 유용합니다. 적어도 '잘못된 일이 벌어지는 시점'과 '프로그램이 죽는 시점'을 똑같이 만들 수 있거든요. (printf에 의존적인 디버깅을 하시는 분들께는 이런 기법이 특히 유용하죠.) Solaris의 경우에는 watchmalloc을 사용하여 Linux와 비슷한 효과를 누릴 수 있습니다. 구글에서  man watchmalloc 해보시면 사용법을 아실 수 있으니까 설명은 생략하겠습니다. Linux와 사용법이 크게 다른 편은 아니랍니다.

하지만 뭐니뭐니 해도 메모리 관련 문제를 잡는 가장 좋은 방법은, 좋은 진단 툴을 사용하는 것입니다. 옛날부터 정평이 나 있는 메모리 누수 탐지 툴로는 purify같은 것이 있습니다만, 고가라 선듯 사용하기가 겁나죠. 하지만 Linux에서 프로그래밍을 하고 있다면, valgrind라는 막강한 툴이 있습니다.

valgrind는 -g 옵션을 주고 컴파일된 프로그램이라면 적용될 수 있습니다. 가령 컴파일된 실행파일의 이름이 a.out이라면, 다음과 같이 실행하면 됩니다.

valgrind --tool=memcheck ./a.out

--tool 옵션을 통해 실행 파일에 어떤 문제들이 내재되어 있는지를 살펴볼 수 있습니다. default는 memcheck이며, 메모리 관련 문제들을 검사하겠다는 뜻입니다. 메모리 누수(leak) 현상이 발생하는지의 여부 등을 이 옵션을 통해 검사할 수 있게 됩니다. 그것도 아주 빨리요.

프로그램(위의 경우에는 a.out)의 실행이 끝나면 valgrind는 다음과 같은 형식으로 탐지된 오류를 보고합니다.

==25832== Invalid read of size 4
==25832==    at 0x8048724: BandMatrix::ReSize(int, int, int) (bogon.cpp:45)
==25832==    by 0x80487AF: main (bogon.cpp:66)
==25832==  Address 0xBFFFF74C is not stack'd, malloc'd or free'd
위의 메시지에는 0xBFFFF74C에 대한 잘못된 메모리 참조가 발생했는데, 그 주소가 가리키는 메모리가 정상적으로 스택에 올라간 메모리도 아니고, malloc된 적도 없으며 free된 적도 없다는 것을 알리는 정보가 포함되어 있습니다. 참조가 발생한 위치도 요약되어 있구요.

최신의 Linux 배포판에는 이제 valgrind가 거의 번들되어 배포되고 있는 것 같습니다. 설사 설치되어 있지 않더라도, 요즘은 apt-get이나 yum 등 네트워크를 통해서 자동으로 프로그램을 설치할 수 있는 툴이 잘 정비되어 있으니까, 그 툴들을 사용하면 간단하게 설치해서 돌려볼 수 있습니다.

디버깅을 할 때 거의 아무런 도구를 사용하지 않는 프로그래머를 많이 볼 수 있습니다만, 메모리 관련 오류를 탐지하는 데 있어서는 이런 도구를 사용하는 쪽이 절대적으로 빠릅니다. 특히 오랜 시간 돌려놓으면 비주기적으로 죽어버리곤 하는 프로그램을 디버깅하는 데는 이런 도구를 활용하는 편이 낫죠.

valgrind에 대한 더 자세한 정보를 원하신다면 일단 여기로.




Posted by 이병준

TRACKBACK http://www.buggymind.com/trackback/114 관련글 쓰기

댓글을 달아 주세요

  1. valgrind.. 저는 memcheck 툴 사용보다 instruction instrumentation 용으로 잠깐 써 봤는데 좋더라고요.. 그런데 지금은 돌아가는 프로세스에 attach가 되나요?? 큰 프로그램에 memcheck같이 프로그램 살펴보는 tool을 붙이려고 하는데 attach가 안되서 처음부터 같이 돌려야 한다는.. 속도 죽음이죠 -_-;; (굼벵이) attach되면 좋겠는데..

    2008/02/17 14:08 [ ADDR : EDIT/ DEL : REPLY ]
    • 그러게요. 프로그램이 커질수록 그런 문제가 더 심각해지죠. 보통 메모리 검사를 시행하도록 해 놓으면 10배에서 20배 가까이는 느려진다고 하네요. Solaris에서 watchmalloc을 실행했을 때에는 정말 끔찍하게 느려지곤 했었죠. 10배 수준이 아니었던듯 ㅋ

      2008/02/17 14:47 [ ADDR : EDIT/ DEL ]