Hacker News

Nix wrote half of my debugger

디버거의 절반을 Nix가 만들어줬습니다

Rewind VM은 Nix 빌드를 결정적으로 재생하고 스레드 실행 순서를 바꿔 경쟁 상태를 찾아냅니다. 글쓴이는 Nix derivation에 입력, 소스, 디버그 심볼이 이미 갖춰져 있어 디버거 기능 대부분을 재현성 있게 구현할 수 있었다고 설명합니다.

AI 요약

Rewind VM은 Nix 빌드를 입력과 스레드 실행 순서까지 고정해 재현하는 가상 머신입니다. 글쓴이는 이 도구로 Nix 빌드의 경쟁 상태를 찾고 재현해 왔으며, 소스 탐색과 스택 프레임, 실행 비교, 특정 지점부터의 재검사 같은 디버거 기능을 추가했습니다. 기능을 만들 때마다 필요한 기반이 이미 Nix에 갖춰져 있었다는 점이 글의 중심입니다.

경쟁 상태를 한 단계까지 좁히기

예제로 든 은행 계좌 프로그램은 두 스레드가 잔액을 읽고 입금 내역을 기록한 뒤 새 잔액을 저장합니다. 한 스레드가 읽기와 저장 사이에 멈추면 다른 스레드의 입금액을 덮어써 돈이 사라집니다. 16코어 노트북에서는 1,000회 실행 중 396회 손실이 발생했지만, taskset으로 한 코어에 고정하면 1,000회 모두 정상 종료했습니다.

Rewind VM은 CPU 하나로 실행하므로 첫 실행은 정상적으로 끝날 수 있습니다. rewind check는 스레드 실행 순서를 바꿔가며 빌드를 다시 실행하고, 두 실행이 처음 달라지는 지점을 찾습니다. 예제에서는 3,237단계에서 스레드 재예약이 일어날 때 실패가 시작됐습니다. 두 실행은 그 단계까지 완전히 같았으며, 노트북에서 검사하는 데 11초가 걸렸습니다.

Nix가 제공하는 재현 조건과 디버그 정보

검사 대상은 Nix derivation이나 flake 참조입니다. Nix는 소스 코드뿐 아니라 컴파일러, 라이브러리, 커널, VM 설정 등 derivation의 입력을 알고 있습니다. Rewind는 입력 전체를 읽기 전용 erofs 이미지로 묶어 VM을 부팅합니다. 실행 ID도 입력의 해시로 만들며, 빌드 결과의 NAR 해시를 로컬 저장소 및 바이너리 캐시와 대조해 VM 안의 빌드와 기존 빌드가 같은지 확인합니다.

소스 코드와 디버그 심볼도 Nix 생태계에서 가져옵니다. nixpkgs는 separateDebugInfo를 사용해 디버그 정보를 별도 출력으로 만들고, 이를 cache.nixos.org에 보관합니다. Rewind는 debuginfod를 통해 build ID로 심볼과 소스를 가져옵니다. 따라서 VM 안의 프로그램은 물론 Linux 커널의 소스도 실행 위치에 맞춰 볼 수 있습니다. rewind where는 현재 단계의 소스 코드와 호출 스택을 출력합니다.

기록을 바꾸지 않고 상태 조사하기

소스만으로 부족하면 rewind gdb로 원하는 단계의 실행을 분기해 GDB를 엽니다. 각 스레드가 포함된 상태에서 중단점과 감시점을 설정하고 메모리, 레지스터, 변수를 살펴봅니다. 디버거 조작은 원래 기록을 바꾸지 않으므로 같은 단계로 돌아와 다시 조사하거나 다른 단계에서 새 분기를 만들 수 있습니다. rewind shell은 지정한 단계의 VM에 접속해 nixpkgs 패키지를 사용하도록 해줍니다.

rewind compare는 두 실행이 갈라지기 직전의 이벤트와 최초로 달라진 이벤트를 나란히 보여줍니다. 또 Rewind는 기록에 없는 실행 사이의 공백을 결정적 재생으로 다시 따라가며, 각 단계에서 게스트 커널에 어떤 스레드가 CPU를 쓰는지 묻습니다. 예제에서는 한 스레드가 잔액을 읽은 뒤 멈춘 사이 다른 스레드가 한 단계 실행돼 같은 오래된 잔액을 읽는 순서를 확인했습니다.

rewind check --run은 기존 실행의 특정 단계부터 여러 스케줄로 분기합니다. 예제는 두 실행이 갈라진 지점 직전인 3,221단계부터 16개 스케줄을 시험했고, 기준 실행을 제외한 모든 분기에서 돈을 잃었습니다. 실행 전 구간은 그대로 유지한 채 해당 지점 뒤의 스레드 순서만 바꾸므로, 이미 발견한 상황에서 실패가 얼마나 자주 생기는지 확인할 수 있습니다. 북마크와 메모는 실행 파일을 내보낼 때 함께 저장돼 다른 사람과 조사 맥락을 공유합니다.

Hacker News 반응

  • @Cyph0n — 정말 대단합니다. Farid가 Nix 관련 좋은 글을 계속 내놓네요. 제약이 조금 더 많은 Antithesis 런타임을 자기 컴퓨터에서 돌리는 느낌입니다. Rewind는 난수를 어떻게 처리하나요?
    • @setheron — 커널 난수 생성기는 부팅 때 전달한 시드로 초기화합니다.
    • @iwixon — 읽으면서 저도 Antithesis를 떠올렸습니다. 전제 조건을 설정하고 실패가 나올 때까지 실행해 동시성 버그를 찾는 데 쓸 수 있을지 궁금합니다.
  • @saagarjha — Nix가 여기서 어떤 역할을 하는지 잘 모르겠습니다. Rewind는 기본적으로 rr을 다시 구현하는 것처럼 보입니다.
  • @peter_d_sherman — Rewind VM의 작동 방식을 설명하는 페이지에서 특히 중요한 부분은 다음과 같습니다. 기본 KVM에서 vCPU 하나로 결정적으로 실행하며, 게스트 커널이 Rewind에 제어권을 넘길 때만 인터럽트와 시간이 진행되고 타임스탬프 카운터와 하드웨어 난수 기능은 숨깁니다. 각 단계는 그런 제어권 전달입니다. 0.25초마다 KVM의 dirty-page 로그를 이용해 스냅샷을 만들고, 페이지는 BLAKE3와 zstd를 사용하는 콘텐츠 주소 저장소에 넣습니다. 키프레임, 실행, 분기에서 공유하는 페이지는 한 번만 저장합니다. 훌륭한 엔지니어링입니다.
    • @firemelt — VM 전체를 대상으로 하는 redux 같은 건가요?
  • @fxtentacle — “이 초능력을 왜 아무도 쓰지 않나요?”라고 묻지만, C/C++용 시간 되돌리기 디버거 Undo는 20년 넘게 존재했습니다. 답은 단순합니다. 많은 사람이 이미 자리 잡았고 신뢰할 수 있지만 지루한 도구 대신 새롭고 반짝이는 것을 쫓습니다.

원문: fzakaria.com / 번역·요약: Trawling