Ich wollte heute schlafen, scheiterte aber wieder. Im Telegramm erschien die Nachricht, dass jemand nicht nach Java gehen würde ... und wir wachten erst nach ein paar Stunden müde und glücklich auf.

Wer kann diesen Beitrag nutzen? Ja, wahrscheinlich für jeden, außer für diejenigen, die auch JDK8 sammeln oder einfach gerne Albtraum-Horror lesen. Im Allgemeinen habe ich Sie gewarnt, schließen Sie den Artikel dringend.
Drei Probleme:
- Nicht gehen ( Stufe eins )
Ein sehr langweiliger Teil zum Überspringen. Nur für diejenigen erforderlich, die die Geschichte der Ereignisse vollständig wiederherstellen möchten. - Nicht gehen ( Level zwei )
Es ist interessanter, weil es einige typische Fehler gibt, Nekromantie, Nekrophilie, welche BSD besser ist als GNU / Linux und warum es sich lohnt, auf neue Versionen von JDK zu migrieren. - Selbst wenn es geht, fällt es in die Kruste
Interessanter. Yahuuu, die JVM ist in die Kruste gefallen, lass es uns treten!
Unter der Katze zeigt sich eine detaillierte Lösung für Probleme, mit unterschiedlichen Nebengedanken über das Leben.
Es wird viel C ++ geben, es wird überhaupt keinen Java-Code geben. Jeder Javist am Ende beginnt nur in C ++ zu schreiben ...
Ich werde nicht gehen
Wer mindestens einmal Java erstellt hat, weiß, dass es ungefähr so aussieht:
hg clone http://hg.openjdk.java.net/jdk8u/jdk8u cd jdk8u sh ./get_source.sh sh ./configure \ --with-debug-level=fastdebug \ --with-target-bits=64 \ --with-native-debug-symbols=internal \ --with-boot-jdk=/home/me/opt/jdk1.8.0_161 make images
(Alle meine Benutzer werden einfach "ich" genannt, sodass Sie die virtuelle Maschine jederzeit jeder Person geben können und keine Ablehnung der Verwendung Ihres eigenen Benutzernamens erstellen können.)
Das Problem ist natürlich, dass dies nicht funktioniert. Und das eher zynisch.
Erste Tauchstufe
Versuchen wir zu rennen:
/home/me/git/jdk8u/hotspot/src/os/linux/vm/os_linux.inline.hpp:127:18: warning: 'int readdir_r(DIR*, dirent*, dirent**)' is deprecated [-Wdeprecated-declarations] if((status = ::readdir_r(dirp, dbuf, &p)) != 0) { ^~~~~~~~~
Damit Sie verstehen, habe ich Folgendes installiert:
$ g++ --version g++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0 Copyright (C) 2017 Free Software Foundation, Inc.
Der Compiler ist nicht die erste Frische, nicht 8.2, aber diese sollte auch funktionieren.
C ++ - Entwickler testen Software nur auf der Version des von ihnen installierten Compilers. Normalerweise endet der Wunsch, auf verschiedenen Plattformen zu testen, irgendwo im Bereich des Unterschieds zwischen gcc und clang im Allgemeinen. Daher ist es ganz normal, zuerst -Werror
("Warnungen als Fehler behandeln") und dann Code zu schreiben, der in allen anderen Versionen als vorings betrachtet wird.
Dies ist ein bekanntes Problem, und es ist klar, wie es gelöst werden kann. Sie müssen Ihre Umgebungsvariable CXX_FLAGS festlegen, in der die richtige Fehlerstufe festgelegt werden soll.
export CXX_FLAGS=-Wno-error=deprecated-declarations -Wno-error-deprecated-declarations
Und dann sehen wir das Wunderbare:
Ignoring CXXFLAGS found in environment. Use --with-extra-cxxflags
Ok, baue ein System, was auch immer du willst! Wir ersetzen configure durch dieses:
hg clone http://hg.openjdk.java.net/jdk8u/jdk8u cd jdk8u sh ./configure \ --with-extra-cflags='-Wno-cpp -Wno-error=deprecated-declarations' \ --with-extra-cxxflags='-Wno-cpp -Wno-error=deprecated-declarations' \ --with-debug-level=fastdebug \ --with-target-bits=64 \ --with-native-debug-symbols=internal \ --with-boot-jdk=/home/me/opt/jdk1.8.0_161 make images
Und der Fehler bleibt gleich!
Wir gehen zur schweren Artillerie über: dem Quellcode.
grep -rl "Werror" .
Eine große Menge von automatisch generierten Hüten fällt heraus, darunter gibt es Einblicke in sinnvolle Dateien:
./common/autoconf/flags.m4 ./hotspot/make/bsd/makefiles/gcc.make ./hotspot/make/solaris/makefiles/gcc.make ./hotspot/make/aix/makefiles/xlc.make
In flags.m4
wir leicht die vorherige Nachricht über "Ignorieren von CXXFLAGS" und das ausgereiftere CCXX_FLGS
Flag CCXX_FLGS
(ja, zwei Buchstaben C), das sofort anstelle von CFLAGS
und anstelle von XX_FLAGS
. Bequem! Zwei Fakten sind interessant:
- Dieses Flag wird nicht über Konfigurationsparameter übergeben.
- Im Standardwert sind diese Parameter sinnvoll und verdächtig ähnlich:
Diese Frage sieht in den Kommentaren sehr gut aus - aber was, sind die Flaggen gemeinsam? Richtig?
Wir werden keine Demokratie spielen und sie dort autoritär fest codieren -w
("keine Fehler zeigen"):
CCXXFLAGS_JDK="$CCXXFLAGS $CCXXFLAGS_JDK -w -ffreestanding -fno-builtin -Wno-parentheses -Wno-unused -Wno-unused-parameter -Wformat=2 \
Und - Prost! - Der erste Fehler, den wir durchgemacht haben. Sie berichtet nicht mehr und im Allgemeinen ist alles in Ordnung. Es scheint.
Zweite Tauchstufe
Aber jetzt fällt es in einen Haufen anderer neuer Orte!
Es stellt sich heraus, dass unser -w
funktioniert, aber nicht an alle Teile der Baugruppe weitergeleitet wird. Wir lesen die Makefiles sorgfältig durch und verstehen nicht, wie genau dieser Parameter überhaupt weitergeleitet werden kann. Wirklich vergessen ihn?
Wenn wir die richtige Google-Frage kennen ("Warum kommt cxx nicht zum Build ?!"), Kommen wir schnell zur Fehlerseite mit dem Spruch "configure --with-extra-cxxflags hat keinen Einfluss auf den Hotspot" ( JDK-8156967 ).
Welches Versprechen soll in JDK 12 behoben werden? Vielleicht. Wunderbar - der wichtigste Build-Parameter wird in der Assembly nicht verwendet!
Die erste Idee ist, die Ärmel hochzukrempeln und die Fehler zu beheben!
Fehler 1.xn [12]
dependencies.cpp: In function 'static void Dependencies::write_dependency_to(xmlStream*, Dependencies::DepType, GrowableArray<Dependencies::DepArgument>*, Klass*)': dependencies.cpp:498:6: error: '%d' directive writing between 1 and 10 bytes into a region of size 9 [-Werror=format-overflow=] void Dependencies::write_dependency_to(xmlStream* xtty, ^~~~~~~~~~~~ dependencies.cpp:498:6: note: directive argument in the range [0, 2147483647]
Nun, wir müssen wahrscheinlich die Region vergrößern. Einhundert Pfund, jemand berechnete den Puffer durch Klicken auf "Ich bin glücklich!" in google.
Aber wie würden Sie verstehen, wie viel Sie brauchen? Es gibt eine andere Art der Verfeinerung unten:
stdio2.h:34:43: note: '__builtin___sprintf_chk' output between 3 and 12 bytes into a destination of size 10 __bos (__s), __fmt, __va_arg_pack ());
Position 12 sieht nach etwas Wertvollem aus, mit dem Sie jetzt mit schmutzigen Füßen in die Quelle einbrechen können.
Wir klettern in dependencies.cpp
und beobachten das folgende Bild:
DepArgument arg = args->at(j); if (j == 1) { if (arg.is_oop()) { xtty->object("x", arg.oop_value()); } else { xtty->object("x", arg.metadata_value()); } } else { char xn[10]; sprintf(xn, "x%d", j); if (arg.is_oop()) { xtty->object(xn, arg.oop_value()); } else { xtty->object(xn, arg.metadata_value()); } }
Achten Sie auf die problematische Linie:
char xn[10]; sprintf(xn, "x%d", j);
Wir wechseln 10 zu 12, bauen wieder zusammen und ... die Montage ist weg!
Aber bin ich der einzige, der so schlau ist und den Fehler aller Zeiten behoben hat? Keine Frage, wir fahren unser Megapatch erneut in Google: char xn[12];
Und wir sehen ... ja, das stimmt. Der von Vladimir Ivanov gesperrte Fehler JDK-8184309 enthält genau das gleiche Update.
Aber das Fazit ist, dass es nur in JDK 10 behoben ist und nifiga nicht auf jdk8u zurückportiert wird. Dies ist die Frage, warum neue Versionen von Java benötigt werden.
Fehler 2. strcmp
fprofiler.cpp: In member function 'void ThreadProfiler::vm_update(TickPosition)': /home/me/git/jdk8ut/hotspot/src/share/vm/runtime/fprofiler.cpp:638:56: error: argument 1 null where non-null expected [-Werror=nonnull] bool vm_match(const char* name) const { return strcmp(name, _name) == 0; }
Aufgrund früherer bitterer Erfahrungen sehen wir uns sofort an, was sich an dieser Stelle in JDK 11 befindet. Und ... diese Datei ist nicht vorhanden. Die Verzeichnisstruktur wurde ebenfalls überarbeitet.
Aber Sie können nicht einfach von uns wegkommen!
Jeder Javist ist ein kleiner Nekromant in seiner Seele und vielleicht sogar ein Nekrophiler. Daher wird es jetzt NECROMANCE IN ACTION geben!
Zuerst müssen Sie an die Seele der Toten appellieren und herausfinden, wann er gestorben ist:
$ hg log --template "File(s) deleted in rev {rev}: {file_dels % '\n {file}'}\n\n" -r 'removes("**/fprofiler.cpp")' File(s) deleted in rev 47106: hotspot/src/share/vm/runtime/fprofiler.cpp hotspot/src/share/vm/runtime/fprofiler.hpp hotspot/test/runtime/MinimalVM/Xprof.java
Jetzt müssen Sie die Todesursache herausfinden:
hg log -r 47106 changeset: 47106:bed18a111b90 parent: 47104:6bdc0c9c44af user: gziemski date: Thu Aug 31 20:26:53 2017 -0500 summary: 8173715: Remove FlatProfiler
Wir haben also einen Mörder: Gziemski . Lassen Sie uns herausfinden, warum er diese unglückliche Akte genagelt hat.
Gehen Sie dazu in dem in der Zusammenfassung des Commits angegebenen Ticket zu Fett. Dies ist JDK-8173715 :
FlatProfiler entfernen:
Wir gehen davon aus, dass diese Technologie nicht mehr verwendet wird und eine Quelle für das Root-Scannen für den GC darstellt.
Für shih bis. Tatsächlich sind wir jetzt eingeladen, die Leiche zu reparieren, damit der Bau läuft. Was sich so sehr zersetzt hat, dass sogar unsere Nekromantenkollegen von OpenJDK es aufgegeben haben.
Lassen Sie uns den Toten wiederbeleben und versuchen, ihn zu fragen, woran er sich zuletzt erinnert hat. Er war bereits in Revision 47106 tot, was bedeutet, dass die Revision eine weniger enthält - dies ist „eine Sekunde vorher“:
hg cat "~/git/jdk11/hotspot/src/share/vm/runtime/fprofiler.cpp" -r 47105 > ~/tmp/fprofiler_new.cpp cp ~/git/jdk8u/hotspot/src/share/vm/runtime/fprofiler.cpp ~/tmp/fprofiler_old.cpp cd ~/tmp diff fprofiler_old.cpp fprofiler_new.cpp
Leider nichts in Bezug auf return strcmp(name, _name) == 0;
in diff nr. Der Patient starb an einem Schlag mit einem stumpfen, scharfen Gegenstand (Dienstprogramm), war jedoch zum Zeitpunkt des Todes bereits todkrank.
Lassen Sie uns die Essenz des Fehlers untersuchen.
Folgendes möchte der Code-Autor uns mitteilen:
const char *name() const { return _name; } bool is_compiled() const { return true; } bool vm_match(const char* name) const { return strcmp(name, _name) == 0; }
Nun eine kleine Philosophie.
Der C11-Standard in Abschnitt 7.1.4, „Verwendung von Bibliotheksfunktionen“, besagt ausdrücklich:
Jede der folgenden Aussagen gilt, sofern in den folgenden detaillierten Beschreibungen nicht ausdrücklich anders angegeben: Wenn ein Argument für eine Funktion einen ungültigen Wert hat (z. B. [...] einen Nullzeiger [...]), [...], Das Verhalten ist undefiniert.
Das heißt, jetzt ist die ganze Frage, ob es einige "ausdrücklich anders angegeben" gibt . In der Beschreibung von strcmp
in Abschnitt 7.24.4 ist strcmp
dergleichen geschrieben, und ich habe keine anderen Abschnitte für Sie.
Das heißt, wir haben hier undefiniertes Verhalten.
Natürlich können Sie diesen Code nehmen und neu schreiben und ihn mit einer Überprüfung umgeben. Ich bin mir jedoch nicht sicher, ob ich die Logik von Menschen, die UB verwenden, dort richtig verstehe, wo es nicht sein sollte. Beispielsweise generieren einige Systeme SIGSERV für die Null-Dereferenzierung, und ein Hack-Liebhaber kann dies nutzen. Dieses Verhalten ist jedoch nicht obligatorisch und kann auf einer anderen Plattform gestartet werden.
Ja, natürlich wird jemand sagen, dass Sie ein Dummkopf für sich selbst sind, dass Sie GCC 7.3 verwenden, aber in GCC 4 hätte sich alles angesammelt. Aber undefiniertes Verhalten! = Nicht spezifiziert! = Implementierung definiert. Dies für die letzten beiden kann festgelegt werden, um im alten Compiler zu arbeiten. Und UB in der sechsten Version war UB.
Kurz gesagt, ich war völlig traurig über diese komplexe philosophische Frage (sollte ich mit meinen Annahmen in den Code einsteigen), als mir plötzlich klar wurde, dass es anders sein könnte.
Es gibt noch einen anderen Weg
Wie Sie wissen, gehen gute Helden immer herum.
Selbst wenn wir unsere Philosophie über UB ignorieren, gibt es dort unglaublich viele Probleme. Nicht die Tatsache, dass sie bis zum Morgen repariert werden können. Nicht die Tatsache, dass ich es nicht mit meinen krummen Händen tun kann. Noch weniger ist die Tatsache, dass dies im Upstream akzeptiert wird: Der letzte Patch in jdk8u war vor 6 Wochen, und dies war die globale Zusammenführung des neuen Tags.
Stellen Sie sich vor, der obige Code ist tatsächlich korrekt geschrieben. Alles, was zwischen uns und seiner Ausführung steht, ist eine Warnung, die aufgrund eines Fehlers im Build-System als Fehler wahrgenommen wurde. Aber wir können ein Build-System aufbauen.
Der Hexer Geralt von Rivia sagte einmal:
"Das Böse ist böse, Stregobor", sagte der Hexer ernst und stand auf. - Kleiner, größer, durchschnittlich - alles ist gleich, die Proportionen sind willkürlich und die Grenzen sind verschwommen. Ich bin kein heiliger Einsiedler, habe nicht nur einem in meinem Leben Gutes getan. Aber wenn Sie zwischen einem Übel und einem anderen wählen müssen, ziehe ich es vor, überhaupt nicht zu wählen.
- Zło bis zło, Stregoborze - rzekł poważnie wiedźmin wstając. - Mniejsze, większe, średnie, wszystko jedno, proporcje są umowne a granice zatarte. Nie jestem świątobliwym pustelnikiem, nie samo dobro czyniłem w życiu. Ale jeżeli mam wybierać pomiędzy jednym złem a drugim, wolę nie wybierać wcale.
Dies ist ein Zitat aus The Last Wish, einer Geschichte namens Lesser Evil. Wir alle wissen, dass Geralt fast nie die Rolle eines wirklich neutralen Charakters spielen konnte und sogar aufgrund eines anderen klassischen chaotischen Verhaltens starb.
Lassen Sie uns also kurz das kleinere Übel zeigen. Lassen Sie uns über das Build-System gehen.
Ganz am Anfang haben wir diesen Auspuff gesehen:
grep -rl "Werror" . ./common/autoconf/flags.m4 ./hotspot/make/linux/makefiles/gcc.make ./hotspot/make/bsd/makefiles/gcc.make ./hotspot/make/solaris/makefiles/gcc.make ./hotspot/make/aix/makefiles/xlc.make
Beim Vergleich dieser beiden Dateien habe ich das ganze Gesicht mit dem Facespalm gebrochen und den Unterschied in der Kultur der beiden Plattformen erkannt:
BSD ist eine Geschichte über Freiheit und Wahlmöglichkeiten:
# Compiler warnings are treated as errors ifneq ($(COMPILER_WARNINGS_FATAL),false) WARNINGS_ARE_ERRORS = -Werror endif
GNU / Linux ist ein autoritäres puristisches Regime:
Nun, es würde über XX_FLAGS
an Linux XX_FLAGS
, diese Variable wird bei der Berechnung von WARNINGS_ARE_ERRORS
nicht berücksichtigt! Im Build für GNU / Linux haben wir einfach keine andere Wahl, als den von oben gestarteten Standardeinstellungen zu folgen.
Nun, oder Sie können es einfacher machen und den Wert von WARNINGS_ARE_ERRORS
in ein kurzes, aber nicht weniger leistungsfähiges -w
ändern. Wie gefällt dir das, Elon Musk?
Wie Sie vielleicht erraten haben, löst dies dieses Build-Problem vollständig.
Wenn der Code zusammengestellt ist, sehen Sie eine Reihe seltsamer, schrecklich aussehender Probleme, die vorbeifliegen. Manchmal war es so beängstigend, dass ich unbedingt Strg + C drücken und versuchen wollte, es herauszufinden. Aber nein, du kannst nicht, du kannst nicht ...
Es scheint, dass sich alles angesammelt hat und keine zusätzlichen Probleme mit sich gebracht hat. Obwohl ich es natürlich nicht gewagt habe, mit dem Testen zu beginnen. Trotzdem bleiben meine Augen nachts zusammen und irgendwie möchte ich nicht zum letzten Ausweg gehen - vier Dosen Energie aus dem Kühlschrank.
Fällt in die Kruste
Die Assembly ist bestanden, die ausführbaren Dateien wurden generiert, wir sind großartig.
Und so kamen wir zur Ziellinie. Oder nicht gekommen?
Unsere Versammlung liegt folgendermaßen vor:
export JAVA_HOME=~/git/jdk8u/build/linux-x86_64-normal-server-fastdebug/jdk export PATH=$JAVA_HOME/bin:$PATH
Wenn Sie versuchen, die ausführbare java
Datei auszuführen, stürzt sie sofort ab. Für diejenigen, die nicht vertraut sind - es sieht ungefähr so aus:

Zur gleichen Zeit hat Alex Debian 9.5 und ich habe Ubuntu. Zwei verschiedene Versionen von GCC, zwei unterschiedlich aussehende Krusten. Ich habe unschuldige Streiche mit dem manuellen Strcmp-Patch und ein paar weiteren Stellen, Alex nicht. Was ist das Problem?
Diese Geschichte verdient eine eigene Geschichte, aber hier gehen wir direkt zu den trockenen Schlussfolgerungen, sonst werde ich diesen Beitrag nie hinzufügen.
Das Problem ist, dass unsere bevorzugten C ++ - Pogromisten wieder undefiniertes Verhalten verwendeten.
(Außerdem hängt es in unbekannter Weise von der Implementierung des Compilers ab. Wir müssen jedoch daran denken, dass UB immer UB ist, selbst bei einer bekannten Version des Compilers ist es unmöglich, darauf zu liegen.)
An einer Stelle wenden wir uns dort dem Feld einer untergestalteten Klasse zu, und alles bricht zusammen. Fragen Sie nicht, wie es passiert ist, alles ist kompliziert.
Für einen Javista ist es sehr schwierig, sich vorzustellen, wie man sich einer unterkonstruierten Klasse zuwenden kann, es sei denn, man gibt direkt vom Konstruktor einen Link dazu. Glücklicherweise kann die wunderbare C ++ - Sprache alles oder fast alles. Ich werde ein Beispiel mit einem bestimmten Pseudocode schreiben:
class A { A() { _b.Show(); } private: static B _b; }; A a; BA::_b; int main() { }
Habt ein schönes Debug!
Wenn Sie sich C ++ 98 [class.cdtor] ansehen:
Für ein Objekt vom Nicht-POD-Klassentyp ... führt der Verweis auf ein nicht statisches Element oder eine nicht statische Basisklasse des Objekts zu undefiniertem Verhalten, bevor der Konstruktor mit der Ausführung beginnt
Beginnend mit GCC einer Version (und ich habe 7.3) wurde eine Optimierung der "Eliminierung von lebenslangen toten Speichern" veröffentlicht, die glaubt, dass wir uns nur während seiner Lebensdauer auf ein Objekt beziehen und alles außerhalb der Lebensdauer aushustet.
Die Lösung besteht darin, die neuen Optimierungen zu deaktivieren und wie im alten GCC zurückzukehren:
CFLAGS += -fno-strict-aliasing -fno-lifetime-dse -fno-delete-null-pointer-checks
Hier gibt es eine Diskussion darüber.
Aus irgendeinem Grund entschieden die Diskussionsteilnehmer, dass dies nicht in den Upstream aufgenommen werden würde. Aber Sie müssen immer noch versuchen, es zu senden.
Fügen Sie diese Optionen zu unserer ./hotspot/make/linux/makefiles/gcc.make
, setzen Sie alles wieder zusammen und sehen Sie die geschätzten Linien:
t$ ~/git/jdk8u/build/linux-x86_64-normal-server-fastdebug/jdk/bin/java -version openjdk version "1.8.0-internal-fastdebug" OpenJDK Runtime Environment (build 1.8.0-internal-fastdebug-me_2018_09_10_08_14-b00) OpenJDK 64-Bit Server VM (build 25.71-b00-fastdebug, mixed mode)
Fazit
Sie dachten wahrscheinlich, dass die Schlussfolgerung lauten würde: "Java ist eine Art Hölle, es gibt Müll im Code, es gibt keine Unterstützung, alles ist schlecht."
Es ist nicht so! Im Gegenteil, die obigen Beispiele zeigen, von welchem schrecklichen Übel unsere Freunde, Nekromanten von OpenJDK, uns abhalten.
Und trotz der Tatsache, dass sie C ++ leben und verwenden müssen, mit jedem UB zittern und die Compilerversion ändern und die Feinheiten der Plattformen lernen müssen, ist der endgültige Benutzercode in Java wahnsinnig stabil und basiert auf Builds, die auf offiziellen Websites von Unternehmen wie Azul veröffentlicht wurden. Red Hat und Oracle können in einem einfachen Fall kaum in die Kruste laufen.
Das einzig traurige ist höchstwahrscheinlich, dass die gefundenen Fehler in jdk8u wahrscheinlich nicht akzeptiert werden. Wir haben JDK 8 einfach genommen, weil es für uns einfacher ist, es hier und jetzt zu patchen, und wir müssen uns mit JDK 11 befassen. Trotzdem ist es meiner Meinung nach IMHO, JDK 8 im Jahr 2018 zu verwenden. Dies ist eine sehr schlechte Praxis, und wir tun dies nicht aus einem guten Leben heraus. Vielleicht wird sich unser Leben in Zukunft verbessern und Sie werden viele weitere unglaubliche Geschichten aus der Welt von JDK 11 und JDK 12 lesen.
Vielen Dank für die Aufmerksamkeit, die einem so langweiligen Text ohne Bilder geschenkt wurde :-)
Minute der Werbung. Die Joker 2018 Konferenz wird sehr bald stattfinden, wo es viele prominente Spezialisten für Java und JVM geben wird. Die vollständige Liste der Redner und Berichte finden Sie auf der offiziellen Website . Ich werde auch dort sein, es wird möglich sein, sich zu treffen und für das Leben und OpenJDK zu mahlen.