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...

CodeHighlighter plugin test page.

This post is for testing CodeHighlighter plugin which uses GeSHi as a fontifier engine. ((Those code blocks are acquired from Google Code Search .)) ((For more supported languages, go CodeHighlighter plugin or GeSHi homepage.)) C++ (<pre lang="cpp" lineno="1">) class nsScannerBufferList { public: /** * Buffer objects are directly followed by a data segment. The start * of the data segment is determined by increment the |this| pointer * by 1 unit. */ class Buffer : public PRCList { public: Buffer() { ++index_; } PHP (<pre lang="php" lineno="4">) for ($i = 0; $i $value = ord( $utf8_string[ $i ] ); if ( $value < 128 ) { // ASCII $unicode .= chr($value); } else { if ( count( $values ) == 0 ) { $num_octets = ( $value } $values[] = $value; Lisp (<pre lang="lisp">) ;;; Assignment (define-caller-pattern setq ((:star var fo...

C++ of the Day #43 - SQLite3 C++ wrapper #1

The Definitive Guide to SQLite 를 읽다가 공부 겸 해서 C++ wrapper를 만들어 보았습니다. 최대한 C++ 냄새(?)가 나도록 만들어 보았습니다. :-) ((SQLite는 복잡한 관리가 필요없이 사용가능한, 파일이나 메모리 기반의, 라이브러리로 제공되는, 약 250kb 용량의, 대부분의 SQL92문을 지원하는, open source RDB입니다.)) 이 wrapper를 사용하기 위해서는 (당연하게도!) sqlite3 와 (당연하게도?) boost 라이브러리가 필요합니다. 사용 예들을 살펴보는 것으로 설명을 대신합니다. 이번 글에서는 다음과 같은 contacts 테이블이 test.db에 존재한다고 가정합니다. CREATE TABLE contacts ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, phone TEXT NOT NULL, UNIQUE(name, phone) ); Command 먼저 test.db 파일을 사용하기 위해 다음과 같이 파일 이름을 주어 connection 객체를 생성합니다. 생성과 동시에 test.db와 연결이 이루어집니다. ((생성자외에 open() 함수를 사용할 수도 있습니다.)) sqlite3pp::connection conn("test.db"); 다음은 contacts 테이블에 정보를 추가하는 가장 간단한 방법입니다. connection 클래스에서 제공하는 execute 함수를 사용합니다. ((executef 함수를 사용하면 printf와 같은 문법을 사용하여 query문을 작성할 수 있습니다.)) conn.execute("INSERT INTO contacts (name, phone) VALUES ('user', '1234')"); 위와 동일한 작업을 parameterized query를 사용하여 할 수도 있습니다. ((step()함수가 실제 query문을 수행하는 함수입니다. ...