TLS, DLL, 그리고 Legacy Windows

오늘 같은 팀 선배가 당한 일 하나.

이미 2년 정도 클라이언트 쪽에서 사용할 라이브러리를 작성하는데, 이 라이브러리를 사용하는 다른 프로젝트 k개는 잘 동작하는 상황. 근데 이걸 최근에 적용한 다른 팀에서 이런 보고가 들어왔다

“예제로 주신 프로그램은 잘 동작하는데, 저희 프로그램에서 사용하면 바로 크래시 합니다”

?!

제목을 보고 유추했을 지도 모르지만 – 그리고 클라이언트 라이브러리 문제란 점에서 감을 잡았다면 더더욱 추측할 수 있을 듯 – 다음과 같이 잘 알려진 문제다. 1 요약 하자면 이런 경우다.

  • __declspec(thread) 형태의 thread local storage를 쓰는 라이브러리
  • 라이브러리를 DLL 형태로 사용하고, 이를 동적으로(dynamically) 로딩2
  • Windows XP, Windows Server 2003 같은 legacy OS 환경

내가 예전에 Win32 TLS + DLL에 당했던 프로그래밍한 경험을 적어놓기도 했는데, 이 상황에서 __declspec(thread) 로 선언한 변수에 접근하면 메모리 접근 오류(General Protection Error)를 겪게 된다. 팀에서 흔히 개발 환경으로 쓰는 Windows 7, Windows Server 2008 혹은 Windows XP라면 별 문제 없겠지만 – 이 경우엔 별 문제없이 동작함 – 실제로 클라이언트 라이브러리라면 Windows XP 같은 환경도 겪게 되니 문제인 것…

그래서 팀 선배는 declspec(thread)로 선언한 묵시적인 TLS를 사용하는 방법을 폐기하고, 다른 방법으로 우회해서 해결했다고 한다 – MSDN에 나온 이런 방법을 쓰거나, 아예 별도 공간(미리 정한 최대 스레드 수 만큼의 길이를 갖는 배열이라거나)을 쓰는 방법을 사용하면 됨.

하여튼, 서버 프로그램처럼 실행 환경을 완벽히 제어할 수 없다면, Windows XP가 지금의 Windows 98처럼 퇴출된 legacy OS가 될 때 까지는, 클라이언트 라이브러리에서 dll 동적 로딩 + implicit TLS 조합은 좀 피해야 할 듯…


  1. The Old New Things에서 최근에도 글이 있고, 여기서 인용한 이 글 – Windows Server 2003(혹은 Windows XP) 이전의 OS의 TLS관련 로더 구현과, Windows Vista 혹은 그 이후의 OS(Windows Server 2008, Windows 7)의 구현 차이를 좀 더 상세히 찾아볼 수 있다. ↩︎

  2. 컴파일 타임에 묵시적으로 링크되는 게 아니라, LoadLibrary() 같은 함수로 명시적으로, 실행 시간에 로딩하는 경우. ↩︎