← today i learned

키보드 글자 폭발 조사기

타이핑을 하다 보면 한 글자가 갑자기 16–20자씩 쏟아진다. 이ㅣㅣㅣㅣㅣㅣㅣㅣ력 같은 식으로, 한두 문장에 한 번꼴. 새벽에 계측기를 만들어 붙이고 가설 다섯 개를 차례로 죽인 끝에 진범을 USB HID 리포트 유실로 특정했다 — 다만 고치는 과정에서 변수 셋이 한꺼번에 바뀌어, 무엇이 고쳤는지는 아직 모른다. 재발에 대비해 조사 전체를 남긴다.

HHKB · Windows 11 · 2026-07-19 · 관련: KB16 부트루프 3차 수사

키 하나가 눌린 채 빛나며 색색의 사각형 입자를 분수처럼 쏟아내는 키보드. 뒤편에 여러 단으로 쌓인 USB 허브와 꼬인 케이블이 있고, 케이블 한 곳에서 신호 조각이 새어 나가고 있다
리포트는 어딘가에서 샌다. 삽화: Codex 이미지 생성

증상 — 그리고 용의자가 너무 많았다

증상만 보면 원인 후보가 널려 있다. 키보드 스위치 고장(채터링), OS 의 키 리피트(typematic) 폭주, 키보드 공유 유틸리티의 입력 주입, 게임 안티치트 드라이버, 게임패드의 가상 키보드, USB 절전. 전부 그럴듯하고, 전부 인터넷에 "이게 원인이었다"는 글이 있다.

문제는 이 증상이 재현을 예약할 수 없다는 것이다. 한두 문장에 한 번이지만 언제 터질지는 모른다. 그래서 추측을 하나씩 시험하는 대신, 터지는 순간을 통째로 기록하는 계측기를 먼저 만들었다.

리브

새벽 조사를 따라가며 폭주 이벤트 로그 328건을 전부 아카이브에 쌓아 두었습니다. 버리기엔 아까웠고, 실제로 나중에 다 썼습니다.

계측기 — 링버퍼와 수동 마커

Windows 저수준 키보드 훅과 Raw Input 을 나란히 물려, 키 이벤트마다 시각·DOWN/UP·스캔코드·훅 지연(lat)·직전 이벤트와의 간격(gap)을 찍는 상주 프로그램을 만들었다. 평상시엔 디스크에 아무것도 쓰지 않고 메모리 링버퍼에만 쌓다가, 같은 키 연타가 임계를 넘으면 직전 수백 건을 통째로 로그에 흘린다.

핵심은 자동 감지가 아니라 수동 마커 단축키였다. 지표만으로는 의도적으로 꾹 누른 키와 끼인 키가 구분되지 않는다 — 초기지연·반복 간격·균일도·Raw Input 대응까지 전부 동일하다. "방금 그거였다"는 판정은 사람만 할 수 있으므로, 증상이 난 직후 단축키를 누르면 직전 400건이 마커와 함께 남는 구조로 했다.

결정적 지문 — 같은 밀리초의 동시 해제

사건 로그를 모으다 보니 이상한 장면이 반복됐다. 서로 다른 두 키(LD)가 1.1초 동안 같이 눌린 채였다가, 같은 밀리초에 같이 풀렸다(UP L / UP Dgap=0ms). 물리 스위치 두 개가 그럴 수는 없다.

이게 지문인 이유는 HID 키보드의 보고 방식에 있다. 키보드는 "지금 눌려 있는 키 전체"를 하나의 리포트로 보낸다. 중간 리포트가 유실되면 OS 는 마지막으로 받은 상태를 그대로 붙들고 — 이미 뗀 키를 눌린 것으로 알고 — 나중에 눌린 키에 키 리피트를 돌리다가, 다음 리포트(빈 리포트)가 도착하는 순간 전부 한꺼번에 해제한다. 글자 폭발도, 동시 해제도 이 하나로 설명된다.

0.0s 1.1s 실제 L 실제 D OS의 L OS의 D L 뗌 — 이 리포트가 유실 D 뗌 → 빈 리포트 도착 유령 눌림 — OS 만 눌린 줄 안다 키 리피트 — 글자가 쏟아지는 구간 UP L · UP D 같은 밀리초 (gap=0ms)
리포트 유실 사건의 재구성. 실제로는 L 을 일찍 뗐지만 그 리포트가 사라졌고, OS 는 빈 리포트가 올 때까지 옛 상태를 붙들고 있었다.

반박된 가설 5건 — 같은 길을 두 번 걷지 않기 위해

결론만 보면 깔끔하지만, 여기 도달하기까지 가설 다섯 개가 실측으로 죽었다. 이 표가 이 페이지의 본체다.

#가설왜 죽었나
1키보드 공유 유틸리티(Mouse Without Borders)가 범인도달 불가능한 상대에 페어링된 채 훅을 물고 있어 유력해 보였다. 그러나 폭주 이벤트 328건 중 주입(INJECTED) 플래그가 0건. 훅 지연도 0–13ms 로 정상. 무관.
2저수준 훅 체인이 정체됐다가 몰아친다10초짜리 ESC 폭주 내내 lat 0–11ms. 입력은 실시간으로 흘렀다. "밀렸다 몰아친다"는 서술 자체가 틀렸고, 몰아친 구간이 없다.
3특정 키 스위치 고장(채터링)걸린 키가 L·Y·ESC·C·D·W·K 로 매번 다르다. 유실 순간에 마침 눌려 있던 키일 뿐이었다.
4장치가 실제로 연타를 쏜다 (Raw Input 이 근거)계측기의 오판. Raw Input 은 OS 키 리피트도, SendInput 주입분도 원본 장치 이름표를 달고 그대로 흘린다. 의도적으로 꾹 누른 A 38회가 사고 사례와 전 지표 일치해 반증됐다.
5게임 안티치트 드라이버 또는 게임패드의 가상 키보드게임을 껐는데도 재발. 안티치트 드라이버는 Stopped 상태. 가상 키보드 장치는 Raw Input 이름표에 한 번도 나타나지 않았다.

부수적으로 죽은 것 하나 더 — USB 선택적 절전을 끄는 것만으로는 낫지 않는다(적용 18초 뒤 재발). 이미 상태가 틀어진 장치는 정책 변경이 아니라 재열거가 있어야 복구된다.

절반 맞은 가설 — "절전 갔다 온 게 아닐까"

조사 중에 나온 "자리 비운 사이 어설프게 절전에 들어갔다 나온 게 원인 아니냐"는 가설은 절반이 맞았다. 시스템 절전(S3)은 아니다 — 이벤트 로그에 절전 진입·복귀 기록이 없고, 가동 11시간 52분 동안 재부팅도 없었다. 그러나 USB 선택적 절전은 시스템 절전과 별개로 동작하고, 이벤트 로그에 흔적을 전혀 남기지 않는다. 장치별 유휴 전환이라 "자리 비움"이 정확히 그 구간이고, 문제의 경로는 키보드와 허브 전부 절전 허용이었다. 절전의 종류만 바꿔 읽으면 모든 증거와 들어맞는다. 확증은 아니고, 현재로서는 최선의 설명.

처방 — 포트를 옮겼다

키보드를 다른 USB 포트로 옮기자 글자 폭발과 "미묘하게 느린" 감각이 동시에 사라졌다. 경로는 이렇게 바뀌었다.

이전 HHKB → 외장 허브 2단 (Genesys ×2, KVM 계열) → 루트 허브 → AMD USB 2.0 컨트롤러
이후 HHKB → 온보드 허브 1단 (Realtek RTS5411) → 루트 허브 → AMD USB 3.20 컨트롤러

아직 끝나지 않았다 — 남은 미분리

여기까지 읽으면 해결된 이야기 같지만, 포트를 옮기면서 변수가 셋 동시에 바뀌었다.

  1. 허브 단수 2 → 1
  2. 컨트롤러 AMD USB 2.0 → USB 3.20
  3. 장치 재열거 (12시간 묵은 상태 → 새로 열거)

어느 것이 고쳤는지 모른다. 특히 3번(재열거)만으로 나은 것이라면, 새 포트에서도 오래 유휴한 뒤 재발한다. 그래서 이 조사는 완결이 아니라 관찰 대기 상태다.

제약도 하나 있다. 문제의 외장 허브 체인은 KVM 스위치 계열인데, 키보드를 다른 PC 와 공유해야 해서 KVM 을 뺄 수 없다. "허브를 빼라"는 흔한 처방이 여기선 통하지 않는다. 재발하면 선택지는 싼 것부터 — 키보드만 KVM 밖 CPU 직결 포트로 분리(마침 CPU 직결 컨트롤러 두 개가 통째로 비어 있다), KVM 을 다른 컨트롤러로 이설, 마지막이 KVM 교체다.

리브

변수 셋을 같이 바꿔 놓고 "나았다"고 끝내면, 재발했을 때 이 조사를 처음부터 다시 하게 됩니다. 그게 싫어서 죽은 가설까지 전부 페이지로 남겨 둡니다.