적용되었는지 어떻게 알 수 있을까요?
이런 종류의 다른 도구에서는 알 수 없습니다. 앱이 시작되고 어떤 날짜를 보여 주면, 우리는 되었으리라 짐작합니다. 하지만 앱은 아무것도 가로채지 못한 경로로 시계를 읽을 수 있고, 그러면 “테스트 통과”는 아무 의미가 없습니다.
증상이 없는 실패
채널 하나를 놓친 시간 이동 도구는 충돌하지도 경고하지도 않습니다. 앱은 아무도 훅을 걸지 않은 함수 하나를 통해 실제 날짜를 읽고, 평범한 오후와 똑같이 동작합니다. 테스트는 초록색이 되고, 버그는 출시됩니다.
RunAsDate는 이 점에 대해 자체 문서에서 솔직합니다. “현재 날짜/시간을 다른 소스에서 가져오는 애플리케이션에는 영향을 주지 않는다”고 말하고, 그 뒤로는 자신의 앱이 거기에 해당하는지 직접 알아내도록 맡깁니다. 이 도구는 같은 문장을 측정해야 할 대상으로 다룹니다.
세션이 실제로 보고하는 내용
이 프로젝트 자체의 테스트 프로브 중 하나, 일부러 네트워크 연결을 여는 프로브에 대해 도구를 실행한 실제 출력입니다.
$ chrono run "...\pnet-x64.exe" --at 2038-01-19T03:14:07 --report session.txt Chrono Mock - session report target: C:\ChronoMock\tools\probes\bin\pnet-x64.exe verdict: WORKS - time substitution took effect (processes: 1) every process that read time saw the session clock session: fake clock reached 2038-01-19T03:14:10 real elapsed 3.4s, fake elapsed 3.4s exited: the target closed itself with code 0 covered channels (substituted, with call counts, 4 calls in all): - pid 9152: GetSystemTimeAsFileTime (1 call) - pid 9152: NtQuerySystemTime (3 calls) ... every other channel, including the ones never called observed channels (hooked but left real): - pid 9152: NtCreateUserProcess (0 calls) - pid 9152: connect (1 call) warnings: - the target opened a network connection - it may read time from a server, which no local hook can cover (source.network_at_start) requested: 2038-01-19T03:14:07 (zone +02:00 (host default), mode flow)
마지막 두 블록이 요점입니다. 여기서는 아무것도 실패하지 않았고 판정은 동작함입니다. 그런데도 보고서는 묻지 않았는데도 이 앱이 끝나기 전에 네트워크와 통신했으므로 로컬 훅만으로는 전체 이야기가 아닐 수 있다고 알려 줍니다. 그 문장이 신뢰할 수 있는 테스트와 그렇지 않은 테스트의 차이입니다.
네 가지 판정과 각각의 종료 코드
판정은 작업을 시작하기 전에 실행 중인 프로세스 내부에서 내려지며, 도구 자신의 종료 코드이므로 파이프라인이 이에 따라 분기할 수 있습니다.
| 판정 | 종료 | 의미 |
|---|---|---|
| 동작함 | 0 | 시간을 읽은 모든 프로세스가 세션 시계를 보았음 |
| 일부 동작함 | 10 | 일부 채널은 커버되고 일부는 커버되지 않음 - 커버되지 않은 것은 나열됨 |
| 동작하지 않음 | 11 | 대체가 핵심 채널에 도달하지 못함 |
| 판단 불가 | 4 | 커버리지를 확인할 수 없음 - 솔직한 “모르겠습니다” |
판정이 “동작하지 않음”이면 프로세스는 상호작용하기 전에 즉시 중지됩니다. 판정이 실행보다 앞설 수는 없습니다. 유일한 진실의 원천이 실행 중인 프로세스 내부에서 얻은 측정값이기 때문입니다. 보장되는 것은 작업을 시작하기 전에 진실을 알게 된다는 것이지, 실패할 수밖에 없는 실행이 아예 일어나지 않는다는 것이 아닙니다. 무시하고 계속할 수 있으며, 그 경우 세션은 내보낸 증거를 포함해 나타나는 모든 곳에서 신뢰할 수 없는 것으로 표시됩니다.
다른 코드는 치환이 아니라 실행 자체가 잘못되었음을 알립니다. 사용법 오류, 시작할 수 없었던 대상, 도중에 끊긴 실행, 이미 실행 중인 다른 세션 등이며, 어느 것도 판정이 아닙니다. 전체 표는 명령줄 참조에 있습니다.
커버됨, 관찰됨, 커버되지 않음
채널은 세 가지 분류로 나뉘며, 가운데가 흥미로운 분류입니다.
- 커버됨 - 훅이 걸리고 대체된 것입니다. 앱이 물었고 세션 시계를 받았습니다. 호출 횟수가 몇 번인지 알려 줍니다.
- 관찰 - 가로채서 횟수를 세고, 일부러 실제 값을 말하게 둡니다. 객체 대기가 여기에 속합니다. 시간 제한을 줄이면 실제 핸들에서 시간 초과를 꾸며낼 수 있기 때문입니다. 멀티미디어 타이머도 속합니다. 배율을 적용하면 오디오와 MIDI 타이밍이 어긋나기 때문입니다. 둘 다 세션이 경과 시간 축에 배율을 적용할 때 감시합니다. 네트워크 연결과 프로세스 생성 경로 하나는 시계가 아니라 증거이므로 모든 세션에서 여기에 속합니다. 고해상도 카운터는 이 그룹에 없습니다.
--scale-qpc로 요청하지 않는 한 손대지 않으며, 요청하면 적용 대상이 됩니다. - 커버되지 않음 - 앱이 읽었지만 대체가 닿지 못한 채널입니다. 판정을 일부 동작함으로 바꾸는 것이 이것이며, 요약되어 사라지지 않고 이름이 나열됩니다.
호출 횟수 0도 정보입니다. 채널에 훅이 걸려 있었고 앱이 한 번도 묻지 않았다는 뜻입니다. 그래서 보고서는 이들을 숨기지 않고 나열합니다.
감사가 볼 수 없는 것
보고서는 커버리지에 대해 솔직하며, 이는 자신의 한계에 대해서도 솔직하다는 뜻입니다. 다음은 가로챌 함수 호출 없이 읽히거나 프로세스 밖에서 읽히는 것들입니다.
- 공유 사용자 모드 데이터 페이지의 직접 읽기 - 값이 메모리에 있으므로 훅을 걸 대상이 없습니다. 일반 애플리케이션에서는 드물고 라이선스·변조 방지 코드에서는 흔합니다
- 표준 export를 우회하는 직접 시스템 호출 - 같은 패턴입니다
- 프로세스 밖에서 WMI, 프로세스 외 COM 서버, RPC를 통해 얻는 시간
- 네트워크에서 오는 시간 - HTTP
Date헤더, NTP, API 응답, 데이터베이스 서버 자체의 시계 - 커널 드라이버에서 오는 시간
네트워크의 경우는 감사가 적어도 알아차릴 수 있는 것이며, 위 보고서에 경고가 있는 이유입니다. 나머지의 경우 동작함 판정은 이 도구가 볼 수 있는 채널이 커버되었다는 뜻이지, 다른 경로가 존재하지 않는다는 뜻이 아닙니다.
파이프라인에서
--json은 같은 사실을 기계용 형식으로 출력하고, --report는 사람이 읽는 버전을 파일에 기록하며, 판정이 동작함이 아닌 세션은 단서를 맨 아래에 묻어 두지 않고 파일 맨 앞에 신뢰할 수 없는 증거라는 배너를 붙입니다.
chrono run "C:\apps\test.exe" --at 2027-12-31T23:59:59 --json --report evidence.txt