2003년인가 2004년에 Visual Studio .NET 따위엔 아무도(적어도 게임업계에선) 관심 없었고 Visual C++ 6.0으로 개발하던 시절에 스택 프레임의 리턴 어드레스가 정상인지 판별하기 위해서 컴파일러가 해줄 수 있는게 없었다.
내가 다니던 회사의 C모 온라인 서버가 동접 1000만 넘어가면 죽어나갔는데 아무도 원인을 몰랐음.
디버거로 보면 IP(Instruction Pointer)가 엉뚱한 어드레스로 튀어있으니 리턴 어드레스에 잘못된 값이 들어간걸로 보이는데 대체 그게 어딘줄 알아.
하여간 회사에서 일단 서버 내리자고 결정한 상황인데 또 스트레스 테스트 환경은 구축이 안되어있는 상태로 베타 테스트 나간거라 서버를 내리면 해당 버그를 재현할 방법도 없었다.
것도 좀 상황이 웃겼던게 내가 게임팀에 소속되어있었으면 빨리 알았을텐데 난 엔진팀이었고 게임쪽 진행하고는 어느 정도 거리를 두고 있던 상태.
아니 애초에 스트레스 테스트 환경을 구축도 안하고 베타 테스트를 할 생각을 한 자체가…
몇시간만이라도 서버를 유지해달라고 요청하고 부랴부랴 스택 프레임 체크하는 코드를 작성했다.
인라인 어셈블리로 함수 진입할때마다 리턴어드레스 저장했다가 나갈때 리턴어드레스 멀쩡한지 확인해주는 코드를 만듦. 스택프레임을 건드리지 않기 위해서 __declspec(naked)에 레지스터와 전역 메모리만 사용해서 짰다.
그걸 게임 루프 여기저기에 박아넣고 디버그 브레이크(int 3)이 걸릴때마다 범위를 좁혀서 결국 서버 닫기 1시간 30분전에 원인을 찾음.
웃겼던건 결국 그 버그 잡은것도 나고… 사실은 그 버그가 생기도록 빌미를 만든것도 나였다.
내가 만들어준 네트워크 라이브러리에서 접속한 커넥션의 IP어드레스를 얻어오는 코드가 있었는데 때때로 이 녀석이 실패하는데 그 케이스를 내가 테스트할땐 겪어보지 못했고 에러 처리를 제대로 안했던것.
동접 1000이상이 되면(대략 그 타이밍에) 어떤 유져들의 연결에 대해선 IP어드레스를 얻을 수 없는 상황이 있었고 그 결과 ouput으로 주어진 문자열 버퍼에 null 터미네이터가 빠지는 경우가 생김.
네트워크 라이브러리를 갖다쓰는 비지니스코드에선 그 문자열 버퍼 가지고 strcpy를 호출했고-이 당시엔 strcpy_s는 없었다-그래서 스택을 오버런 해서 리턴 어드레스를 깨먹었다.
이후에는….당연히 말도 안되는 어드레스로 점프.
당시 컴파일러는 디버그든 릴리즈든 스택 오버런이 발생했는지 체크해주는 기능 따윈 없었다. 그 시절엔 MS도 IIS스택 오버런 버그 땜에 오피스 소스 코드 털렸던 사건이 있던 시절임. 보안코드란거 생소하던 시절.
요새 Visual Studio에선 오버런 방지를 위해 문자열 처리 함수들에 _s가 붙은 보안 함수들을 제공하고 이거 안쓰면 경고 뜸. 아니 vs2015에선 아예 빌드가 안되던가.
그리고 디버그 모드에선 스택 오버런을 기본적으로 체크해주게 되어있음. 리턴어드레스 및 스택에 잡혀있는 메모리가 손상됐는지 체크해준다. 뿐만 아니라 리턴 어드레스를 어셈 안쓰고도 intrinsic으로 얻어올 수 있음.
그때 스택 프레임 체크 코드 짜면서 어떤 생각을 했냐면…
‘아 내가 이 버그를 못잡으면 회사가 망하겠구나.’
결과적으로 회사는 망했는데..아 내가 만든 네트워크 라이브러리 버그 때문에 망한건가? ㅋㅋㅋ
그 때의 그 심장이 쫄깃해지는 느낌은 잊을수가 없어.