Miri 是 Rust 的未定義行為偵測工具。它可以運行貨物項目的二進位和測試套件,並檢測未能滿足其安全要求的不安全代碼。例如:
unreachable_unchecked
,呼叫具有重疊範圍的copy_nonoverlapping
,...)bool
,或無效的枚舉判別式)最重要的是,Miri 還會告訴您有關內存洩漏的信息:當執行結束時仍有分配的內存,並且該內存無法從全局static
訪問時,Miri 將引發錯誤。
您可以使用 Miri 模擬其他目標上的程序,例如確保位元組級資料操作在小端和大端系統上都能正確運作。請參閱下面的交叉解釋。
Miri 已經發現了許多現實世界的錯誤。如果您發現 Miri 的錯誤,請告訴我們,我們將不勝感激,我們會將其添加到清單中!
預設情況下,Miri 確保完全確定性的執行並將程式與主機系統隔離。一些通常會存取主機的 API,例如收集隨機數產生器、環境變數和時鐘的熵,被確定性的「假」實作所取代。設定MIRIFLAGS="-Zmiri-disable-isolation"
以存取真實系統 API。 (特別是,「假」系統 RNG API 使 Miri不適合加密使用!請勿使用 Miri 產生金鑰。)
儘管如此,請注意,Miri不會捕捉程式中所有違反 Rust 規範的行為,尤其是因為沒有這樣的規範。 Miri 使用自己的近似值來描述 Rust 中什麼是未定義行為以及什麼不是未定義行為。據我們所知,Miri可以偵測到所有可能影響程式正確性的未定義行為(模錯誤),但您應該查閱參考資料以取得未定義行為的官方定義。 Miri 將使用 Rust 編譯器進行更新,以防止當前編譯器理解的 UB,但它不對 rustc 的未來版本做出任何承諾。
Miri 使用者應注意的進一步警告:
-Zrandomize-layout
來檢測其中一些情況。)-Zmiri-seed
值來運行 Miri 來在一定程度上緩解這種情況,但到目前為止仍然無法探索所有可能的執行。--target x86_64-unknown-linux-gnu
以獲得更好的支援。SeqCst
柵欄時,弱記憶體模擬可能會產生弱行為,而且它無法產生在真實硬體上可能觀察到的所有行為。而且,Miri根本無法保證你的程式碼是健全的。健全性是指從任意安全程式碼呼叫時,即使與其他健全程式碼結合使用,也不會導致未定義行為的屬性。相反,Miri 只能告訴您與程式碼互動的特定方式(例如,測試套件)是否會導致特定執行中的任何未定義行為(其中可能有很多,例如當並發或其他形式的非確定性時)參與) 。當 Miri 找到 UB 時,你的程式碼肯定是不健全的,但是當 Miri 沒有找到 UB 時,那麼你可能只需要測試更多的輸入或更多可能的非確定性選擇。
每晚透過rustup
在 Rust 上安裝 Miri:
rustup +nightly component add miri
以下所有指令假設 nightly 工具鏈是透過rustup override set nightly
固定的。或者,對以下每個命令使用cargo +nightly
。
現在您可以在 Miri 運行您的專案:
cargo miri test
。cargo miri run
透過Miri運行它。第一次執行 Miri 時,它將執行一些額外的設定並安裝一些依賴項。在安裝任何東西之前它會要求您確認。
cargo miri run/test
支援與cargo run/test
完全相同的標誌。例如, cargo miri test filter
僅運行名稱中包含filter
的測試。
您可以透過MIRIFLAGS
將標誌傳遞給 Miri。例如, MIRIFLAGS="-Zmiri-disable-stacked-borrows" cargo miri run
運作程式而不檢查引用的別名。
透過cargo miri
編譯程式碼時,會為將在 Miri 下解釋的程式碼設定cfg(miri)
配置標誌。您可以使用它來忽略在 Miri 下失敗的測試案例,因為它們執行 Miri 不支援的操作:
# [ test ]
# [ cfg_attr ( miri , ignore ) ]
fn does_not_work_on_miri ( ) {
tokio :: run ( futures :: future :: ok :: < _ , ( ) > ( ( ) ) ) ;
}
沒有辦法列出所有 Miri 不能做的無限事情,但是解釋器在發現不支援的東西時會明確告訴你:
error: unsupported operation: can't call foreign function: bind
...
= help: this is likely not a bug in the program; it indicates that the program
performed an operation that Miri does not support
Miri 不僅可以為您的主機目標運行二進位或測試套件,還可以對任意外部目標執行交叉解釋: cargo miri run --target x86_64-unknown-linux-gnu
將像 Linux 一樣運行您的程式程序,無論您的主機作業系統如何。如果您使用 Windows,這尤其有用,因為 Linux 目標比 Windows 目標得到更好的支援。
您也可以使用它來測試與主機平台具有不同屬性的平台。例如, cargo miri test --target s390x-unknown-linux-gnu
將在大端目標上運行測試套件,這對於測試端敏感程式碼非常有用。
執行的某些部分是由 Miri 隨機選擇的,例如儲存的確切基底位址分配以及並發執行緒的交錯。有時,探索多個不同的執行可能很有用,例如,確保您的程式碼不依賴新分配的偶然「超級對齊」並測試不同的執行緒交錯。這可以透過--many-seeds
標誌來完成:
cargo miri test --many-seeds # tries the seeds in 0..64
cargo miri test --many-seeds=0..16
預設的 64 個不同種子相當緩慢,因此您可能需要指定較小的範圍。
在 CI 上執行 Miri 時,使用以下程式碼片段透過 Miri 元件安裝夜間工具鏈:
rustup toolchain install nightly --component miri
rustup override set nightly
cargo miri test
以下是 GitHub Actions 的範例作業:
miri :
name : " Miri "
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- name : Install Miri
run : |
rustup toolchain install nightly --component miri
rustup override set nightly
cargo miri setup
- name : Test with Miri
run : cargo miri test
明確的cargo miri setup
有助於保持實際測試步驟的輸出乾淨。
Miri 不支援 Rust 支援的所有目標。然而,好消息是,無論您的主機作業系統/平台如何,都可以輕鬆使用--target
為任何目標運行程式碼!
以下目標在 CI 上進行了測試,因此應該始終有效(達到下面記錄的程度):
s390x-unknown-linux-gnu
作為我們的「選擇的大端目標」。linux
、 macos
或windows
所有其他目標,Miri 通常應該可以工作,但我們不做出任何承諾,也不對此類目標執行測試。solaris
/ illumos
:由@devnexen 維護。支援std::{env, thread, sync}
,但不支援std::fs
。freebsd
:需要維護者。支援std::env
和部分std::{thread, fs}
,但不支援std::sync
。android
:需要維護者。支援非常不完整,但基本的“hello world”可以工作。wasi
:需要維護者。支援非常不完整,甚至標準輸出都不起作用,但是一個空的main
函數可以起作用。main
函數之前就失敗。然而,即使對於我們支援的目標,不同目標對存取平台 API(例如檔案系統)的支援程度也有所不同:一般來說,Linux 目標具有最佳的支持,macOS 目標通常處於同等水平。 Windows 的支援不太好。
儘管它實現了 Rust 線路,但 Miri 本身是一個單線程解釋器。這意味著,在運行cargo miri test
時,由於固有的解釋器速度減慢和並行性損失,您可能會發現運行整個測試套件所需的時間急劇增加。
您可以透過執行cargo miri nextest run -jN
來恢復測試套件的並行性(請注意,您需要安裝cargo-nextest
)。這是有效的,因為cargo-nextest
收集所有測試的列表,然後為每個測試啟動單獨的cargo miri run
。您需要指定-j
或--test-threads
;預設情況下, cargo miri nextest run
一次執行一個測試。有關更多詳細信息,請參閱cargo-nextest
Miri 文件。
注意:這個每個行程一個測試的模型意味著, cargo miri test
能夠偵測到兩個測試在共享資源上競爭的資料競爭,但cargo miri nextest run
不會偵測到此類競爭。
注意: cargo-nextest
不支援 doctests,請參閱 nextest-rs/nextest#16
使用上述說明時,您可能會遇到許多令人困惑的編譯器錯誤。
RUST_BACKTRACE=1
環境變數運行以顯示回溯”當嘗試讓 Miri 顯示回溯時,您可能會看到這一點。預設情況下,Miri 不會向程式公開任何環境,因此執行RUST_BACKTRACE=1 cargo miri test
不會執行您期望的操作。
要獲得回溯,您需要使用-Zmiri-disable-isolation
禁用隔離:
RUST_BACKTRACE=1 MIRIFLAGS= " -Zmiri-disable-isolation " cargo miri test
std
由不相容版本的 rustc 編譯”您可能正在使用與用於建置 Miri 使用的自訂 libstd 的編譯器版本不同的編譯器版本來執行cargo miri
,而 Miri 無法偵測到這一點。嘗試運行cargo miri clean
。
-Z
標誌和環境變量Miri 添加了自己的一組-Z
標誌,這些標誌通常透過MIRIFLAGS
環境變數設定。我們首先記錄最相關和最常用的標誌:
-Zmiri-address-reuse-rate=
變更將釋放的非堆疊分配新增至池中以進行位址重用的機率,以及從池中取得新的非堆疊分配的機率。堆疊分配永遠不會添加到池中或從池中取出。預設值為0.5
。-Zmiri-address-reuse-cross-thread-rate=
更改嘗試重複使用先前釋放的記憶體區塊的分配也會考慮其他執行緒釋放的區塊的機率。預設值為0.1
,這表示預設值下,在 90% 嘗試重複使用位址的情況下,只會考慮來自相同執行緒的位址。重複使用另一個執行緒的位址會導致這些執行緒之間的同步,這可能會掩蓋資料爭用和弱記憶體錯誤。-Zmiri-compare-exchange-weak-failure-rate=
更改compare_exchange_weak
操作的失敗率。預設值為0.8
(因此 5 個弱操作中有 4 個會失敗)。您可以將其變更為0.0
和1.0
之間的任何值,其中1.0
表示它始終會失敗, 0.0
表示它永遠不會失敗。請注意,將其設為1.0
可能會導致掛起,因為這表示使用compare_exchange_weak
的程式無法取得進展。-Zmiri-disable-isolation
停用主機隔離。因此,程式可以存取主機資源,例如環境變數、檔案系統和隨機性。-Zmiri-disable-leak-backtraces
禁用記憶體洩漏的回溯報告。預設情況下,建立每個分配時都會捕獲回溯,以防洩漏。這會產生一些記憶體開銷來儲存幾乎從未使用過的資料。該標誌由-Zmiri-ignore-leaks
暗示。-Zmiri-env-forward=
將var
環境變數轉送到解釋程式。可以多次使用來轉發多個變數。如果轉發變數的值保持不變,執行仍然是確定性的。如果設定了-Zmiri-disable-isolation
則無效。-Zmiri-env-set==
將var
環境變數設定為解釋程序中的value
。它可用於傳遞環境變量,而無需更改主機環境。它可以多次使用來設定多個變數。如果設定了-Zmiri-disable-isolation
或-Zmiri-env-forward
,則使用此選項設定的值將優先於主機環境中的值。-Zmiri-ignore-leaks
禁用記憶體洩漏檢查器,並且還允許主線程退出時一些剩餘線程存在。-Zmiri-isolation-error=
配置 Miri 在啟用隔離時對需要主機存取的操作的回應。 abort
、 hide
、 warn
和warn-nobacktrace
是支援的操作。預設是abort
,這會停止機器。某些(但不是全部)操作也支援繼續執行,並向程式傳回「權限被拒絕」錯誤。 warn
每次發生時都會印出完整的回溯; warn-nobacktrace
不太詳細,每次操作最多顯示一次。 hide
完全隱藏警告。-Zmiri-num-cpus
表示 miri 報告的可用 CPU 數量。預設情況下,可用 CPU 數量為1
。請注意,此標誌不會影響 miri 以任何方式處理線程的方式。-Zmiri-permissive-provenance
停用整數到指標轉換和ptr::with_exposed_provenance
的警告。這必然會錯過一些錯誤,因為這些操作在清理程序中無法有效且準確地實現,但它只會錯過與受這些操作影響的記憶體/指標相關的錯誤。-Zmiri-preemption-rate
配置在基本區塊結束時,活動線程被搶佔的機率。預設值為0.01
(即 1%)。將其設為0
將禁用搶佔。-Zmiri-report-progress
使 Miri 時不時地列印當前的堆疊跟踪,這樣您就可以知道當程式繼續運行時它正在做什麼。您可以透過-Zmiri-report-progress=
自訂列印報告的頻率,每 N 個基本區塊列印一次報告。-Zmiri-seed=
配置 Miri 用於解決非確定性問題的 RNG 種子。此 RNG 用於選擇分配的基底位址,確定compare_exchange_weak
的搶佔和失敗,以及控制弱記憶體模擬的儲存緩衝。當啟用隔離(預設)時,這也用於模擬系統熵。預設種子為 0。-Zmiri-strict-provenance
可在美里進行嚴格的出處檢查。這意味著將整數轉換為指標會產生出處「無效」的結果,即出處不能用於任何記憶體存取。-Zmiri-symbolic-alignment-check
使對齊檢查更加嚴格。預設情況下,透過將指標轉換為整數並確保它是對齊方式的倍數來檢查對齊方式。這可能會導致程式純粹偶然通過對齊檢查,因為事情「碰巧」充分對齊——此執行中沒有 UB,但其他執行中會有 UB。為了避免這種情況,符號對齊檢查僅考慮相關分配的請求對齊以及該分配的偏移量。這可以避免遺漏此類錯誤,但當程式碼執行手動整數運算以確保對齊時,也會產生一些誤報。 (標準庫的align_to
方法在兩種模式下都可以正常工作;在符號對齊下,當分配保證足夠的對齊時,它僅填充中間切片。)其餘標誌僅供進階使用,並且更有可能更改或刪除。其中一些是不健全的,這意味著它們可能導致 Miri 無法檢測程式中未定義行為的情況。
-Zmiri-disable-alignment-check
停用檢查指向對齊,因此您可以專注於其他故障,但這意味著 Miri 可能會錯過程式中的錯誤。使用這個標誌是不合理的。-Zmiri-disable-data-race-detector
停用資料競爭檢查。使用這個標誌是不合理的。這意味著-Zmiri-disable-weak-memory-emulation
。-Zmiri-disable-stacked-borrows
禁用檢查實驗別名規則以追蹤借用(堆疊借用和樹借用)。這可以使 Miri 運行得更快,但這也意味著不會偵測到別名衝突。使用此標誌是不合理的(但受影響的健全性規則是實驗性的)。後面的標誌優先:可以透過-Zmiri-tree-borrows
重新啟動借用追蹤。-Zmiri-disable-validation
停用強制執行預設強制執行的有效性不變量。這對於首先關注其他故障(例如越界訪問)非常有用。設定此標誌意味著 Miri 可以錯過程式中的錯誤。然而,這也有助於讓 Miri 跑得更快。使用這個標誌是不合理的。-Zmiri-disable-weak-memory-emulation
禁用某些 C++11 弱記憶體效應的模擬。-Zmiri-native-lib=
是一個實驗性標誌,用於為透過 FFI 從解釋器內部呼叫本機函數提供支援。該文件未提供的功能仍透過常用的 Miri 墊片執行。警告:如果指定了無效/不正確的.so
文件,這可能會導致 Miri 本身出現未定義的行為!當然,Miri 無法對本機程式碼所採取的操作進行任何檢查。請注意,Miri 有自己的文件描述符處理方式,因此如果您想要替換某些處理文件描述符的函數,則必須替換所有函數,否則兩種文件描述符將會混淆。這是正在進行中的工作;目前,僅支援整數參數和傳回值(不,用於解決此限制的指標/整數強制轉換將不起作用;它們將嚴重失敗)。目前它也僅適用於 Unix 主機。-Zmiri-measureme=
啟用解釋程序的measureme
分析。這可用於查找程式的哪些部分在 Miri 下執行緩慢。設定檔被寫入名為
的目錄中的文件,並且可以使用儲存庫 https://github.com/rust-lang/measureme 中的工具進行處理。-Zmiri-mute-stdout-stderr
默默地忽略對 stdout 和 stderr 的所有寫入,但向程式報告它實際寫入的情況。當您對實際程式的輸出不感興趣而只想查看 Miri 的錯誤和警告時,這非常有用。-Zmiri-recursive-validation
是一個高度實驗性的標誌,使有效性檢查在引用下面遞歸。-Zmiri-retag-fields[=]
控制何時堆疊借用重新標記遞歸到欄位。 all
表示它總是遞歸(預設值,相當於沒有顯式值的-Zmiri-retag-fields
), none
表示它從不遞歸, scalar
表示它只對我們也會在產生的 LLVM IR 中發出noalias
註釋的類型進行遞歸(類型作為單一標量或標量對傳遞)。將其設為none
是不合理的。-Zmiri-provenance-gc=
配置指標來源垃圾收集器運作的頻率。預設是每10000
個基本區塊搜尋並刪除一次無法到達的來源。將其設為0
會停用垃圾收集器,這會導致某些程式出現爆炸性記憶體使用和/或超線性運行時。-Zmiri-track-alloc-accesses
不僅顯示追蹤分配的分配和空閒事件,還顯示讀取和寫入。-Zmiri-track-alloc-id=,,...
顯示分配或釋放給定分配時的回溯。這有助於調試記憶體洩漏和釋放後使用錯誤。多次指定此參數不會覆寫先前的值,而是將其值附加到清單中。多次列出一個 id 沒有效果。-Zmiri-track-pointer-tag=,,...
當創建給定的指標標籤時以及當(如果有的話)從借用堆疊(這是標籤變成的位置)彈出它時顯示回溯無效,以後使用它將會出錯)。這可以幫助您找出發生 UB 的原因以及程式碼中的何處是查找它的好地方。多次指定此參數不會覆寫先前的值,而是將其值附加到清單中。多次列出一個標籤沒有任何效果。-Zmiri-track-weak-memory-loads
當弱記憶體模擬從載入傳回過時的值時顯示回溯。這可以幫助診斷在-Zmiri-disable-weak-memory-emulation
下消失的問題。-Zmiri-tree-borrows
用樹借用規則替換堆疊借用規則。樹形借用比堆疊借用更具實驗性。雖然 Tree Borrows 在捕獲當前版本的編譯器可能利用的所有別名違規方面仍然是合理的,但 Rust 的最終別名模型很可能會比 Tree Borrows 更嚴格。換句話說,如果您使用 Tree Borrows,即使您的程式碼今天被接受,將來也可能會被聲明為 UB。對於堆疊借用來說,這種情況的可能性要小得多。-Zmiri-force-page-size=
覆蓋架構的預設頁面大小,以 1k 的倍數表示。 4
是大多數目標的預設值。該值應始終為 2 的冪且非零。-Zmiri-unique-is-unique
對core::ptr::Unique
執行額外的別名檢查,以確保理論上它可以被視為noalias
。該標誌是實驗性的,僅在與-Zmiri-tree-borrows
一起使用時才有效。一些本機 rustc -Z
標誌也與 Miri 非常相關:
-Zmir-opt-level
控制執行多少次 MIR 最佳化。 Miri 覆蓋預設值0
;請注意,使用任何更高的等級都可能使 Miri 錯過程式中的錯誤,因為它們已被優化掉。-Zalways-encode-mir
使 rustc 轉儲 MIR,甚至對於完全單態的函數也是如此。這是 Miri 可以執行此類函數所必需的,因此 Miri 預設會設定此標誌。-Zmir-emit-retag
控制是否發出Retag
語句。 Miri 預設啟用此功能,因為堆疊借用和樹借用需要它。此外,Miri 還可以辨識一些環境變數:
MIRIFLAGS
定義了傳遞給 Miri 的額外標誌。MIRI_LIB_SRC
定義 Miri 期望建構並用於解釋的標準庫來源的目錄。該目錄必須指向rust-lang/rust
儲存庫簽出的library
子目錄。MIRI_SYSROOT
指示要使用的 sysroot。當使用cargo miri test
/ cargo miri run
時,這會跳過自動設定 - 僅當您不想使用自動建立的 sysroot 時才設定此項目。當呼叫cargo miri setup
時,這指示 sysroot 將放置的位置。MIRI_NO_STD
確保目標的 sysroot 是在沒有 libstd 的情況下建構的。這允許測試和運行 no_std 程式。通常不應使用此方法; Miri 有一種啟發式方法,可以根據目標名稱偵測非標準目標。在支援 libstd 的目標上設定此選項可能會導致令人困惑的結果。 extern
函數Miri 提供了一些extern
函數,程式可以匯入這些函數來存取 Miri 特定的功能。它們在 /tests/utils/miri_extern.rs 中聲明。
不使用標準庫的二進位檔案應該聲明這樣的函數,以便 Miri 知道它應該從哪裡開始執行:
# [ cfg ( miri ) ]
# [ no_mangle ]
fn miri_start ( argc : isize , argv : * const * const u8 ) -> isize {
// Call the actual start function that your project implements, based on your target's conventions.
}
如果你想為美里做出貢獻,那就太好了!請查看我們的貢獻指南。
如需執行 Miri 的協助,您可以在 GitHub 上提出問題或使用 Rust Zulip 上的 Miri 串流。
該計畫始於 2015 年薩斯喀徹溫大學@solson 本科生研究課程的一部分。該項目提供了幻燈片和報告。 2016 年,@oli-obk 加入準備 Miri 最終在 Rust 編譯器本身中用作 const 求值器(基本上,用於const
和static
內容),取代直接在 AST 上工作的舊求值器。 2017 年,@RalfJung 在 Mozilla 實習,開始開發 Miri 作為檢測未定義行為的工具,並使用 Miri 來探索 Rust 中未定義行為的各種可能定義的後果。 @oli-obk 將 Miri 引擎移入編譯器的工作終於在 2018 年初完成。的別名限制。
Miri 已經在 Rust 標準函式庫及其他版本中發現了許多錯誤,我們在這裡收集了其中一些錯誤。如果 Miri 幫助您在程式碼中發現了一個微妙的 UB bug,我們將不勝感激將其添加到清單中的 PR!
發現的明確錯誤:
Debug for vec_deque::Iter
存取未初始化的內存Vec::into_iter
執行未對齊的 ZST 讀取From<&[T]> for Rc
建立未充分對齊的引用BTreeMap
建立一個指向太小的分配的共享引用Vec::append
建立懸空引用str
將共用引用轉換為可變引用rand
執行未對齊讀取posix_memalign
getrandom
以無效方式呼叫getrandom
系統調用Vec
和BTreeMap
在某些(恐慌)條件下洩漏內存beef
洩漏內存EbrCell
錯誤地使用未初始化的內存servo_arc
建立懸空共享引用encoding_rs
進行越界指標算術Vec::from_raw_parts
AtomicPtr
和Box::from_raw_in
的文件測試不正確ThinVec
中的對齊不足crossbeam-epoch
在部分初始化的MaybeUninit
上呼叫假設assume_init
integer-encoding
取消引用未對齊的指針rkyv
從過度對齊的分配構造Box<[u8]>
arc-swap
中的數據競爭thread::scope
中的資料競爭regex
錯誤地處理未對齊的Vec
緩衝區once_cell
中錯誤使用compare_exchange_weak
vec::IntoIter
中刪除未對齊的指針Iterator::collect
新專業化中處理錯誤的佈局portable-atomic-util
中高度對齊類型的偏移計算不正確std::mpsc
通道中偶爾發生記憶體洩漏(crossbeam 中的原始程式碼)違反 Stacked Borrows 發現可能是錯誤(但 Stacked Borrows 目前只是實驗):
VecDeque::drain
建立重疊的可變引用BTreeMap
問題BTreeMap
迭代器建立與共享引用重疊的可變引用BTreeMap::iter_mut
建立重疊的可變引用BTreeMap
節點LinkedList
遊標插入建立重疊的可變引用Vec::push
使向量中的現有引用無效align_to_mut
違反可變引用的唯一性sized-chunks
建立別名可變引用String::push_str
使字串中的現有引用無效ryu
在其有效記憶體區域之外使用原始指針Env
迭代器在其有效記憶體區域之外使用原始指針VecDeque::iter_mut
建立重疊的可變引用<[T]>::copy_within
在無效貸款後使用貸款已獲得以下任一許可
由您選擇。
除非您另有明確說明,否則您有意提交的包含在作品中的任何貢獻均應獲得上述雙重許可,無任何附加條款或條件。