我今天想睡觉,但又失败了。 电报中出现一条消息,提示某人不会使用Java ...,只有几个小时,又累又快乐,我们才醒来。

谁可以使用此帖子? 是的,除了那些也收集JDK8或只喜欢阅读噩梦恐怖片的人之外,大概是任何人。 总的来说,我警告过您,请紧急关闭这篇文章。
三个问题:
- 不参加(第一级 )
要跳过的部分非常无聊。 仅对于那些想要完全恢复事件历史的人而言; - 不参加(第二级 )
它更有趣,因为存在一些典型的错误,如死灵症,死灵症,哪个BSD优于GNU / Linux,以及为什么要升级到新版本的JDK。 - 即使发生,它也会掉入地壳
更有趣。 Yahuuu,JVM掉进了地壳,让我们踢吧!
在猫的下方显示了对问题的详细解决方案,对生活有不同的想法。
将会有很多C ++,根本不会有Java代码。 最后,任何最笨拙的事情都只开始用C ++编写。
不去
至少一次构建Java的人都知道它看起来像这样:
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
(我的所有用户都简称为“我”,因此您可以随时将虚拟机提供给任何人,而不会因为使用您自己的用户名而遭到拒绝)
当然,问题在于这不起作用。 并以一种愤世嫉俗的方式。
第一次潜水
让我们尝试运行:
/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) { ^~~~~~~~~
首先,为了您理解,我已经安装了此软件:
$ g++ --version g++ (Ubuntu 7.3.0-16ubuntu3) 7.3.0 Copyright (C) 2017 Free Software Foundation, Inc.
编译器不是第一个版本,不是8.2,但是这个也应该可以工作。
C ++开发人员喜欢仅在已安装的编译器版本上测试软件。 通常,在一般意义上,在不同平台上进行测试的愿望会终止于gcc和clang之间差异的区域中的某个位置。 因此,先运行-Werror
(“将警告视为错误”),然后编写代码,将其在所有其他版本中视为恶意代码,这是很正常的。
这是一个已知的问题,很清楚如何解决。 您需要设置环境变量CXX_FLAGS,在其中设置正确的错误级别。
export CXX_FLAGS=-Wno-error=deprecated-declarations -Wno-error-deprecated-declarations
然后,我们看到了奇妙的效果:
Ignoring CXXFLAGS found in environment. Use --with-extra-cxxflags
好的,构建系统,随您便! 我们用以下命令替换configure:
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
和错误保持不变!
我们传递给重型火炮:源代码。
grep -rl "Werror" .
大量自动生成的帽子都会掉落,其中包括一些有意义的文件:
./common/autoconf/flags.m4 ./hotspot/make/bsd/makefiles/gcc.make ./hotspot/make/solaris/makefiles/gcc.make ./hotspot/make/aix/makefiles/xlc.make
在flags.m4
我们可以轻松找到上一条有关“忽略CXXFLAGS”的消息和更成熟的硬编码标志CCXX_FLGS
(是的,两个字母C),该标志立即代替CFLAGS
和XX_FLAGS
。 方便! 有两个事实很有趣:
- 此标志不通过configure参数传递;
- 在默认值中,这些参数有意义且可疑地类似于以下参数:
这个问题在注释中看起来非常好-但是,标记是常见的吗? 对不对
我们将不使用民主制度,而在-w
那里对其进行威权主义的硬编码(“不显示任何错误”):
CCXXFLAGS_JDK="$CCXXFLAGS $CCXXFLAGS_JDK -w -ffreestanding -fno-builtin -Wno-parentheses -Wno-unused -Wno-unused-parameter -Wformat=2 \
还有-干杯! -我们遇到的第一个错误。 她不再举报了,一般来说一切都很好。 好像。
第二次潜水
但是现在它落入了其他许多新地方!
事实证明,我们的-w
可以工作,但不能转发到程序集的所有部分。 我们仔细阅读了makefile,完全不知道该参数的转发方式。 真的忘了他吗?
知道了正确的Google问题(“为什么cxx无法进入构建版本?!”),我们很快进入错误页面,并说“ configure --with-extra-cxxflags不会影响热点” ( JDK-8156967 )。
哪个承诺将在JDK 12中修复。 太棒了-程序集中没有使用最重要的build参数!
第一个想法是,让我们卷起袖子,纠正错误!
错误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]
好吧,我们可能需要扩大区域。 一百磅,有人通过单击“我很幸运!”来计算缓冲区。 在谷歌。
但是您如何理解您的需求呢? 以下是另一种改进:
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 ());
位置12看起来很有价值,现在您可以用脏脚闯入信号源。
我们进入dependencies.cpp
,观察下图:
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()); } }
注意有问题的行:
char xn[10]; sprintf(xn, "x%d", j);
我们将10更改为12,重新组装,而...组装已经消失了!
但是我是唯一一个如此聪明并且修复了所有时间错误的人吗? 毫无疑问,我们再次将巨型补丁带入Google: char xn[12];
我们看到...是的,没错。 弗拉基米尔·伊万诺夫(Vladimir Ivanov)禁止的错误JDK-8184309包含完全相同的修复程序。
但最重要的是,它仅在JDK 10中已修复,而nifiga并未反向移植到jdk8u。 这就是为什么需要新版本的Java的问题。
错误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; }
通过先前的痛苦经验,我们立即去看看JDK 11中的这个位置。而且...此文件不存在。 目录结构也进行了一些重构。
但是,您不能摆脱我们!
任何勇敢者在他的灵魂中都是个死灵法师,甚至是个死灵法师。 因此,现在将有紧急行动!
首先,您需要吸引死者的灵魂,并找出死者的死亡时间:
$ 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
现在,您需要找出他的死因:
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
因此,我们有一个杀手:: gziemski 。 让我们找出为什么他钉了这个不幸的文件。
为此,请在提交摘要中指定的故障单中找到胖子。 这是JDK-8173715 :
删除FlatProfiler:
我们假定该技术已不再使用,并且是GC根扫描的来源。
对于shih bis。 实际上,现在我们被邀请修理尸体,以使建造顺利进行。 它已经分解得如此之多,以至于我们甚至来自OpenJDK的死灵法师同事都放弃了它。
让我们复活死者,并尝试问他最后记得的事情。 他已经在版本47106中死了,这意味着版本中少了一个-这是“第二个”:
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
不幸的是,与return strcmp(name, _name) == 0;
无关return strcmp(name, _name) == 0;
在差异号 病人因用钝的锋利物体(rm功用)击打而死亡,但在死亡时他已经身患绝症。
让我们深入研究错误的本质。
这是代码作者想要告诉我们的:
const char *name() const { return _name; } bool is_compiled() const { return true; } bool vm_match(const char* name) const { return strcmp(name, _name) == 0; }
现在有点哲学。
第7.1.4节“库函数的使用”中的C11标准明确指出:
除非在后面的详细说明中另有明确说明,否则以下每个语句均适用:如果函数的参数具有无效值(例如[...]空指针[...]),行为是不确定的。
也就是说,现在的整个问题是是否存在“另有明确说明” 。 在7.24.4节中对strcmp
的描述中没有任何内容,我没有其他要介绍的部分。
也就是说,我们在这里有未定义的行为。
当然,您可以获取并重写这段代码,并在其周围进行验证。 但是我完全不确定我是否正确理解了应该使用UB的人们的逻辑。 例如,某些系统生成SIGSERV进行零解引用,黑客爱好者可以利用它,但是这种行为不是强制性的,可能会在另一个平台上启动。
是的,当然,有人会说您是一个傻瓜,您使用的是GCC 7.3,但是在GCC 4中,一切都会聚集在一起。 但是未定义的行为!=未指定!=实现已定义。 可以将这最后两个放在旧的编译器中。 第六版的UB是UB。
简而言之,当我突然意识到它可能与众不同时,我为这个复杂的哲学问题(我应该以自己的假设进入代码)感到完全难过。
还有另一种方法
如您所知,好英雄总是四处走动。
即使我们忽略关于UB的哲学,那里也存在着令人难以置信的大量问题。 他们不是可以修理直到早上的事实。 不是我无法用弯曲的双手做这件事。 更不用说它会在上游被接受的事实:jdk8u中的最后一个补丁是在6周前,这是新标签的全球合并。
试想一下上面的代码实际上是正确编写的。 我们与执行之间存在的只是警告,由于构建系统中的错误,该警告被视为错误。 但是我们可以构建一个构建系统。
里维亚的巫师杰拉特曾经说过:
“邪恶是邪恶的,斯崔戈博尔,”女巫严肃地说,站起来。 -较小,较大,平均-一切都一样,比例是任意的,边界也模糊了。 我不是一个神圣的隐士,不仅在我的生活中做了一件好事。 但是,如果您必须在一种邪恶与另一种邪恶之间做出选择,我宁愿根本不选择。
-Zło到Zło,Stregoborze-rzekłpoważniewiedźminwstając。 -Mniejsze,większe,średnie,wszystko jedno,proporcjesąumrane格兰尼萨特zatarte。 Nie jestemświątobliwympustelnikiem,nie samo dobroczyniłemwżciu。 Alejeżelimamwybieraćpomiędzyjednymzłem吸毒,向wolęniewybieraćwcale致意。
这摘自《最后的愿望》,一个名为《小恶魔》的故事。 众所周知,杰拉特(Geralt)几乎永远无法扮演真正中立的角色,甚至因另一种典型的混沌行为而死亡。
因此,让我们快速展示一下较小的邪恶。 让我们开始构建系统。
在开始的时候,我们已经看到了这种疲惫:
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
比较这两个文件,我用facespalm弄乱了脸,意识到了两个平台的文化差异:
BSD是关于自由和选择的故事:
# Compiler warnings are treated as errors ifneq ($(COMPILER_WARNINGS_FATAL),false) WARNINGS_ARE_ERRORS = -Werror endif
GNU / Linux是一种专制的纯粹主义政权:
好吧,它将通过XX_FLAGS
转发到linux,在计算WARNINGS_ARE_ERRORS
时不会考虑该变量! 在GNU / Linux构建中,我们别无选择,只能遵循从上面启动的默认设置。
好吧,或者您可以简化它,并将WARNINGS_ARE_ERRORS
的值更改为简短但功能-w
。 埃隆·马斯克(Elon Musk),您觉得怎么样?
您可能已经猜到了,这完全解决了该构建问题。
汇编代码后,您会看到一堆奇怪的,看上去非常可怕的问题。 有时它发生得太可怕了,以至于我真的想按ctrl + C并尝试找出答案。 但是不,你不能,你不能...
似乎一切都聚集了,没有带来任何其他问题。 尽管我当然不敢开始测试。 仍然,到了晚上,我的眼睛开始粘在一起,以某种方式我不想去不得已-从冰箱里取出四罐能量。
掉入地壳
程序集已经通过,可执行文件已经生成,我们很棒。
因此,我们来到了终点线。 还是没来?
我们的组装方式如下:
export JAVA_HOME=~/git/jdk8u/build/linux-x86_64-normal-server-fastdebug/jdk export PATH=$JAVA_HOME/bin:$PATH
当您尝试运行java
可执行文件时,它立即崩溃。 对于那些不熟悉的人-看起来像这样:

同时,Alex有Debian 9.5,而我有Ubuntu。 GCC的两个不同版本,两个不同外观的外壳。 我对strcmp手动补丁和其他几个地方很无知,但Alex没有。 怎么了
这个故事值得一个单独的故事,但在这里让我们直接得出结论,否则我将永远不会添加这篇文章。
问题是我们最喜欢的C ++种族主义者再次使用了未定义的行为。
(此外,它以某种未知的方式取决于编译器的实现。但是,我们必须记住,UB始终是UB,即使在已知版本的编译器中,也无法将其放在上面)
在一个地方,我们转到那里设计不足的课程领域,一切都中断了。 不要问它是怎么发生的,一切都很复杂。
一个javista很难想象一个人如何可以转向一个构造欠佳的类,除非通过直接从构造函数中发出指向它的链接。 幸运的是,出色的C ++语言可以完成所有或几乎所有事情。 我将用一个特定的伪代码编写一个示例:
class A { A() { _b.Show(); } private: static B _b; }; A a; BA::_b; int main() { }
有一个不错的调试!
如果您查看C ++ 98 [class.cdtor]:
对于非POD类类型的对象...在构造函数开始执行之前...引用该对象的任何非静态成员或基类都会导致未定义的行为
从某个版本的GCC(我有7.3)开始,出现了“终生消除死存储”的优化,它认为我们仅在对象的生命周期内引用它,并且所有事物在生命周期之外都会咳嗽。
解决方案是禁用新的优化并返回旧的GCC:
CFLAGS += -fno-strict-aliasing -fno-lifetime-dse -fno-delete-null-pointer-checks
这里有一个讨论。
由于某些原因,讨论参与者决定不将其包含在上游。 但是您仍然必须尝试发送它。
将这些选项添加到我们的./hotspot/make/linux/makefiles/gcc.make
,再次重新组装所有内容,然后查看下面的代码行:
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)
结论
您可能以为结论是:“ Java真是个地狱,代码中有垃圾,没有支持,一切都不好。”
事实并非如此! 相反,以上示例表明,我们的朋友(来自OpenJDK的巫师)使我们远离了什么可怕的邪恶。
尽管事实上他们必须生活和使用C ++,与每个UB颤抖并更改编译器版本并了解平台的精妙之处,但Java的最终用户代码还是非常稳定的,并且在诸如Azul这样的公司的官方网站上发布,在一个简单的案例中,红帽和甲骨文几乎不会陷入困境。
唯一可悲的是,很可能发现的错误不太可能在jdk8u中接受。 我们之所以选择JDK 8,仅仅是因为它使我们现在更容易修补它,而我们将不得不处理JDK 11。 尽管如此,恕我直言,要在2018年使用JDK 8是一个非常糟糕的做法,我们并非一帆风顺。 也许将来我们的生活会有所改善,并且您将从JDK 11和JDK 12的世界中读到更多令人难以置信的故事。
感谢您对无图文字的关注:-)
分钟的广告。 Joker 2018大会将很快举行,届时将有许多Java和JVM的知名专家。 在官方网站上查看发言人和报告的完整列表。 我也将在那里,有可能与OpenJDK相遇并为之奋斗。