如何随心所欲调试HotSpot VM源代码?(改造为CMakeLists项目)
常有小伙伴问我是怎么调试HotSpot VM源代码的,我之前通过视频和文章介绍过一种大家都用的调试方法,如下:
文章地址:第1.2篇-调试HotSpot VM源代码(配视频)
视频地址:https://space.bilibili.com/27533329
网上所有的文章都介绍的是这种方式,先将HotSpot VM编译为动态链接库并生成对应的调试符号文件,然后在IDE中加载启动器这个二进制文件进行调试。不过这种方式对我这种频繁查看和修改HotSpot VM源代码的人来说有一些不方便。主要体现在如下几个方面:
(1)有些函数链接不过去,这个是正常的,因为没有被IDE识别为合法的Makefile项目。另外还有一些其它原因,如HotSpot VM源代码中包含有针对主流操作系统和CPU架构的不同实现,此时的IDE并不知道要跳转到哪个实现;
(2)崩溃的问题,在Ubuntu16.04 x86_64位操作系统上进行调试时,CLion频繁崩溃,Eclipse有时也会崩溃,无语,Visual Studio Code没有经常用,不知道。
第一个问题促使我下决心将HotSpot VM这个Makefile项目改为CMakeLists项目,因为CLion在我改造那时候还不支持创建Makefile项目,对CMakeLists项目支持的较好。
第二个问题在将CMakeLists项目改造完成后,突然有一次调试如下一行代码时遇到卡顿问题:
- 源代码来源:openjdk/hotspot/src/os/linux/os_linux.cpp
- // 函数anon_mmap()在为堆分配内存时会调用
- addr = (char*)::mmap(requested_addr, bytes, PROT_NONE, flags, -1, 0);
调用函数mmap()为堆分配内存时,传递了一个参数PROT_NONE,这个表示映射的保护级别,PROT_NONE表示该映射不能被访问。所以如果在调试模式下,即使读取地址也会卡死,不过有些情况下会崩溃。我们将这个参数改为PROT_READ|PROT_WRITE(可读可写)即可。
我怀疑在CLion和Eclipse上崩溃也和这个有很大关系,不过我后来并没有试过原来的那种调试方式。
下面将HotSpot VM项目更改为一个合法的、能被CLion识别的CMakeLists项目,CLion识别后就不会有源代码报红的情况,也不会出现链接不过去的情况,如果有,那在CLion上是无法编译出虚拟机的动态链接库的。
1、按常见方式编译出OpenJDK
具体的编译可以参考我之前录制的视频和写的文章,如下:
第1.1篇-在Ubuntu 16.04上编译OpenJDK8的源代码(配视频)
编译时可参考官方文档:openjdk/README-builds.html
需要说明的是,要想启动Java应用程序,除了HotSpot VM外,还要有JDK类库以及一系列的、针对特定CPU和操作系统编译出的动态链接库,这些动态链接库大部分都是native方法的实现。由于我只编译HotSpot VM为动态链接库,所以还需要按之前的方式将除libjvm.so外的其它运行时环境准备好。我们自己编译libjvm.so并替换掉之前编译好的libjvm.so即可。
2、调整HotSpot VM源代码目录
左侧是我调整后的源代码目录,右侧为HotSpot VM调整前的目录结构。因为我只研究HotSpot VM在Linux下的x86_64位实现,所以删除了其它平台和CPU架构下的实现,只保留了linux、linux_x86和x86目录,并将所有的源代码都放在了src目录下。目录怎么调整无所谓,不过需要将其中每个源文件的引用路径都更正一遍才行。
原share目录中存储着共同的代码,如果要在共同代码中需要引入特定CPU架构和操作系统的实现时,可通过如下宏来实现:
- 源代码位置:openjdk/hotspot/src/share/vm/runtime/os.hpp
- #ifdef TARGET_OS_FAMILY_linux
- # include "os_linux.hpp"
- # include "os_posix.hpp"
- #endif
- #ifdef TARGET_OS_FAMILY_solaris
- # include "os_solaris.hpp"
- # include "os_posix.hpp"
- #endif
- #ifdef TARGET_OS_FAMILY_windows
- # include "os_windows.hpp"
- #endif
- #ifdef TARGET_OS_FAMILY_bsd
- # include "os_posix.hpp"
- # include "os_bsd.hpp"
- #endif
遇到类似如上的代码,可直接删除宏判断,保留特定的文件引用即可。如:
- # include "os_linux.hpp"
- # include "os_posix.hpp"
在share目录中的代码还有许多使用宏来选择编译特定的代码片段,如下:
- 源代码位置:openjdk/hotspot/src/share/interpreter/interpreterRuntime.cpp
- #if defined(IA32) || defined(AMD64) || defined(ARM)
- // 相关的实现
- #endif
可以选择删除宏,保留特定的代码片段,不过由于这样的宏太多,所以这可以直接在CMakeLists.txt文件中定义相关的宏即可,如下:
- add_definitions(-DAMD64 -D_LP64 -DCOMPILER1 -DCOMPILER2 -DINCLUDE_ALL_GCS -DASSERT -DVM_LITTLE_ENDIAN -D_GNU_SOURCE -DLINUX -DINCLUDE_JVMTI=1)
根据宏来选择对应的代码。
另外,如果某些文件缺失,需要从之前编译好的目录下搜索出对应的文件,然后放到对应目录中即可。
3、编写CMakeLists文件内容
具体内容如下:
- cmake_minimum_required(VERSION 3.15)
- project(jvm)
- enable_language(C ASM)
- set(CMAKE_C_STANDARD 99)
- set(CMAKE_CXX_STANDARD 98)
- add_compile_options(-fpermissive)
- # 用到了操作系统线程,编译时需要加参数-pthread
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
- # 将汇编文件和C++源代码一起编译
- SET(ASM_OPTIONS "-x assembler-with-cpp")
- SET(CMAKE_ASM_FLAGS "${CFLAGS} ${ASM_OPTIONS}")
- # 针对操作系统和CPU架构定义了一些宏
- add_definitions(-DAMD64 -D_LP64 -DCOMPILER1 -DCOMPILER2 -DINCLUDE_ALL_GCS -DASSERT -DVM_LITTLE_ENDIAN -D_GNU_SOURCE -DLINUX -DINCLUDE_JVMTI=1)
- # 将编译出的动态链接库libjvm.so替换之前编译出的libjvm.so动态链接库
- set(CMAKE_LIBRARY_OUTPUT_DIRECTORY /media/mazhi/system2-ssd/openjdks/updated/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/lib/amd64/server)
- aux_source_directory(./src/asm SOURCE_FILES)
- aux_source_directory(./src/c1 SOURCE_FILES)
- aux_source_directory(./src/ci SOURCE_FILES)
- aux_source_directory(./src/classfile SOURCE_FILES)
- aux_source_directory(./src/code SOURCE_FILES)
- aux_source_directory(./src/compiler SOURCE_FILES)
- aux_source_directory(./src/gc_implementation SOURCE_FILES)
- aux_source_directory(./src/gc_implementation/concurrentMarkSweep SOURCE_FILES)
- aux_source_directory(./src/gc_implementation/g1 SOURCE_FILES)
- aux_source_directory(./src/gc_implementation/parallelScavenge SOURCE_FILES)
- aux_source_directory(./src/gc_implementation/parNew SOURCE_FILES)
- aux_source_directory(./src/gc_implementation/shared SOURCE_FILES)
- aux_source_directory(./src/gc_interface SOURCE_FILES)
- aux_source_directory(./src/interpreter SOURCE_FILES)
- aux_source_directory(./src/libadt SOURCE_FILES)
- aux_source_directory(./src/linux SOURCE_FILES)
- aux_source_directory(./src/linux_x86 SOURCE_FILES)
- aux_source_directory(./src/memory SOURCE_FILES)
- aux_source_directory(./src/oops SOURCE_FILES)
- aux_source_directory(./src/opto SOURCE_FILES)
- aux_source_directory(./src/posix SOURCE_FILES)
- aux_source_directory(./src/precompiled SOURCE_FILES)
- aux_source_directory(./src/prims SOURCE_FILES)
- aux_source_directory(./src/prims/wbtestmethods SOURCE_FILES)
- aux_source_directory(./src/runtime SOURCE_FILES)
- aux_source_directory(./src/services SOURCE_FILES)
- aux_source_directory(./src/trace SOURCE_FILES)
- aux_source_directory(./src/utilities SOURCE_FILES)
- aux_source_directory(./src/x86 SOURCE_FILES)
- aux_source_directory(./src/tracefiles SOURCE_FILES)
- aux_source_directory(./src/adfiles SOURCE_FILES)
- add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ./src/linux_x86/linux_x86_64.s)
将以.s结尾的汇编文件和.cpp源代码一起编译,最终会将编译出的libjvm.so动态链接库放到指定的目录下,替换之前编译出的libjvm.so文件。
4、编写虚拟机启动逻辑
HotSpot VM的启动逻辑在之前也有介绍过,如下:
第1.4篇-HotSpot VM的启动过程(配视频进行源码分析)
不过因为要考虑跨平台兼容以及用户输入等一系列因素,所以这个启动逻辑太繁琐,我们直接在CMakeLists项目中创建一个main.cpp文件,简化这个启动逻辑,如下:
- #include <iostream>
- #include "src/prims/jni.h"
- #include <stdlib.h>
- #include <string.h>
- #include <stdio.h>
- #include <unistd.h>
- #include "dlfcn.h"
- #include "src/include/jni.h"
- typedef jint (JNICALL *CreateJavaVM_t)(JavaVM **pvm, void **env, void *args);
- typedef jint (JNICALL *GetDefaultJavaVMInitArgs_t)(void *args);
- typedef jint (JNICALL *GetCreatedJavaVMs_t)(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
- typedef struct {
- CreateJavaVM_t CreateJavaVM;
- GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;
- GetCreatedJavaVMs_t GetCreatedJavaVMs;
- } InvocationFunctions;
- typedef jclass (JNICALL FindClassFromBootLoader_t(JNIEnv *env,
- const char *name));
- static FindClassFromBootLoader_t *findBootClass = NULL;
- jclass FindBootStrapClass(JNIEnv *env, const char* classname){
- if (findBootClass == NULL) {
- findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,"JVM_FindClassFromBootLoader");
- if (findBootClass == NULL) {
- return NULL;
- }
- }
- return findBootClass(env, classname);
- }
- jboolean
- LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn){
- void *libjvm;
- // dlopen() 函数以指定模式打开指定的动态链接库文件
- libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
- if (libjvm == NULL) {
- std::cout << ::dlerror() << std::endl;
- return JNI_FALSE;
- }
- // dlsym() 函数在动态链接库中查找指定的符号,并返回符号对应的地址
- ifn->CreateJavaVM = (CreateJavaVM_t)
- dlsym(libjvm, "JNI_CreateJavaVM");
- if (ifn->CreateJavaVM == NULL) {
- return JNI_FALSE;
- }
- ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
- dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
- if (ifn->GetDefaultJavaVMInitArgs == NULL) {
- return JNI_FALSE;
- }
- ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
- dlsym(libjvm, "JNI_GetCreatedJavaVMs");
- if (ifn->GetCreatedJavaVMs == NULL) {
- return JNI_FALSE;
- }
- }
- static jclass helperClass = NULL;
- jclass GetLauncherHelperClass(JNIEnv *env){
- if (helperClass == NULL) {
- helperClass = FindBootStrapClass(env,"sun/launcher/LauncherHelper");
- }
- return helperClass;
- }
- static jclass GetApplicationClass(JNIEnv *env){
- jmethodID mid;
- jobject result;
- jclass cls = GetLauncherHelperClass(env);
- mid = env->GetStaticMethodID(cls,"getApplicationClass","()Ljava/lang/Class;");
- return static_cast<jclass>(env->CallStaticObjectMethod(cls, mid));
- }
- static jmethodID makePlatformStringMID = NULL;
- static jstring NewPlatformString(JNIEnv *env, char *s)
- {
- int len = (int)strlen(s);
- jbyteArray ary;
- jclass cls = GetLauncherHelperClass(env);
- if (s == NULL){
- return 0;
- }
- ary = (env)->NewByteArray(len);
- if (ary != 0) {
- jstring str = 0;
- (env)->SetByteArrayRegion(ary, 0, len, (jbyte *)s);
- if (!(env)->ExceptionOccurred()) {
- if (makePlatformStringMID == NULL) {
- makePlatformStringMID = (env)->GetStaticMethodID(cls, "makePlatformString", "(Z[B)Ljava/lang/String;");
- }
- str = static_cast<jstring>((env)->CallStaticObjectMethod(cls, makePlatformStringMID, JNI_TRUE, ary));
- (env)->DeleteLocalRef(ary);
- return str;
- }
- }
- return 0;
- }
- static jclass LoadMainClass(JNIEnv *env, int mode, char *name){
- jmethodID mid;
- jstring str;
- jobject result;
- jlong start, end;
- jclass cls ;
- cls = GetLauncherHelperClass(env);
- mid = (env)->GetStaticMethodID(cls,"checkAndLoadMain","(ZILjava/lang/String;)Ljava/lang/Class;");
- str = NewPlatformString(env, name); // 这里的name为主类的名称,如com.test/Test
- result = env->CallStaticObjectMethod(cls, mid, JNI_TRUE, mode, str);
- return (jclass)result;
- }
- jobjectArray
- NewPlatformStringArray(JNIEnv *env, char **strv, int strc)
- {
- jclass cls;
- jobjectArray ary;
- int i;
- cls = FindBootStrapClass(env, "java/lang/String");
- ary = (env)->NewObjectArray( strc, cls, 0);
- for (i = 0; i < strc; i++) {
- jstring str = NewPlatformString(env, *strv++);
- (env)->SetObjectArrayElement(ary, i, str);
- (env)->DeleteLocalRef(str);
- }
- return ary;
- }
- int main() {
- int count = 5;
- JavaVMOption *options = (JavaVMOption *)malloc( count * sizeof(JavaVMOption));
- int numOptions = 0;
- options[numOptions].optionString = "-Djava.class.path=.";
- options[numOptions++].extraInfo = NULL;
- options[numOptions].optionString = "-Djava.class.path=.:/media/mazhi/sourcecode/workspace/projectjava/projectjava01/target/mazhimazh-0.0.1-SNAPSHOT-jar-with-dependencies.jar";
- options[numOptions++].extraInfo = NULL;
- options[numOptions].optionString = "-Dsun.java.command=com.test/TestInlineMethod";
- options[numOptions++].extraInfo = NULL;
- options[numOptions].optionString = "-Dsun.java.launcher=SUN_STANDARD";
- options[numOptions++].extraInfo = NULL;
- char *substr = "-Dsun.java.launcher.pid=";
- char *pid_prop_str = (char *)malloc(strlen(substr) + 10 + 1);
- sprintf(pid_prop_str, "%s%d", substr, getpid());
- options[numOptions].optionString = substr;
- options[numOptions++].extraInfo = NULL;
- // 为启动虚拟机传递的参数
- JavaVMInitArgs args = {
- 65538,
- count,
- options,
- true
- };
- JavaVM *vm = 0;
- JNIEnv *env = 0;
- InvocationFunctions ifn;
- ifn.CreateJavaVM = 0;
- ifn.GetDefaultJavaVMInitArgs = 0;
- // 加载动态链接库并查找相关的符号
- char *jvmpath = "/media/mazhi/system2-ssd/openjdks/updated/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/lib/amd64/server/libjvm.so";
- LoadJavaVM(jvmpath,&ifn);
- // 创建一个虚拟机实例,目录不能以直接调用的方式启动虚拟机HotSpot
- // jint r = JNI_CreateJavaVM(&vm, (void **)&env, &args);
- jint r = ifn.CreateJavaVM(&vm, (void **)&env, &args);
- free(options);
- if(r == JNI_OK){
- printf("success");
- }
- // 查找Java主类
- char* what = "com.test/TestInlineMethod";
- jclass mainClass = LoadMainClass(env, 1, what);
- // 找到Java主类main()方法对应的唯一ID
- jmethodID mainID = env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;)V");
- // 为应用程序传递的参数
- jobjectArray mainArgs = NewPlatformStringArray(env, 0, NULL);
- // 调用Java的main()方法
- env->CallStaticVoidMethod(mainClass, mainID, mainArgs);
- return 0;
- }
由于我们现在还不能在main()中直接调用HotSpot VM源代码函数的方式启动,所以在编译好了libjvm.so库后,在CMakeLists.txt文件中注释掉编译动态链接库的逻辑(注释掉aux_source_directory和add_library即可),加上编译可执行程序的逻辑即可,如下:
- add_executable(${PROJECT_NAME} main.cpp)
- target_link_libraries(${PROJECT_NAME} dl pthread)
运行main()函数即可开启断点调试。
如有对虚拟机感兴趣的,可扫码群,加过虚拟机群的就不要再加入了。
本人最近准备出一个手写Hotspot VM的课程,超级硬核,从0开始写HotSpot VM,将HotSpot VM所有核心的实现全部走一遍,如感兴趣,速速入群。
如何随心所欲调试HotSpot VM源代码?(改造为CMakeLists项目)的更多相关文章
- 014-通过JDB调试,通过HSDB来查看HotSpot VM的运行时数据
一.JDB调试 在预发环境下进行debug时,时常因为工具和环境的限制,导致debug体验非常差,那么有什么方法能够简化我们进行debug的体验吗?JDB就是一种. JDB ...
- 调试HotSpot源代码(配视频)
本文将详细介绍在Ubuntu16.04 LTS上对OpenJDK8进行编译,为了方便大家快速搭建起OpenJDK8的调试开发环境,我还录制了对应的视频放到了B站上,大家可以参考. 视频地址:https ...
- 调试HotSpot源代码
之前的文章在Ubuntu 16.04上编译OpenJDK8的源代码 已经介绍过在Ubuntu上编译OpenJDK8的源代码,这一篇将介绍在Ubuntu上调试OpenJDK8源代码的2种方式. 1.GD ...
- 关于HotSpot VM以及Java语言的动态编译 你可能想知道这些
目录 1 HotSpot VM的历史 2 HotSpot VM 概述 2.1 编译器 2.2 解释器 2.3 解释型语言 VS 编译型语言 3 动态编译 3.1 什么是动态编译 3.2 HotSpot ...
- JVM详解之:HotSpot VM中的Intrinsic methods
目录 简介 什么是Intrinsic Methods 内置方法的特点 多样性 兼容性 java语义的扩展 Hotspot VM中的内置方法 intrinsic方法和内联方法 intrinsic方法的实 ...
- HotSpot VM执行引擎的实现
Java代码的执行分类: 第一种是将源代码编译成字节码文件,然后再运行时通过解释器将字节码文件转为机器码执行 第二种是编译执行(直接编译成机器码).现代虚拟机为了提高执行效率,会使用即时编译技术(JI ...
- 从HotSpot VM源码看字符串常量池(StringTable)和intern()方法
引言 字符串常量池(StringTable)是JVM中一个重要的结构,它有助于避免重复创建相同内容的String对象.那么StringTable是怎么实现的?"把字符串加入到字符串常量池中& ...
- 转:什么是即时编译(JIT)!?OpenJDK HotSpot VM剖析
重点 应用程序可以选择一个适当的即时编译器来进行接近机器级的性能优化. 分层编译由五层编译构成. 分层编译提供了极好的启动性能,并指导编译的下一层编译器提供高性能优化. 提供即时编译相关诊断信息的JV ...
- 关于Linux x64 Oracle JDK7u60 64-bit HotSpot VM 线程栈默认大小问题的整理
JVM线程的栈默认大小,oracle官网有简单描述: In Java SE 6, the default on Sparc is 512k in the 32-bit VM, and 1024k in ...
- 什么是HotSpot VM & 深入理解Java虚拟机
参考 http://book.2cto.com/201306/25434.html 另外,这篇文章也是从一个系列中得出的: <深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)> ...
随机推荐
- 谈谈ChatGPT是否可以替代人
起初我以为我是搬砖的,最近发现其实只是一块砖,哪里需要哪里搬. 这两天临时被抽去支援跨平台相关软件开发,帮忙画几个界面.有了 ChatGPT 之后就觉得以前面向 Googel 编程会拉低我滴档次和逼格 ...
- DB事物
1.事物概念:一组逻辑操作单元 始数据从一种状态变换到另一种状态事物处理:所有事物 都作为一个工作单元来执行 , 即使出现了故障 都不能改变这种执行方式, commint提交之后 这些修改就永久的保存 ...
- 看看Angular有啥新玩法!手把手教你在Angular15中集成报表插件
摘要:本文由葡萄城技术团队于博客园原创并首发.葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. Angular15新特性 Angular框架(以下简称"Angular" ...
- 手记系列之六 ----- 分享个人使用kafka经验
前言 本篇文章主要介绍的关于本人从刚工作到现在使用kafka的经验,内容非常多,包含了kafka的常用命令,在生产环境中遇到的一些场景处理,kafka的一些web工具推荐等等.由于kafka这块的记录 ...
- 如何使用Stable Diffusion生成艺术二维码?
硬件准备 物理内存:至少16G(8G直接安装阶段就卡死) N卡:此处我使用GTX 1660 6G (2019年双12购买) 操作系统 windows 11 软件准备 网络要通畅 git: https: ...
- @Deprecated注解的使用
被注解@Deprecated标记的程序元素是不鼓励使用的程序元素,通常是因为它很危险,或者是因为存在更好的替代方案. 除了对象自身引用自己用@Deprecated标记的方法外,其他情况使用@Depre ...
- GO通道:无缓冲通道与缓冲通道
转载请注明出处: 1.通道定义 在多个协程之间进行通信和管理,可以使用 Go 语言提供的通道(Channel)类型.通道是一种特殊的数据结构,可以在协程之间进行传递数据,从而实现协程之间的通信和同步. ...
- ElasticSearch的使用和介绍
1.概述 功能 Elasticsearch 是一个分布式的 RESTful 搜索和分析引擎,可用来集中存储您的数据,以便您对形形色色.规模不一的数据进行搜索.索引和分析. 例如: 在电商网站搜索商品 ...
- GPT3的内部结构:基于自回归、注意力机制等技术的语言处理框架
目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 6. 结论与展望 7. 附录:常见问题与解答 GPT-3 是当前最为先进的自然语言处理框架之一,由 Open ...
- springboot使用Websocket写一个聊天室
1 <!--websocket 依赖--> 2 <dependency> 3 <groupId>org.springframework.boot</group ...