DllMain, lpReserved는 어디에 사용될까?

@codemaru · January 18, 2010 · 3 min read

지난 주에 프로그램 기능을 확장하면서 이상한 현상을 겪게 되었다. DLL을 확장한 별도의 모듈 타입을 만들어 쓰는데 그 녀석이 CRT만 연결하면 언로딩할 때 문제가 생기는 것이었다. 정확하게는 DLL을 FreeLibrary 할 때에는 문제가 없는데, 프로그램이 종료될 때 문제가 생겼다.

디버거를 돌려보니 프로그램이 종료되는데 이미 언로딩된 그 DLL의 메모리를 참조하는 것이 문제였다. 정확하게는 Fiber Callback을 호출하면서 문제가 생겼다. 왜 이럴까, 라는 생각을 하다가 여친님께서 납시셨다는 소식과 함께 디버깅 세션을 종료했다.

오늘 출근해서 디버깅을 하고 소스를 보고 하다가 엄청난 사실을 알게 되었다. 문제는 DllMain에 있는 lpReserved에 있었다. 우리가 만든 확장 DLL은 이 lpReserved를 사용해서 뭔가를 하도록 되어 있는데 VC++ CRT 소스에는 아래와 같은 엄청난 라인이 숨어 있었던 것이다. #ifdef _DEBUG가 아니라 #ifndef _DEBUG란 사실에 주의해야 한다. 즉, lpreserved를 비교하는 루틴은 릴리즈 버전에만 포함된다는 사실이다. 결국은 우리가 이 lpresreved로 NULL이 아닌 포인터를 공급했고, 그래서 CRT 정리 루틴이 타지 않았고, 프로세스 종료 시에 CRT가 싸놓은 Fiber Callback을 타면서 크래시가 난 것 이었다.

#ifndef _DEBUG
    if ( lpreserved == NULL )
    {
#endif  /* _DEBUG */
        /*
         * The process is NOT terminating so we must clean up...
         */
        /* Shut down lowio */
        _ioterm();

        _mtterm();

        /* This should be the last thing the C run-time does */
        _heap_term();   /* heap is now invalid! */
#ifndef _DEBUG
    }
#endif  /* _DEBUG */

나의 순진한 믿음, lpReserved는 말 그대로 예약된 것일 거라는 사실이 화근이 되었다. 크게는 남의 것을 함부로 건드린 죄라고 할 수 있겠다. 어쨌든 이제는 알게 되었지만 lpReserved는 아무 곳에도 사용되지 않는 값은 아니다. 디버깅 정보에 의하면 lpreserved는 보통 0이고, 특별하게 프로세스가 종료될 때 같이 DLL이 정리되는 상황에서는 1이 넘어 왔다.

 

@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중