'Debugging'에 해당되는 글 2건

  1. 2009/09/03 김성민 미니덤프 확장
  2. 2009/09/03 김성민 [번역] 디버깅이 쉬운 코드 작성하기

미니덤프 확장

개발 2009/09/03 09:09 김성민
한달쯤전에 GPG 7을 받았습니다.

1.10 장에 눈에 띄는 내용이 있더군요.

스택 오버 플로 예외가 발생했을 때, 다른 스레드를 하나 생성해서 그쪽으로 스레드 핸들과 예외 포인터를 넘겨서 덤프를 기록한다는 내용이었습니다. 슈도 코드로 보자면 대충 아래와 같은 모양입니다.

typedef struct _DUMP_PARAMETER
{
  HANDLE              Thread;
  PEXCEPTION_POINTERS ExPtrs;
} DUMP_PARAMETER, *PDUMP_PARAMETER;

DWORD WINAPI WriteDump(LPVOID param)
{
  PDUMP_PARAMETER dumpParam = reinterpret_cast<PDUMP_PARAMETER>(param);
  ...
}

LONG WINAPI HandleException(PEXCEPTION_POINTERS exPtrs)
{
  if (exPtrs)
  {
      PDUMP_PARAMETER dumpParam = (PDUMP_PARAMETER)malloc(sizeof(DUMP_PARAMETER));
      dumpParam->Thread = GetCurrentThread();
      dumpParam->ExPtrs = exPtrs;
      if (exPtrs->ExceptionRecord->ExceptionCode != EXCEPTION_STACK_OVERFLOW)
      {
          WriteDump(dumpParam);
      }
      else // 스택 오버플로 발생 시에는 별도의 스레드를 생성해서 덤프를 기록한다.
      {
          HANDLE hThread = CreateThread(NULL, 102400, WriteDump, dumpParam, 0, NULL);
          WaitForSingleObject(hThread, INFINITE);
          CloseHandle(hThread);
      }
  }
  return EXCEPTION_EXECUTE_HANDLER;
}

void main()
{
  ...
  // 예외 처리 핸들러를 설정한다.
  SetUnhandledExceptionFilter(HandleException);
  ...
}


보다시피 그리 어려운 내용이 아니라 얼른 사용 중인 코드에다 저 로직을 집어넣었습니다. 하지만 실제로 스택 오버플로가 발생했을 때, 스레드 생성을 위한 스택은 남아있을까... 라는 생각에 미심쩍었는데 얼마 전에 요놈 덕분에 버그를 하나 잡았습니다. 깔깔깔~

또한 2주일쯤 전에는 Jochen Kalmbach라는 분의 블로그에서 SetUnhandledExceptionFilter 함수와 관련된 글을 읽었습니다. 자세한 내용은 여기 있는데 CRT 내부에서 뭔가 건드리기 때문에 예외가 발생해도 유저가 설치한 예외 핸들러가 호출되지 않을 수 있다는 이야기였습니다.

스택 오버플로도 그렇고, CRT 내부에서 삽질하는 것도 그렇고 그냥 프로세스가 사라지죠. 미니 덤프를 사용하시는 분이라면 저 2가지 내용을 처리해두시는 것이 도움이 될 겁니다.

다만 2번째 글의 함수는 뭔가 코드 영역에다 쓰기를 하는 함수라서 그런지 비스타에서 동작하지 않더군요. 뭐 상관없습니다. 게임 서버 비스타에서 돌릴 일 없으니까~ 음핫핫.
2009/09/03 09:09 2009/09/03 09:09
TAG
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/13

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/13


프로그래머들은 프로그램을 만들 때 다음과 같은 사항들을 생각한다.

  1. 좋은 기능을 지나치고 있지 않은가?
  2. 이 코드가 어떻게 수행되는가?
  3. 얼마나 읽기 쉬운 코드인가?
  4. 기타 등등

하지만 뛰어난 프로그래머들을 포함해 대부분의 프로그래머들은 코드를 작성할 때 디버깅의 용이성을 생각하지는 않는다. 이를 위한 몇 가지 팁이 아래에 있다.

  1. 역할을 설명하는 변수(explaining variable)들을 많이 사용하라. (http://c2.com/cgi/wiki?IntroduceExplainingVariable) 디버거 안에서 그 변수의 값을 쉽게 값을 치환할 수 있다. 다음 코드를 실행하기 전에 리턴값을 쉽게 체크할 수 있다.
  2. 덩치가 큰 여러 함수를 한 구문 안에서 호출하지 말라. 예를 들어 다음과 같은 형식이다. Func1(Func2(Func3())); 디버거 상에서 함수를 골라서 들어가려면 상당히 골치아프다. 리턴값을 체크하기도 어렵고, 리턴값을 변경하기도 어렵다.
  3. IfFailGo(Func()) 류의 구문을 사용하지 말라. IfFailGo는 매크로로서 다음과 같은 내용이다.
    #ifndef IfFailGoto
    #define IfFailGoto(EXPR, LABEL) \
    do { hr = (EXPR); if(FAILED(hr)) { goto LABEL; } } while (0);
    #endif

    #ifndef IfFailGo
    #define IfFailGo(EXPR) IfFailGoto(EXPR, ErrExit)
    #endif

    이 또한 리턴값을 체크하기 어렵다. 대신 아래와 같은 형식을 사용하라.

    hr = Func();
    IfFailGo(hr);
  4. 매크로보다는 인라인 함수를 사용하라. C++ 컴파일러는 매크로의 내용을 PDB에다 집어넣지 않는다. 그러므로 인라인 함수를 사용하면, 디버거에서 단계별로 실행하는 것이 가능하다.
  5. #define이나 GUID보다는 enum 값을 사용하라. enum 값의 경우에는 디버거가 사람이 읽을 수 있는 포맷으로 변환해주는 것이 가능하다. 하지만 #define을 사용한 경우에는 이 변환이 절대 안 되고, GUID는 가끔 될 뿐이다. 분명히 #define을 사용할 수 밖에 없는 때가 있기는 하다. 그러나 둘 다 가능한 경우라면 반드시 enum을 사용하라.
  6. void* 형식의 데이터를 받은 다음 캐스트해서 사용하지 말고, 애초에 원래 데이터 타입을 넘겨라. 원래 형식으로 사용하면, 디버거에서 쉽게 표시할 수 있기 때문이다.
2009/09/03 08:51 2009/09/03 08:51
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://serious-code.net/tc/rss/response/4

댓글+트랙백 ATOM :: http://serious-code.net/tc/atom/response/4