wind2412的部落格✨~


  • 首页

  • 归档

  • 标签

JIT okay~

发表于 2019-06-15

在 wind_jvm 上的即时编译器在今年的 4 月 4 日完成了。并不打算放在 github 上。
学到了很多的东西,对 JIT 有了新的认识。
毕设答辩前夕,不由得唏嘘不已。
从 HIR 到 LIR,从静态单赋值的建立到解构。
Cliff Click 大神的 GCM 算法,简直是拯救了这个编译器,哈哈。它让我的 HIR 能够成功编译成 LIR。当时琢磨怎样将一个图形态的 IR 编译到混合形态的低层 IR,也是挺痛苦的,哈哈。头发掉了一斤。
线性寄存器分配也是非常有意思,让人难受的有意思。可能是自己的功力不够吧。还是要多努力啊。Christian Wimmer 大神真的是强啊。人家的硕士论文。比不了啊。
生成 X86 平台的机器码也非常的有趣。
过几天看看心情,把论文的东西发一部分上来。心情不好就算了。
哎呀。真是不错的一段时光。
虽然不知道以后会如何,但是还是乐观些吧。
突然想到,博客不设置评论才是最好的。邮件才是交流的好方式。
还是非常期待的。就先这样吧。

编译 hsdis mac 版本,打印 jvm 执行时汇编信息

发表于 2018-02-09

别人家的仓库

wget 一下,然后把 hsids-amd64.dylib 放到 /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/server/ 目录下,用 java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly <Java Main Class Name(no suffix)> 即可。
以及放在 debug 版本:~/jvm/jdk8_mac/build/macosx-x86_64-normal-server-slowdebug/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home/jre/lib/server 目录下,但是这时候要指定:export LD_LIBRARY_PATH=~/jvm/jdk8_mac/build/macosx-x86_64-normal-server-slowdebug/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home/jre/lib/server 才行,要不链接找不到。之后 java1 -XX:+PrintAssembly <Java Main Class Name(no suffix)> 即可。详见:http://yueyemaitian.iteye.com/blog/2042772。

唉……其实在我的机器上并没有编译成功,错误信息比较诡异。所以请用上边取巧的办法。
https://www.chrisnewland.com/building-hsdis-amd64dylib-on-mac-osx-376
最后需要加一个参数 ARCH=amd64…… 要不一直默认编译 i386 的……
用 clang 编译的,不会有问题。

虚拟机完成~

发表于 2017-12-28

前言

首先甩仓库:wind jvm
一开始只是想写一个 java 的 class file parser。后来把这东西变成了一个 tool,请左转我的javap。后来看到 @racaljk 同学用 java 实现了一个小虚拟机,感觉很有意思,遂学习了一波规范,然后写了个 C++ 的。大三下准备实习,但是还没什么能拿出手的。所以正好现在大三上有点时间,于是花了两个月写了一个。

类似项目:

@racaljk 的 YVM。不过 @racaljk 学习貌似很忙,一直没有更新QAQ。最初知道你是因为你的 hosts,实在是帮了不少忙,非常感激。而且 programming 也很强,实在佩服。
@lfkdsk 学长的 JustVM。学长实在很强,看到一堆的个人项目就五体投地了。当时我也因为 Jar 文件解包比较麻烦,看到 @lfkdsk 是直接文件读取 zip 的。因为没有现成的 zip 库也不想在上边花太多时间,所以我就采取直接把 rt.jar 全部解压的省事策略了。话说回来,大工和我们还是邻居(,而且 @lfkdsk 学长貌似也是哈尔滨人OWO。
以及 @zxh0 大大的 jvm.go。虽然没学过 go 语言,不过还是能看懂一些,也参考了部分思路。
不过最重点的还是 openjdk 的 hotspot…… 虽然管中窥豹,不过也可以略见一斑了,学到了非常多的东西~

具体

我的 wind jvm 也就是个小玩具。代码总量用 cloc 去一下水分,也就 15k 左右。一共花了两个月代码时间,其实还有一个月是在学习各种乱七八糟的支持项目的知识。还 reference 了各种东西,列举如下:

  • jvm8 spec
  • 周志明大大的《深入理解 java 虚拟机》
  • 陈涛大大的《HotSpot 实战》
  • (日)中村成洋先生的《垃圾回收的算法与实现》(中译)
  • 等等。重要的还有一堆网络资源。比如 R大的 hllvm 论坛:hllvm论坛~

那么说下打开方式

我的 README 上都有写,在这里重写一遍。

  1. 首先我只支持 linux 和 mac。因为底层用了各种操作系统函数,pthread,stat 啥的。我的机器是 mac,所以就不支持 Windows 了。然后呢,我们需要 boost 库。用 brew 安装和 apt-get 啥的,yum 啥的都行。mac 就是 brew install boost,然后 ubuntu 应该是 sudo apt-get install libboost-all-dev。
  2. 这样我们就有了 boost 支持了。于是我们应该去 Makefile 修改一下,因为我配置的是我机器的环境,而且没用 cmake。所以要手动修改,把我机器上的 boost 路径目录换成你的就可以了。比如如果是 mac 的话,把 ifeq 中的 $(CC) $(LINK_FLAGS) -L/usr/local/Cellar/boost/1.60.0_2/lib/ .... 里边的目录换成你自己的就行。如果是 linux,就把 else 中的 $(CC) $(LINK_FLAGS) -L/usr/lib/x86_64-linux-gnu/ 换成你自己的。不过如果是 ubuntu,八成不需要改,因为目录的版本无关。其他的 linux 就不知道了。
  3. 然后呢,你需要知道你的 jdk class 文件路径。mac 上,一般在 /Library/Java/JavaVirtualMachines/jdk1.8xxx.jdk/Contents/Home/jre/lib/ 下的 rt.jar 文件。如果是 linux,一般在 /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ 下。配置到 config.xml 中相应位置就可以了。
  4. 于是应该就完事了。直接跑 make -j 8 啥的 8 线程编译就可以。当然如果你是虚拟机,虚拟内存没配置够的话就算了,直接跑 make -j 2 或者 make -j 3 这种就行了。
  5. 之后 bin 目录会出现 wind_jvm 这个 executable file。注意:一定要在 wind_jvm/ 目录运行 ./bin/wind_jvm Test1 这样的命令。因为内部我的 system lib path 是通过当前路径来获取的。如果不在 wind_jvm/ 目录下跑,就应该会报错。然后我给了十几个 TestX.java 文件,执行 make test 就能编译。有一个 Test7.java 是不行的。那个只有 debug version jvm 的工具才能编译。所以我编译好了直接放上去了。然后运行 ./bin/wind_jvm Test1 这种命令就好。不加 .class 后缀,参数必须有且仅有一个。
  6. 然后就可以玩了。不过只支持特定实现好的库,你要 socket 什么的都是没有的。不过日后实现看情况可以往上加,你也可以来 pull request 哦。
  7. 如果有 issue 请在 github 上上传 issue。

特性支持

  1. 完整的 ClassFileParser。那个 tool 的地址我已经刚才写过了。这东西才 3k 行多一点而已。
  2. 支持大部分常用反射机制。其他的是我没写的。因为太多了……不过想写的话肯定是有的。具体在 src/native/sun_reflect_Reflection.hpp(cpp),src/native/sun_reflect_NativeConstructorAccessorImpl.hpp(cpp) 等等文件中。
  3. 支持底层的 Unsafe 类中的大部分。如果想要支持 jdk 类库,这个类是必须要写且必须实现的。这个类可以添加 java 不能而 C++ 能的指针操作。必须强行交换两个对象什么的。当然,还需要有少量 CAS support。并发非常必要。
  4. 支持简单多线程。Thread 类的底层方法是通过操作系统级别的线程支持的。比如 pthread 库。
  5. 支持异常机制。stack unwind 栈回溯,athrow 以及可以 catch 字节码已经处理好的异常表。Test7.java 就是测试多线程异常的。
  6. 支持 GC。parallel GC 支持的保证是 stop-the-world (调 bug 可是好长好长时间好痛苦哇),使用了 GC-Root 算法,以及 GC 复制算法。见:gc.cpp……虽然代码量不大确是调试时间最长最难受的部分……毕竟这不是单线程 GC,是多线程的……不过我的实现肯定也是 too young 的,因为并没有各种菊苣的 paper 的支持。
  7. 部分支持 lambda,比如简单的 invokedynamic 类似 Thread t = new Thread(() -> System.out.println("hello world"));,Test4,5,6,8,11,13 是测试 lambda 和 invoke(MethodHandle) 的。不过很遗憾这一部分理解有些跟不上,虽然支持是比较容易,但是想要理解类库究竟是怎么实现 lambda,还需要积累和进一步研究。因此只能支持部分。[注:部分测试用例 from network]。具体实现代码请见:invokedynamic。当然不可能只有这点。这里是核心部分。还有待支持更多。
  8. 字节码方面,支持绝大多数,没用到的就没写(怕写错),wide 指令这种,我是没加的。

实现细节

  1. 和 openjdk 一样的,klass oop 二分模型。
  2. 解释型。完全在解释 bytecode。因此效率上肯定差强人意。
  3. 按照真正的 jvm 跑 class 文件的流程运行(几乎)。初始化 mirror,初始化基础类,使用 C++ 实现的 bootstraploader,进而调用 java 的 AppClassLoader 来加载 main class。
  4. 每个线程配上了不同的颜色(,方便观看(其实更重要的是方便调试哇。(笑)
  5. 等等。细节太多了。如果你想看更多的细节,下文有字节码执行的流程输出。

糟粕

  1. 一开始什么也不会的时候,用字符串查找类……这是特别悲伤的设计。严重拖慢速度,历史遗留问题。
  2. 有时跑 Test7 这种多线程测试用例碰到异常的时候,最后会段错误。其实是完全可以解决的。用 pthread_cancel 配上 pthread_join 就可以完全解决。不过一开始的设计是没考虑到这么多,直接把所有线程 detach 了。如果要改,势必代码的形状会特别悲伤。所以左思右想还是维持原状,并不影响运行结果。其实这样是并不对的,java 要求某一线程抛异常,不会影响其他线程的执行。其实真正的实现是要用 join 的。
  3. 等等。

输出细节

如果想要开启更多的细节,可以在 Makefile 中修改,原本是 CPP_FLAGS := -std=c++14 -O3 -pg,我在后边写了一个加上 -DBYTECODE_DEBUG -DDEBUG 的,启用这个就可以启用所有的字节码调试代码。引用一张月初放在博客上的图:
IMAGE
如果还要看 classfile 的文件 parse,运行时常量池的解析,以及字符串池的常量字符串,可以打开 -DKLASS_DEBUG -DPOOL_DEBUG -DSTRING_DEBUG 宏,然后重新编译就好。
不过!如果这么打开,由于一开始初始化虚拟机,需要加载各种类,需要跑各种字节码,输出会是巨量的。没记错的话跑一个 hello world 貌似就要上十w的字节码执行量吧。因为我是解释型,自然执行的机器码比这还多;如果是编译型,那就快了。所以如果发现终端吃不消,请及时关闭。后期由于字节码输出量巨大,我从来都是关闭这些宏的,只看结果忽略执行过程。

写文章的目的

为了骗 star(,同时也是为了交友(。希望有同样爱好和兴趣的童鞋能够一起偷(yu)税(yue)地交流。那么就写到这吧。同时发到博客和知乎。

最终的感谢

感谢这个领域的先驱们,感谢各种热心的回复,感谢给我思路和灵感的人们。(热泪盈眶)
尤为感谢《spec》《深入java虚拟机》《hotspot实战》的作者们,以及R大的论坛和R大在知乎上传播的各种深入的虚拟机知识。实在是感激不尽!
另:
就是一个区区玩具,就不胡乱@了。供学习和交流~随便乱加了一个开源协议,虽然没什么用处就是了。

纪念伟大的 hello world!

发表于 2017-12-02

截图留念!
2017.12.2
IMAGE
以上这句话由伟大的 System.out.println 提供技术支持~

在我的机器上 ./hotspot 会出现错误

发表于 2017-10-27

RT。错误是:

1
2
<YourOpenJDK>/build/macosx-x86_64-normal-server-slowdebug/hotspot/bsd_amd64_compiler2/debug   ./hotspot
Error: Cannot find the java launcher "/java/re/j2se/1.8.0/promoted/latest/binaries/bsd-amd64/bin/java"

通过查找 Makefile 中 hotspot.script 文件中此错误位置发现需要引用环境变量 ALT_JAVA_HOME。因而只要 export ALT_JAVA_HOME=<YourOpenJDK>/build/macosx-x86_64-normal-server-slowdebug/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home 即可。

VSCode cpp 检索 include 路径的问题

发表于 2017-10-26

问题

碰到了一个 VSCode include 路径的问题。我的项目路径在 {workspaceRoot}/include 下,一开始想要以 -I./include 的方式来编译的。不过在 VSCode 中遇到了些麻烦。

解决

使用的是 Microsoft C/C++ Extension。这个插件非常棒,但是在 mac 上因为 bug 的原因之前经常烫烫烫。好在已经 fix 了。在项目路径下的 .vscode 的文件夹下一般而言有四大金刚:c_cpp_properties.json,launch.json,settings.json 以及 tasks.json。其中 $1 用于配置 path……我都没怎么碰过这东西……还以为没啥用呢(。一般配置的都是 $2 和 $4。$3 是自动生成的用户配置,不用管就好。$2 launch.json 用于调试,而 tasks.json 用于 build && run。

查了半天,在 VSCode 的官方博客上找到了:应该在 $1 c_cpp_properties.json 中配置路径。所以变成了现在这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"configurations": [
{
"name": "Mac",
"includePath": [
"/usr/include",
"/usr/local/include",
"/usr/include/c++/4.2.1",
"/usr/include/c++/4.2.1/tr1",
"${workspaceRoot}/include"
],
"browse": {
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": "",
"path": [
"/usr/include",
"/usr/local/include",
"${workspaceRoot}/include"
]
},
......

emmmm。虽然我不知道 browse 那是什么东西。不过还是加上吧。
然而鬼畜的来了:特么竟然还是报错!!按官博说的,应该已经好了!!
痛苦地找了一个晚上。。。。。。
现在已经是第二天中午了QAQ。
正当我打开 issue,想要像 vscode-cpptools 官方 repository 上报 issue 的时候,看到了官方的提示:上报 issue 之前,请关闭其他插件。看看是否是其他插件的影响。

上报 issue 之前,请关闭其他插件。看看是否是其他插件的影响。
上报 issue 之前,请关闭其他插件。看看是否是其他插件的影响。
上报 issue 之前,请关闭其他插件。看看是否是其他插件的影响。

卧槽,其实这个时候我都没怎么在意……因为另一个插件是我非常喜欢的 clang 的插件啊!虽然不是 clang 官方的(,叫 C/C++ Clang Command Adapter。是五星的插件啊!而且是个霓虹开发者开发的!OWO
然后我关了它。
竟然好了!!可以索引了!!
于是我看了下介绍……
这插件里边写的:

1
2
3
4
5
6
7
Variables
Configurations support some variables which are available in tasks.json. They can be used inside of strings (e.g. "-I${workspaceRoot}/include")
${workspaceRoot}
${cwd}
${env.ENVIRONMENT_VARIABLE}

于是我跑到 settings.json,加了一行这个:
"clang.cxxflags": ["-std=c++11", "-I${workspaceRoot}/include"]
然后就好了……QAQ
然而这就比较诡异了……在点击头文件跳转的时候,关了微软官方的插件就不能跳转;关了 clang 插件,还是能够跳转。说明 clang 插件不对跳转有任何的作用;但是,只要没有配置 args,头文件就会出现红线报错,这时微软的插件检测到错误,也不会帮你跳转……卧槽。这是插件冲突的玄学吗……

emmmm。下次一定要注意,别因为印象好就单独开小灶……因为真正的错误往往藏在你放心的位置,然后耽误你大量的时间……QAQ。

Native 方法的探究有了小进展~~

发表于 2017-10-25

卧槽。痛苦的一天。

因为需要读取 jar 文件,还用 C++ 语言QAQAQAQ,没有现成的库……我又不可能直接去解析……QAQ,于是就想到直接调用 Hotspot 写好的现成的 jar 文件库(我好聪明,逃。
然后就开始了日狗的一天。啥都没干,就一直在干动态库(。mac 的动态库 dylib 实在是气死了。但是这个设计也真是让人眼前一亮啊。学到了不少东西。

一开始只想要调用 Java 的 Native 方法

想写一个小型 jvm 的话,(虽然是玩具,不过我是不可能仅仅局限于只是跑一个 hello world 这种的。自然,想要打造一个运行时环境,那么就必须能够和原生java挂钩。这是目标,同样也是底线。不能让步,如果写完了之后连 java 的类库都不能调,可以狗带了。比如说日后我要是想要实现一个 C 语言的玩具编译器的话,那么不可能仅仅调用我自己底层写的 printf,一定要调用 stdio.h 中的 printf 才好啊。但是这样的话,就肯定涉及解释执行 C 宏了。因为 stdio.h 全都是宏,大家也不是不知道。当然……就我这辣鸡水平估计是要弃坑了……不过这是一年以后的话题了。我们先放着。和这一样,如果要写一个小型 jvm,那么就必须要支持 java 类库的调用才行。要不然就只能跑自己 XJB 写的不带任何库的 java 文件,一点意义也没有。因而,必须要调用 Native 方法。但是,这时就产生了一个问题啊。这个 Native 方法,每个 Native 是不是都是纯函数呢?这是个很重要的话题呢。如果不纯,我即使强行从动态库中把他们扒出来,调用也一定会出错。因为可能这个 Native 方法还涉及到他周边的上下文环境,而我只调用了这个方法,那么肯定会出错的。这个 blog 就是一个栗子。。因为要拆包 jar 文件,我模仿 hotspot 的调用方式调用了一波 jre lib,于是代码如下:

1
2
3
4
5
6
7
8
9
string path = "<YourOpenJDK>/build/macosx-x86_64-normal-server-slowdebug/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home/jre/lib/"; // 自己编译的 jdk。如果是你的,应该路径(Mac OS)是 /Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/jre/lib/
string jvm_path = path + "server/";
string target = path + "libzip.dylib"; // java 中,java.util.ZipFile 类的 native 文件最后全被打包成了 jre/lib 中的 libzip.dylib。在 linux 上可能是 libzip.so 吧。我们将要解包,并且调用里边 sun 公司开发者写好的 Zip_Open 函数,这个函数可以解开一个 zip 包并且读文件。源代码在 <YourOpenJDK>/jdk/src/share/native/java/util/zip/zip_util.c 中。
// [1] 占位符,这里一会要插入代码
void *handle = ::dlopen((target).c_str(), RTLD_LAZY); // posix 的 <dlfcn.h> 头文件,这个头文件是 <DynamicLibaryFunCtioN> 的缩写。即动态库 utils。而 dlopen 函数则根据你给定的库路径,使用 RTLD_LAZY 宏采用 lazy load 的策略——即再用到此动态库的时候再解开。懒人策略。
cout << (unsigned long)handle << endl; // 测试。如果是 0 则说明错误。
if (handle == nullptr) cout << dlerror(); // 如果错误输出为啥错。
void *fun = ::dlsym(handle, "ZIP_Open"); // dlsym 方法,在库中选择一个函数 ZIP_Open,然后传回地址。
cout << (unsigned long)fun << endl; // 同上,输出 0 则说明错误。

emmm。然后 mac 的动态库给我报错

1
2
3
dyld: Library not loaded: @rpath/libjvm.dylib
Referenced from: <此执行文件名>
Reason: image not found

这一段非常恶心。本质上原因是:由于你这个调用的动态库引用了别的动态库,如图:

1
2
3
4
5
6
7
<YourOpenJdk>/build/macosx-x86_64-normal-server-slowdebug/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home/jre/lib> otool -L libzip.dylib
libzip.dylib:
@rpath/libzip.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)
@rpath/libjava.dylib (compatibility version 1.0.0, current version 1.0.0)
@rpath/libjvm.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

我们能够发现这个动态库 libzip.dylib 引用了一堆动态库,有两个 libz.1.dylib 和 libSystem.B.dylib 是绝对路径,那么系统绝对不可能找不着。关键在于另三个:前边都有个 @rpath。
这个 @rpath 是执行的路径。貌似没法直接输出,只能通过强行指定才行。有的大神用 otool -l libzip.dylib(注意 l 小写) 然后得到了如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...
Load command 11
cmd LC_LOAD_DYLIB
cmdsize 48
name @rpath/libjvm.dylib (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1.0.0
compatibility version 1.0.0
Load command 12
cmd LC_LOAD_DYLIB
cmdsize 56
name /usr/lib/libSystem.B.dylib (offset 24)
time stamp 2 Thu Jan 1 08:00:02 1970
current version 1238.60.2
compatibility version 1.0.0
Load command 13
cmd LC_RPATH // 这里!
cmdsize 32
path @loader_path/. (offset 12)
...

大概能看出来后边的 LC_RPATH 的指定位置,是 @loader_path。也就是,应该是你的可执行文件调用这个库所在的位置。比如说,我在 /usr/tmp/haha 这个可执行文件中调用 <YourOpenJdk>/build/macosx-x86_64-normal-server-slowdebug/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home/jre/lib/libzip.dylib,那么八成这个 @loader_path 只是 /usr/tmp/haha。然后由于 @rpath 是 @loader_path/.,那么就一样。于是 libzip.dylib 引用的 libjvm.dylib 等共三个的目录变成了 /usr/tmp/libjvm.dylib……应该是这样。为啥说是应该,因为我也没有验证过,存在错误的可能。不过八成是真的(逃。但是总之你是肯定索引不到真正的 libjvm.dylib 就是了,哈哈。
于是我们要引入 mac 的一个工具:install_name_tool。这个工具可以在可执行文件/动态库中加入 @rpath。由于我们正在写程序,所以不可能通过程序把自己设置 rpath。。。所以,我们只能在程序内去修改 libzip.dylib 的 rpath 了。那么我们在刚才的 [1] 处添加几行代码:

1
2
string cmd = "install_name_tool -add_rpath " + jvm_path + " " + target;
system(cmd.c_str()); // 执行:install_name_tool -add_rpath <Your Real JVM Path> <Need Modified Lib/Execute File>

这样就可以成功了 QAQ。我说的比较容易,其实花了我将近半天才弄完QAQ。

调用不纯的 Native 方法是一个巨坑。

到这一步能够解析动态库了,那么我们就开始搜刮里边的 sun 公司写好的函数了~于是加入下边的代码来 get 那个 ZIP_Open 函数~

1
2
3
4
5
6
7
char *error_msg = nullptr;
typedef void ** (JNICALL *ZIPOPEN) (const char *name, char **pmsg); // jdk native 函数中 ZIP_Open 的定义。
typedef void * jzfile; // 意为 jar/zip file 的句柄
ZIPOPEN zip_open = (ZIPOPEN)fun; // 函数指针强转。
jzfile *zip = zip_open("<YourOpenJdk>/build/macosx-x86_64-normal-server-slowdebug/jdk/lib/jce.jar", &error_msg); // 随手解压一个 jar 文件
cout << (unsigned long)zip << endl; // 不是 0 则没问题。

然后就开始了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
# To suppress the following error report, specify this argument
# after -XX: or in .hotspotrc: SuppressErrorAt=/os.hpp:203
......
无限循环......

Oh my god。
试了无数遍都这样,我是崩溃的!!
卧槽,后来仔细想了想……这一段错误提示是不是在 jvm 中的啊???
然后我在 /hotspot/src 下 grep 了一发,发现这两句出现在 hotspot/src/share/vm/utilities/debug.cpp 下……
然而!然而!!
我调用的 ZIP_Open 函数分明是 jdk 中的 Native 函数啊!!jdk 和 hotspot 是两个模块互不干扰,而且我就是写了一段 cpp 代码,根本没用到 jvm 啊!!
emmmm。细心的小伙伴肯定发现了。刚才的 libzip.dylib 引用了 libjvm.dylib。
那只能说明一点!!
(双眼一亮)
ZIPOpen 函数内部肯定检测了 JVM 的启动!!
是的就是这样。查阅源码之后我们能看到内部的各种检测自家的 JVM 启没启动的代码……
所以我们必须启动一个虚拟机才行。调用 JNI:
在整个代码的前部加上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int res;
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[3];
// 设置初始化参数:
options[0].optionString = const_cast<char *>("-Djava.compiler=NONE"); // ISO-C++11 不允许把 const char* 转为 char*。
// classpath 有多个的时候,Windows 用 ";" 分割。 unix 用 ":" 分割。
options[1].optionString = const_cast<char *>("-Djava.class.path=.");
// 用于跟踪运行时信息
options[2].optionString = const_cast<char *>("-verbose:jni");
// 版本号不能设漏
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 2; // 想要 JVM 的输出调试信息的话,请改为 3。
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
// 初始化虚拟机
res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
if (res == JNI_ERR) {
cerr << "Can't create Java VM!" << endl;
exit(1);
}

然后就可以了!输出就是正常的了。不过我们在编译的时候也要用 install_name_tools:

1
2
3
4
> clang++ -I/usr/include/jni -L/<YourOpenJdk>/build/macosx-x86_64-normal-server-slowdebug/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home/jre/lib/server/ <thisFile>.cc -o <a.out> -ljvm
> install_name_tool -add_rpath /<YourOpenJdk>/build/macosx-x86_64-normal-server-slowdebug/images/j2sdk-bundle/jdk1.8.0.jdk/Contents/Home/jre/lib/server <a.out>
> ./a.out
# 输出即正常!

这说明,如果要自己写的话,Native 方法也要自己实现,不能调用人家的啊……(

解析 dylib 文件并且找到 java native 方法的源代码

发表于 2017-10-23

参见博客

1
2
nm libjava.dylib
gobjdump -tT libjava.dylib // 使用 brew install binutils 安装

Native 方法在 Oracle Java 是不让看的。我们只能从 Openjdk 中看。我在编译好的 openjdk 源码的 build.log 中找到了一大堆 native 方法的编译 log,但是却没有发现它们到底最后被编译到哪了。而且挨个翻 Makefile 也是不太可能的(看不懂哈哈)。于是只能暴力一些了。找到 Openjdk 目录下的各种 libs,首先我的 jdk 在前一个博客也说过是 openjdk8。在 <yourjdk>/build/macosx-x86_64-normal-server-****/jdk/lib 下,有一大堆的 libs。我估计就是他们(逃)。但是怎么确认呢?请参加上方的 blog。执行那两个命令之中的任意一个。然后你就会得到一大堆的输出。然后随便找一个函数,直接在源码目录 yourjdk/jdk/src/share/native 目录下使用 grep -rn <method_name> . 就好了!然后发现确实能查到的!虽然不能一一对应就是了。不过这样也是大有进展,这样就可以直接调用人家写好的 Native 方法了~

外赠一个 shell 脚本来检查到底某个 native 方法被放到哪个 dylib 中去了:

1
2
3
4
5
6
7
8
9
cd <yourjdk>/build/macosx-x86_64-normal-server-****/jdk/lib # 此句是伪码
for file in ./*
do
if [[ "$file" =~ "dylib$" ]] # 以 dylib 结尾的文件
then
nm $file | grep registerNative | xargs -0 echo $file {} # registerNative 可以换成你要查找的方法名~
fi
done

1
2
3
4
5
6
7
8
9
10
11
# 输出:
./libawt.dylib {} 00000000000403e0 T _Java_sun_java2d_loops_GraphicsPrimitiveMgr_registerNativeLoops
./libhprof.dylib {} 000000000001ab63 T _registerNatives # 很清晰地知道,我们要找的 registerNatives 函数在 libhprof.dylib 中~
./libjava.dylib {} 00000000000028d0 T _Java_java_lang_ClassLoader_registerNatives
0000000000002460 T _Java_java_lang_Class_registerNatives
0000000000003f10 T _Java_java_lang_Compiler_registerNatives
0000000000006ea0 T _Java_java_lang_Object_registerNatives
00000000000096d0 T _Java_java_lang_System_registerNatives
000000000000f110 T _Java_java_lang_Thread_registerNatives

随后我还是查看了 Makefile……
比如 libjava 的 output 指定的 Makefile 脚本在:

/jdk/make/lib/CoreLibraries.gmk 下。(gmk 即 Gnu Makefile)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 116 - 129 行
LIBJAVA_SRC_DIRS := $(JDK_TOPDIR)/src/$(OPENJDK_TARGET_OS_API_DIR)/native/java/lang \
$(JDK_TOPDIR)/src/share/native/java/lang \
$(JDK_TOPDIR)/src/share/native/java/lang/reflect \
$(JDK_TOPDIR)/src/share/native/java/io \
$(JDK_TOPDIR)/src/$(OPENJDK_TARGET_OS_API_DIR)/native/java/io \
$(JDK_TOPDIR)/src/share/native/java/nio \
$(JDK_TOPDIR)/src/share/native/java/security \
$(JDK_TOPDIR)/src/share/native/common \
$(JDK_TOPDIR)/src/share/native/sun/misc \
$(JDK_TOPDIR)/src/share/native/sun/reflect \
$(JDK_TOPDIR)/src/share/native/java/util \
$(JDK_TOPDIR)/src/share/native/java/util/concurrent/atomic \
$(JDK_TOPDIR)/src/$(OPENJDK_TARGET_OS_API_DIR)/native/common \
$(JDK_TOPDIR)/src/$(OPENJDK_TARGET_OS_API_DIR)/native/java/util
# 这一部分指出了 libjava 的 src。所有的这些 Native 文件最后都被打包成为 libjava.dylib 的一部分。
// 220 - 224 行
$(BUILD_LIBJAVA): $(LIBJLI_BINARY) # 这个命令开始 build libjli.dylib,在 <yourjdk>/build/macosx-x86_64-normal-server-****/jdk/lib/ 下
$(BUILD_LIBJAVA): $(BUILD_LIBVERIFY) # 这个命令开始 build libverify.dylib,位置同上
$(BUILD_LIBJAVA): $(BUILD_LIBFDLIBM) # 这个命令开始 build libfdlibm.a,在 <yourjdk>/build/macosx-x86_64-normal-server-****/jdk/obj/ 下。
# libjava.dylib 依赖这三个 lib。

嗯嗯。还想要继续找的话,请各种使用 grep 命令会方便很多~

Mac 环境使用 clang 编译 OpenJDK8

发表于 2017-10-20

经历

搞了 3 个晚上,终于搞定了。在 mac 上各种版本不适配,也是很有意思的~需要折腾,需要折腾~


参看博客:

主要参照
主要参照的补充: 编译 debug 版 openjdk
R大的答案
很好的博客
重点!对 mac 下 libiconv 的操作
调试你的 openjdk


历程

按照主要参照的 github 上边的过程一步步来,注意要使用 clang 而不是 gcc。其实在 mac 就应该使用 clang,因为 gcc 不知道会引发什么潜在的隐患那…… 而且 clang 生成的调试信息貌似要比 gcc 好些吧。但是要注意:如果你想要调试的话,就要看看主要参照的补充那个博客了。因为 sh configure 后边需要有参数。我将会列在下边。


各种版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> clang -v
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /usr/bin
> clang++ -v
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
> java -version
java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)
  1. 以上即是各种 tools 的信息。注意,最好开着 VPN 进行 hg 的 pull 操作。因为这样下载速度会飙得很快~ 而且用 hg 貌似易于更新的话说。
  2. 其中,XQuartz 请下载最新的官网版本。不要用主要参照给出的链接,请自行使用 brew cask install XQuartz 来进行 brew 安装。因为链接给出的 XQuartz 版本太老了,是 SnowLeopard 版本的 mac 才适配的。
  3. xcode-select -install 在我这里已经没用了。不过貌似工具链完整,也不需要安装了。
  4. 编译开始之前,请一定要把 /usr/bin/gcc 和 /usr/bin/g++ 改成 clang 和 clang++!因为 ./configure 的时候会搜索这两个路径确认编译器 gcc 以及 g++。当然,如果你原先没有做过交叉编译啥的修改过 /usr/bin/gcc 和 /usr/bin/g++ 的话,那么请忽略我这些话。
  5. 我的 shell 脚本,参照了一堆人的脚本,列举如下。我自己略加修改:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # 设定语言选项,必须设置
    export LANG=C
    # Mac平台,C编译器不再是GCC,是clang
    export CC=gcc
    # 跳过clang的一些严格的语法检查,不然会将N多的警告作为Error
    export COMPILER_WARNINGS_FATAL=false
    # 链接时使用的参数
    export LFLAGS='-Xlinker -lstdc++'
    # 是否使用clang
    export USE_CLANG=true
    # 使用64位数据模型
    export LP64=1
    # 告诉编译平台是64位,不然会按32位来编译
    export ARCH_DATA_MODEL=64
    # 允许自动下载依赖
    export ALLOW_DOWNLOADS=true
    # 并行编译的线程数,编译时间长,为了不影响其他工作,我选择为2
    export HOTSPOT_BUILD_JOBS=6
    export ALT_PARALLEL_COMPILE_JOBS=6
    # 是否跳过与先前版本的比较
    export SKIP_COMPARE_IMAGES=true
    # 是否使用预编译头文件,加快编译速度
    export USE_PRECOMPILED_HEADER=true
    # 是否使用增量编译
    export INCREMENTAL_BUILD=true
    # 编译内容
    export BUILD_LANGTOOLS=true
    export BUILD_JAXP=false
    export BUILD_JAXWS=false
    export BUILD_CORBA=false
    export BUILD_HOTSPOT=true
    export BUILD_JDK=true
    # 编译版本
    export SKIP_DEBUG_BUILD=true
    export SKIP_FASTDEBUG_BUILD=false
    export DEBUG_NAME=debug
    # 避开javaws和浏览器Java插件之类的部分的build
    export BUILD_DEPLOY=false
    export BUILD_INSTALL=false
    # 加上产生调试信息时需要的 objcopy
    # export OBJCOPY=gobjcopy

把它命名为 jvm.sh 的话,我们只需要执行 source ./jvm.sh 即可把临时环境变量设置好。

  1. 主要参照 的所有问题都遇到了。不得不说这是十分良心的经验~
  2. 不过,如果你要进行调试的话,需要把一开始的 sh configure 变成:sh configure --with-target-bits=64 --with-debug-level=slowdebug --enable-debug-symbols ZIP_DEBUGINFO_FILES=0。这样就会产生足够多数量的调试信息,方便调试~
  3. 如果你遵循了第 6 条,那么最后会提示你缺少 objcopy。那么请使用 brew install binutils 来安装 GNU 的组件,并且把 jvm.sh 的最后一行取消注释才行!
  4. 然后愉快地 make all 即可!

坑们

在我这里只出现了一个大坑,然而弄到了半夜QAQ。就是当编译 make all 的时候,到了编译 jdk 的时候,编译了一半出现了诡异的

1
2
3
4
5
6
7
Undefined symbols for architecture x86_64:
"_libiconv", referenced from:
......
"_libiconv_close", referenced from:
......
"_libiconv_open", referenced from:
......

这种狗血的错误。然后一看名就知道了……分明应该是 libiconv 的问题……然而特么一查,电脑中有各种 libiconv.dylib 包,还有各种花式的 libiconv.2.dylib 以及 libiconv.2.4.0.dylib…… 而且散布在不同目录下……我都不知道链接哪个才是正确的……于是不得不上网查,谷歌谷不出来还是百度百出来的(逃,(因为可能是谷歌不支持特殊字符的原因吧……),然后就查到了有一个回答支持,链接也贴出来了:重点!对 mac 下 libiconv 的操作。这个回答只要做第一步就好。即,把 /usr/lib/libiconv.dylib 变成 /usr/lib/libiconv1.dylib 即可,让链接库找不到就 ok。结果链接的是 /usr/bin 目录下的这个文件……其实 /usr/local/bin/ 里边还有同名的一套 libiconv 呢……
不过改完之后就能碰到 主要参照 的第三个错误了,照改不误就可以。对于这份 github 的经验实在是感激不尽!帮了大忙。


世界的终结

打开你自己的 build/macosx-x86_64-normal-server-slowdebug/jdk/bin 目录,然后 ./java,出现

1
2
3
4
5
6
用法: java [-options] class [args...]
(执行类)
或 java [-options] -jar jarfile [args...]
(执行 jar 文件)
其中选项包括:
......

就算大功告成!
但是,如果没有出现这,而是出现什么类似 某个文件800行有问题,提示你编译不完全 的错误提示的话,那你怕是要返工了。当时在 Docker 下 的 ubuntu 虚拟机编译的(其实我就把它当轻量的虚拟机来用HAHA,当时就出现了这个错误。而且错误信息中显示时间戳不对,肯定是编译的过程中出了些岔子吧。

如果你产生了调试信息,那么可以参见 调试你的 openjdk 的最后方,有调试的方法,很简单~

Enjoy it!

使用 mac 同步 Markdown 的方法......

发表于 2017-10-15

用 Quiver 写完之后,导出为 Markdown 然后放在一个文件夹下,然后用 MWeb 进行导入,但是导入后的图片格式会有 Quiver 自动生成的xxx = 380x300这种图片格式,需要用正则替换:\s=[0-9]+x[0-9]+ 替换成全空字符即可,这样图片格式就只剩下原先的xxx了,而后边由 Quiver 自动生成的图片尺寸格式就会被正则删除了。然后,用 MWeb 直接发布到 wordpress 上,由于 MWeb 会直接转成 wordpress 的图片地址,所以直接就可以发布上去;但是,如果仅仅正常发布的话,我们就只能看到最后的结果,但是却并不能得到 Markdown 的格式。所以最后我们需要勾选:发布为 Markdown 格式 这个选项!然后发布到 wordpress 上,之后直接复制粘贴所有的,然后本地 hexo new 之后复制粘贴即可……关键问题是我特别喜欢用 Quiver 写,但是 Quiver 还没有一键发布的功能……所以只能采用这样间接迂回的方案了……不过这样也特别快速,不过唯一的缺点就是最后要手动清理发布的 wordpress 版本和本地由 Quiver 导出的 Markdown 了……但是其他的使用体验还是相当棒的!欢迎来尝试~其实如果你用 MWeb 这种就可以的话,那么 MWeb 一键就可以上传~不过我更喜欢 Quiver 的界面~

123
wind2412

wind2412

22 日志
8 标签
Github
友情链接o(*////▽////*)q
  • Google~
© 2017 - 2019 wind2412
由 Hexo 强力驱动
主题 - NexT.Pisces