Betrachten Sie diesen Code:
%Vor%Beim Kompilieren mit gcc 7.2:
%Vor%Dies emittiert:
%Vor%Wenn Sie jedoch den gleichen Code mit denselben Flags unter Verwendung von clang 3.9 erstellen:
%Vor% gcc ruft next()
über den PLT auf, clang inline es. Beide suchen immer noch last
von der GOT. Für das Kompilieren unter Linux ist es richtig, diese Optimierung zu machen, und gcc fehlt beim einfachen Inlining, oder ist falsch falsch, um diese Optimierung zu machen, oder ist das ein reines QoI-Problem?
Ich glaube nicht, dass der Standard so sehr ins Detail geht. Es sagt nur, dass wenn das Symbol eine externe Verbindung in verschiedenen Übersetzungseinheiten hat, es dasselbe Symbol ist. Das macht die Version von clang korrekt.
Von da an sind wir nach meinem besten Wissen nicht mehr im Standard. Die Auswahlmöglichkeiten von Compilern unterscheiden sich darin, was sie als nützliche -fPIC
-Ausgabe betrachten.
Beachten Sie, dass g++ -c -std=c++11 -O3 -fPIE
outputs:
GCC weiß also, wie man das optimiert. Es wählt nur nicht, wenn -fPIC
verwendet wird. Aber warum? Ich kann nur eine Erklärung sehen: es ist möglich, das Symbol während der dynamischen Verknüpfung zu überschreiben und die Effekte konsistent zu sehen. Die Technik ist als Symbol interposition bekannt.
Wenn index
in einer gemeinsam genutzten Bibliothek next
aufruft, da next
global sichtbar ist, muss gcc die Möglichkeit in Betracht ziehen, dass next
eingefügt werden könnte. Es nutzt also den PLT. Wenn Sie -fPIE
verwenden, dürfen Sie jedoch keine Symbole einfügen, daher aktiviert gcc die Optimierung.
Stimmt das falsch? Nein. Aber gcc scheint die Symbolinterposition besser zu unterstützen, was praktisch ist, um den Code zu instrumentieren. Es tut dies auf Kosten von etwas Gemeinkosten, wenn man -fPIC
anstelle von -fPIE
benutzt, um seine ausführbare Datei zu erstellen.
Zusätzliche Hinweise:
In diesem Blogeintrag von einem der gcc-Entwickler, erwähnt er, gegen Ende der Post:
Beim Vergleich einiger Benchmarks mit clang habe ich festgestellt, dass clang die ELF-Interpositionsregeln ignoriert. Während es ein Fehler ist, habe ich beschlossen,
-fno-semantic-interposition
flag zu GCC hinzuzufügen, um ein ähnliches Verhalten zu erhalten. Wenn Interposition nicht erwünscht ist, ist die offizielle Antwort von ELF, versteckte Sichtbarkeit zu verwenden, und wenn das Symbol exportiert werden soll, definieren Sie einen Alias. Dies ist nicht immer praktisch mit der Hand zu tun.
Nachdem ich diesen Vorsprung erreicht hatte, landete ich auf der x86-64-ABI-Spezifikation . In Abschnitt 3.5.5 schreibt es vor, dass alle Funktionen, die ein global sichtbares Symbol aufrufen, den PLT durchlaufen müssen (bis zur Definition der genauen Befehlsfolge, die je nach Speichermodell zu verwenden ist).
Obwohl es den C ++ - Standard nicht verletzt, scheint das Ignorieren der semantischen Interposition die ABI zu verletzen.
Letztes Wort: Ich wusste nicht, wo ich das hinstellen sollte, aber es könnte Sie interessieren. Ich erspare Ihnen die Dumps, aber meine Tests mit objdump und Compiler-Optionen haben gezeigt, dass:
Auf der gcc-Seite der Dinge:
gcc -fPIC
: Zugriffe auf last
gehen durch GOT, Aufrufe von next()
durchläuft PLT. gcc -fPIC -fno-semantic-interposition
: last
läuft über GOT, next()
ist inline. gcc -fPIE
: last
ist IP-relativ, next()
ist inline. -fPIE
bedeutet -fno-semantic-interposition
Auf der klirrenden Seite der Dinge:
clang -fPIC
: last
läuft über GOT, next()
ist inline. clang -fPIE
: last
läuft über GOT, next()
ist inline. Und eine modifizierte Version, die kompiliert zu IP-relativ, inline auf beiden Compilern:
%Vor%Grundsätzlich bedeutet dies ausdrücklich, dass wir, obwohl sie global verfügbar sind, eine versteckte Version dieser Symbole verwenden, die jede Art von Interposition ignorieren. Beide Compiler optimieren dann die Zugriffe vollständig, unabhängig von den übergebenen Optionen.