UB 2017. Teil 1

Originalautor: Pascal Cuoq und John Regehr
  • Übersetzung
Vom Übersetzer:
Die Übersetzungen des Artikels über unbestimmtes Verhalten in der C-Sprache von Chris Lattner, einem der führenden Entwickler des LLVM-Projekts, erweckten großes Interesse und auch einige Missverständnisse bei denen, die den beschriebenen Phänomenen in der Praxis nicht begegneten. In seinem Artikel bietet Chris einen Link zu John Regers Blog und seinem Artikel 2010 über UB in C und C ++. Es gibt jedoch viel neuere Artikel zu diesem Thema in Regers Blog (was den Wert der alten jedoch nicht negiert).

Ich möchte Sie auf den neuesten Artikel „Undefined Behavior in 2017“ aufmerksam machen. Der Originalartikel hat ein sehr großes Volumen und ich habe ihn in Teile zerlegt.

Im ersten Teil werden wir über verschiedene UB-Suchwerkzeuge sprechen: ASan, UBSan, TSan usw.
ASan - Address Sanitizer von Google, entwickelt auf Basis von LLVM.
Ubsan- Undefined Behavior Sanitizer zur Erkennung verschiedener UBs in C- und C ++ - Programmen, verfügbar für Clang und GCC.
TSan - Thread Sanitizer zur Erkennung von UB in Multithread-Programmen.
Wenn Ihnen dieses Thema alles andere als praktisch erscheint, empfehle ich, darauf zu warten, dass es fortgesetzt wird, denn am Ende finden Sie eine wirklich große Liste von C ++ UB (es sollten ungefähr 200 sein!)
. Ich empfehle, auch die alten Artikel von Reger zu lesen, sie haben ihre Relevanz nicht verloren.
Über den Autor: John Reger ist Professor für Informatik an der Universität von Utah in den Vereinigten Staaten.


Wir hören oft, dass einige Leute behaupten, dass Probleme, die durch Undefined Behaviour (UB) in C und C ++ entstehen, hauptsächlich durch die weit verbreitete Verwendung von dynamischen Validierungswerkzeugen wie ASan, UBSan, MSan und TSan gelöst werden. Wir werden hier das Offensichtliche zeigen: Obwohl diese Tools in den letzten Jahren viele wunderbare Verbesserungen erfahren haben, sind die Probleme von UB noch lange nicht gelöst, und wir werden die Situation im Detail betrachten.



Valgrind und die meisten Desinfektionsmittel sind für das Debuggen vorgesehen: Sie generieren benutzerfreundliche Diagnosemeldungen in Bezug auf Fälle von undefiniertem Verhalten, die während des Tests aufgetreten sind. Solche Tools sind äußerst nützlich und helfen uns, von einem Zustand der Welt, in dem fast jedes nicht-triviale C- und C ++ - Programm als kontinuierlicher UB-Fluss ausgeführt wird, in einen Zustand der Welt zu gelangen, in dem eine beträchtliche Anzahl wichtiger Programme in ihren gebräuchlichsten Konfigurationen und Anwendungsfällen meistens UB-frei ist .

Das Problem bei dynamischen Debugging-Tools besteht darin, dass sie nichts unternehmen, um die schlimmsten Fälle von UB zu bewältigen: Wir wissen nicht, wie sie beim Testen funktionieren, aber jemand anderes kann herausfinden, wie UB in der Version erscheinen wird und nutzen Sie es als Sicherheitslücke. Das Problem besteht im Testen der Qualität, was schwierig ist. Tools wie afl-fuzz sind gut, haben aber kaum angefangen, große Programme anzufassen. Eine Möglichkeit, das Testproblem zu umgehen, besteht in der Verwendung statischer UB-Erkennungstools. Sie werden ständig verbessert, aber eine zuverlässige und genaue statische Analyse ist nicht unbedingt einfacher als eine gute Testabdeckung. Natürlich zielen diese beiden Techniken darauf ab, ein Problem zu lösen, Ermittlung möglicher Wege zur Ausführung des Programms, jedoch aus verschiedenen Blickwinkeln. Dieses Problem war schon immer sehr komplex und wird es vielleicht immer sein. Es wurde viel darüber geschrieben, UB durch statische Analyse zu finden. In diesem Artikel konzentrieren wir uns auf dynamische Tools. Eine andere Möglichkeit, das Testproblem zu lösen, besteht in der Verwendung von UB-Tools zur Schadensbegrenzung: Sie wandeln undefiniertes Verhalten in definiertes Verhalten um, wenn C und C ++ verwendet werden, wodurch einige Vorteile der Verwendung sicherer Programmiersprachen erzielt werden. Die Schwierigkeiten beim Entwerfen von Tools zur Minderung von UB sind wie folgt: Eine andere Möglichkeit, das Testproblem zu lösen, besteht in der Verwendung von UB-Tools zur Schadensbegrenzung: Sie wandeln undefiniertes Verhalten in definiertes Verhalten um, wenn C und C ++ verwendet werden, wodurch einige Vorteile der Verwendung sicherer Programmiersprachen erzielt werden. Die Schwierigkeiten beim Entwerfen von Tools zur Minderung von UB sind wie folgt: Eine andere Möglichkeit, das Testproblem zu lösen, besteht in der Verwendung von UB-Tools zur Schadensbegrenzung: Sie wandeln undefiniertes Verhalten in definiertes Verhalten um, wenn C und C ++ verwendet werden, wodurch einige Vorteile der Verwendung sicherer Programmiersprachen erzielt werden. Die Schwierigkeiten beim Entwerfen von Tools zur Minderung von UB sind wie folgt:

- in Eckfällen den Code nicht knacken
- geringe Overhead-Kosten verursachen
- keine zusätzlichen Sicherheitslücken hinzufügen, z. B. das Verknüpfen mit einer ungeprüften Laufzeitbibliothek erfordern
- Angriffe erschweren
- miteinander kombinieren (im Gegensatz zu einigen Debugging-Tools, B. ASan und TSan, sind nicht kompatibel und erfordern zwei Testfälle für ein Projekt, für das beide Tools erforderlich sind.

Bevor wir uns die einzelnen Fälle von UB ansehen, definieren wir unsere Ziele. Sie gelten für alle C- und C ++ - Compiler.

Ziel 1:Jeder Fall von UB (ja, es gibt ungefähr 200 davon, wir geben eine vollständige Liste am Ende) muss entweder als ein bestimmtes Verhalten dokumentiert oder vom Compiler als schwerwiegender Fehler diagnostiziert werden oder als letzte Möglichkeit über ein Desinfektionsprogramm verfügen, das UB zur Laufzeit erkennt. Dies sollte keine Kontroversen hervorrufen. Dies ist die Mindestanforderung für die Entwicklung in C und C ++ in der modernen Welt, in der Angreifer Netzwerkpakete und Compiler-Optimierungen verwenden können.

Ziel 2:Jeder Fall von UB muss entweder dokumentiert oder vom Compiler als schwerwiegender Fehler diagnostiziert werden, oder es muss ein optionaler „Schadensbegrenzungsmechanismus“ vorhanden sein, der die vorherigen Anforderungen erfüllt. Das ist schwieriger. Wir glauben, dass dies auf vielen Plattformen erreicht werden kann. Die Kernel von Betriebssystemen und anderen Codes, für die Geschwindigkeit von entscheidender Bedeutung ist, müssen andere Technologien verwenden, z. B. formale Methoden. Im Rest dieses Artikels werden wir die aktuelle Situation für verschiedene Klassen von unbestimmtem Verhalten untersuchen.

Beginnen wir mit der großen UB-Klasse.

Verstöße gegen die räumliche Speichersicherheit


Beschreibung: Der Zugriff außerhalb des Repositorys und sogar das Erstellen solcher Zeiger ist in C und C ++ UB. 1988 deutete der Morris-Wurm an, was uns in den nächsten N Jahren erwartet. Wie wir wissen, ist N> = 29, und möglicherweise wird der Wert von N 75 erreichen.

Debugging: Valgrind und ASan sind großartige Debugging-Tools. In vielen Fällen ist ASan besser, weil es weniger Overhead verursacht. Beide Tools stellen Adressen als 32- oder 64-Bit-Werte dar und reservieren die verbotene rote Zone um gültige Blöcke. Dies ist ein zuverlässiger Ansatz, mit dem Sie nahtlos mit gewöhnlichen Binärbibliotheken arbeiten können, die dieses Tool verwenden. Außerdem wird regulärer Code unterstützt, der Operationen zum Konvertieren von Zeigern in Ganzzahlen enthält.

Valgrind arbeitet mit ausführbarem Code und kann keine roten Zonen zwischen Stapelvariablen einfügen Die Platzierung von Objekten auf dem Stapel ist bereits in den Versatzwerten in den auf den Stapel zugreifenden Anweisungen codiert, und es ist unmöglich, die Adressen für den Zugriff auf den Stapel "on the fly" zu ändern. Infolgedessen kann Valgrind Fehler bei der Manipulation von Objekten auf dem Stapel nur eingeschränkt erkennen. ASan wird zur Kompilierungszeit ausgeführt und fügt rote Zonen um Stapelvariablen ein. Stapelvariablen sind klein und zahlreich, und Überlegungen zum Adressraum und zur Lokalität verhindern die Verwendung sehr großer roter Zonen. In der Standardeinstellung werden die Adressen zweier benachbarter lokaler ganzzahliger Variablen x und y durch 16 Bytes getrennt. Mit anderen Worten, die von ASan und Valgrind erstellten Überprüfungen beziehen sich nur auf die Platzierung von Objekten im Speicher.

Ein Nachteil von ASan und Valgrind ist, dass sie UB überspringen können, wenn Code vom Optimierer entfernt wurde und nicht wie im Beispiel ausgeführt werden kann.

Schadensbegrenzung : Wir haben seit langem einen Mechanismus zur Schadensbegrenzung für unsichere Speicheroperationen, einschließlich ASLR, "Stack Canaries", "Protected Allocators" und NX-Bits.

ASLR
Randomisierung des Adressraum-Layouts (Adressraum-Layout-Randomisierung) - eine in Betriebssystemen verwendete Technologie, bei der der Ort im Adressraum des Prozesses wichtiger Datenstrukturen, nämlich Images der ausführbaren Datei, geladene Bibliotheken, Heap und Stack, nach dem Zufallsprinzip geändert wird.
https://en.wikipedia.org/wiki/Address_space_layout_randomization
Hinweis perev.




Kanarienvögel stapeln
„Stapel-Kanarienvogel“ - der Name stammt von dem Kanarienvogel, den die Bergleute mitnahmen, um eine erhöhte Konzentration von Minengas festzustellen.
Eine Pufferüberlaufschutzmethode, bei der ein "Kanarischer Wert" im Stapelrahmen vor die Rücksprungadresse geschrieben wird. Jeder Versuch, eine Adresse mit einem Pufferüberlauf neu zu schreiben, führt dazu, dass der kanarische Wert neu geschrieben wird und ein Pufferüberlauf erkannt wird.
Hinweis perev.


geschützte Allokatoren
„Hardened Allocators“ - Speicherzuordnungen in LLVM, mit denen die mit dynamisch zugewiesenem Speicher verbundenen Sicherheitsanfälligkeiten weiter verringert werden sollen. Weitere Einzelheiten finden Sie unter :. Https://llvm.org/docs/ScudoHardenedAllocator.html
Hinweis. perev.


Nx Bit
NX-Bit - Attribut (Bit) NX-Bit (dt. Kein Ausführungsbit) - Ein Teil des Ausführungsverbots, das der Seite hinzugefügt wurde, um die Fähigkeit zu implementieren, die Ausführung von Daten als Code zu verhindern. Wird verwendet, um Pufferüberlauf-Schwachstellen zu verhindern. Weitere Informationen finden Sie unter https://en.wikipedia.org/wiki/NX_bit
. perev.


Später wurde CFI (Control Flow Integrity Control) für die Produktion verfügbar. Eine weitere interessante Neuentwicklung ist die Zeigeridentifikation in ARMv8.3. Dieser Artikel enthält eine Übersicht über die UB-Schutzmaßnahmen im Zusammenhang mit der Speichersicherheit.

ASans schwerwiegender Fehler als Mittel zur Minderung von UB wird hier gezeigt:

$ cat asan-defeat.c
#include 
#include 
#include 
char a[128];
char b[128];
int main(int argc, char *argv[]) {
  strcpy(a + atoi(argv[1]), "owned.");
  printf("%s\n", b);
  return 0;
}
$ clang-4.0 -O asan-defeat.c
$ ./a.out 128
owned.
$ clang-4.0 -O -fsanitize=address -fno-common asan-defeat.c
$ ./a.out 160
owned.
$ 

Mit anderen Worten, ASan wird den Angreifer einfach zwingen, einen anderen Versatz zu berechnen, um den gewünschten Speicherbereich zu verderben. (Dank an Yuri Gribov, der uns aufgefordert hat, das Flag -fno-common in ASan zu verwenden.)

Um diese Variante undefinierten Verhaltens zu mildern, sollte eine echte Überprüfung des Auslandsaufenthalts durchgeführt werden und keine einfache Überprüfung, auf die bei jedem Zugriff zugegriffen wird Speicher tritt in einer gültigen Region auf. Speichersicherheit ist hier der Goldstandard. Obwohl es viele wissenschaftliche Arbeiten zur Speichersicherheit gibt und einige Ansätze mit vertretbarem Aufwand und guter Kompatibilität mit vorhandener Software zeigen, sind sie nicht weit verbreitet. Checked Cis ist ein sehr cooles Projekt in diesem Bereich.

Fazit:Debugging-Tools für solche Fehler sind sehr gut. Es ist möglich, diesen UB-Typ wesentlich zu verringern. Um ihn jedoch vollständig zu eliminieren, benötigen Sie eine vollständige Typ- und Speichersicherheit.

Zeitliche Verstöße gegen die Speichersicherheit


Beschreibung: Eine Verletzung der Sicherheit temporärer Speicherobjekte ist die Verwendung eines Speicherorts nach Ablauf seiner Lebensdauer. Dies beinhaltet die Adressierung von automatischen Variablen außerhalb des Gültigkeitsbereichs dieser Variablen, die Verwendung nach der Freigabe, die Verwendung eines baumelnden Zeigers zum Lesen oder Schreiben, die doppelte Freigabe, was in der Praxis sehr gefährlich sein kann, weil free () ändert die Metadaten, die normalerweise zu dem freizugebenden Block gehören. Wenn ein Block bereits freigegeben wurde, kann das Schreiben in diese Daten die für andere Zwecke verwendeten Daten beschädigen und im Prinzip dieselben Konsequenzen haben wie jeder andere ungültige Datensatz.

Debugging:ASan wurde entwickelt, um "use after release" -Fehler zu erkennen, die häufig zu schwer reproduzierbaren, fehlerhaften Verhaltensweisen führen. Er macht dies, indem er die freigegebenen Blöcke unter Quarantäne stellt und ihre sofortige Wiederverwendung verhindert. Bei einigen Programmen und Eingaben kann dies den Speicherverbrauch erhöhen und die Lokalität verringern. Der Benutzer kann die Quarantänegröße so konfigurieren, dass ein Kompromiss zwischen Fehlalarmen und Ressourcennutzung gefunden wird.

ASan kann auch die Adressen von automatischen Variablen ermitteln, die den Gültigkeitsbereich dieser Variablen überleben. Die Idee ist, automatische Variablen in Blöcke umzuwandeln, die im dynamischen Speicher zugeordnet sind. Der Compiler weist sie automatisch zu, wenn die Ausführung in einen Block eintritt, und gibt sie frei (unter Quarantäne), wenn die Ausführung den Block verlässt. Diese Option ist standardmäßig deaktiviert, da das Programm in Bezug auf den Arbeitsspeicher noch unersättlicher sein soll.

Die Verletzung der Sicherheit temporärer Speicherobjekte im folgenden Programm führt zu einem unterschiedlichen Verhalten bei der Standardoptimierung und -O2. ASan kann ein Problem im Programm ohne Optimierung erkennen, jedoch nur, wenn die Option detect_stack_use_after_return gesetzt ist, und nur, wenn es nicht mit Optimierung kompiliert wurde.

$ cat temporal.c
#include 
int *G;
int f(void) {
  int l = 1;
  int res = *G;
  G = &l;
  return res;
}
int main(void) {
  int x = 2;
  G = &x;
  f();
  printf("%d\n", f());
}
$ clang -Wall -fsanitize=address temporal.c
$ ./a.out 
1
$ ASAN_OPTIONS=detect_stack_use_after_return=1 ./a.out 
=================================================================
==5425==ERROR: AddressSanitizer: stack-use-after-return ...
READ of size 4 at 0x0001035b6060 thread T0
^C
$ clang -Wall -fsanitize=address -O2 temporal.c
$ ./a.out 
32767
$ ASAN_OPTIONS=detect_stack_use_after_return=1 ./a.out 
32767
$ clang -v
Apple LLVM version 8.0.0 (clang-800.0.42.1)
...

In einigen anderen Beispielen kann ein Desinfektionsprogramm kein UB erkennen, das vom Optimierungsprogramm entfernt wurde, und ist daher sicher, da ein Remote-Code mit UB keine Konsequenzen haben kann. Das ist aber nicht der Fall! Ein Programm ist in jedem Fall bedeutungslos, aber ein nicht optimiertes Programm arbeitet deterministisch, als ob die Variable x als statisch deklariert worden wäre, während sich ein optimiertes Programm, in dem ASan nichts Verdächtiges festgestellt hat, deterministisch verhält und einen internen Zustand aufdeckt, der nicht beabsichtigt ist konnte sehen:

$ clang -Wall -O2 temporal.c
$ ./a.out 
1620344886
$ ./a.out 
1734516790
$ ./a.out 
1777709110

Schadensbegrenzung: Wie oben erläutert, wurde ASan nicht zum Schutz vor Sicherheitsanfälligkeiten entwickelt, es stehen jedoch verschiedene geschützte Zuweiser zur Verfügung, die dieselbe Quarantänestrategie verwenden, um die Sicherheitsanfälligkeit "Nach Veröffentlichung verwenden" zu schließen.

Schlussfolgerung: Verwenden Sie ASan (zusammen mit „ASAN_OPTIONS = detect_stack_use_after_return = 1“ zum Testen in kleinen Fällen). Auf verschiedenen Optimierungsebenen können Fehler erkannt werden, die auf anderen Ebenen nicht erkannt werden.

Integer Overflow


Beschreibung: Es gibt keinen Überlauf von ganzen Zahlen, aber es kann in beide Richtungen überlaufen. Überlauf von Ganzzahlen mit Vorzeichen, dies ist UB, dies umfasst INT_MIN / -1, INT_MIN% -1, minus INT_MIN, negative Zahlenverschiebungen, Linksverschiebung der Zahl mit der Einheit nach dem Vorzeichenbit und (manchmal) auch Linksverschiebung der Zahl mit der Einheit im Vorzeichenbit.
Die Division durch Null und die Verschiebung um einen Wert, der größer als die Ziffernkapazität der Zahl ist, ist für vorzeichenbehaftete und vorzeichenlose Zahlen UB. Siehe auch: Grundlegendes zum Integer-Überlauf beim C / C ++ -

Debuggen:UBSan ist ein sehr gutes Tool zum Auffinden von UB in Bezug auf Ganzzahlen. Da UBSan auf Source-Ebene arbeitet, ist es sehr zuverlässig. Es gibt einige Kuriositäten in Bezug auf Berechnungen zur Kompilierungszeit. Beispielsweise kann ein Programm eine Ausnahme abfangen, wenn es als C ++ 11 kompiliert wird, und diese nicht abfangen, wenn es in C11 kompiliert wird. Wir sind der Meinung, dass dies den Standards entspricht, gingen jedoch nicht auf Details ein. GCC hat eine eigene Version von UBSan, kann jedoch nicht zu 100% als vertrauenswürdig eingestuft werden, da die Konstanten reduziert werden, bevor dieses Tool ausgeführt wird.

Minderung:UBSan im "Trapping-Modus" (wenn UB abgefangen wird, stoppt der Prozess ohne Diagnoseausgabe) kann verwendet werden, um UB zu mildern. Es ist effektiv und fügt keine Sicherheitslücken hinzu. Zum Teil verwendet Android UBSan, um diese Art von UB zu mildern. Obwohl das Überlaufen von ganzen Zahlen im Grunde genommen ein logischer Fehler ist, sind solche Fehler in C und C ++ besonders gefährlich, da sie zu Verletzungen der Speichersicherheit führen können. In Sprachen mit sicherem Speicherzugriff sind sie viel weniger gefährlich.

Fazit:Integer UB ist nicht sehr schwer zu fangen, UBSan, das ist alles, was Sie dafür brauchen. Das Problem ist, dass das Erweichen der Ganzzahl UB zu Redundanz führt. Zum Beispiel läuft die SPEC CPU 2006 30% langsamer. Es gibt viele Möglichkeiten zur Verbesserung und Beseitigung von Überlaufprüfungen, bei denen dies nicht schaden kann, und Verringerung der Beeinträchtigung des Schleifenoptimierers durch die restlichen Überprüfungen. Jemand mit ausreichenden Ressourcen sollte dies tun.

Strikte Aliasing-Verstöße


Beschreibung: Aufgrund der strengen Aliasing-Regeln in den C- und C ++ - Standards kann der Compiler davon ausgehen, dass zwei Zeiger auf unterschiedliche Typen verweisen, jedoch nicht auf dasselbe Objekt . Dinge (und es wird geschätzt , dass 100% der großen Programme in C und C ++) Für eine detailliertere Überprüfung Abschnitt 1-3 dieses Artikels (. werden im nächsten Teil von ca. Stiften veröffentlicht ... ).

Debugging:Der aktuelle Stand der Debugging-Tools für strikte Aliasing-Verstöße ist schwach. Compiler geben in einigen einfachen Fällen Warnungen aus, die jedoch sehr unzuverlässig sind. libcrunch warnt davor, dass der Zeiger in einen Typ "Zeiger auf etwas" konvertiert wird, obwohl er tatsächlich auf etwas anderes verweist. Auf diese Weise können Sie Typkonvertierungen über einen Zeiger auf ungültig ausführen, es werden jedoch falsche Zeigerkonvertierungen abgefangen, die ebenfalls dieser Typ von UB sind. Dank des C-Standards und der Interpretation von C-Compilern bei der Optimierung von TBAA (typbasierte Alias-Analyse) ist libcrunch weder zuverlässig (es werden einige Verstöße während der Programmausführung nicht erkannt) noch vollständig (es wird gewarnt) über das Konvertieren von Zeigern, wenn es verdächtig aussieht, aber nicht gegen den Standard verstößt).

Abhilfe: Es ist ganz einfach: Übergeben Sie das Flag (-fno-strict-aliasing) an den Compiler, und deaktivieren Sie strikte aliasingbasierte Optimierungen. Infolgedessen verlässt sich der Compiler auf das gute alte Speichermodell, bei dem mehr oder weniger willkürliche Konvertierungen zwischen Zeigertypen durchgeführt werden können und der resultierende Code sich erwartungsgemäß verhält. Von den Big Three unterliegen nur GCC und LLVM einer solchen UB, MSVC implementiert keine solche Optimierungsklasse.

Fazit: Code, der für dieses UB empfindlich ist, muss sorgfältig geprüft werden: Es ist immer verdächtig und gefährlich, Zeiger in etwas anderes als char * zu konvertieren. Alternativ können Sie die TBAA-Optimierung einfach mithilfe des Flags deaktivieren und sicherstellen, dass niemand Code kompiliert, ohne dieses Flag zu verwenden.

Ausrichtungsverletzungen


Beschreibung: RISC-Prozessoren neigen dazu, den Speicherzugriff auf nicht ausgerichtete Adressen zu verweigern. Auf der anderen Seite verfügen C- und C ++ - Programme, die einen unsymmetrischen Zugriff verwenden, über UB, unabhängig von der Zielarchitektur. In der Vergangenheit haben wir dies mit den Fingern betrachtet, zum einen, weil x86 / x64 unsymmetrischen Zugriff unterstützt, und zum anderen, weil Compiler diese UB noch nicht für Optimierungen verwendet haben. Aber auch in diesem Fall gibt es einen wunderbaren Artikel, der erklärt, wie der Compiler Code mit nicht ausgerichtetem Zugriff auf x64 brechen kann. Der Code in diesem Artikel verstößt zusätzlich zu einer fehlerhaften Ausrichtung gegen striktes Aliasing und stürzt trotz des Flags -fno-strict-aliasing ab (getestet unter GCC 7.1.0 in OS X).

Debugging: UBSan kann Fehlausrichtungen erkennen.

Minderung:unbekannt

Fazit: Verwenden UBSan

Schleifen, die weder E / A ausführen noch beenden


Beschreibung: Schleifen in C- oder C ++ - Code, die keine E / A ausführen und nicht abgeschlossen werden, sind undefiniert und können vom Compiler willkürlich beendet werden. Siehe diesen Artikel und diesen Hinweis .

Debugging: keine Tools

Softening: Nein, außer, dass die Compiler nicht zu stark optimiert werden.

Fazit: Diese UB ist kein praktisches Problem (auch wenn es für einen von uns unangenehm ist).

Datenrennen


Beschreibung: Datenwettbewerbe treten auf, wenn auf ein Speicherelement mehr als ein Thread zugreifen kann und mindestens einer von ihnen für die Aufzeichnung verfügbar ist. Der Zugriff wird nicht durch Mechanismen wie Sperren synchronisiert. Datenwettbewerbe führen in modernen Versionen von C und C ++ zu UB (sie sind in älteren Versionen nicht sinnvoll, da diese Standards keinen Multithread-Code beschreiben).

Hinweis perev.
Hier stimme ich nicht mit dem Autor überein, da Multithread-Code mit der Betriebssystem-API gestartet werden könnte, wie beispielsweise POSIX-Threads, und dies kann in jeder Version von C und C ++ erfolgen, egal wie alt. Auch der Code, der Interrupts im Mikrocontroller verarbeitet, kann zu ähnlichen Effekten führen, wenn Daten mit der Hauptprogrammschleife geteilt werden. Es hängt auch nicht vom Jahr des Standards C und C ++ ab. Hinweis perev.

Beschreibung: TSan ist ein hervorragender Detektor für dynamische Speicherwettbewerbe. Es gibt andere ähnliche Tools, wie das Helgrind-Plugin für Valgrind, aber wir haben sie in letzter Zeit nicht verwendet. Die Verwendung dynamischer Wettkampfdetektoren wird durch die Tatsache erschwert, dass der Wettkampf nur sehr schwer zum Laufen zu bringen ist. Das Schlimmste ist, dass sein Betrieb von der Anzahl der Kerne, dem Flow Scheduler-Algorithmus, dem Status der Testmaschine, den Mondphasen usw. abhängt.

Schadensbegrenzung: Erstellen Sie keine Threads.

Fazit: Es gibt eine gute Idee für diese bestimmte UB: Wenn Sie keine Objekte sperren möchten, verwenden Sie keinen parallel laufenden Code, sondern atomare Aktionen.

Nichtsequenzierte Änderungen


Beschreibung: In C begrenzt der „Verfolgungspunkt“, wie früher oder später ein Ausdruck mit einem Nebeneffekt wie x ++ wirksam wird. C ++ hat einen anderen, aber mehr oder weniger gleichwertigen Wortlaut dieser Regel. In beiden Sprachen führen Änderungen mit Verletzung der Sequenzpunkte zu UB.

Debugging: Einige Compiler erzeugen eine Warnung, wenn offensichtliche Verstöße gegen die folgenden Regeln vorliegen:

$ cat unsequenced2.c
int a;
int foo(void) {
  return a++ - a++;
}
$ clang -c unsequenced2.c
unsequenced2.c:4:11: warning: multiple unsequenced modifications to 'a' [-Wunsequenced]
  return a++ - a++;
          ^     ~~
1 warning generated.
$ gcc-7 -c unsequenced2.c -Wall
unsequenced2.c: In function 'foo':
unsequenced2.c:4:11: warning: operation on 'a' may be undefined [-Wsequence-point]
   return a++ - a++;
          ~^~

Eine kleine indirekte Verletzung verursacht jedoch keine Warnungen:

$ cat unsequenced.c
#include 
int main(void) {
  int z = 0, *p = &z;
  *p += z++;
  printf("%d\n", z);
  return 0;
}
$ gcc-4.8 -Wall unsequenced.c ; ./a.out
0
$ gcc-7 -Wall unsequenced.c ; ./a.out
1
$ clang -Wall unsequenced.c ; ./a.out
1

Schadensbegrenzung: Unbekannt, aber es ist fast trivial, die Reihenfolge zu bestimmen, in der Nebenwirkungen auftreten. Die Java-Sprache ist ein Beispiel dafür. Wir hatten eine schwierige Zeit, als wir glaubten, dass eine solche Einschränkung jeden modernen Optimierungs-Compiler behindern würde. Wenn das Standardisierungskomitee von ganzem Herzen der Ansicht ist, dass dies nicht der Fall ist, müssen Compilerentwickler die Regeln befolgen. Im Idealfall sollten alle wichtigen Compiler in solchen Fällen dasselbe tun.

Fazit:Mit etwas Übung ist es nicht sehr schwierig, eine mögliche Verletzung der Sequenzpunkte während der Codierung zu bemerken. Wir müssen uns Sorgen machen, sehr komplexe Ausdrücke mit Nebenwirkungen zu sehen. Es passiert in altem Code, aber es funktioniert immer noch, was bedeutet, dass dies möglicherweise kein Problem ist. Tatsächlich sollte dieses Problem in Compilern behoben werden.

Nicht-UB im Zusammenhang mit der Verletzung von Sequenzpunkten ist eine "unbestimmte Sequenz", in der Anweisungen in der vom Compiler angegebenen Reihenfolge ausgeführt werden können. Ein Beispiel ist die Reihenfolge, in der zwei Funktionen bei der Berechnung von f (a (), b ()) aufgerufen werden. Diese Reihenfolge muss ebenfalls definiert werden. Zum Beispiel von links nach rechts. Es gibt keinen Geschwindigkeitsverlust, wenn Sie nicht völlig verrückte Situationen in Betracht ziehen.

Fortsetzung folgt.

Jetzt auch beliebt: