Skip to main content

Core Dump Pattern 두번째

Code Dump Pattern에 대해서는 지난 번 글에서 얘기한 바 있습니다. 한동안 잠잠하다가 다시 core dump가 발생했는데 이번에도 제가 담당하는 부분이 아니어서 보지 않고 있다가 다른 분이 pstack을 구해 공지한 것을 보니 대략 원인이 보이더군요.

그럼 먼저 증상을 보여 드릴테니 원인을 찾아보세요. :-)

먼저 log 파일에는 pure virtual function called라는 문장이 출력되었으며 해당 pstack은 다음과 같았습니다.


Thread 127 (process 18441):
...
#17 0x01089514 in std::terminate () from /usr/lib/libstdc++.so.5
#18 0x01089a17 in __cxa_pure_virtual () from /usr/lib/libstdc++.so.5
#19 0x08ebc0d9 in DpOMidCallActiveInitial::process (this=0x555ff748,
bcsm=0x89faec1c)
#20 0x08e2bd77 in Bcsm (this=0x89faec1c,
bcsmState=_DpOMidCallActiveInitial, legId=1,
destinationRoutingAddress=0x0, cs=0x8f88aa14,
isArmedTDPExist=true)
#21 0x08e50e51 in BcsmKo (this=0x89faec1c, st=_DpOMidCallActiveInitial,
lt=1, ds=0x0, cs=0x8f88aa14)
#22 0x081eda42 in NPFactory::makeNewBcsm (st=_DpOMidCallActiveInitial,
lt=1, ds=0x0, cs=0x8f88aa14, isArmedTDBExist=true)
...


.

찾으셨나요?

사실 몇주 전에 pure virtual function called 에러에 관한 글을 쓰려고 한적이 있었는데 마침 똑같은 주제의 글이 The C++ Source에 올라오더군요. 여기의 글을 읽으시는 분들이면 대부분 구독하고 있는 사이트일 것 같아 쓰지 않기로 했었는데 이번에 이 내용의 core dump가 발생했네요.

이 에러의 원인에 대해 자세히 알고 싶으신 분들은 위의 링크를 참고하세요. 이해하기 쉽게 그림까지 곁들여 설명해 주고 있습니다. ((예전에 썼던 Study About Double Delete글에도 비슷한 내용이 있었습니다.))

이번에 발생한 문제는 위 글의 Two of the Classic Blunders 부분에 나와 있는 내용입니다. 그리고 Effective C++ 3rd Ed.의 Item 9: "Never call virtual functions during construction or destruction."에서도 설명하고 있는 내용이죠. 바로 클래스의 생성자에서 virtual function을 호출한 경우입니다. 다만 직접 호출한 것이 아니라 다른 함수를 한단계 거쳐 호출한 것이라 컴파일러가 warning을 발생시키지 않았습니다.

위의 pstack을 보면 BcsmKo는 Bcsm을 상속받은 클래스로 BcsmKo가 new되면서 먼저 Bcsm의 생성자가 호출되고 있습니다. 여기서 DpOMidCallActiveInitial 클래스의 process()라는 함수를 호출하면서 생성중인 자신의 this (Base*)를 넘기고 있습니다. 이 process()함수에서는 이 this (Base*)를 사용하여 Base 클래스의 어떤 함수를 호출하는데 이 함수가 바로 pure virtual function이었습니다.

원래 있었던 코드였는데 새로운 기능이 추가되기 전까진 한번도 실행되지는 않던 코드의 조합이었다고 하더군요. 에러가 발생한 원인을 설명해주자 관련 개발자들이 모여 얘기하는걸 들었는데 아마 다른 조합도 있을 것 같다며 괴로워하더군요. :-|

이번에도 지난번 글에서 만들었던 패턴의 형식으로 만들어 보았습니다.


  1. Name
    • Core on Constructor with "pure virtual function called" message


  2. Problem
    • pstack에서 생성자가 보이며 "pure virtual function called"라는 메시지가 출력된다.

      Thread 127 (process 18441):
      ...
      #17 0x01089514 in std::terminate () from /usr/lib/libstdc++.so.5
      #18 0x01089a17 in __cxa_pure_virtual () from /usr/lib/libstdc++.so.5
      #19 0x08ebc0d9 in bar(base=0x89faec1c)
      #20 0x08e2bd77 in Base (this=0x89faec1c)
      #21 0x08e50e51 in Derived (this=0x89faec1c)
      ...


  3. Cause
    • 원인은 다음과 같이 생성자에서 pure virtual function을 호출했을 가능성이 매우 높다.

      void bar(Base* );

      struct Base
      {
      Base() {
      bar(this);
      }
      virtual ~Base() {}
      virtual void foo() = 0;
      };

      struct Derived : Base
      {
      virtual ~Derived();
      };

      void bar(Base* b)
      {
      b->foo(); // oops! this is pure virtual function.
      }

      Base* obj = new Derived; // core dump


  4. Solution

    • 생성자에서 해당 pure virtual function을 호출하지 않아도 되는 방법이 있다면 그 방법을 사용한다.

    • 생성자외에 init()와 같은 별도의 초기화 함수를 만들어 사용한다. 이 방법으로 위의 코드를 수정해 보면 다음과 같다.

      struct Base
      {
      void init() {
      bar(this);
      do_init();
      }
      virtual ~Base() {}
      virtual void foo() = 0;
      virtual void do_init() {}
      };

      Base* obj = new Derived;
      obj->init();
      ((여기서 do_init()은 C++ of the Day #39 - State with NVI (or Template Method)에서 설명했던 NVI 함수입니다. 이렇게 함으로써 Base를 상속받는 클래스는 자신을 위한 초기화 내용을 추가할 수 있습니다.))



  5. Other Possible Causes
    • 없음




패턴으로 만들고 보니 "pstack에 생성자가 보이고 pure virual function called 에러가 발생한다"라는 원인에 해답이 다 있군요. 다음 패턴을 위해 core dump가 더 자주 발생하길 바래야 하는걸까요? ;-)

Comments

  1. 비슷한 주제의 글을 만나게 되어 반갑습니다. 저도 http://www.buggymind.com/84 여기에 같은 문제에 대한 글을 올렸습니다만... 트랙백남길까 하다가 못찾아서 덧글 남기고 갑니다. 좋은 하루 되세요 ^^

    ReplyDelete

Post a Comment

Popular posts from this blog

1의 개수 세기 - 해답

벌써 어제 말한 내일이 되었는데 답을 주신 분이 아무도 없어서 좀 뻘쭘하네요. :-P 그리고 어제 문제에 O(1)이라고 적었는데 엄밀히 얘기하자면 O(log 10 n)이라고 적었어야 했네요. 죄송합니다. ... 문제를 잠시 생각해보면 1~n까지의 수들 중 1의 개수를 얻기 위해서는 해당 숫자 n의 각 자리의 1의 개수가 모두 몇개나 될지를 구해서 더하면 된다는 사실을 알 수 있습니다. 예를 들어 13이라는 수를 생각해 보면 1~13까지의 수에서 1의 자리에는 1이 모두 몇개나 되는지와 10의 자리에는 모두 몇개나 되는지를 구해 이 값을 더하면 됩니다. 먼저 1의 자리를 생각해 보면 1, 11의 두 개가 있으며 10의 자리의 경우, 10, 11, 12, 13의 네 개가 있습니다. 따라서 2+4=6이라는 값을 구할 수 있습니다. 이번엔 234라는 수에서 10의 자리를 예로 들어 살펴 보겠습니다. 1~234라는 수들 중 10의 자리에 1이 들어가는 수는 10, 11, ..., 19, 110, 111, ... 119, 210, 211, ..., 219들로 모두 30개가 있음을 알 수 있습니다. 이 규칙들을 보면 해당 자리수의 1의 개수를 구하는 공식을 만들 수 있습니다. 234의 10의 자리에 해당하는 1의 개수는 ((234/100)+1)*10이 됩니다. 여기서 +1은 해당 자리수의 수가 0이 아닌 경우에만 더해집니다. 예를 들어 204라면 ((204/100)+0)*10으로 30개가 아닌 20개가 됩니다. 이런 방식으로 234의 각 자리수의 1의 개수를 구하면 1의 자리에 해당하는 1의 개수는 ((234/10)+1)*1=24개가 되고 100의 자리에 해당하는 개수는 ((234/1000)+1)*100=100이 됩니다. 이들 세 수를 모두 합하면 24+30+100=154개가 됩니다. 한가지 추가로 생각해야 할 점은 제일 큰 자리의 수가 1인 경우 위의 공식이 아닌 다른 공식이 필요하다는 점입니다. 예를 들어 123에서 100의 자리에 해당하는 1의 개수는 ((123/1

std::map에 insert하기

얼마전 회사 동료가 refactoring한 코드를 열심히 revert하고 있어서 물어보니 다음과 같은 문제였습니다. 원래 코드와 refactoring한 코드는 다음과 같더군요. nvp[name] = value; // original code nvp.insert(make_pair(name, value)); // refactored 아시겠지만 위의 두 라인은 전혀 다른 기능을 하죠. C++03에 보면 각각 다음과 같이 설명되어 있습니다. 23.1.2/7 Associative containers a_uniq.insert(t): pair<iterator, bool> inserts t if and only if there is no element in the container with key equivalent to the key of t. The bool component of the returned pair indicates whether the insertion takes place and the iterator component of the pair points to the element with key equivalent to the key of t. 23.3.1.2/1 map element access [lib.map.access] T& operator[](const key_type& x); Returns: (*((insert(make_pair(x, T()))).first)).second. 원래 코드는 매번 새 값으로 이전 값을 overwrite했지만 새 코드는 이전에 키가 존재하면 새값으로 overwrite하지 않습니다. 따라서 원래 기능이 제대로 동작하지 않게 된것이죠. 그래서 물어봤죠. "왜 이렇게 했어?" "insert가 성능이 더 좋다 그래서 했지." :-? 사실 Fowler 아저씨는 Refactoring 책에서 refactoring은 성능을 optimizing하기 위한 것이 아니다라

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("