ORC ist ein Tool zum Auffinden von Verstößen gegen die One Definition Rule von C++ in der OSX-Toolchain.
ORC ist eine Anspielung auf DWARF und wiederum eine Anspielung auf ELF. ORC ist ein Akronym; Während das O für ODR steht, stehen R und C ironischerweise für mehrere (möglicherweise widersprüchliche) Wörter.
Es gibt viele Artikel über die One Definition Rule (ODR), einschließlich des C++-Standards selbst. Der Kern der Regel besteht darin, dass ein in einem Programm definiertes Symbol nur einmal definiert werden darf. Für einige Symbole gilt eine Ausnahme von dieser Regel und sie dürfen mehrfach definiert werden. Diese Symbole müssen jedoch durch identische Token-Sequenzen definiert werden.
Beachten Sie, dass sich einige Compilereinstellungen auch auf Token-Sequenzen auswirken können. Beispielsweise kann die Aktivierung oder Deaktivierung von RTTI die Definition eines Symbols (in diesem Fall der vtable einer Klasse) ändern.
Jedes Symbol, das gegen die obige Regel verstößt, stellt einen ODR-Verstoß (ODRV) dar. In einigen Fällen kann der Linker die doppelte Symboldefinition abfangen und eine Warnung oder einen Fehler ausgeben. Der Standard besagt jedoch, dass ein Linker dazu nicht verpflichtet ist. Andy G beschreibt es gut:
Aus Leistungsgründen schreibt der C++-Standard vor, dass das Verhalten einfach undefiniert ist, wenn Sie in Bezug auf Vorlagen gegen die One-Definition-Regel verstoßen. Da es dem Linker egal ist, sind Verstöße gegen diese Regel still. Quelle
Nicht-Vorlagen-ODRVs sind möglich, und der Linker schweigt möglicherweise auch darüber.
Ein ODRV bedeutet normalerweise, dass Sie über ein Symbol verfügen, dessen Binärlayout je nach Kompilierungseinheit, die es erstellt hat, unterschiedlich ist. Aufgrund der Regel steht es einem Linker jedoch frei, wenn er auf mehrere Definitionen trifft, eine davon auszuwählen und sie als binäres Layout für ein Symbol zu verwenden. Wenn das ausgewählte Layout nicht mit dem internen binären Layout des Symbols in einer Kompilierungseinheit übereinstimmt, ist das Verhalten undefiniert.
In solchen Fällen ist der Debugger oft nutzlos. Auch hier wird eine einzige Symboldefinition für das gesamte Programm verwendet, und wenn Sie versuchen, ein ODRV zu debuggen, liefert Ihnen der Debugger möglicherweise fehlerhafte Daten oder verweist auf eine Position in einer Datei, die nicht korrekt erscheint. Am Ende wird es so aussehen, als ob der Debugger Sie anlügt, Ihnen aber insgeheim keine Hinweise auf das zugrunde liegende Problem gibt.
Wie alle Fehler dauert die Behebung von ODRVs Zeit. Warum sollten Sie also einen ODR-Verstoß in getestetem (und vermutlich funktionierendem) Code beheben?
ORC ist ein Tool, das Folgendes ausführt:
Sofern kein Fehler im Tool vorliegt, generiert ORC keine Fehlalarme. Alles, was es meldet, ist ein ODRV.
Derzeit erkennt ORC nicht alle möglichen Verstöße gegen die One-Definition-Regel. Wir hoffen, dass wir mit der Zeit das, was damit möglich ist, erweitern und verbessern können. Bis dahin bedeutet dies, dass ORC zwar eine wertvolle Prüfung ist, ein sauberer Scan jedoch keine Garantie dafür ist, dass ein Programm frei von ODRVs ist.
ORC kann finden:
Ein Hinweis zu vtables: ORC erkennt virtuelle Methoden, die sich in verschiedenen Slots befinden. (Das ist ein äußerst korruptes Programm.) Zu diesem Zeitpunkt wird keine Klasse erkannt, die über virtuelle Methoden verfügt, die eine „Obermenge“ einer ODR-verletzenden doppelten Klasse darstellen.
Zusätzlich zu den wichtigsten ORC-Quellen versuchen wir, eine Reihe von Beispielanwendungen bereitzustellen, die ODRVs enthalten, die das Tool abfangen soll.
ORC wurde ursprünglich für macOS konzipiert. Obwohl die aktuelle Implementierung auf diesen Bereich ausgerichtet ist, muss sie nicht auf diese Toolchain beschränkt sein.
ORC wird von cmake verwaltet und unter Verwendung der typischen Build-Konventionen eines von CMake verwalteten Projekts erstellt:
mkdir build
cd build
cmake -GXcode ..
Es gibt eine Handvoll Beispielanwendungen, in die ORC zu Testzwecken integriert wird. Diese können über das Targets-Popup in Xcode ausgewählt werden.
ORC verwendet Tracy als bevorzugtes Profilierungstool und ist standardmäßig aktiviert . Um Tracy zu deaktivieren, geben Sie die cmake-Befehlszeile wie folgt an:
cmake .. -GXcode -DTRACY_ENABLE=OFF
Die Tracy-Abhängigkeit ist auch dann erforderlich, wenn die Profilerstellung deaktiviert ist (sie wird aus der Laufzeit kompiliert). Beachten Sie, dass diese Option zwischengespeichert wird, sodass Sie sie explizit auf OFF
oder ON
schalten müssen. Wenn Sie den Befehlszeilenaufruf erneut ausführen, während die Option fehlt, wird der vorherige Wert verwendet.
ORC kann direkt über die Befehlszeile aufgerufen oder im Linker-Schritt in die Toolkette eingefügt werden. Die Ausgabe bleibt unverändert; Es ist einfach eine Frage der Bequemlichkeit Ihres Arbeitsablaufs.
Dieser Modus ist nützlich, wenn Sie über den Linker-Befehl und seine Argumente verfügen und getrennt vom eigentlichen Build nach ODRVs suchen möchten.
Konfigurationsdatei (siehe unten)
'forward_to_linker' = false
'standalone_mode' = false
Sie benötigen die ld
Befehlszeilenargumente von XCode. Erstellen Sie mit Xcode (wenn Sie keine Verknüpfung herstellen können, kann ORC nicht helfen), kopieren Sie den Verknüpfungsbefehl und fügen Sie ihn nach dem ORC-Aufruf ein. Etwas wie:
/path/to/orc /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -target ... Debug/lem_mac
(Es handelt sich um eine riesige Befehlszeile, hier abgekürzt.)
ORC führt ODR-Verstöße aus und protokolliert sie in der Konsole.
Wenn Sie eine Liste von Bibliotheksdateien haben, die ORC verarbeiten soll, ist dies ebenfalls möglich.
Konfigurationsdatei (siehe unten)
'forward_to_linker' = false
'standalone_mode' = true
In diesem Modus übergeben Sie einfach eine Liste von Bibliotheksdateien zur Verarbeitung an ORC.
Konfigurationsdatei (siehe unten)
'forward_to_linker' = true
'standalone_mode' = false
Um ORC in Ihrem Xcode-Build-Projekt zu verwenden, überschreiben Sie die folgenden Variablen mit einem vollständig qualifizierten Pfad zu den ORC-Skripten:
"LIBTOOL": "/absolute/path/to/orc",
"LDTOOL": "/absolute/path/to/orc",
"ALTERNATE_LINKER": "/absolute/path/to/orc",
Wenn diese Einstellungen vorhanden sind, sollte Xcode ORC als Tool sowohl für libtool
als auch für ld
Phase der Projekterstellung verwenden. Aufgrund der Einstellung forward_to_linker
ruft ORC das richtige Link-Tool auf, um eine Binärdatei zu erstellen. Sobald dies abgeschlossen ist, beginnt ORC mit dem Scan.
ORC kann unter anderem so konfiguriert werden, dass es beendet wird oder lediglich eine Warnung ausgibt, wenn ein ODRV erkannt wird.
ORC durchsucht das aktuelle Verzeichnis und sucht nach einer Konfigurationsdatei mit dem folgenden Namen:
.orc-config
, oder_orc-config
Wenn sie gefunden werden, können viele Schalter die Logik des ORC steuern. Beispiele finden Sie in der _orc_config
im Repository. ORC bevorzugt .orc-config
daher ist es einfach, die ursprüngliche _orc_config
zu kopieren und Werte lokal in .orc-config
zu ändern.
Zum Beispiel:
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
ist als ODRV-Kategorie bekannt und beschreibt genau, welche Art von Verstoß dieser Fehler darstellt. Anschließend werden die beiden im Konflikt stehenden Kompilierungseinheiten zusammen mit den DWARF-Informationen ausgegeben, die zur Kollision geführt haben.
struct object { ... }
In ao:
und In main.o
befinden sich die beiden Objektdateien oder Archive, die nicht übereinstimmen. Die ODR wird wahrscheinlich durch nicht übereinstimmende Kompilierungs- oder #define-Einstellungen beim Kompilieren dieser Archive verursacht. byte_size
ist der tatsächliche Wert, der einen Fehler verursacht.
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
In welcher Zeile und Datei das Objekt deklariert wurde. In diesem Beispiel also Zeile 3 von a.cpp
.
Für dieselbe ORC-Version und dieselbe Eingabe schreibt ORC immer dieselbe Ausgabe. Wobei „das Gleiche“ byteidentisch ist und ein Diff-Tool keine Unterschiede anzeigt.
Das Erreichen (und wahrscheinlich auch das Aufrechterhalten) einer konsistenten Ausgabe ist in einer Anwendung mit hohem Multithread-Gehalt eine überraschende Herausforderung.
Bitte beachten Sie jedoch, dass dies NICHT für verschiedene Versionen von ORC gilt. Änderungen an ORC werden mit ziemlicher Sicherheit zu Ausgabeänderungen führen.
Es gibt auch keine Garantie dafür, dass eine „kleine“ Änderung in den Eingabedateien eine „kleine“ Änderung in der ORC-Ausgabe garantiert. Dieses Verhalten ist wünschenswert und wird wahrscheinlich ein Bereich für zukünftige Verbesserungen sein.
orc_test
) Es wird eine Unit-Test-Anwendung bereitgestellt, um sicherzustellen, dass ORC das fängt, was es zu fangen vorgibt. orc_test
führt ein Miniatur-„Build-System“ ein, um Objektdateien aus bekannten Quellen zu generieren, um bekannte ODR-Verstöße zu erzeugen. Anschließend verarbeitet es die Objektdateien mit derselben Engine wie das ORC-Befehlszeilentool und vergleicht die Ergebnisse mit einer erwarteten ODRV-Berichtsliste.
Jeder Komponententest in der Batterie ist diskret und enthält:
odrv_test.toml
, eine TOML-Datei auf hoher Ebene, die die Parameter des Tests beschreibtIm Allgemeinen sollte ein einzelner Test einen einzelnen ODR-Verstoß hervorrufen, dies ist jedoch möglicherweise nicht in allen Fällen möglich.
Bei diesen Dateien handelt es sich um Standard-C++-Quelldateien. Ihre Menge und Größe sollte sehr gering sein – nur so groß, dass sie den beabsichtigten ODRV verursachen.
odrv_test.toml
Die Einstellungsdatei beschreibt der Testanwendung, welche Quellen kompiliert werden müssen, welche Kompilierungsflags für den Test verwendet werden sollen und nach welchen ODRVs das System als Ergebnis der Verknüpfung der generierten Objektdateien Ausschau halten muss ) zusammen.
Testquellen werden mit einer [[source]]
Direktive angegeben:
[[ source ]]
path = " one.cpp "
obj = " one "
flags = [
" -Dfoo=1 "
]
Das path
beschreibt einen Pfad zur Datei relativ zu odrv_test.toml
. Es ist das einzige Pflichtfeld.
Das obj
Feld gibt den Namen der zu erstellenden (temporären) Objektdatei an. Wenn dieser Name weggelassen wird, wird ein pseudozufälliger Name verwendet.
Das flags
-Feld gibt Kompilierungsflags an, die speziell für diese Kompilierungseinheit verwendet werden. Mit diesem Feld ist es möglich, dieselbe Quelldatei mit unterschiedlichen Kompilierungsflags wiederzuverwenden, um einen ODRV auszulösen.
ODRVs werden mit der [[odrv]]
Direktive angegeben:
[[ odrv ]]
category = " subprogram:vtable_elem_location "
linkage_name = " _ZNK6object3apiEv "
Das category
beschreibt die spezifische Art von ODR-Verstoß, mit der die Test-App rechnen sollte.
Das Feld linkage_name
beschreibt das spezifische Symbol, das den ODRV verursacht hat. Es ist derzeit ungenutzt, wird jedoch mit zunehmender Reife der Test-App erzwungen.
Die folgenden Flags werden derzeit nicht verwendet oder werden im Zuge der Weiterentwicklung der Unit-Test-App erheblichen Änderungen unterzogen.
[compile_flags]
: Eine Reihe von Kompilierungsflags, die auf jede Quelldatei im Komponententest angewendet werden sollten.
[orc_test_flags]
: Eine Reihe von Laufzeiteinstellungen, die für diesen Test an die Test-App übergeben werden.
[orc_flags]
: Eine Reihe von Laufzeiteinstellungen, die für diesen Test an die ORC-Engine übergeben werden.