ORC 是一款用於尋找 OSX 工具鏈上違反 C++ 單一定義規則的工具。
ORC 是 DWARF 的翻版,而 DWARF 則是 ELF 的翻版。 ORC 是一個縮寫; O代表 ODR,諷刺的是, R和C代表多個(可能是衝突的)單字。
有許多關於單一定義規則 (ODR) 的文章,包括 C++ 標準本身。該規則的要點是,如果一個符號在程式中定義,則只允許定義一次。某些符號被授予此規則的例外,並且允許被多次定義。但是,這些符號必須由相同的標記序列定義。
請注意,某些編譯器設定也會影響令牌序列 - 例如,啟用或停用 RTTI 可能會改變符號的定義(在本例中為類別的 vtable)。
任何違反上述規則的符號均屬於 ODR 違規 (ODRV)。在某些情況下,連結器可能會捕獲重複的符號定義並發出警告或錯誤。然而,標準規定連結器不需要這樣做。 Andy G 描述得很好:
出於效能原因,C++ 標準規定,如果違反了範本的“單一定義規則”,則該行為將是未定義的。由於連結器不在乎,因此違反此規則的行為不會發生。來源
非模板 ODRV 是可能的,而且連結器也可能同樣對它們保持沉默。
ODRV 通常意味著您有一個符號,其二進位佈局因建置它的編譯單元而異。然而,由於該規則,當連結器遇到多個定義時,可以自由選擇其中任何一個並將其用作符號的二進位佈局。當所選佈局與編譯單元中符號的內部二進位佈局不符時,行為未定義。
通常,偵錯器在這些場景中毫無用處。它還將為整個程式使用符號的單一定義,當您嘗試偵錯 ODRV 時,偵錯器可能會提供錯誤的數據,或指向檔案中看起來不正確的位置。最後,調試器似乎在對您撒謊,但默默地不提供有關根本問題是什麼的任何線索。
與所有錯誤一樣,ODRV 需要時間來修復,那麼為什麼要修復經過測試(並且可能有效)的程式碼中的 ODR 違規呢?
ORC 是一個執行以下操作的工具:
除非工具中有錯誤,否則 ORC 不會產生誤報。它報告的任何內容都是 ODRV。
目前,ORC 並未偵測到所有可能違反單一定義規則的情況。我們希望隨著時間的推移,能夠擴展和改進它所能捕捉的內容。在此之前,這意味著雖然 ORC 是一項有價值的檢查,但乾淨的掃描並不能保證程式沒有 ODRV。
ORC 可以找到:
關於 vtable 的註解:ORC 將偵測位於不同槽中的虛擬方法。 (這是一種令人討厭的損壞程序。)此時,它不會檢測到具有虛擬方法的類,該虛擬方法是違反 ODR 的重複類的「超集」。
除了主要的 ORC 來源之外,我們還嘗試提供一系列範例應用程序,其中包含該工具應捕獲的 ODRV。
ORC 最初是在 macOS 上構思的。雖然其當前的實作集中於此,但它不必局限於該工具鏈。
ORC 由 cmake 管理,並使用 CMake 管理專案的典型建置約定進行建置:
mkdir build
cd build
cmake -GXcode ..
為了測試目的,ORC 整合到了一些範例應用程式中。這些可以透過 Xcode 中的目標彈出視窗進行選擇。
ORC 使用 Tracy 作為其選擇的分析工具,並且預設為啟用狀態。若要停用 Tracy,請指定 cmake 命令列,如下所示:
cmake .. -GXcode -DTRACY_ENABLE=OFF
即使停用分析,也需要 Tracy 依賴項( OFF
在運行時編譯ON
)。在缺少選項的情況下重新運行命令列呼叫將導致使用其先前的值。
ORC 可以直接從命令列調用,或在連結器步驟中插入工具鏈中。輸出不變;這只是您工作流程中的便利問題。
如果您有連結器命令及其參數,並且想要與實際建置分開搜尋 ODRV,則此模式非常有用。
設定檔(見下文)
'forward_to_linker' = false
'standalone_mode' = false
您需要來自 XCode 的ld
命令列參數。使用Xcode構建,(如果無法鏈接,ORC無法幫助),複製鏈接命令,並將其貼上到ORC調用之後。像這樣的東西:
/path/to/orc /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -target ... Debug/lem_mac
(這是一個巨大的命令行,此處縮寫。)
ORC 將執行並將 ODR 違規記錄到控制台。
如果您有一個供 ORC 處理的庫文件列表,它也可以做到這一點。
設定檔(見下文)
'forward_to_linker' = false
'standalone_mode' = true
在此模式下,只需將庫文件清單傳遞給 ORC 進行處理即可。
設定檔(見下文)
'forward_to_linker' = true
'standalone_mode' = false
若要在 Xcode 建置專案中使用 ORC,請使用 ORC 腳本的完全限定路徑覆寫下列變數:
"LIBTOOL": "/absolute/path/to/orc",
"LDTOOL": "/absolute/path/to/orc",
"ALTERNATE_LINKER": "/absolute/path/to/orc",
完成這些設定後,Xcode 應該使用 ORC 作為專案建置的libtool
和ld
階段的工具。由於forward_to_linker
設置,ORC將調用適當的連結工具來產生二進位。一旦完成,ORC 將開始掃描。
除其他設定外,ORC 可配置為在偵測到 ODRV 時退出或僅發出警告。
ORC 將遍歷目前目錄,尋找名為以下之一的設定檔:
.orc-config
,或_orc-config
如果找到的話,很多開關都可以控制ORC的邏輯。請參閱儲存庫中的_orc_config
以取得範例。 ORC 偏好.orc-config
因此複製原始_orc_config
並在.orc-config
中本地更改值很簡單。
例如:
error: ODRV (structure:byte_size); conflict in `object`
compilation unit: a.o:
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
calling_convention: pass by value; 5 (0x5)
name: object
byte_size: 4 (0x4)
compilation unit: main.o:
definition location: /Volumes/src/orc/extras/struct0/src/main.cpp:3
calling_convention: pass by value; 5 (0x5)
name: object
byte_size: 1 (0x1)
structure:byte_size
被稱為 ODRV 類別,並詳細說明了該錯誤代表的違規類型。然後,輸出發生衝突的兩個編譯單元以及導致衝突的 DWARF 資訊。
struct object { ... }
In ao:
和In main.o
是不匹配的 2 個目標檔案或存檔。 ODR 可能是由於編譯這些檔案時編譯或 #define 設定不符造成的。 byte_size
是導致錯誤的實際值。
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
該物件是在哪一行和a.cpp
中聲明的。
對於相同版本的 ORC 和相同的輸入,ORC 將始終寫入相同的輸出。其中「相同」是位元組相同,且 diff 工具不會顯示任何差異。
在高度多執行緒的應用程式中,實現(並可能保持)一致的輸出非常具有挑戰性。
但請記住,這不適用於不同版本的 ORC。 ORC 的變化幾乎肯定會導致輸出變化。
也不能保證輸入檔中的「小」變更將保證 ORC 輸出中的「小」變更。這種行為是可取的,並且可能是未來改進的一個領域。
orc_test
)提供了一個單元測試應用程式來確保 ORC 捕獲了想要捕獲的內容。 orc_test
引入了一個微型“構建系統”,用於從已知來源生成目標文件,以產生已知的 ODR 違規。然後,它使用與 ORC 命令列工具相同的引擎處理物件文件,並將結果與預期的 ODRV 報告清單進行比較。
電池中的每個單元測試都是離散的,並且包含:
odrv_test.toml
,描述測試參數的高階 TOML 文件一般來說,單一測試應該引發單一 ODR 違規,但這可能並不適用於所有情況。
這些檔案是標準 C++ 原始檔。它們的數量和尺寸應該非常小——僅足夠大以產生預期的 ODRV。
odrv_test.toml
文件設定檔向測試應用程式描述需要編譯哪些來源、測試應使用哪些編譯標誌以及由於連結產生的目標檔案而係統需要監視哪些 ODRV ) 一起。
測試來源使用[[source]]
指令指定:
[[ source ]]
path = " one.cpp "
obj = " one "
flags = [
" -Dfoo=1 "
]
path
欄位描述相對於odrv_test.toml
的檔案路徑。這是唯一必填欄位。
obj
欄位指定要建立的(臨時)物件檔案的名稱。如果省略此名稱,將使用偽隨機名稱。
flags
欄位指定專門用於該編譯單元的編譯標誌。使用此字段,可以重複使用具有不同編譯標誌的相同原始檔案來引發 ODRV。
ODRV 使用[[odrv]]
指令指定:
[[ odrv ]]
category = " subprogram:vtable_elem_location "
linkage_name = " _ZNK6object3apiEv "
category
欄位描述了測試應用程式應該發現的特定 ODR 違規類型。
linkage_name
欄位描述了導致 ODRV 的特定符號。目前尚未使用,但隨著測試應用程式的成熟將強制執行。
以下標誌目前尚未使用,或隨著單元測試應用程式的不斷成熟,這些標誌將發生重大變化。
[compile_flags]
:一系列應套用於單元測試中每個原始檔的編譯標誌。
[orc_test_flags]
:傳遞給測試應用程式以進行此測試的一系列執行時間設定。
[orc_flags]
:傳遞給 ORC 引擎進行此測試的一系列執行時間設定。