命令列參考
兩個命令的全部參數、兩張結束代碼表,以及如何閱讀回傳的內容。附有設計思路的敘述版在 CLI 與 CI 頁面。
兩個命令
chrono run 以另一個日期與時間啟動應用程式。chrono calc 算出一個日期,並告訴你它為什麼值得測。兩者共用同一套步驟語法,所以為其中一個寫的偏移,在另一個裡意義完全相同。
chrono version 回答第三個小得多的問題:這是哪個建置、可執行檔是兩個核心中的哪一個,以及它使用的通訊協定版本。在 stdout 上只輸出一行,方便指令碼擷取。
chrono license 回答第四個問題:這個建置以什麼授權條款散布、免責聲明,以及連結進來的第三方元件。它還會告訴你完整授權文字與第三方聲明在磁碟上的位置;如果你執行的這份副本旁邊沒有它們,也會如實說明。 --components 會列出每個隨附元件及其版本,而不是只給出摘要。
chrono presets 依 --preset 讀取的方式列出預設目錄:資料夾中的每個預設,以計算機文法讀取的詞語寫出,以及每個會被拒絕的檔案和原因。--json 輸出 chronomock.presets/1,--dir 指定另一個資料夾。
chrono run <target> [options] chrono calc [options] chrono version # chrono 0.4.0 (x64, protocol 1) chrono license # GPL-3.0-only,以及隨附了什麼 chrono presets # 依 --preset 讀取方式列出的目錄 chrono --help # 檢視所有命令的用法
chrono run——選項
| 參數 | 作用 |
|---|---|
--at <moment> |
起始時刻,依工作階段時區書寫。可以是絕對時間(2038-01-19T03:14:07),也可以是帶前導正負號的相對時間(+1d、-2h)。單位:s m h d w mo q y——m 是分鐘,mo 是月。不帶此參數時,工作階段時鐘從真實的目前時間開始,因此單獨使用 --mode xN 只是讓目標程式跑得更快。 |
--zone +HH:MM |
工作階段時區:既是書寫該時刻所用的時區,也是目標程式的本地時間呼叫所回報的時區。固定偏移量,沒有日光節約時間。不帶此參數時使用主機自己的時區。 |
--mode flow|frozen|xN |
flow 依真實速度執行,frozen 讓時鐘保持不動,xN 讓它快 N 倍(x60 讓一小時變成一分鐘,x1440 讓一天變成一分鐘)。 |
--scale-duration |
同時加快經過時間軸:滴答計數、中斷時間、休眠和計時器。物件等待只計數並保持真實時長。預設關閉,因為牆上時鐘和碼錶是兩個不同的問題。設定在某個日期和時間的計時器或延遲,無論有沒有這個選項都會跟隨工作階段時鐘。 |
--scale-qpc |
同時縮放高解析度計數器,Python 3.13+ 的 monotonic、.NET 的 Stopwatch 與 Java 的 nanoTime 都是從這裡讀取經過的時間。預設關閉:用該計數器為畫面轉譯計時的應用程式可能會顯得畫面失真。 |
--ticks N |
在 N 次心跳(每次一個真實秒)之後結束工作階段並回報。不帶此參數時,工具會一直保持連線,直到目標程式以及它啟動的、執行在工作階段時鐘上的所有程式都結束——立即結束的啟動器會把工作階段留給它所啟動的應用程式。 |
--timeout <s> |
超過這麼多秒就放棄並以 6 結束,適用於不允許卡住的管線。與此參數無關,核心如果 15 秒沒有任何回應,也會被停止並同樣以 6 結束——兩種情況下執行都被中途截斷,所以都不帶判定。 |
--args "..." |
傳給目標程式的引數,寫成一個字串,依 Windows 使用的同一規則拆分——因此含空格的引數可以完整保留。對於 Chromium 目標,--user-data-dir 與 --remote-debugging-port 會被拒絕而不是被覆寫:工作階段需要自己獨立的設定檔,而悄悄地在你真實的瀏覽器設定檔上執行,是絕不能發生的一件事。 |
--cwd <dir> |
在該目錄中啟動目標程式。不帶此參數時,目標程式繼承我們自己的工作目錄,這也是此參數出現之前每次執行的行為。空值是用法錯誤,而不是「沒有目錄」——未提供與為空是兩回事,只有未提供才是 Windows 能接受的。目錄不存在時,工作階段會在啟動任何東西之前以 target.cwd_missing 終止,而不是等到後來表現為目標程式無法啟動。 |
--preset <id> |
從 presets/<id>.json 中取得時刻與速度。與 --at、--mode 和 --scale-duration 互斥,因為此時預設是這兩者唯一的來源。 |
--param id=value |
填寫預設的參數。可重複使用,需要搭配 --preset。僅在 run 中,試用期開始日期預設取目標可執行檔自己的建立日期,因此試用類預設會從應用程式安裝的那一天開始計算,無需你手動輸入。 |
--set-after T:M |
在第 T 次心跳時,把速度改為 M。執行過程中倍率至少為 1——這裡不提供工作階段中途凍結。 |
--jump-after T:moment |
在第 T 次心跳時,把時鐘跳到該時刻,絕對或相對皆可,依工作階段時區解讀。 |
--force |
即使開場判定表示替換沒有生效,也保留工作階段——預設會停止目標程式,因為什麼也沒證明的工作階段比沒有工作階段更糟。判定不會改變,結束代碼也不會改變。它只決定目標程式是否繼續執行。 |
--no-embedded |
讓應用程式內部的網頁(WebView2、Qt WebEngine)保持在真實時鐘上,並且不開啟偵錯連接埠。不加此參數時,工作階段會請這類引擎開啟本機連接埠,並透過它把其頁面放到工作階段時鐘上。連接埠開啟期間,這台電腦上的任何其他程式都可以用它存取這些頁面。 |
--elevated-embedded |
用於以系統管理員身分執行的應用程式,需要從同樣以系統管理員身分執行的 Chrono Mock 啟動。對這類應用程式,WebView2 會忽略工作階段用來存取其頁面的設定,所以工作階段在持續期間向電腦登錄(HKLM,以應用程式的 .exe 命名)寫入一個值,並在結束時刪除。應用程式執行期間,這台電腦上的任何其他程式都可以透過該連接埠存取其頁面,而該應用程式擁有系統管理員權限。不能與 --no-embedded 同用。 |
--keep-background-timers |
啟動 Web 引擎(Chromium 或 Electron 應用程式的瀏覽器,或應用程式內部的引擎)時關閉其對隱藏視窗中計時器的減速,這樣最小化的視窗仍保持工作階段速度。此時應用程式在隱藏狀態下也會全速執行。 透過 --elevated-embedded 以系統管理員身分啟動的應用程式不會套用此設定。 |
--dry-run |
只列印工作階段將會是什麼樣子,不啟動任何東西:解析出的時刻與時區及其來源、預設為每個參數填入了什麼、拆分後的引數,以及目標程式將採用兩種機制中的哪一種。不會啟動任何處理序,也不會寫入任何檔案,包括 --report 指定的那個。計畫可行時以 0 結束,目標程式不存在時以 2 結束,這與真實執行遇到同樣情況時給出的結束代碼一致。計畫不帶判定,並且會如此聲明——只有真實的工作階段才有判定。 加上 --json 時,計畫以資料形式給出,其中包括 session.elevated_embedded 和 session.elevated_embedded_value。後者是在該選項適用於 .exe 目標時登錄值將以其命名的檔名,其他情況為 null。這裡有名稱並不保證一定會寫入:只有 Chrono Mock 本身以系統管理員身分執行時,工作階段才會寫入該值。 |
--report <path> |
把給人讀的報告寫入檔案。判定不是生效時,該檔案開頭會有一條證據不可靠的橫幅,因為隱藏自身疑點的證據比沒有證據更糟。判定為生效的工作階段,如果應用程式沒有讀取工作階段所替換的任何時鐘,或者它是 Chrono Mock 碰不到其牆上時鐘的 Go 程式,也會帶上這條橫幅。檔案還會寫明這次執行本身:版本、開始時間、目標程式、其資料夾和各個選項。 |
--report-args |
與 --report 一起使用時,也會把目標程式的參數原樣寫入該檔案。預設關閉,因為參數裡可能帶有密碼,此時檔案只寫明參數的個數。 |
--json |
以機器可讀的記錄輸出工作階段,而不是報告——與視窗自己使用的是同一個資料流。主控台應用程式自己的輸出會寫到標準錯誤,因此標準輸出裡只有這些記錄,沒有別的東西。工作階段結束時仍在執行的應用程式會繼續往那裡寫,所以任何一直讀到標準錯誤結尾的程式都要等它,而這次執行會如實說明。 |
chrono run——結束代碼
結束代碼是工作階段的判定,而不是應用程式回傳的值。應用程式自己的結束代碼會單獨回報,寫在報告與工作階段資料中——CLI 與 CI 頁面解釋了為什麼要這樣設計。
| 代碼 | 意義 |
|---|---|
| 0 | 生效——所有讀取過時間的處理序看到的都是工作階段時鐘 |
| 10 | 部分生效——部分通道已涵蓋,部分未涵蓋 |
| 11 | 未生效——替換沒有到達關鍵通道 |
| 4 | 無法確定——無法確認涵蓋情況。是一句誠實的「無法判斷」,絕不假裝通過 |
| 12 | 目標程式在注入後立刻消失,並且沒有留下任何執行在工作階段時鐘上的東西,這表示它可能是單一執行個體的應用程式,把工作交給了已在執行的副本,或是交給了工作階段無法進入的程式,通常是位元數不同的另一個程式。啟動應用程式後就結束的目標程式屬於啟動器,工作階段會繼續跟隨它所啟動的那個應用程式 |
| 1 | 用法錯誤——參數錯誤、運算式錯誤,或是此命令不接受的預設 |
| 2 | 無法啟動目標程式,或無法對其注入 |
| 3 | 內部錯誤——引擎無法啟動、遇到非預期的狀態,或在關閉工作階段之前就停止了(無論目標程式自己的結束代碼是什麼)。這樣的執行不帶判定 |
| 5 | 此版本未內建該操作——例如某個預設的時刻需要日曆,而 run 目前還無法載入日曆。刻意與用法錯誤區分開:你要求的是目前還不存在的東西,而不是錯誤的東西 |
| 6 | 執行被中途截斷——--timeout 到期,或是引擎 15 秒沒有回應而被停止。這不是判定:工作階段被中斷了,因此對目標程式無論哪種情況都說明不了什麼 |
| 7 | 已有另一個工作階段在執行,一次只能執行一個工作階段。等它結束後重試即可 |
| 8 | 工作階段已得出判定,但證據沒有寫入 --report 指定的檔案:磁碟已滿,或另一個程式開著舊檔案,舊檔案因此保持不變。判定仍在命令輸出的報告中 |
如何閱讀報告
不帶 --json 時,工作階段以一份純文字報告結束。每一行要麼是工作階段觀察到的事實,要麼就不出現——沒有任何內容是用看似合理的預設值填上去的。
Chrono Mock - session report
target: .\build\test.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:08
real elapsed 1.3s, fake elapsed 1.3s
exited: the target closed itself with code 3
covered channels (substituted, with call counts, 10 calls in all):
- pid 21240: GetSystemTimeAsFileTime (5 calls)
- pid 21240: GetLocalTime (5 calls)
observed channels (hooked but left real):
- pid 21240: NtCreateUserProcess (0 calls)
warnings:
- the target opened a network connection ...
- verdict——針對父處理序與所有子處理序得出的整體判定,附帶處理序數。結束代碼承載的就是這個數。
- exited——應用程式自己的結束代碼,而且只在它自行關閉時才有。由
--ticks限定的執行會讓目標程式繼續執行,Chromium 工作階段是由工具自己關閉的,從未啟動的工作階段也沒有這樣的結束代碼——這時這一行會直接不出現,而不是顯示一個捏造的零。當機代碼會以很大的負數形式出現,所以也會給出它的十六進位形式。 - covered——替換到達的通道,以及每個通道實際被呼叫的次數,依處理序分別統計。次數絕不會跨處理序相加:一個處理序的稽核不能借用另一個處理序的數字。計數在 1048576 處停止,之後顯示為至少這麼多次。
- observed——已掛鉤但刻意保持真實的通道,例如物件等待與多媒體計時器。它們自成一類,因此絕不會被當作已替換。
- uncovered——應用程式要求過但工作階段沒有涵蓋的通道。正是它讓判定變成部分生效。
- not fully cleaned up——清理階段無法移除的任何東西。正常結束時不會出現,這也是常態。
chrono calc——選項
不支援自然語言:每個參數就是一個步驟,參數的順序就是步驟的順序。每一步之後的結果都會列印出來,所以哪一步出錯一目瞭然,而不是被埋起來。
| 參數 | 作用 |
|---|---|
--base today|now|<moment> |
從哪裡開始,預設是 today。依工作階段時區解析。像 2025-02-31 這樣不可能存在的日期會被拒絕,絕不會悄悄規整到下個月。 |
--base-utc <moment> |
與 --base 類似,但起點是 UTC 中的一個時刻(結尾可帶 Z),而不是工作階段時區中的牆上時鐘讀數。 |
--shift ±N<unit> |
可重複使用。必須帶正負號。單位:s m h d w mo q y bd,或是它們的全名。m 是分鐘,mo 是月。月、季與年依日曆日期折算,並做截斷處理,因此 1 月 31 日之後的一個月是 2 月月底,而不是固定數目的刻度。bd(工作日)需要 --calendar。 |
--set-time HH:MM:SS | 在目前結果上設定一天中的時間。 |
--snap <target> |
跳到某個週期的邊界:som eom soq eoq soy eoy(月、季、年的開始或結束)。開始是該日的 00:00:00,結束是最後一天的 23:59:59。 |
--nearest nbd|pbd |
往前或往後最近的工作日,如果目前日期本身就是工作日則取當天。需要 --calendar。 |
--to-zone +HH:MM |
一個步驟:用另一個固定偏移量重新表示同一個時刻。時刻保持不變,所以紀元值不動,變的只有民用時間欄位。不要與 --zone 混淆。 |
--zone +HH:MM |
工作階段時區,用於解析 today 與 now 並呈現結果。不帶此參數時使用主機的時區。 |
--calendar us-banking|us-federal|pl |
載入一份假日日曆。它會在中繼資料中加入工作日與假日,並啟用 bd 與 --nearest。兩份美國日曆的差別在於遇到週末的假日如何順延,這恰恰是測試要找出的那種差一天的錯誤。 每個日曆只對一段年份作答:pl 自 2002 年起,us-federal 自 1960 年起,us-banking 自 2008 年起。工作日步驟如果到達這個範圍之前的日子,會以結束代碼 1 被拒絕,範圍之前的日期則顯示為未作判斷。如果日曆的假日上次對照法律檢查已超過一年,它所判斷的較晚日期會被標記為 calendar_outdated,因為可能缺少較新的假日。 |
--preset <id> --param id=value |
來自 presets/<id>.json 的具名時刻,其參數已填入。與步驟參數以及 --analyze 互斥,並且可與 --calendar、--zone 和 --format 組合使用。 |
--analyze <date> |
讀回一個貼上來的日期,並說明它是什麼。有歧義的 04/08 會以美國與波蘭兩種讀法同時顯示,而不是靠猜——這種歧義確實是錯誤回報出錯的一個來源。 |
--format <mask> |
同時用你自己的格式遮罩列印結果,使其與受測應用程式的格式一致:yyyy-MM-dd HH:mm:ss。標記區分大小寫,沿用 .NET 與 Java 的意義——M 是月,m 是分鐘。 |
--json |
為上述任何一種用法提供機器輸出(chronomock.calc/1),讓管線可以算出一個日期,並直接把它傳回 chrono run。 |
# 本季的最後一個工作日,使用美國銀行業日曆 chrono calc --base today --snap eoq --nearest pbd --calendar us-banking
chrono calc——結束代碼
單獨的一張更小的表:計算機沒有涵蓋判定可以回報。
| 代碼 | 意義 |
|---|---|
| 0 | 已算出 |
| 1 | 用法錯誤——參數錯誤、不可能存在的日期、超出範圍,或是日曆檔裡一個工作日都沒有 |
| 5 | 此版本未內建該步驟或單位,或是該步驟需要你沒有傳入的日曆。特意與用法錯誤分開,讓指令碼能區分「尚未支援」與「寫錯了」 |