经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
如何随心所欲调试HotSpot VM源代码?(改造为CMakeLists项目)
来源:cnblogs  作者:鸠摩(马智)  时间:2023/9/18 9:02:14  对本文有异议

常有小伙伴问我是怎么调试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项目改造完成后,突然有一次调试如下一行代码时遇到卡顿问题:

  1. 源代码来源:openjdk/hotspot/src/os/linux/os_linux.cpp
  2. // 函数anon_mmap()在为堆分配内存时会调用
  3. 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架构和操作系统的实现时,可通过如下宏来实现: 

  1. 源代码位置:openjdk/hotspot/src/share/vm/runtime/os.hpp
  2.  
  3. #ifdef TARGET_OS_FAMILY_linux
  4. # include "os_linux.hpp"
  5. # include "os_posix.hpp"
  6. #endif
  7. #ifdef TARGET_OS_FAMILY_solaris
  8. # include "os_solaris.hpp"
  9. # include "os_posix.hpp"
  10. #endif
  11. #ifdef TARGET_OS_FAMILY_windows
  12. # include "os_windows.hpp"
  13. #endif
  14. #ifdef TARGET_OS_FAMILY_bsd
  15. # include "os_posix.hpp"
  16. # include "os_bsd.hpp"
  17. #endif

遇到类似如上的代码,可直接删除宏判断,保留特定的文件引用即可。如:

  1. # include "os_linux.hpp"
  2. # include "os_posix.hpp"

在share目录中的代码还有许多使用宏来选择编译特定的代码片段,如下:

  1. 源代码位置:openjdk/hotspot/src/share/interpreter/interpreterRuntime.cpp
  2. #if defined(IA32) || defined(AMD64) || defined(ARM)
  3. // 相关的实现
  4. #endif

可以选择删除宏,保留特定的代码片段,不过由于这样的宏太多,所以这可以直接在CMakeLists.txt文件中定义相关的宏即可,如下:

  1. add_definitions(-DAMD64 -D_LP64 -DCOMPILER1 -DCOMPILER2 -DINCLUDE_ALL_GCS -DASSERT -DVM_LITTLE_ENDIAN -D_GNU_SOURCE -DLINUX -DINCLUDE_JVMTI=1)

根据宏来选择对应的代码。 

另外,如果某些文件缺失,需要从之前编译好的目录下搜索出对应的文件,然后放到对应目录中即可。 

3、编写CMakeLists文件内容

具体内容如下:

  1. cmake_minimum_required(VERSION 3.15)
  2. project(jvm)
  3.  
  4. enable_language(C ASM)
  5.  
  6. set(CMAKE_C_STANDARD 99)
  7. set(CMAKE_CXX_STANDARD 98)
  8.  
  9. add_compile_options(-fpermissive)
  10. # 用到了操作系统线程,编译时需要加参数-pthread
  11. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
  12. # 将汇编文件和C++源代码一起编译
  13. SET(ASM_OPTIONS "-x assembler-with-cpp")
  14. SET(CMAKE_ASM_FLAGS "${CFLAGS} ${ASM_OPTIONS}")
  15.  
  16. # 针对操作系统和CPU架构定义了一些宏
  17. add_definitions(-DAMD64 -D_LP64 -DCOMPILER1 -DCOMPILER2 -DINCLUDE_ALL_GCS -DASSERT -DVM_LITTLE_ENDIAN -D_GNU_SOURCE -DLINUX -DINCLUDE_JVMTI=1)
  18.  
  19. # 将编译出的动态链接库libjvm.so替换之前编译出的libjvm.so动态链接库
  20. set(CMAKE_LIBRARY_OUTPUT_DIRECTORY /media/mazhi/system2-ssd/openjdks/updated/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/lib/amd64/server)
  21.  
  22. aux_source_directory(./src/asm SOURCE_FILES)
  23. aux_source_directory(./src/c1 SOURCE_FILES)
  24. aux_source_directory(./src/ci SOURCE_FILES)
  25. aux_source_directory(./src/classfile SOURCE_FILES)
  26. aux_source_directory(./src/code SOURCE_FILES)
  27. aux_source_directory(./src/compiler SOURCE_FILES)
  28. aux_source_directory(./src/gc_implementation SOURCE_FILES)
  29. aux_source_directory(./src/gc_implementation/concurrentMarkSweep SOURCE_FILES)
  30. aux_source_directory(./src/gc_implementation/g1 SOURCE_FILES)
  31. aux_source_directory(./src/gc_implementation/parallelScavenge SOURCE_FILES)
  32. aux_source_directory(./src/gc_implementation/parNew SOURCE_FILES)
  33. aux_source_directory(./src/gc_implementation/shared SOURCE_FILES)
  34. aux_source_directory(./src/gc_interface SOURCE_FILES)
  35. aux_source_directory(./src/interpreter SOURCE_FILES)
  36. aux_source_directory(./src/libadt SOURCE_FILES)
  37. aux_source_directory(./src/linux SOURCE_FILES)
  38. aux_source_directory(./src/linux_x86 SOURCE_FILES)
  39. aux_source_directory(./src/memory SOURCE_FILES)
  40. aux_source_directory(./src/oops SOURCE_FILES)
  41. aux_source_directory(./src/opto SOURCE_FILES)
  42. aux_source_directory(./src/posix SOURCE_FILES)
  43. aux_source_directory(./src/precompiled SOURCE_FILES)
  44. aux_source_directory(./src/prims SOURCE_FILES)
  45. aux_source_directory(./src/prims/wbtestmethods SOURCE_FILES)
  46. aux_source_directory(./src/runtime SOURCE_FILES)
  47. aux_source_directory(./src/services SOURCE_FILES)
  48. aux_source_directory(./src/trace SOURCE_FILES)
  49. aux_source_directory(./src/utilities SOURCE_FILES)
  50. aux_source_directory(./src/x86 SOURCE_FILES)
  51. aux_source_directory(./src/tracefiles SOURCE_FILES)
  52. aux_source_directory(./src/adfiles SOURCE_FILES)
  53.  
  54. 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文件,简化这个启动逻辑,如下:

  1. #include <iostream>
  2.  
  3. #include "src/prims/jni.h"
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <stdio.h>
  7. #include <unistd.h>
  8. #include "dlfcn.h"
  9.  
  10. #include "src/include/jni.h"
  11.  
  12. typedef jint (JNICALL *CreateJavaVM_t)(JavaVM **pvm, void **env, void *args);
  13. typedef jint (JNICALL *GetDefaultJavaVMInitArgs_t)(void *args);
  14. typedef jint (JNICALL *GetCreatedJavaVMs_t)(JavaVM **vmBuf, jsize bufLen, jsize *nVMs);
  15.  
  16. typedef struct {
  17. CreateJavaVM_t CreateJavaVM;
  18. GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;
  19. GetCreatedJavaVMs_t GetCreatedJavaVMs;
  20. } InvocationFunctions;
  21.  
  22. typedef jclass (JNICALL FindClassFromBootLoader_t(JNIEnv *env,
  23. const char *name));
  24. static FindClassFromBootLoader_t *findBootClass = NULL;
  25.  
  26. jclass FindBootStrapClass(JNIEnv *env, const char* classname){
  27. if (findBootClass == NULL) {
  28. findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,"JVM_FindClassFromBootLoader");
  29. if (findBootClass == NULL) {
  30. return NULL;
  31. }
  32. }
  33. return findBootClass(env, classname);
  34. }
  35.  
  36.  
  37. jboolean
  38. LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn){
  39. void *libjvm;
  40.  
  41. // dlopen() 函数以指定模式打开指定的动态链接库文件
  42. libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
  43. if (libjvm == NULL) {
  44. std::cout << ::dlerror() << std::endl;
  45. return JNI_FALSE;
  46. }
  47.  
  48. // dlsym() 函数在动态链接库中查找指定的符号,并返回符号对应的地址
  49. ifn->CreateJavaVM = (CreateJavaVM_t)
  50. dlsym(libjvm, "JNI_CreateJavaVM");
  51. if (ifn->CreateJavaVM == NULL) {
  52. return JNI_FALSE;
  53. }
  54.  
  55. ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
  56. dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
  57. if (ifn->GetDefaultJavaVMInitArgs == NULL) {
  58. return JNI_FALSE;
  59. }
  60.  
  61. ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
  62. dlsym(libjvm, "JNI_GetCreatedJavaVMs");
  63. if (ifn->GetCreatedJavaVMs == NULL) {
  64. return JNI_FALSE;
  65. }
  66.  
  67. }
  68. static jclass helperClass = NULL;
  69.  
  70. jclass GetLauncherHelperClass(JNIEnv *env){
  71. if (helperClass == NULL) {
  72. helperClass = FindBootStrapClass(env,"sun/launcher/LauncherHelper");
  73. }
  74. return helperClass;
  75. }
  76.  
  77. static jclass GetApplicationClass(JNIEnv *env){
  78. jmethodID mid;
  79. jobject result;
  80. jclass cls = GetLauncherHelperClass(env);
  81. mid = env->GetStaticMethodID(cls,"getApplicationClass","()Ljava/lang/Class;");
  82.  
  83. return static_cast<jclass>(env->CallStaticObjectMethod(cls, mid));
  84. }
  85.  
  86. static jmethodID makePlatformStringMID = NULL;
  87. static jstring NewPlatformString(JNIEnv *env, char *s)
  88. {
  89. int len = (int)strlen(s);
  90. jbyteArray ary;
  91. jclass cls = GetLauncherHelperClass(env);
  92. if (s == NULL){
  93. return 0;
  94. }
  95.  
  96. ary = (env)->NewByteArray(len);
  97. if (ary != 0) {
  98. jstring str = 0;
  99. (env)->SetByteArrayRegion(ary, 0, len, (jbyte *)s);
  100. if (!(env)->ExceptionOccurred()) {
  101. if (makePlatformStringMID == NULL) {
  102. makePlatformStringMID = (env)->GetStaticMethodID(cls, "makePlatformString", "(Z[B)Ljava/lang/String;");
  103. }
  104. str = static_cast<jstring>((env)->CallStaticObjectMethod(cls, makePlatformStringMID, JNI_TRUE, ary));
  105. (env)->DeleteLocalRef(ary);
  106. return str;
  107. }
  108. }
  109. return 0;
  110. }
  111.  
  112. static jclass LoadMainClass(JNIEnv *env, int mode, char *name){
  113. jmethodID mid;
  114. jstring str;
  115. jobject result;
  116. jlong start, end;
  117. jclass cls ;
  118. cls = GetLauncherHelperClass(env);
  119. mid = (env)->GetStaticMethodID(cls,"checkAndLoadMain","(ZILjava/lang/String;)Ljava/lang/Class;");
  120.  
  121. str = NewPlatformString(env, name); // 这里的name为主类的名称,如com.test/Test
  122. result = env->CallStaticObjectMethod(cls, mid, JNI_TRUE, mode, str);
  123.  
  124. return (jclass)result;
  125. }
  126.  
  127. jobjectArray
  128. NewPlatformStringArray(JNIEnv *env, char **strv, int strc)
  129. {
  130. jclass cls;
  131. jobjectArray ary;
  132. int i;
  133.  
  134. cls = FindBootStrapClass(env, "java/lang/String");
  135. ary = (env)->NewObjectArray( strc, cls, 0);
  136. for (i = 0; i < strc; i++) {
  137. jstring str = NewPlatformString(env, *strv++);
  138. (env)->SetObjectArrayElement(ary, i, str);
  139. (env)->DeleteLocalRef(str);
  140. }
  141. return ary;
  142. }
  143.  
  144. int main() {
  145. int count = 5;
  146. JavaVMOption *options = (JavaVMOption *)malloc( count * sizeof(JavaVMOption));
  147.  
  148. int numOptions = 0;
  149. options[numOptions].optionString = "-Djava.class.path=.";
  150. options[numOptions++].extraInfo = NULL;
  151.  
  152. options[numOptions].optionString = "-Djava.class.path=.:/media/mazhi/sourcecode/workspace/projectjava/projectjava01/target/mazhimazh-0.0.1-SNAPSHOT-jar-with-dependencies.jar";
  153. options[numOptions++].extraInfo = NULL;
  154.  
  155. options[numOptions].optionString = "-Dsun.java.command=com.test/TestInlineMethod";
  156. options[numOptions++].extraInfo = NULL;
  157.  
  158. options[numOptions].optionString = "-Dsun.java.launcher=SUN_STANDARD";
  159. options[numOptions++].extraInfo = NULL;
  160.  
  161. char *substr = "-Dsun.java.launcher.pid=";
  162. char *pid_prop_str = (char *)malloc(strlen(substr) + 10 + 1);
  163. sprintf(pid_prop_str, "%s%d", substr, getpid());
  164. options[numOptions].optionString = substr;
  165. options[numOptions++].extraInfo = NULL;
  166.  
  167. // 为启动虚拟机传递的参数
  168. JavaVMInitArgs args = {
  169. 65538,
  170. count,
  171. options,
  172. true
  173. };
  174. JavaVM *vm = 0;
  175. JNIEnv *env = 0;
  176.  
  177. InvocationFunctions ifn;
  178. ifn.CreateJavaVM = 0;
  179. ifn.GetDefaultJavaVMInitArgs = 0;
  180.  
  181. // 加载动态链接库并查找相关的符号
  182. char *jvmpath = "/media/mazhi/system2-ssd/openjdks/updated/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/lib/amd64/server/libjvm.so";
  183. LoadJavaVM(jvmpath,&ifn);
  184.  
  185. // 创建一个虚拟机实例,目录不能以直接调用的方式启动虚拟机HotSpot
  186. // jint r = JNI_CreateJavaVM(&vm, (void **)&env, &args);
  187. jint r = ifn.CreateJavaVM(&vm, (void **)&env, &args);
  188. free(options);
  189. if(r == JNI_OK){
  190. printf("success");
  191. }
  192.  
  193. // 查找Java主类
  194. char* what = "com.test/TestInlineMethod";
  195. jclass mainClass = LoadMainClass(env, 1, what);
  196.  
  197. // 找到Java主类main()方法对应的唯一ID
  198. jmethodID mainID = env->GetStaticMethodID(mainClass, "main", "([Ljava/lang/String;)V");
  199.  
  200. // 为应用程序传递的参数
  201. jobjectArray mainArgs = NewPlatformStringArray(env, 0, NULL);
  202.  
  203. // 调用Java的main()方法
  204. env->CallStaticVoidMethod(mainClass, mainID, mainArgs);
  205.  
  206. return 0;
  207. }

由于我们现在还不能在main()中直接调用HotSpot VM源代码函数的方式启动,所以在编译好了libjvm.so库后,在CMakeLists.txt文件中注释掉编译动态链接库的逻辑(注释掉aux_source_directory和add_library即可),加上编译可执行程序的逻辑即可,如下:

  1. add_executable(${PROJECT_NAME} main.cpp)
  2. target_link_libraries(${PROJECT_NAME} dl pthread)

运行main()函数即可开启断点调试。  

如有对虚拟机感兴趣的,可扫码群,加过虚拟机群的就不要再加入了。

本人最近准备出一个手写Hotspot VM的课程,超级硬核,从0开始写HotSpot VM,将HotSpot VM所有核心的实现全部走一遍,如感兴趣,速速入群。

 

 

  

 

原文链接:https://www.cnblogs.com/mazhimazhi/p/17587540.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号