최근에 XIGNCODE3를 리눅스 게임 서버에 적용한 적이 있다. 뭐 리눅스 게임 서버야 여러 군데 적용을 했었는데 그 곳이 조금 특별했던 건 자바를 사용한다는 점이었다. 학부 때 자바 실험한 기억을 떠올리며 회사에 비치된 자바 책을 참고하여 난 뚝딱뚝딱 자바 인터페이스를 만들었다. 뭐 별로 어렵진 않았다. 그리고 적용도 잘 됐다. 적용하고 나서 한 가지 문제가 발생했다. 해당 게임 서버에 XIGNCODE3 기능을 ON/OFF 시키는 걸 만들면서 시작된 문제였는데 XIGNCODE3를 OFF 상태로 시작했다가 ON을 시키면 크래시가 난다고 했다. 자바 크래시 로그를 같이 보내 왔는데 XIGNCODE3 so 파일에서 다른 boost 공유 라이브러리에 있는 심벌에 바인딩되면서 오류가 발생했다.
한동안 이 문제는 나를 굉장히 골치 아프게 만들었다. 그도 그럴것이 해당 게임 서버에서만 발생하는 문제이기 때문에 우리 쪽에서 재현하기가 쉽지 않았다. 모든 것을 동일하게 구축하고 해당 boost so 파일을 먼저 로드하고 고갱님 환경과 동일해 보이도록 만들고는 테스트를 해도 우리 쪽에서는 재현되지 않았다. 그래서 빌드할 때마다 고갱님께 테스트를 부탁 드려야 했기에 참 쉽지 않았다. 나와 같은 문제로 고통받는 개발자가 분명히 있을거란 생각에 내가 멘붕을 탈출했던 경로를 여기에다 조금 끄적여 본다.
#0. ABI가 뭔가요?
ABI라고 하면 뭔가 거창하고 있어보이는데 전산 약어들이 다 그렇듯이 아무거도 아닌 용어다. application binary interface의 약자로 바이너리 단계에서의 인터페이스를 의미한다고 보면 되겠다. 구조체가 메모라상에 실제로 어떻게 존재하는지 호출 규약이 어떻게 되는지를 나타내는 말이라고 생각하면 된다. 이렇게 말해도 참 어려운데 윈도우 프로그래머들이 더 쉽게 이해할 수 있게 말하자면 dll 버전 업이 되었을 때, 해당 dll을 사용하는 프로그램을 새로 컴파일할 필요없이 dll만 교체해서 실행이 되면 ABI 호환성을 지원한다고 하고, 재컴파일이 필요하다면 ABI 호환성을 지원하지 않는다고 말한다.
어쨌든 장황하게 설명했는데 boost는 ABI 호환성을 제공하지 않는다. 버전 업이 되면 새로 컴파일을 해야 한다. 이건 여담이지만 여러분이 API 세트를 디자인해보면 알겠지만 ABI 호환성을 제공하는 것이 생각처럼 녹록한 일은 아니다.
#1. ELF는 또 뭐죠?
ELF는 리눅스의 실행 파일 포맷이다. 난 이 포맷에 대해서 자세한 정보를 알진 못하는데 이번 사례를 통해서 아주 심각한 한가지 문제에 대해서 알게 되었다. 윈도우 dll은 export 시킨 심볼에 대해서만 외부로 노출시킨다. 근데 이 망할 ELF는 별도 설정을 하지 않으면 내가 가진 모든 심벌이 외부로 그대로 노출된다. 도대체 왜 이런지 모르겠다. 더 욱긴건 난 우리 so 파일에 boost를 정적으로 링크 시켰음에도 이 망할 시스템은 고객님의 게임 서버에 있는 다른 boost so 파일에 있는 심벌로 바인딩을 시도한다는 점이었다. 물론 그 다른 boost so 파일이 올라오기 전에 우리가 먼저 로딩되면 우리꺼를 쓴다. 근데 OFF로 시작해서 그 다른 버전의 boost so파일이 올라오고 우리 so 파일을 로딩하면 우리 so 파일 안에 정적으로 링크된 boost 심벌을 쓰는 것이 아니라 다른 버전의 boost so 파일에 있는 동일한 이름의 심벌에 바인딩을 한다는 점이었다. 그러니 크래시가 났다. ABI가 다르기 때문이다.
ELF에 심벌 노출 정책을 설정하는 컴파일러 플래그들이 있는데 그걸 통해서 해당 심벌이 노출되지 않도록 설정해도 제대로 적용이 되질 않았다. 그 망할 문제가 생기는 심벌은 항상 공유됐다. 희한했다. 궁극의 ms-compatible 따위의 기능이 gcc 있었는데, 우리가 사용하는 버전에서는 지원되지 않았다. 그래서 더 골치 아팠다.
#2. 똑같은 boost를 쓴다면?
온갖 컴파일 플래그 설정으로 문제를 해결하려 했지만 제대로 되지 않았기에 난 멘붕에 빠졌다. 그러다 결국 비굴하게 그냥 고객님께서 사용하시는 boost 최신 버전으로 우리 꺼를 컴파일하기로 했다. 그러면 ABI가 호환되니까 크래시는 발생하지 않을 것이라 생각한 것이다. 그렇게 온갖 것들을 다 바꿔서 새로 컴파일을 해서 테스트를 했는데 고객님께서 크래시가 발생하지 않는다고 했다. 근데 더 이상한 알 수 없는 온갖 문제들이 발생했다. 흙~
#3. 심벌 이름 변경하기
이 단계에서 이제 내가 선택할 수 있는 방법은 많은 선지자들이 갔던 길을 따르는 수 밖에는 없다. 심벌 이름이 바뀌도록 boost 소스 코드를 고치는 방법이었다. C++의 경우 이름 장식에 namespace가 포함되기 때문에 이 경우에 boost 네임스페이스 명을 boost2와 같이 변경하기만해도 심벌 이름이 모두 바뀌는 효과를 얻을 수 있다. 당연히 난 처음에 boost 라이브러리에서 ABI 호환성을 제공하지 않으니 이런 기능은 아주 자연스럽게 config.hpp 따위의 #define BOOST_NAMESPACE 값만 변경하면 싹 바뀌어서 컴파일이 될 줄 알았는데 그리 간단한 문제가 아니었다.
똑똑한 우리 선배 개발자들은 파이썬 스크립트를 사용해서 네임스페이스를 변경하는 기능을 이미 만들어 두었다.
http://lists.boost.org/boost-build/2009/05/21911.php
나는 그냥 그걸 다운받아서 쓰기만 하면 되는 줄 알았다. 하지만 세상일이 모두 그리 간단하지만은 않다. 스크립트로 모든 네임스페이스를 변경하고는 boost를 컴파일 시키는데 당연한 이야기겠지만 제대로 동작하지 않았다. 멘붕, 멘붕, 멘붕… 난 어뜩하지?… 고객님 환경의 특성상 우리 라이브러리를 제일 처음 일단 로드만 시켜주세요. 이럴수는 없지 않겠는가? 핡~
이제 난 어떡해야 하는가? boost 그 많은 파일들을 찾아서 모든 네임스페이스를 일일이 손으로 고쳐야 할까? 그 작업만 하는데도 올해가 다 지날지도 모른다. 그 전에 그 지겨운 일을 하다 내가 개발자를 그만둘지도 모른다. 그 때 생각났다. 그래 #define boost boost2 따위로는 안될까? 근데 config.hpp 처럼 모든 boost 파일이 공통으로 참고하는 헤더 따위를 알 수가 없었다. 그 때 떠올랐다. 그럼 gcc에 시키지 머. 그렇다. 이런 마법 같은 커맨드가 존재했던 것이다.
- bjam cxxflags=-Dboost=boost2
bjam cxxflags=-Dboost=boost2
저렇게 할 경우에 include 경로도 모두 변경되기 때문에 boost 폴더에 있는 것을 boost2 폴더로 모두 복사한 다음에 컴파일을 시켜야 한다. 그렇게 하면 우리의 boost는 네임스페이스가 boost2로 변경돼서 멋지게 빌드된다. 당연한 이야기겠지만 이 경우에 boost를 사용하는 우리 모듈의 makefile에도 -Dboost=boost2를 넣어서 빌드를 해야한다.
두둥 이렇게 네임스페이스를 변경했다. 그리고 고갱님께 한땀한땀 빌드한 so 파일을 제공하고는 테스트를 부탁한다. 그랬더뉘 마법같이 된단다. 살았다. boost 산을 넘었다.
0 0