---
title: "아폴로 11호 유도 컴퓨터에서 57년간 숨어있던 미발견 버그를 찾아냄"
published: 2026-04-07T10:25:12.000Z
canonical: https://jeff.news/article/1620
---
# 아폴로 11호 유도 컴퓨터에서 57년간 숨어있던 미발견 버그를 찾아냄

JUXT팀이 오픈소스 행동 명세 언어 Allium과 Claude를 사용해 아폴로 유도 컴퓨터(AGC)의 자이로 제어 코드에서 57년간 발견되지 않았던 리소스 락 누수 버그를 찾아냈다. 에러 경로에서 LGYRO 락을 해제하지 않는 결함으로, 누락된 코드는 단 4바이트. 기존 코드 리뷰와 에뮬레이션으로는 잡을 수 없었던 걸 리소스 생명주기 모델링으로 발견한 사례다.

- 역사상 가장 많이 분석된 코드베이스 중 하나인 아폴로 유도 컴퓨터(AGC) 코드에서 **57년간 아무도 발견하지 못한 버그**를 찾아냄
  - 자이로 제어 코드의 에러 경로에서 리소스 락(`LGYRO`)을 해제하지 않는 결함
  - 누락된 코드는 딱 2줄, 4바이트 — `CAF ZERO` / `TS LGYRO`
  - 수천 명의 개발자가 읽었고, 에뮬레이터로 명령어 하나하나 검증했지만 못 찾은 걸 행동 명세(behavioral specification) 접근법으로 잡아냄

## 버그의 정체

- AGC는 자이로스코프를 토크할 때 `LGYRO`라는 공유 락을 획득함 — 두 루틴이 동시에 자이로 하드웨어를 건드리지 못하게 하는 장치
  - 정상 완료 시 `STRTGYR2` 경로로 나가면서 락 해제 → 문제없음
  - 하지만 **케이징(caging)** 중에 토크가 진행되면 `BADEND` 경로로 빠지는데, 여기서 락을 안 풀어줌
- `BADEND`는 범용 종료 루틴이라 일반 리소스(`MODECADR` 등)는 제대로 정리함
  - 문제는 `LGYRO`가 자이로 토크 코드에서만 쓰는 특수 락이라 `BADEND`가 존재 자체를 모른다는 것
- 한번 `LGYRO`가 걸리면? 이후 모든 자이로 조작이 영원히 블로킹됨
  - 정밀 정렬, 드리프트 보정, 수동 자이로 토크 전부 먹통
  - 알람도 안 뜨고, 프로그램 라이트도 안 들어옴 — 그냥 명령을 받고 아무것도 안 함

> [!IMPORTANT]
> 하드 리셋하면 해결되지만, 이 버그의 증상은 하드웨어 고장처럼 보임. 명령은 받아들이는데 동작만 안 하니까 소프트웨어 문제를 의심할 단서가 전혀 없음

## 마이클 콜린스가 혼자 달 뒤를 돌 때

- 1969년 7월 21일, 암스트롱과 올드린이 달 표면에 있는 동안 콜린스는 사령선 컬럼비아호에서 혼자 달 궤도를 돌고 있었음
  - 2시간마다 달 뒤로 사라지면서 지구와 통신 두절
  - 매 패스마다 Program 52(별 관측 정렬)를 실행해서 유도 플랫폼 방향을 유지해야 했음 — 이게 틀어지면 귀환 엔진 점화 방향이 빗나감
- 만약 콜린스가 좁은 조종석에서 움직이다 팔꿈치로 케이지 스위치를 건드렸다면?
  - P52 실패 → 케이지 해제 후 재정렬 시도 → **두 번째 P52가 영원히 멈춤**
  - 첫 번째 실패는 정상적인 상황이라 훈련받은 대로 대응하지만, 두 번째 행(hang)은 원인을 알 수 없음
  - "지난 6개월간 나의 비밀스런 공포는 그들을 달에 두고 혼자 지구로 돌아오는 것이었다" — 콜린스 자서전 *Carrying the Fire*

## 어떻게 찾았나

- JUXT팀이 오픈소스 행동 명세 언어 **Allium**과 **Claude**를 사용해서 발견
  - AGC 어셈블리 13만 줄을 1만 2,500줄의 행동 명세로 증류(distill)
  - 명세는 코드 자체에서 파생 — 공유 리소스의 생명주기를 모델링함 (획득 → 보유 → 해제)
- 핵심 접근 차이: 기존 검증은 "코드가 작성된 대로 동작하는가"를 확인, 행동 명세는 **"코드가 무엇을 위한 것인가"**를 질문함
  - `gyros_busy = true`가 되면, 모든 경로에서 반드시 `false`로 돌려야 한다는 의무를 명시적으로 선언
  - Claude가 `gyros_busy = true` 이후의 모든 경로를 추적 → 정상 경로는 해제하지만 `BADEND` 경로는 해제 안 하는 걸 발견

```mermaid
sequenceDiagram
    participant 토크코드 as 토크 코드
    participant LGYRO as LGYRO 락
    participant 자이로 as 자이로 하드웨어
    participant 케이지 as 케이지 이벤트

    토크코드->>LGYRO: 락 획득 (gyros_busy = true)
    토크코드->>자이로: 축별 토크 펄스 전송
    케이지-->>토크코드: 케이지 감지 (CAGETEST)
    토크코드->>토크코드: BADEND로 분기
    Note over LGYRO: ⚠️ 락 해제 누락!
    토크코드->>토크코드: 다음 P52 시도
    토크코드->>LGYRO: 락 획득 시도 → 영원히 대기
```

## 왜 57년간 안 잡혔나

- AGC 코드가 워낙 방어적으로 작성돼서 오히려 버그가 숨음
  - 재시작 로직이 전체 소거 메모리를 초기화하면서 `LGYRO`도 부수효과로 클리어함
  - 테스트 중 우연히 재시작이 발생하면 시스템이 매끄럽게 복구돼서 문제가 드러나지 않음
- 기존 검증 방식의 한계가 명확히 드러남
  - 코드 리뷰: `BADEND`만 보면 모든 리소스를 올바르게 정리하는 것처럼 보임
  - 에뮬레이션: 특정 시나리오를 테스트하는 거라 "케이지 후 재시작 없이 다시 토크"라는 경로를 아무도 안 돌려봄
  - 형식 검증/정적 분석: AGC 비행 코드 대상으로 발표된 논문이 없음

## 현대 코드에 주는 교훈

- 마가렛 해밀턴 팀이 '소프트웨어 엔지니어링'이라는 용어 자체를 만들었고, 우선순위 스케줄링/비동기 멀티태스킹/재시작 보호 같은 개념을 선도함
  - 아폴로 11호 착륙 중 1202 알람이 발생했을 때 저우선순위 태스크를 버리는 방식으로 대응 — 현대 시스템 대부분이 이만큼 우아하게 과부하를 처리하지 못함
- 현대 언어들이 구조적으로 락 누수를 막으려 시도하지만 여전히 한계가 있음
  - Go의 `defer`, Java의 `try-with-resources`, Python의 `with`, Rust의 소유권 시스템
  - 하지만 DB 커넥션, 분산 락, 셸 스크립트의 파일 핸들, 인프라 해체 순서 등은 여전히 프로그래머 책임
  - MITRE는 이 패턴을 CWE-772("유효 수명 이후 리소스 미해제")로 분류하고 악용 가능성을 '높음'으로 평가

> [!TIP]
> AGC에서 표면화된 가장 심각한 버그들은 코딩 실수가 아니라 명세 오류였음. 유명한 1202 알람도 랑데부 레이더 ICD에서 위상 동기화 언급을 빠뜨린 명세 문제가 원인. 테스트는 "코드가 맞는지"를 검증하지만, 행동 명세는 "코드가 뭘 해야 하는지"를 질문함

---

## 기술 맥락

- **행동 명세(Behavioral Specification)** 가 기존의 코드 리뷰나 에뮬레이션과 근본적으로 다른 이유가 있어요. 코드 리뷰는 "이 루틴이 제대로 정리하는가"를 확인하는데, 행동 명세는 반대 방향에서 "이 리소스가 획득된 후 모든 경로에서 해제되는가"를 질문하거든요. 관점의 차이가 57년간 숨어있던 버그를 찾아낸 핵심이에요.

- **Allium**은 JUXT에서 만든 오픈소스 행동 명세 언어인데, AI 네이티브를 표방해요. 13만 줄의 어셈블리를 1만 2,500줄의 명세로 증류하는 과정에서 Claude가 코드를 읽고 리소스 생명주기를 추적하는 역할을 했거든요. 사람이 어셈블리를 한 줄씩 읽는 게 아니라 AI가 모든 경로를 체계적으로 훑는 방식이에요.

- **리소스 락 누수(CWE-772)** 는 현대 코드에서도 흔한 문제예요. 언어 런타임이 관리하는 락은 `defer`나 `with` 같은 구문으로 구조적으로 막을 수 있지만, 분산 락이나 DB 커넥션풀처럼 프로그래머가 직접 해제해야 하는 리소스는 여전히 같은 패턴의 버그가 발생할 수 있어요. AGC의 `LGYRO`가 정확히 이 케이스였고요.

- AGC의 **방어적 코딩**이 오히려 버그를 은폐한 것도 재밌는 포인트예요. 재시작 로직이 메모리를 초기화하면서 락도 같이 풀려버리니까, 테스트 중에 버그가 발현돼도 시스템이 알아서 복구한 것처럼 보였거든요. "테스트 통과 = 버그 없음"이 아니라는 걸 극적으로 보여주는 사례에요.

## 핵심 포인트

- AGC 자이로 제어 코드의 BADEND 에러 경로에서 LGYRO 락 미해제 — 누락 코드 4바이트
- 케이징 후 재시작 없이 자이로 재사용 시 모든 자이로 조작이 영구 블로킹
- Allium + Claude로 13만 줄 어셈블리를 1만 2,500줄 행동 명세로 증류하여 발견
- AGC의 방어적 코딩(재시작 시 메모리 초기화)이 오히려 버그를 57년간 은폐
- 현대 코드에서도 CWE-772(리소스 미해제) 패턴은 여전히 흔한 문제

## 인사이트

테스트와 코드 리뷰가 '코드가 올바르게 동작하는가'를 확인하는 반면, 행동 명세는 '코드가 무엇을 해야 하는가'를 질문한다는 근본적 차이를 보여주는 인상적인 사례. AI를 활용한 대규모 레거시 코드 분석의 실용적 가능성을 입증했다.
