新系统构建老 JDK 的问题处理
前话
前段时间简单地写了个 JVM,遗留一些憾事,是知能力与人力的缺失,每想于此,便不免心生愧疚。
在网络查找提升 JVM 知识的巧合中,无意看到大家对《深入理解Java虚拟机》的赞赏,便读之。由于作者使用的是 JDK 7,而如今就 JDK 15 也出世了,在新系统编译 JDK 7,难免出现疑难症状,特记之。
准备
并非所有环境都是一致,但是有类似的问题,可以保留参考,也可以当作一种启发,去解决过程所遇。
我的环境是在 LXD 下的 Archlinux,首先的是需要 gcc,因为涉及到编译,直接就将 Archlinux 的 devel 包也安装上:
pacman -Syy gcc base-devel
gcc --version
# gcc (GCC) 10.1.0
C/C++ 环境可以参考OpenJDK 所支持构建平台的 Wiki,不然版本不同要绕很多的弯路。
更改编译器,如 jdk 子项目的 make/common/shared/Compiler-gcc.gmk
或者 hotspot 子项目的 make/linux/makefiles/gcc.make
文件
因为需要使用 JDK 7 来编译 JDK 7 的源码和 Java 的自动化工具 ant,所以安装 JDK 7 和 ant:
pacman -Syy jdk7-openjdk ant
archlinux-java status
# Available Java environments:
# java-11-openjdk (default)
# java-7-openjdk
如果你的环境是 Debian 系的,上面安装的可以执行如下:
apt-get install -y default-jdk ant build-essential gawk m4 libasound2-dev libcups2-dev libxrender-dev xorg-dev xutils-dev x11proto-print-dev binutils zip unzip
下载 JDK 7 的源码,可以在 官方 或者 Github 下载,如:
git clone https://github.com/openjdk-mirror/jdk7u-jdk
其中 jdk7u 为主分支,里头分了几个子项目,如 corba、hotspot、jdk、langtools,他们可以在主分支编译或者单独编译,编译步骤都是一样,我们就以 jdk 子项目为例子。
进入源码的 ./make
文件夹,执行 make sanity
可以检查编译环境,通过之后使用 make
编译。编译前需要设置一些环境变量,不然会出现 error 和 warn,需要处理的只是 error,简单来说只需:
export LANG=C
假如为了加速编译,可以多线程编译:
export HOTSPOT_BUILD_JOBS=16
export ALT_PARALLEL_COMPILE_JOBS=16
为了更好知道编译过程的错误,可能还需要:
export SKIP_DEBUG_BUILD=false
export SKIP_FASTDEBUG_BUILD=false
export DEBUG_NAME=debug
每次出现错误,最好是 make clean
一下先再 make
。
编译后的 JDK 会在 ./build
目录中:
./build/linux-amd64/bin/java -version
# openjdk version "1.7.0-internal-debug"
症状
缺少 freetype 2.3 的依赖
直接使用发行版的包管理安装,如 Archlinux:
pacman -Syy freetype2
已经有 freetype,且版本大于 2.3,但是还是提示版本低
看了系统是 2.10 的,可是还是提示低于 2.3,看到 freetypecheck.c 文件里面是用字符串来判断版本高低,那自然排序 2.10 肯定小于 2.3,所以需要到 freetype 官网下载源码,在本地执行 ./configure && make
,然后编译 JDK 7 的时候指定该编译的地址。
export _freetype_temp=./freetype-2.3.0
export ALT_FREETYPE_LIB_PATH=${_freetype_temp}/objs/.libs
export ALT_FREETYPE_HEADERS_PATH=${_freetype_temp}/include
提示系统不支持
export DISABLE_HOTSPOT_OS_VERSION_CHECK=ok
出现属于 Java Class 的报错或者找不到虚拟机
因为 JDK 的代码主要是 C++ 和 Java,Java 需要 JDK 7 的编译器,所以需要为其指定一个:
export ALT_BOOTDIR=/usr/lib/jvm/java-7-openjdk
export ALT_JDK_IMPORT_PATH=/usr/lib/jvm/java-7-openjdk
export ALT_HOTSPOT_IMPORT_PATH=/usr/lib/jvm/java-7-openjdk
提示 CurrencyData 超过 10 年的报错
以当前年为限,选定一个 10 年内的年份去替换 ./src/share/classes/java/util/CurrencyData.properties
里的所有超过 10 年的时间,如现在 2020,可以选择 2018:
sed -i 's/[0-9]\{4\}-/2018-/g' src/share/classes/java/util/CurrencyData.properties
出现编译 -Wxxx 的编译错误
在对应项目的编译器配置中修改编译选项,如 hotspot 项目里提示 -Werror=deprecated-declarations
,直接在 make/linux/makefiles/defs.make
里的文件前面加入如下:
MAKE_ARGS=CXXFLAGS=" \
-Wno-error=deprecated-declarations \
"
提示认不了 -mimpure-text 选项
这个选项是 g++ 带的,新版已经去掉该选项了,所以我们也在编译选项去掉,在 ./make/common/shared/Compiler-gcc.gmk
里头找到 SHARED_LIBRARY_FLAG = -shared -mimpure-text
改为 SHARED_LIBRARY_FLAG = -shared
。
提示函数 multiple definition 的报错
这是 gcc 版本 10 之后出现的问题,需要做的就是将 header 文件的声明改为 extern
,然后对应的 c 文件在引入 header 头后追加那些声明。
现在有三处地方有该问题:
src/solaris/native/sun/security/jgss/wrapper/NativeFunc.h 和 src/solaris/native/sun/security/jgss/wrapper/NativeFunc.c
将在 header 头的
ftab
函数加上extern
:extern GSS_FUNCTION_TABLE_PTR ftab;
在 c 源码文件中,引入 header 头之后加入声明:
#include "NativeFunc.h" GSS_FUNCTION_TABLE_PTR ftab;
src/solaris/native/sun/nio/ch/Sctp.h 和 src/solaris/native/sun/nio/ch/SctpNet.c
里面涉及几个函数,但是修改方法同 1。
src/solaris/native/sun/awt/gtk2_interface.h 和 src/solaris/native/sun/awt/gtk2_interface.c
修改方法一样,但是函数有点多,所以采用脚本,倒数 160 行开始,复制到 c 源码文件中,然后执行:
tail -160 src/solaris/native/sun/awt/gtk2_interface.h | sed -i 's/^\([^#\/[:space:]]\)/extern \1/g'
提示找不到 <sys/sysctl.h>
新版的 glibc 已经没有这个 header 头,这里可以简单使用 <linux/systcl.h>
代替,现在需要改的有:
- ./src/solaris/native/java/net/PlainSocketImpl.c
- ./src/solaris/native/java/net/PlainDatagramSocketImpl.c
返回类型 bool 无法转为 jobject
根据提示找到 ./src/share/native/com/sun/java/util/jar/pack/jni.cpp
文件对应的行数,然后修改该函数的返回类型,由 return false
改为 return 0
。
C++ 关键字 register 已经废除的问题
在 make 文件夹的 defs.make 文件里头 MAKE_ARGS
追加忽略该特性:
MAKE_ARGS=CXXFLAGS=-Wno-error=register
一些 C++ 的头文件找不到
诸如 hotspot/src/share/vm/adlc/adlc.hpp 里头找不到 opto/opcodes.hpp 和 opto/adlcVMDeps.hpp,如果是需要自己修改文件地址,那确实是麻烦,在 defs.make 的加入全部文件夹:
INCDIRS := $(addprefix -I,$(shell find . -type d -print))
MAKE_ARGS=CXXFLAGS="-Wno-error=register \
-I ${INCDIRS}"
值得注意的是,有些依赖的头文件是在 build 后生成的文件,所以也需要加上类似 .../hotspot/build/linux/linux_i486_compiler2/generated
的路径。
编译时候提示未实现的断言失败和 $XMMRegister 变量问题
找到 hotspot/src/share/vm/adlc/output_c.cpp
对应的地方注释掉:
/* printf("emit_field: %s\n",rep_var);
globalAD->syntax_err(_inst._linenum, "Unknown replacement variable %s in format statement of %s.", rep_var, _inst._ident);
assert( false, "UnImplemented()"); */
fpermissive 错误的问题
这个在新版 gcc 是个无法忽略的错误,主要是两个文件 hotspot/src/share/vm/oops/cpCacheOop.hpp
和 hotspot/src/share/vm/oops/cpCacheOop.hpp
。分别将语句改成如下,其实就是 -1
改为 ~0u
:
option_bits_mask = ~(((~0u) << tos_state_shift) | (field_index_mask | parameter_size_mask))
all_types = ((1 << TYPE_LIMIT) - 1) & ((~0u) << FIRST_TYPE),
friend 函数默认值问题
在对应文件,如 hotspot/src/share/vm/code/relocInfo.hpp
中去掉默认值:
inline friend relocInfo *prefix_relocInfo*(int datalen);
系统函数没法调用到
这是因为定义识别到 LINUX 的宏,在编译参数追加:
MAKE_ARGS=CXXFLAGS=" \
-Wno-error=deprecated-declarations \
-DLINUX=1 \
-DTARGET_COMPILER_gcc=1 \
"
jlong 没有定义
这个因为 Linux 下没有 __int 64
的 bug,可以追加个,最简单是在编译参数里追加:
MAKE_ARGS=CXXFLAGS=" \
-D__int64=\"long long\" \
"
没有找到 OpenJDK 自己的头文件
INCDIRS := $(addprefix -I,$(shell find $(shell pwd)/.. -type d -print))
-I ${INCDIRS} \
"