Tentang membangun JDK 8 di Ubuntu, kualitas kode Hotspot, dan mengapa semuanya gagal di C ++

Saya ingin tidur hari ini, tetapi sekali lagi gagal. Sebuah pesan muncul di Telegram bahwa seseorang tidak akan ke Jawa ... dan kami bangun hanya setelah beberapa jam, lelah dan bahagia.




Siapa yang dapat menggunakan pos ini? Ya, mungkin bagi siapa pun kecuali mereka yang juga mengumpulkan JDK8 atau hanya suka membaca kengerian mimpi buruk. Secara umum, saya memperingatkan Anda, tutup artikel ini dengan segera.

Tiga masalah:


  • Tidak mau ( level satu )
    Bagian yang sangat membosankan untuk dilewati. Hanya diperlukan bagi mereka yang ingin sepenuhnya memulihkan sejarah peristiwa;
  • Tidak mau ( level dua )
    Ini lebih menarik karena ada beberapa kesalahan khas, necromancy, necrophilia, yang BSD lebih baik daripada GNU / Linux dan mengapa perlu beralih ke versi baru JDK.
  • Bahkan jika itu terjadi, ia jatuh ke kerak bumi
    Lebih menarik. Yahuuu, JVM jatuh ke kerak, ayo tendang!

Di bawah kucing menunjukkan solusi terperinci untuk masalah, dengan pemikiran sisi berbeda tentang kehidupan.


Akan ada banyak C ++, tidak akan ada kode Java sama sekali. Setiap javist pada akhirnya mulai menulis hanya dalam C ++ ...


Tidak akan


Siapa pun yang membangun Java setidaknya sekali tahu bahwa itu terlihat seperti ini:


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 

(Semua pengguna saya hanya disebut "saya", sehingga Anda dapat memberikan mesin virtual kepada siapa saja kapan saja dan tidak membuat penolakan dari menggunakan nama pengguna Anda sendiri)


Masalahnya, tentu saja, ini tidak berhasil. Dan dengan cara yang agak sinis.



Tingkat penyelaman pertama


Mari kita coba jalankan:


 /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) { ^~~~~~~~~ 

Pertama, agar Anda mengerti, saya telah menginstal ini:


 $ g++ --version g++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0 Copyright (C) 2017 Free Software Foundation, Inc. 

Kompiler bukan kesegaran pertama, bukan 8.2, tetapi yang ini juga harus berfungsi.


Pengembang C ++ suka menguji perangkat lunak hanya pada versi kompiler yang telah mereka instal. Biasanya keinginan untuk menguji pada platform yang berbeda berakhir di suatu tempat di wilayah perbedaan antara gcc dan dentang dalam arti umum. Oleh karena itu, pada awalnya cukup normal untuk mengirimkan -Werror ("memperlakukan peringatan sebagai kesalahan") dan kemudian menulis kode yang di semua versi lain akan dianggap sebagai vorings.


Ini adalah masalah yang diketahui, dan jelas bagaimana menyelesaikannya. Anda perlu mengatur variabel lingkungan Anda CXX_FLAGS, untuk mengatur tingkat kesalahan yang benar.


 export CXX_FLAGS=-Wno-error=deprecated-declarations -Wno-error-deprecated-declarations 

Dan kemudian kita melihat yang indah:


 Ignoring CXXFLAGS found in environment. Use --with-extra-cxxflags 

Oke, bangun sistem, apa pun yang Anda mau! Kami ganti configure dengan yang ini:


 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 

Dan kesalahannya tetap sama!
Kami beralih ke artileri berat: kode sumber.


 grep -rl "Werror" . 

Sejumlah besar topi yang dibuat secara otomatis jatuh, di antaranya ada sekilas file yang bermakna:


 ./common/autoconf/flags.m4 ./hotspot/make/bsd/makefiles/gcc.make ./hotspot/make/solaris/makefiles/gcc.make ./hotspot/make/aix/makefiles/xlc.make 

Di flags.m4 kita dengan mudah menemukan pesan sebelumnya tentang "Mengabaikan CXXFLAGS" dan flag yang lebih matang dengan kode keras CCXX_FLGS (ya, dua huruf C), yang langsung bertindak sebagai ganti CFLAGS dan bukannya XX_FLAGS . Dengan nyaman! Dua fakta menarik:


  • Bendera ini tidak melewati parameter konfigurasi;
  • Dalam nilai default bermakna dan mencurigakan mirip dengan parameter ini:

  # Setup compiler/platform specific flags to CFLAGS_JDK, # CXXFLAGS_JDK and CCXXFLAGS_JDK (common to C and CXX?) if test "x$TOOLCHAIN_TYPE" = xgcc; then # these options are used for both C and C++ compiles CCXXFLAGS_JDK="$CCXXFLAGS $CCXXFLAGS_JDK -Wall -Wno-parentheses -Wextra -Wno-unused -Wno-unused-parameter -Wformat=2 \ -pipe -D_GNU_SOURCE -D_REENTRANT -D_LARGEFILE64_SOURCE" 

Pertanyaan ini terlihat sangat bagus di komentar - tetapi apa, apakah benderanya umum? Benar?


Kami tidak akan memainkan demokrasi dan mengesahkannya secara otoriter di sana -w ("jangan tampilkan kesalahan"):


  CCXXFLAGS_JDK="$CCXXFLAGS $CCXXFLAGS_JDK -w -ffreestanding -fno-builtin -Wno-parentheses -Wno-unused -Wno-unused-parameter -Wformat=2 \ 

Dan - bersorak! - kesalahan pertama yang kami alami. Dia tidak lagi melaporkan, dan secara umum semuanya baik-baik saja. Tampaknya.



Tingkat penyelaman kedua


Tapi sekarang jatuh di tumpukan tempat baru lainnya!


Ternyata -w kami berfungsi, tetapi tidak diteruskan ke semua bagian perakitan. Kami dengan cermat membaca makefile dan tidak mengerti bagaimana tepatnya parameter ini dapat diteruskan sama sekali. Benar-benar lupa tentang dia?


Mengetahui pertanyaan Google yang benar ("mengapa cxx tidak sampai ke build?!"), Kami dengan cepat sampai ke halaman bug dengan pepatah "mengkonfigurasi - dengan ekstra-cxxflag tidak memengaruhi hotspot" ( JDK-8156967 ).


Yang janji akan diperbaiki di JDK 12. Mungkin. Hebat - parameter build terpenting tidak digunakan dalam perakitan!


Ide pertama adalah, yah, mari kita menyingsingkan lengan baju kita dan memperbaiki kesalahan!


Kesalahan 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] 

Yah, kita mungkin perlu memperbesar wilayah. Seratus pound, seseorang menghitung buffer dengan mengklik tombol "I'm Lucky!" di google.


Tetapi bagaimana Anda memahami berapa banyak yang Anda butuhkan? Ada jenis penyempurnaan lain di bawah ini:


 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 ()); 

Posisi 12 terlihat seperti sesuatu yang berharga, yang sekarang Anda dapat membobol sumber dengan kaki kotor.


Kami naik ke dependencies.cpp dan mengamati gambar berikut:


 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()); } } 

Perhatikan garis yang bermasalah:


 char xn[10]; sprintf(xn, "x%d", j); 

Kami mengubah 10 menjadi 12, memasang kembali dan ... perakitan telah hilang!


Tetapi apakah saya satu-satunya yang begitu pintar dan memperbaiki bug sepanjang masa? Tidak diragukan, kami kembali mengarahkan megapatch kami ke Google: char xn[12];


Dan kita lihat ... ya, itu benar. Bug JDK-8184309 , dilarang oleh Vladimir Ivanov, berisi perbaikan yang persis sama.


Tetapi intinya adalah bahwa ini hanya diperbaiki di JDK 10 dan nifiga tidak di-backport ke jdk8u. Ini adalah pertanyaan mengapa versi baru Java diperlukan.


Kesalahan 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; } 

Diajarkan oleh pengalaman pahit sebelumnya, kami segera pergi untuk melihat apa yang ada di tempat ini di JDK 11. Dan ... file ini tidak ada di sana. Struktur direktori juga telah mengalami beberapa refactoring.


Tapi Anda tidak bisa pergi begitu saja dari kami!


Setiap javist adalah ahli nujum kecil dalam jiwanya, dan mungkin bahkan seorang necrophile. Karenanya, sekarang akan ada KEBUTUHAN BERAKSI!


Pertama, Anda perlu memohon jiwa orang mati dan mencari tahu kapan dia meninggal:


 $ 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 

Sekarang Anda perlu mencari tahu penyebab kematiannya:


 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 

Jadi, kita punya pembunuh: gziemski . Mari kita cari tahu mengapa dia memakukan file malang ini.


Untuk melakukan ini, lihat lemak di tiket yang ditentukan dalam ringkasan komit. Ini adalah JDK-8173715 :


Hapus FlatProfiler:
Kami berasumsi bahwa teknologi ini tidak lagi digunakan dan merupakan sumber pemindaian root untuk GC.


Untuk shih bis. Faktanya, sekarang kita diundang untuk memperbaiki jenazah agar bangunannya berjalan. Yang telah membusuk begitu banyak sehingga bahkan rekan ahli nujum kami dari OpenJDK meninggalkannya.


Mari membangkitkan orang mati dan mencoba bertanya padanya apa yang dia ingat terakhir. Dia sudah mati dalam revisi 47106, yang berarti ada satu kurang dalam revisi - ini adalah "sedetik sebelum":


 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 

Sayangnya, tidak ada sama sekali tentang return strcmp(name, _name) == 0; dalam diff no. Pasien meninggal karena pukulan dengan benda tajam tumpul (utilitas rm), tetapi pada saat kematian dia sudah sakit parah.


Mari menggali esensi dari kesalahan.


Inilah yang ingin disampaikan oleh pembuat kode:


  const char *name() const { return _name; } bool is_compiled() const { return true; } bool vm_match(const char* name) const { return strcmp(name, _name) == 0; } 

Sekarang sedikit filosofi.


Standar C11 dalam klausa 7.1.4, "Penggunaan fungsi perpustakaan", secara eksplisit mengatakan:


Setiap pernyataan berikut ini berlaku kecuali secara eksplisit dinyatakan lain dalam uraian terperinci yang mengikuti: Jika argumen ke suatu fungsi memiliki nilai yang tidak valid (seperti [...] penunjuk nol [...]) [...] perilaku tidak terdefinisi.

Yaitu, sekarang seluruh pertanyaannya adalah apakah ada beberapa "secara eksplisit dinyatakan sebaliknya" . Tidak ada yang tertulis dalam deskripsi strcmp di bagian 7.24.4, dan saya tidak punya bagian lain untuk Anda.


Artinya, kita memiliki perilaku yang tidak jelas di sini.


Tentu saja, Anda dapat mengambil dan menulis ulang bagian kode ini, mengelilinginya dengan verifikasi. Tetapi saya benar-benar tidak yakin bahwa saya memahami dengan benar logika orang yang menggunakan UB di tempat yang seharusnya. Sebagai contoh, beberapa sistem menghasilkan SIGSERV untuk zero dereferencing, dan pencinta retas dapat memanfaatkannya, tetapi perilaku ini tidak wajib dan dapat dimulai pada platform lain.


Ya, tentu saja, seseorang akan mengatakan bahwa Anda bodoh untuk diri sendiri, bahwa Anda menggunakan GCC 7.3, tetapi dalam GCC 4 semuanya akan terkumpul. Tapi perilaku tidak terdefinisi! = Tidak ditentukan! = Implementasi didefinisikan. Ini untuk dua yang terakhir dapat diletakkan untuk bekerja di kompiler lama. Dan UB dalam versi keenam adalah UB.


Singkatnya, saya benar-benar sedih dengan pertanyaan filosofis yang rumit ini (haruskah saya masuk ke kode dengan asumsi saya) ketika saya tiba-tiba menyadari bahwa itu bisa berbeda.


Ada cara lain


Seperti yang Anda tahu, pahlawan yang baik selalu berkeliling.


Bahkan jika kita mengabaikan filosofi kita tentang UB, ada banyak masalah di sana. Bukan fakta bahwa mereka dapat diperbaiki sampai pagi hari. Bukan fakta bahwa saya tidak bisa melakukannya dengan tangan saya yang bengkok. Apalagi fakta bahwa ini akan diterima di hulu: tambalan terakhir di jdk8u adalah 6 minggu yang lalu, dan ini adalah gabungan global dari tag baru.


Bayangkan saja kode di atas sebenarnya ditulis dengan benar. Semua yang berdiri di antara kami dan pelaksanaannya adalah peringatan, yang dianggap sebagai kesalahan karena bug dalam sistem build. Tetapi kita dapat membangun sistem pembangunan.


The Witcher Geralt of Rivia pernah berkata:


"Kejahatan itu jahat, Stregobor," kata si tukang sihir dengan muram, bangkit. - Lebih kecil, lebih besar, rata-rata - semuanya sama, proporsi sewenang-wenang, dan batas-batasnya kabur. Saya bukan seorang pertapa suci, tidak hanya melakukan satu kebaikan dalam hidup saya. Tetapi jika Anda harus memilih antara satu kejahatan dan yang lainnya, saya memilih untuk tidak memilih sama sekali.

- Zło ke zło, Stregoborze - rzekł poważnie wiedźmin wstając. - Mniejsze, większe, średnie, wszystko jedno, proporcje so umowne a granice zatarte. Dengan kata lain, pustelnikiem, maka Anda tidak perlu czyniłem dengan życiu. Ale jeżeli mam wybierać pomiędzy jednym złem a drugim, untuk wolę nie wybierać wcale.

Ini adalah kutipan dari The Last Wish, sebuah cerita berjudul Lesser Evil. Kita semua tahu bahwa Geralt hampir tidak pernah bisa memainkan peran karakter yang benar-benar netral, dan bahkan mati karena perilaku klasik yang kacau.


Jadi mari kita tunjukkan kejahatan yang lebih ringan dengan cepat. Mari kita lanjutkan tentang sistem build.


Pada awalnya, kita sudah melihat knalpot ini:


 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 

Membandingkan dua file ini, saya mematahkan seluruh wajah dengan facepalm dan menyadari perbedaan dalam budaya kedua platform:


BSD adalah kisah tentang kebebasan dan pilihan:


 # Compiler warnings are treated as errors ifneq ($(COMPILER_WARNINGS_FATAL),false) WARNINGS_ARE_ERRORS = -Werror endif 

GNU / Linux adalah rezim puritan otoriter:


 # Compiler warnings are treated as errors WARNINGS_ARE_ERRORS = -Werror 

Yah, itu akan diteruskan ke linux melalui XX_FLAGS , variabel ini tidak diperhitungkan saat menghitung WARNINGS_ARE_ERRORS ! Dalam build untuk GNU / Linux, kita tidak punya pilihan selain mengikuti default yang telah diluncurkan dari atas.


Ya, atau Anda dapat membuatnya lebih mudah dan mengubah nilai WARNINGS_ARE_ERRORS menjadi singkat, tetapi tidak kalah kuat -w . Bagaimana Anda suka itu, Elon Musk?


Seperti yang mungkin sudah Anda duga, ini sepenuhnya memecahkan masalah build ini.


Ketika kode dirakit, Anda melihat banyak masalah aneh, tampak sangat terbang melewati. Kadang-kadang itu terjadi begitu menakutkan sehingga saya benar-benar ingin menekan ctrl + C dan mencoba mencari tahu. Tapi tidak, kamu tidak bisa, kamu tidak bisa ...


Tampaknya semuanya berkumpul dan tidak membawa masalah tambahan. Meskipun, tentu saja, saya tidak berani memulai pengujian. Tetap saja, malam, mataku mulai bersatu, dan entah bagaimana aku tidak ingin pergi ke jalan terakhir - empat kaleng energi dari lemari es.



Jatuh ke kerak bumi


Majelis telah berlalu, executable telah dihasilkan, kami hebat.


Jadi kami sampai di garis finish. Atau tidak datang?


Majelis kami terletak dengan cara berikut:


 export JAVA_HOME=~/git/jdk8u/build/linux-x86_64-normal-server-fastdebug/jdk export PATH=$JAVA_HOME/bin:$PATH 

Ketika Anda mencoba menjalankan executable java , itu langsung macet. Bagi mereka yang tidak terbiasa - terlihat seperti ini:




Pada saat yang sama, Alex memiliki Debian 9.5, dan saya memiliki Ubuntu. Dua versi GCC yang berbeda, dua kerak yang terlihat berbeda. Saya memiliki pranks bersalah dengan patch manual strcmp dan beberapa tempat lagi, Alex tidak. Apa masalahnya?


Kisah ini layak untuk kisah yang terpisah, tetapi di sini mari kita langsung ke kesimpulan kering, kalau tidak saya tidak akan pernah menambahkan posting ini.


Masalahnya adalah pogromis C ++ favorit kami sekali lagi menggunakan perilaku yang tidak terdefinisi.


(Terlebih lagi, di mana dalam beberapa cara yang tidak diketahui tergantung pada implementasi kompiler. Namun, kita harus ingat bahwa UB selalu UB, bahkan pada versi kompiler yang diketahui tidak mungkin untuk diletakkan di atasnya)


Di satu tempat kita beralih ke bidang kelas yang tidak dirancang di sana, dan semuanya pecah. Jangan tanya bagaimana itu terjadi, semuanya rumit.


Sangat sulit bagi javista untuk membayangkan bagaimana seseorang dapat beralih ke kelas yang belum dibangun, kecuali dengan mengeluarkan tautan langsung dari konstruktor. Untungnya, bahasa C ++ yang luar biasa dapat melakukan semua atau hampir semua hal. Saya akan menulis contoh dengan pseudocode tertentu:


 class A { A() { _b.Show(); } private: static B _b; }; A a; BA::_b; int main() { } 

Selamat debug!


Jika Anda melihat C ++ 98 [class.cdtor]:


Untuk objek dengan tipe kelas non-POD ... sebelum konstruktor memulai eksekusi ... mengacu pada anggota non-statis atau kelas dasar dari objek yang menghasilkan perilaku tidak terdefinisi

Dimulai dengan GCC dari beberapa versi (dan saya memiliki 7,3), sebuah optimasi dari "eliminasi toko mati seumur hidup" muncul, yang percaya bahwa kita merujuk ke objek hanya selama waktu hidupnya, dan semuanya batuk di luar waktu hidup.


Solusinya adalah menonaktifkan optimisasi baru dan kembali seperti semula di GCC lama:


 CFLAGS += -fno-strict-aliasing -fno-lifetime-dse -fno-delete-null-pointer-checks 

Ada diskusi tentang ini di sini .
Untuk beberapa alasan, peserta diskusi memutuskan bahwa ini tidak akan dimasukkan dalam hulu. Tetapi Anda masih harus mencoba mengirimkannya.


Tambahkan opsi-opsi ini ke ./hotspot/make/linux/makefiles/gcc.make kami, ./hotspot/make/linux/makefiles/gcc.make kembali semuanya dan lihat baris yang dihargai:


 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) 

Kesimpulan


Anda mungkin berpikir bahwa kesimpulannya adalah: "Java adalah semacam neraka, ada sampah dalam kode, tidak ada dukungan, semuanya buruk."


Ini tidak benar! Sebaliknya, contoh di atas menunjukkan betapa jahatnya teman-teman kita, ahli nujum dari OpenJDK, yang menjauhkan kita.


Dan terlepas dari kenyataan bahwa mereka harus hidup dan menggunakan C ++, gemetar dengan masing-masing UB dan mengubah versi kompiler dan mempelajari seluk-beluk platform, kode pengguna akhir di Jawa sangat stabil, dan dibangun di situs web resmi perusahaan seperti Azul, Red Hat dan Oracle sulit menemukan kerak dalam kasus sederhana.


Satu-satunya hal yang menyedihkan adalah, kemungkinan besar, kesalahan yang ditemukan tidak mungkin diterima di jdk8u. Kami menggunakan JDK 8 hanya karena lebih mudah bagi kami untuk menambalnya di sini dan sekarang, dan kami harus berurusan dengan JDK 11. Namun demikian, untuk menggunakan JDK 8 pada 2018 adalah IMHO, ini adalah praktik yang sangat buruk, dan kami tidak melakukannya dari kehidupan yang baik. Mungkin hidup kita akan membaik di masa depan, dan Anda akan membaca banyak lagi kisah luar biasa dari dunia JDK 11 dan JDK 12.


Terima kasih atas perhatiannya pada teks yang membosankan tanpa gambar :-)


Menit periklanan. Konferensi Joker 2018 akan diadakan segera, di mana akan ada banyak spesialis terkemuka di Jawa dan JVM. Lihat daftar lengkap pembicara dan laporan di situs web resmi . Saya akan berada di sana juga, akan memungkinkan untuk bertemu dan mengerjakan sesuatu seumur hidup dan OpenJDK.

Source: https://habr.com/ru/post/id422861/


All Articles