😷 메모리패딩과 Heisenbug 추억담

Lua 써도 메모리 문제가 깜빡이도 켜지 않고 들어올 수 있다🚔

추억을 돌아보며

아마 이 기억은 2010년 전후였던거 같다. 당시 나는 작은 회사에 소속이었는데, 회사가 작을 때에 다같이 SI에 파견되었을 때에, 내가 S/W 솔루션 제품을 설계/개발한 것을 납품하며, 그 솔루션과 함께 SI구축을 하는 프로젝트들을 수주 받는 회사였었다.

원래는 그 SI프로젝트에서 ActiveX 정도를 몇 개 개발하고 덮었어도 될 것을, 솔루션화하고 납품처에 따라 쉽게 적용할 수 있도록 설계/개발을 사실상 나 혼자 했었었다. 자세한 기술적 사항을 모두 설명하지는 않겠지만, Windows API, C++, Lua, HTTP/1.0, JSON을 잘 조합해서 필요한 것을 만들어냈다. 지금 돌아봐도 훌륭한 창조력이었던거 같다.1

자기들이 가진 ‘분석툴’이 이해할 수 있도록 어째서 Visual Basic 6으로 짜지 않았느냐며 징징거리는, 그 자리에 앉으면 안될 책임자라던가 하는 괴상한 경험을 많이 했었었다.2 …이 포스팅의 이야기도 그런 경험에 대한 것이다.

문제

나는 처음 그 ‘SI프로젝트’를 우리 회사팀이 성공시켜서 고도화 계약까지 따내서 아예 그 사이트에 상주하며 지내고 있었다. 분당이었다.

그렇게 세월을 보내던 중에, ‘근처’ 수원쪽에 구축을 위해 혼자 파견된 직원에서 문제가 발생했다. 처음엔 같은 개발자였던 이가 갔었지만 제대로 해결해내지 못했다. 그는 스스로가 훌륭한 개발자라고 믿는, 하지만 역시나 주변에 “값싼 talk”만 해대는 사람들만 남기게 되는 그런 정도의 사람이었다. 그리고 “똑똑하다”는 평가를 받던 혼자 파견되었던 직원은 결국 견되다 못하고 도망가고야 말았다.3

책임을 내게 돌리기 위해서겠지만, 나를 끌어들였다. 아마 그는 실제로는 C++/윈도API/COM으로 만들어진걸 만져야 하면서, 자기가 뭘 하는지 잘 이해하지 못했을거 같다. 그는 개발자로선 당시 트렌디한 키워드인 OOP, Java, SOA 같은 것만이 진리라고 믿으며, 그렇지 않은 다이나믹언어나 네이티브언어, 로우레벨은 평가절하하던 적당히 당시 시류에 적합한 평범한 사람이었다.

문제는, 정상적으로 동작하다가 예상치 못한 상황에 ‘잘못된 연산’ 정도의 에러를 내고 프로그램이 종료된다는 것.

원래의 솔루션의 memory leak이나 access violation 등을 검사했었고, 또 동일한 바이너리가 다른 사이트에서는 아무런 문제가 없어서 더 골치가 아팠다.

특이한 현상: heisenbug

우선 mingw32 gdb을 붙이고 문제가 되는 영역을 좁혀나갔다. 그리고 해당 부근의 Lua에서 호출하는 Windows API, COM 호출을 하는 부분의 루아코드 앞뒤로 로깅을 추가해나갔다.4 로직은 Lua으로 스크립팅하는 방식이었기 때문이다.

더 골때리는 것은, 정상적인 루아코드가 에러가 있다면서 실행을 거부하기도 했다는 점.

그런데, 여기에서 재밌는 일이 일어났다. 루아코드를 변경할 때마다 조금씩 오류발생시점이 변했다.💣

그리고 흥미로운 일도 관찰했다: 어느 지점에 어떻게 접근하고 있었는지 알기 위해서 루아코드에 주석을 달아가며 디버깅상황을 marking을 해나갈 때마다 안정화되어갔다. 🦾

결국 관측(observe)하고 개입할수록 뭔가 문제의 양상이 변해갔다: 👉 Wikipedia: Heisenbug

분석과 매듭짓기

분석은, 해당 사이트에 특이한 ‘보안솔루션’ 때문이었다고 결론 내렸다. 보안솔루션이 커널모드으로 동작하며, 다른 프로세스들을 감시하기 위해 동작하면서 이상하게도 메모리영역에 write을 했기 때문일 것 같다. 추측은 단순히 데이터버퍼(data segment)으로 인식하고 루아코드 영역에 어떤 패턴을 주입하고 이를 감시하거나 했을거 같다.

그래서 루아코드를 키우거나 불필요한 주석영역을 늘여서 메모리에 영역을 더 할당 받고, 그 data영역의 앞뒤로 조금 corrupted되어도 무시될 수 있는 주석영역을 늘려줄 수록 안정화되었으리라.

그렇게 해당 사이트의 다른 엔지니어에게 설명하고, 주석을 추가하고 늘리면서 안정하게 동작하는 상태로 만들고, 앞으로 변경이 있을 때에도 그렇게 heuristic하게 체크하며 진행하라고 당부하고 마무리지었다.

돌아보며

그 이후에 그 이슈가 다시 내게 오는 일은 없었다. 다행이라고 생각한다. 그리고 그 납품처는 이름만 대면 그때나 지금이나 대한민국 산업의 가장 중요하다고 할만한 곳이어서, 지금은 당연히 그런 문제가 더 개선되었으리라 생각한다.

실은 제목과 달리, 정확하게 컴파일러가 말하는 ‘memory padding’은 아닐 것이다. 컴파일러가 말하는 메모리패딩은 메모리정렬을 통해 특정 CPU/아키텍처가 빠르게 접근할 수 있도록 구조체의 각 필드를 메모리주소에 정렬/지정하는 방식이니까. …하지만 안정성을 위해, memory corruption을 대비해서 안전영역을 앞뒤로 더 붙여준다는 패딩의 의미로 사용했으니 이해 바란다.

정석적인 상황이라면, 이런 문제가 발생한다면 offender측에 문제를 보고하고 해결을 바라는게 맞겠지만, 그러기 정말 어려운 상황이었다. 왜냐하면 그 보안솔루션은 그룹사 전체에 동일하게 적용하는 정책이었고, 그런 문제를 분석과 증거를 수집하여 전달한다 한들 그게 고쳐지는 시간도 물론이거니와 다른데에선 별 문제가 없는데 거기만 그러냐면 문제를 바로는 인정하지 않을 가능성도 높을테니까.

결국 운이 좋았던 것 같다. ㅎㅎ짧게 말한다면.

그리고 다른 이들은 이런 이야기의 대부분은 바로는 이해하거나 직관하기는 어려울 것 같다. 이슈의 문맥과 배경지식이 더 필요하리라 싶다.

Footnotes


1

물론 나중에 조인한 사람들, 특히 스스로의 입지를 넓히거나 하고 싶던 사람들, 혹은 기존의 사람들도 마찬가지였던 사람들은 비평하기 바빴지만, 언제나 세상은 그런거 같다. “Talk is cheap”, 하지만 그렇다고 뭔가 발전시키거나 하지도 못하거나 그럴 생각도 없을 사람들이지만.

2

원래 이런 그 자리에 앉으면 안될 분들은, 남들이 좋은 성과를 내고 성공시켜서 그 자리에 가도 그게 고마운줄 이해하지 못한다. 그리고 나아가서 이런식으로 스스로가 발 디딘 곳을 파내지 않고서는 못베기는 것 같다.

3

그도 그럴 것이, 함수형 프로그래밍이나 고수준의 추상화 같은 무언가만 자기 일이라고 꿈꾸며 살다가, 막상 해야 하는 일은 기계나 어셈블리, 메모리, OS에 대한 조금 당사자에겐 낯선 것들을 겪어야 했을테니까. cmd.exe 열고 gdb 접속하고 하는게 그렇게 화려해 보이기도 어렵고 말이다.

4

(기억이 가물가물하지만 아마도) OutputDebugString