Skip to main content

Core Dump Pattern?

지난 주에 현장에 있던 시스템에서 core dump가 발생했습니다. :-(

제가 담당하는 부분은 아니어서 쳐다보지도 않고 있다가 오늘 뭔가 의심스러운게 있는지 확인하기 위해 불려갔지요. 사실 core dump를 열어 보지도 않아서 그냥 듣고만 있었는데 듣다 보니 원인이 뭔지는 짐작이 가더군요.

소멸자에서 core가 났는데 소멸자 코드안에는 아무 것도 없습니다. 그리고 call stack의 마지막 부분은 std::string에 있는 lock 관련 함수에서 났어요.


해당 pstack은 담당자의 말대로 다음과 같았습니다. 4~6 라인을 보시면 됩니다. ((c++filt를 적용한 pstack입니다.)) ((어제 밤에 CodeHighlighter에 line number 출력 기능을 넣었어요. :-) ))


----------------- lwp# 153 / thread# 73 --------------------
...
--- called from signal handler with signal 11 (SIGSEGV) ---
fe42b1d8 _mutex_lwp_lock (ef1860, fe44e000, 2ef1800, de55cc, ef1860, 40000013) + 20
00de55cc void std::basic_string,std::allocator >::__unLink() (9e0070c, 1ec9fdb8, 1ec9fdb8, f7901774, 1daa6770, 1e1cbc80) + 4c
00dc3f64 BRISession::~BRISession() (9e003a0, 2df4c44, 88f2e0, 1, 0, 1e3799b8) + 124
00de75e4 __SLIP.DELETER__G (9e003a0, 1, f7901860, f790185c, 44, 4122640) + 4
...


아마 core dump를 많이 보셨다면 이미 위의 말만으로도 원인을 짐작하실 수 있었을겁니다.

원인은 바로 double delete죠. 한번 지워진 object에 대해 다시 delete가 불리면 해당 object의 소멸자가 다시 호출되게 됩니다. 만약 이 소멸자에서 직접적으로 this 포인터를 사용하지 않았다면 문제가 발생하지 않았을 수도 있습니다. ((물론 double delete의 결과는 undefined behavior이기 때문에 실제 어떤 문제가 발생할지는 알 수 없습니다.))

하지만 위의 경우처럼 해당 클래스가 소멸자를 가진 member를 가지고 있고 그 소멸자에서 this가 가리키는 영역을 사용한다면 문제가 발생하게 됩니다. 이 경우에는 std::string을 member로 가지고 있었지요.

core의 내용에 대해선 이 정도로 마치고요, 중요한 사실은 제가 담당자의 말만 듣고도 해당 원인을 알 수 있었다는 사실입니다. 왜 알 수 있었을까를 생각해보니 이미 이와 유사한 core dump가 여러번 반복되었기 때문이었죠. 즉, 이번 core와 정확히 똑같지는 않으나 뭔가 유사한 core의 경험이 여러번 있었다라는 것이죠.

그럼 이쯤에서 Christopher AlexanderA Pattern Language라는 책에 쓰셨다는 말을 다시 기억해 보죠.

Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice.


각각의 패턴은 우리 주위에서 계속해서 발생하는 문제들과 그 문제에 대한 해결책을 설명한다고 되어 있습니다.

Core dump의 경우도 마찬가지로 뭔가 반복해서 나타나는 유사한 경험들을 모아서 pattern 형식으로 정리를 해놓으면 debugging시 도움이 되지 않을까하는 생각을 하게 되었지요. Design pattern이 design시에, 혹은 refactoring catalog가 refactoring시에 도움을 주는 것처럼 말이죠.

그래서 위의 문제를 가지고 만들어 본 Core Dump Pattern의 예제입니다. ;-)


  1. Name
    • Core on Empty Destructor


  2. Problem
    • pstack에서 소멸자가 보이며 call stack의 마지막 부분은 해당 클래스에 있는 멤버 변수의 소멸자이다.

      --- called from signal handler with signal 11 (SIGSEGV) ---
      00de55cc void std::basic_string::__unLink() (9e0070c, 1ec9fdb8, 1ec9fdb8, f7901774, 1daa6770, 1e1cbc80) + 4c
      00dc3f64 Foo::~Foo() (9e003a0, 2df4c44, 88f2e0, 1, 0, 1e3799b8) + 124


  3. Cause
    • 원인은 다음과 같이 double delete일 가능성이 매우 높다.

      class Foo
      {
      std::string s_;
      };

      Foo* f1 = new Foo;
      Foo* f2 = f1;

      delete f1;
      ...
      delete f2;


  4. Solution

    • Code review나 inspection을 통해 double delete에러를 찾는다.

    • Purify와 같은 runtime memory analysis 툴을 사용하여 디버깅한다.

    • Prevent와 같은 static code analysis 툴을 사용하여 code를 검사한다.



  5. Other Possible Causes
    • double delete외에 다음과 같이 memcpy에 의해서도 발생할 수 있다.

      struct POD
      {
      std::string s;
      };

      POD s, t;
      memcpy(&t, &s, sizeof(POD));




다른 부분은 그럭저럭 괜찮은데 Solution 부분이 마음에 들지 않네요. 물론 문제가 발생한 소스 코드도 없이 어디다라고 콕 찍어줄 순 없지만 너무 당연한 해결책들만 들어가 있는건 아닌지 모르겠네요. Solution 부분은 아예 없애고 Core Dump Pattern은 원인만 알려주는데 만족해야 하는 걸까요? :-|

혹시 비슷한 생각을 하신 분들이 계신지 궁금하네요. 다음 core dump가 발생하면 그때도 pattern을 찾을 수 있을지 확인해 봐야 겠습니다.

Comments

  1. 흥미있는 글이였습니다.

    최초에 생긴문제는 스마트 포인터로 해결이 가능하네요. boost::shared_ptr같은걸 사용하면 대부분의 경우에 방지가 가능하다고 생각합니다.

    5.Other Possible Causes 읽고 제가 사용하고 있는 매크로를 다음과 같이 고칠까 하다가 보류했습니다.

    // NO copy, No assign.
    #define SET_NO_COPYING(type) \
    type(const type&); \
    type& operator= (const type&);

    이런 매크로를 사용하고 있거든요.

    #define SET_NO_COPYING(type) \
    type(const type&); \
    type& operator= (const type&); \
    type* operator& ( void );

    이렇게 한줄을 추가하려고 했죠.

    사용은

    class Foo
    {
    private:
    SET_NO_COPYING( Foo );
    };

    와 같이 사용합니다. 근데 매크로에 한줄 추가로 이름이 적절치도 않고, 클래스의 포인터를 얻는게 원천적으로 금지되는터라 보류 상태입니다. 포인터를 얻어야 하면 타이핑을 따로 해야 하는 일이 생긴다는 귀찬음을 이기지 못할것 같습니다-_-;

    ReplyDelete
  2. 저희는 애시당초 pointer의 사용을 금지하다 시피한... ( '')
    무조건 shared_ptr을 이용하도록 강요한다는... ('' )

    delete가 사용되는 경우는 shared_ptr에 사용하는 삭제자를 제외하곤 거의 없답니다. :)

    ... 자랑인가요? -_-

    ReplyDelete
  3. @mkseo: :eek:


    @neux님: 매크로를 만들어서 사용하고 계시군요. 그런데 operator &를 private으로 만든다고 해도 다음과 같은 경우에는 compile-time 에러가 발생하지 않기 때문에 별로 도움이 안될듯 합니다.

    Foo *f1, *f2;
    memcpy(f1, f2, sizeof(Foo));

    그리고 아시겠지만 boost::noncopyable이 사용하고 계신 매크로와 같은 작업을 하죠.

    class Foo : boost::noncopyable { ... };

    memcpy를 좀 더 효과적으로 막을 방법이 있을지 좀 더 생각해봐야겠네요. :-)


    @까막: 크~ 자랑 맞네요. shared_ptr 좋죠. 저희는 공식적으로 외부 라이브러리를 가져다 사용하기가 어렵습니다. 그래서 제가 몰래 :roll: 파일 몇개만 체크인해서 사용하고 있는데 그 두개가 바로 shared_ptr와 BOOST_STATIC_ASSERT죠.

    개인적으로 하는 프로젝트들에는 모두 shared_ptr를 다음과 같은 naming convention을 써서 사용하고 있답니다.

    class Foo { ... };

    typedef boost::shared_ptr<Foo> FooSP;

    ReplyDelete

Post a Comment

Popular posts from this blog

1의 개수 세기

저도 간단한 알고리즘 문제 하나... :-)

어떤 수 n이 주어졌을때 1~n까지의 수를 쭈욱 썼을때 나오는 1의 개수를 구하는 문제입니다.

예를 들어 13이라는 수가 주어지면 1~13까지의 수 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13에서 1은 1, 10, 11, 12, 13에 나오며 그 개수는 6이 됩니다. 즉, f(13)=6.

원래 문제는 f(n)=n이 되는 1이 아닌 가장 작은 수를 구하는 문제인데 이 문제의 경우에는 처음부터 쭈욱 세어나가면 되기 때문에 간단히 다음과 같이 구현을 하면 됩니다. ((한가지 주의할 점은 이전에 찾았던 n-1값을 사용하지 않고 다시 처음부터 n까지 값을 계산하면 시간이 너무 많이 걸린다는 점입니다. 위의 코드에서는 static 변수를 사용하여 이전 값에 계속 더해나가는 방법을 사용했습니다.))


#include

int count1(int n)
{
static int cnt = 1; // not 0 because n starts from 2. see main.

while (n > 0) {
if ((n % 10) == 1) ++cnt;
n /= 10;
}

return cnt;
}

int main()
{
using namespace std;

int n = 2;

while (count1(n) != n) ++n;
cout << n << endl;
}


좀 재미가 없죠? 그래서 이번 문제는 어떤 수 n에 대해서 f(n)을 O(1)시간에 구하는 알고리즘을 만드는 것입니다. 관심있으신 분들은 한번 풀어보세요. 제가 만든 코드는 내일 올려보겠습니다.

C++ of the Day #9 - Boost.Python 사용하기 #1

Python은 가장 인기있는 interpret 언어중의 하나입니다. Python의 장점 중 하나는 C/C++ 모듈과 쉽게 연동할 수 있다는 점입니다. 물론 손으로 일일히 wrapper를 만드는 것은 손이 많이 가고 에러를 만들수 있는 작업이나 SWIG등과 같은 도구를 사용하면 쉽게 python 모듈을 만들 수 있습니다.

Boost.Python은 이런 SWIG와 같이 python 모듈을 쉽게 만들 수 있도록 도와주는 라이브러리로 순수 C++만을 사용한다는 점이 SWIG와 다른 점입니다. 그리고 개인적으로는 Boost 라이브러리에 포함되어 있는 것들이 왠지 좀 더 믿음직스러워서... :-)

이번 글에서는 Boost.Python 문서에 나와 있는 예제를 가지고 간단하게 python 모듈을 만드는 방법에 대해서 알아보겠습니다.

Requirements리눅스
이 글에서는 리눅스 환경에서의 사용 방법을 설명한다.Boost.Python 라이브러리 (1.33.1)
Boost 라이브러리를 다운로드받아 아래와 유사한 명령으로 라이브러리를 빌드한다.
bjam -sTOOLS=gcc -with-python install

bjam의 --prefix 옵션으로 라이브러리가 설치될 위치를 변경할 수 있다.Python 라이브러리 (2.4.3)
Python을 다운로드 받아 빌드하여 설치한다.
위의 경우와 유사하게 configure의 --prefix 옵션으로 설치될 위치를 변경할 수 있다.

Write C++ Code다음과 같이 코드를 작성한다.

// greet.cpp #include <stdexcept> char const* greet(unsigned x) { static char const* const msgs[] = { "hello", "Boost.Python", "world!" }; if (x > 2) throw std::range_error("greet: index out of range"…

Hello Wordpress, again.

한 두주일 정도 Textpattern을 사용해봤는데 다시 Wordpress로 돌아오기로 결정했습니다. 무엇보다 스킨 변경이 너무 복잡하고 사용자층이 Wordpress에 비해 너무 앏네요. 원하는 plugin도 찾기 어렵고... :-|

그동안 Textpattern에 썼던 글들은 모두 Wordpress로 옮겼습니다. 2개 있던 댓글도 옮겼는데 그중의 하난 제가 쓴... ;-)

애초에 wp-dokuwiki plugin이 무거워서 옮겼던 것이라 이 plugin은 설치를 안할 예정인데 몇가지 아쉬운 점이 있네요.

첫째는 code highlighting 기능인데 이 기능은 예전에 만들어 놨던 것을 조금 수정해서 쓰려고 준비중입니다. 두번째는 Footnote 기능인데 찾아보니 Footnotes 0.9 Plugin for WordPress 2.0.x라는게 있네요.

이정도면 비록 wiki syntax에 비할바는 아니지만 쓸만할 것 같습니다. :-)