Infidel goes wild
Infidel에서 발견한 메모리 손상 버그
텍스트 어드벤처 Infidel의 ZIL 코드에서 선택적 인수의 기본값으로 전역 변수를 지정한 부분이 컴파일 오류를 일으킵니다. 사막에 물건을 여러 개 버리면 메모리 주소 30번대부터 덮어쓰고, 결국 문자열 압축용 약어 데이터까지 손상됩니다.
- 주제
AI 요약
Infocom의 텍스트 어드벤처 Infidel에는 게임을 진행하는 동안 드러나기 어려운 메모리 손상 버그가 있습니다. 개발자는 메모리 디버거로 상태를 살피다 문제를 발견했습니다. 원인은 사막에서 물건을 임시 보관하는 ZIL 루틴과 컴파일러의 선택적 인수 처리 방식입니다.
끝없이 이어지는 사막의 물건 관리
Infidel의 사막에는 지도에 표시된 3×3 구역 바깥으로 나가면 계속 같은 ENDLESS-DESERT 방이 나타납니다. 게임은 위도와 경도를 따로 기록해 실제로는 서로 다른 장소처럼 보이게 합니다. 이곳에 물건을 떨어뜨린 뒤 이동하면 게임은 물건을 방에서 치우고 DESERT-TABLE 배열에 좌표와 물건 ID를 저장합니다. 나중에 같은 좌표로 돌아오면 배열에서 물건을 꺼내 다시 방에 놓습니다.
물건을 떨어뜨릴 때마다 3분의 1 확률로 모래에 묻혀 사라지는 요소도 있습니다. 이 때문에 버그를 재현하기가 더 어렵습니다. 글에서 분석한 Macintosh판 릴리스 22, 시리얼 840522에는 이 배열에 100개 값, 즉 물건 50개분의 공간이 있습니다. 게임의 휴대 가능 물건은 전부 합쳐도 약 45개라 배열 크기 자체는 충분해 보입니다.
전역 변수를 상수로 처리한 컴파일러
DESERT-TO-TABLE 루틴은 인수 SLOC을 받고, 선택적 인수 TBL을 DESERT-TABLE로 초기화하도록 작성됐습니다. 하지만 게임은 이 루틴을 호출할 때 SLOC만 전달합니다. 글쓴이가 게임 메모리 화면에서 DESERT-TABLE을 확인했을 때 배열은 비어 있었습니다. 물건은 사라졌다가 다시 나타나는데 배열에 기록되지 않는 이유를 찾으려고 게임 파일을 역어셈블했습니다.
역어셈블 결과, TBL을 DESERT-TABLE 주소 11129로 초기화하는 명령이 없었습니다. Z-machine 함수 헤더에는 지역 변수의 초기값을 넣는 슬롯이 있고, TBL에는 16진수 1e, 즉 30이 들어 있었습니다. DESERT-TABLE은 상수가 아니라 전역 변수입니다. ZIL 컴파일러는 전역 변수의 값 대신 전역 변수 번호를 기본값으로 넣었고, 이 변수 번호가 30이었습니다. 그 결과 루틴은 주소 11129의 배열이 아니라 메모리 주소 30부터 데이터를 기록했습니다.
TABLE-TO-DESERT 루틴도 같은 방식으로 기본 인수를 처리합니다. 그래서 물건을 다시 꺼낼 때도 주소 30에서 읽어 겉으로는 정상적으로 작동합니다. 하지만 그동안 메모리는 계속 덮어쓰입니다. Z-machine 버전 3에서는 주소 0~29의 헤더 데이터만 의미가 있어, 이 버그는 우연히 더 눈에 띄는 데이터를 건드리지 않았습니다. 만약 DESERT-TABLE이 세 번째 전역 변수였다면 게임 시리얼 번호를 손상했을 수 있습니다.
문자열 약어가 바뀌고 인터프리터가 멈추기도 합니다
주소 64부터는 문자열 압축에 쓰는 약어 데이터가 시작됩니다. 여러 물건을 사막에 버리면 메모리 기록이 이 영역까지 침범합니다. 글쓴이는 물건 아홉 개를 버린 뒤 이동해 약어 데이터 첫 부분이 덮이는 현상을 확인했습니다. 그 뒤 출력된 문장에서는 원래 ‘the’로 표시되던 단어가 ‘You’로 바뀌었습니다. 메모리의 문자열 데이터가 손상된 결과입니다. 다른 시험에서는 인터프리터가 멈추기도 했습니다.
Infidel의 1984년 개정판도 이 버그를 고치지 않았습니다. 글쓴이는 게임을 많이 플레이해도 알아채기 어려웠고, 사막 구석에 물건을 여덟 개 넘게 버릴 이유도 거의 없어 플레이테스터가 발견하지 못했을 가능성이 크다고 설명합니다. 수정 방법은 TBL의 기본 인수를 없애고 루틴 안에서 DESERT-TABLE을 대입하거나, 호출할 때 DESERT-TABLE을 두 번째 인수로 전달하는 것입니다.
비슷한 형태의 오류는 PROP-TO-TBL과 TBL-TO-PROP 루틴에도 있습니다. 이 루틴은 모래에 판 구덩이 정보를 다루는 듯하지만, 게임에서는 ENDLESS-DESERT에서 땅을 팔 수 없습니다. 개발 중 빠진 기능의 흔적인지, 메모리 손상 때문에 제거한 기능인지는 글에서 확인하지 못했다고 밝혔습니다.
Hacker News 반응
- @jdw64 — 나도 이런 기술적으로 흥미로운 글을 쓰고 싶지만 어떻게 해야 할지 모르겠습니다. 나는 주로 여러 인터넷 게시물을 따라가며 역사를 추적하는 글을 씁니다. 개인 경험을 바탕으로 흥미로운 글은 어떻게 만드나요?
- @ajkjk — 먼저 그런 경험을 하거나, 아니면 자기 경험을 하세요. 남의 경험처럼 할 필요는 없습니다. 다른 사람도 관심을 가질 것 같다면 그 경험을 글로 쓰면 됩니다.
- @zem — 요즘 Reddit이나 HN, Lobsters에서 본 이야기가 내 경력에서 겪은 흥미로운 순간을 떠올리게 하면 짧은 답글을 씁니다. 블로그가 있었다면 글이 될 수도 있겠지만, 결국 아무것도 안 하곤 합니다. 영감을 주는 우연한 계기를 그냥 넘기지 말고 끝까지 써보는 것도 블로그를 쓰는 방법입니다. Julia Evans 블로그처럼 새로 배운 내용을 정리하는 것도 좋습니다. 모든 글이 같은 무게를 가질 필요는 없습니다. 깊이 파고드는 글도 있고, 짧은 생각이나 사용법도 있습니다.
- @embedding-shape — 내가 아는 사람 대부분은 다양한 방식으로 작업 일지를 씁니다. 공개할 글을 만드는 일은 백지에서 시작하는 대신, 이미 쌓아 둔 기록과 글을 정리해 읽을 만한 형태로 다듬는 일이 됩니다.
- @kwertyoowiyop — 1980년대에 메모리를 망가뜨리는 버그를 출시하지 않은 게임 프로그래머는 손을 들어보세요. 그렇죠! :-)
- @morkalork — 인턴을 막 시작했을 때 나도 정말 엉망인 메모리 손상 버그를 쓴 적이 있습니다. 하나는 팀 리드와 스태프 엔지니어가 대체 무슨 일인지 한참 고민하게 만들었습니다. 또 하나는 인턴십이 끝난 지 몇 달 뒤 누군가 내 이메일을 찾아 욕을 보내게 했습니다. 하하. C를 더는 쓰지 않는 편이 나을지도 모르겠습니다.
- @saghm — 자신을 너무 몰아붙이는 것 같습니다. 현실에서 쓸 C 코드를 꽤 많이 작성한 사람과 메모리 손상 버그를 작성한 사람의 벤다이어그램은 원이라고 봅니다.
- @Waterluvian — “C 프로그래머로서 나는 법적으로 메모리 손상을 가장 나쁜 죄악으로 여겨야 합니다.” 자기 학대는 해롭습니다.
- @egeozcan — 많은 사람이 LLM을 새로운 컴파일러라고 생각한다는 의견에는 동의하지 않지만, LLM이 잘못된 코드를 생성하는 모습을 사람들이 놀라지 않고 받아들이는 때가 올지 궁금합니다. 자연어는 프로그래밍 언어보다 훨씬 모호하고, C조차 그렇습니다. 게다가 LLM은 비결정적입니다. 두 번째 문제는 해결할 수 있을지도 모르지만, 첫 번째를 해결하려면 프로그래밍 언어를 다시 발명해야 할 것 같습니다. 실제로 그렇게 된다면 꽤 우스운 일입니다.
- @kqr — Zarf가 여전히 활동한다는 이야기가 나와서 덧붙입니다. 텍스트 어드벤처 커뮤니티도 계속 활발합니다. IntFiction.org 포럼이 가장 잘 알려져 있습니다. 텍스트 어드벤처가 특정 시대의 유물처럼 보이고 요즘 수익을 내기 어려운 것도 사실이지만, 해마다 수준 높은 작품이 여러 편 나오고 완성도도 계속 높아집니다. 대회 출품작은 무료 공개 조건이 붙는 경우가 많아 무료로 플레이할 수 있습니다. 파서 입력 방식은 관습을 익혀야 해서 처음 진입하기 어려울 수 있습니다. 최신 게임 디자인을 따르는 현대 작품부터 시작하는 편을 권합니다. 텍스트 어드벤처는 그래픽이나 음악에 시간과 돈을 들이지 않고 게임 디자인을 고민하게 해줘 개발 입문에도 좋습니다. Inform 7, Inform 6, Dialog, TADS 등 개발 도구도 여럿 있으며, 원래 Infocom 게임은 Lisp 매크로를 ZIL로 변환해 만들었습니다.
원문: Hacker News / 번역·요약: Trawling