zoukankan      html  css  js  c++  java
  • JVM源码分析-JVM源码编译与调试

    要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白。所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试。

    编译环境

    本文使用的JDK版本:OpenJDK7,分支b147
    下载页面:https://download.java.net/openjdk/jdk7
    下载地址:http://download.java.net/openjdk/jdk7/promoted/b147/openjdk-7-fcs-src-b147-27_jun_2011.zip
    MD5:c284c89a104f64a95afde3a96138ef0f

    其他环境说明:

    • CentOS 7.4 64位
    • gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
    • GNU Make 3.82

    安装依赖

    yum -y install gcc gcc-c++ make 
    yum -y install alsa-lib-devel
    yum -y install cups-devel
    yum -y install libX*
    yum -y install gcc gcc-c++
    yum -y install libstdc++-static
    wget http://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo -O /etc/yum.repos.d/epel-apache-maven.repo
    yum -y install ant 
    

    编译JVM,需要使用到更早之前一个版本的JDK,比如我们编译的是7,就需要安装OracleJDK6:
    下载地址:https://www.oracle.com/java/technologies/javase-java-archive-javase6-downloads.html
    http://gcdncs.101.com/v0.1/static/test_mzb/jdk-6u38-linux-x64-rpm.bin
    下载:jdk-6u38-linux-x64-rpm.bin

    $ sh jdk-6u38-linux-x64-rpm.bin
    $ sudo rpm -ivh jdk-6u38-linux-amd64.rpm
    

    编写编译脚本

    解压openjdk:

    unzip openjdk-7-fcs-src-b147-27_jun_2011.zip
    

    在openjdk目录下添加一个build.sh脚本:

    #!/bin/bash
    export LANG=C
    
    #将一下两项设置为你的BootstrapJDK安装目录
    export ALT_BOOTDIR=/usr/java/jdk1.6.0_38
    export ALT_JDK_IMPORT_PATH=/usr/java/jdk1.6.0_38
    
    #允许自动下载依赖包
    export ALLOW_DOWNLOADS=true
    
    #使用预编译头文件,以提升便以速度
    export USE_PRECOMPILED_HEADER=true
    
    #要编译的内容,我只选择了LANGTOOLS、HOTSPOT以及JDK
    export BUILD_LANGTOOLS=true
    export BUILD_JAXP=false
    export BUILD_JAXWS=false
    export BUILD_CORBA=false
    export BUILD_HOSTPOT=true
    export BUILD_JDK=true
    
    #要编译的版本
    export SKIP_DEBUG_BUILD=false
    export SKIP_FASTDEBUG_BUILD=true
    export DEBUG_NAME=debug
    
    #避免javaws和浏览器Java插件等的build
    BUILD_DEPLOY=false
    
    #不build安装包
    BUILD_INSTALL=false
    
    #包含全部的调试信息
    export  ENABLE_FULL_DEBUG_SYMBOLS=1
    
    #调试信息是否压缩,如果配置为1,libjvm.debuginfo会被压缩成libjvm.diz,将不能被debug。
    export  ZIP_DEBUGINFO_FILES=0
    
    #用于编译线程数
    export  HOTSPOT_BUILD_JOBS=3
    
    #设置存放编译结果的目录
    #export ALT_OUTPUTDIR=/root/jvm/output
    
    unset CLASSPATH
    unset JAVA_HOME
    make sanity
    DEBUG_BINARIES=true make 2>&1
    

    然后执行 sh build.sh 进行编译。如果编译过程中遇到问题,可以查阅下文的编译问题解决的部分。

    运行HotSpot

    编译成功后,编译的输出默认在openjdk/build目录下。HotSpot的编译输出在openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg目录下。

    使用HotSpot提供的命令执行测试:

    cd build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg
    
    # test_gamma是HotSpot提供的一个测试程序,可以成功执行说明编译成功
    ./test_gamma
    
    # 使用hotspot脚本进行GDB调试
    ./hotspot -gdb HelloWorld
    

    ./hotspot是一个脚本,查阅代码可以看出他做了一些简单的事情,主要是会设置环境变量:

    JAVA_HOME=/usr/java/jdk1.6.0_38
    LD_LIBRARY_PATH=/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64
    

    然后生成GDB参数脚本,并运行GDB命令:

    gdb -x /tmp/hsl.26037
    

    /tmp/hsl.26037是脚本生成的gdb参数,我们可以看看都设置了什么:

    cd /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg
    handle SIGUSR1 nostop noprint
    handle SIGUSR2 nostop noprint
    set args HelloWorld 
    file /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/gamma
    directory /root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg
    # Get us to a point where we can set breakpoints in libjvm.so
    break InitializeJVM
    run
    # Stop in InitializeJVM
    delete 1
    # We can now set breakpoints wherever we like
    

    可以看出设置了源码目录,设置了一个默认断点。分析了hotspot脚本后,我们可以根据需要用最原始的方式来启动hotspot和gdb来实现更复杂的调试需求,比如远程调试。

    直接执行的方式:

    JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" ./gamma HelloWorld
    

    GDB调试:

    JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" gdb ./gamma HelloWorld
    

    GDB远程调试:

    JAVA_HOME=/usr/java/jdk1.6.0_38 LD_LIBRARY_PATH="/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg:/usr/java/jdk1.6.0_38/jre/lib/amd64" gdbserver :8011 ./gamma HelloWorld
    

    Hotspot代码调试技巧

    GDB的使用和技巧这里就不说了,我自己也是遇到问题现查资料的,这里列几个常用的和HotSpot有关的调试技巧。

    如何打印HotSpot内部符号对象Symbol对应的字符串?

    Symbol是一个非常常见的类,所有的符号引用对应的字符串,都会用Symbol来表示,比如类名、方法名、方法签名等等,可以用一下方法输出Symbol对应字符串:

    p *name._body@name._length
    

    如何打印KlassHandle对应的类名?

    p Klass::cast(current_klass.obj())->external_name()
    

    添加加载特定类时的断点

    break ClassFileParser::parseClassFile if strncmp(class_name._body, "XXX", 3) == 0
    break SystemDictionary::load_instance_class if strncmp(class_name._body, "XXX", 3) == 0
    

    遇到的问题

    BootstrapJDK一开始设置为JDK8会失败,要改为JDK6
    
    #错误
    echo "*** This OS is not supported:" `uname -a`; exit 1;
    
    #解决
    sudo vim openjdk/hotspot/make/linux/Makefile
    注释掉以下三行
    238 #ifeq ($(DISABLE_HOTSPOT_OS_VERSION_CHECK)$(EMPTY_IF_NOT_SUPPORTED),)
    239 # $(QUIETLY) >&2 echo "*** This OS is not supported:" `uname -a`; exit 1;
    240 #endif
    
    #错误
    error:"__LEAF"redefined [-Werror]
    
    #解决
    ubuntu12的glibc比较新,在linux的头文件cdefs.h里,有个__LEAF的宏,
    这个和hotspot/src/share/vm/runtime/interfaceSupport.hpp
    这个头文件中的宏定义有冲突,我们在428行下面增加一个#undef __LEAF如下:
    428 // LEAF routines do not lock, GC or throw exceptions
    #ifdef __LEAF
    #undef __LEAF
    #define __LEAF(result_type, header)                                  
      TRACE_CALL(result_type, header)                                    
      debug_only(NoHandleMark __hm;)                                     
      /* begin of body */
    #endif
    
    #错误
    Error:/openjdk/hotspot/src/share/vm/oops/constantPoolOop.cpp:272:39: 
    error: converting 'false' to pointer type 'methodOop' [-Werror=conversion-null]
    
    #解决
    vi hotspot/src/share/vm/oops/constantPoolOop.cpp
    将272行 return false  改为 return NULL
    
    #错误
    /usr/openjdk/hotspot/src/share/vm/opto/loopnode.cpp:896:49: 
    error: converting 'false' to pointer type 'Node*' [-Werror=conversion-null]
    
    #解决
    vi hotspot/src/share/vm/opto/loopnode.cpp
    将896行 return false  改为 return NULL
    
    #错误
    Using java runtime at: /usr/lib/jvm/java-1.6.0/jre
    ./gamma: relocation error: /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.41.x86_64/jre/lib/amd64/libjava.so: symbol JVM_FindClassFromCaller, version SUNWprivate_1.1 not defined in file libjvm.so with link time reference
    
    #解决
    这里是有一个坑的,为了避免大家踩坑,请提前安装好Oracle JDK 1.6。
    # ALT_BOOTDIR 用到的是 OpenJDK 1.6.0 会有此报错, OpenJDK 的bug,需要使用 Oracle JDK
    # 见到类似下方的报错了,恭喜童鞋您入坑了
    # ./gamma: relocation error: /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.41.x86_64/jre/lib/amd64/libjava.so: symbol JVM_FindClassFromCaller, version SUNWprivate_1.1 not defined in file libjvm.so with link time reference
    下载传送门可能需要登陆Oracle,没有帐号的童鞋请注册一下。笔者下载安装的是jdk-6u38-linux-x64-rpm.bin。
    如果上一个传送门失效,请继续传送!找到此页面上的Java SE 6进入传送哦~如果这个传送也失效了(T_T),那接着传送,拉到页面最下方,找到Java Archive栏,点击右侧DOWNLOAD按钮自行传送。再不行就只能找baidu了~~~
    
    
    # 对下载到的bin动动手脚(不要想多,释放里面的rpm包而已)
    $ sh jdk-6u38-linux-x64-rpm.bin
    # 查看下得到的rpm包
    $ ll *.rpm
    # 安装Oracle JDK
    $ sudo rpm -ivh jdk-6u38-linux-amd64.rpm
    # OK 至此已完成Oracle JDK安装
    # 查找安装的Oracle JDK目录
    # 查找jdk安装名称
    $ rpm -qa | grep ^jdk-1.6.0
    jdk-1.6.0_38-fcs.x86_64
    # 根据安装名称查找安装到本地的文件列表
    $ rpm -ql jdk-1.6.0_38-fcs.x86_64
    ...
    /usr/java/jdk1.6.0_38 # Oracle JDK HOME
    ...
    # 以上查找到的目录后面会用到
    
    错误:
    gcc: error: unrecognized command line option '-mimpure-text'
    
    解决:
    vi jdk/make/common/shared/Compiler-gcc.gmk
    在70行remove the command "-mimpure-text" in the code: 
    
    错误:
    Error: time is more than 10 years from present: 1136059200000
    
    解决:
    # 修改以下文件,将日期改为十年以内,JDK的Bug。
    vi jdk/src/share/classes/java/util/CurrencyData.properties
    # line: 108  377  439  529  555
    
    错误:
    ../../../src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java:661: error: no suitable constructor found for SslRMIServerSocketFactory(SSLContext,String[],String[],boolean)
    
    解决:
    vi ./jdk/src/share/classes/sun/management/jmxremote/ConnectorBootstrap.java
    注释掉662行的参数
    

    资料

    本文独立博客地址:JVM源码分析-JVM源码编译与调试 | 木杉的博客

  • 相关阅读:
    通过盘古分词自定义规则功能实现软件版本号的提取
    Js event事件在IE、FF兼容性问题
    Android动画效果 translate、scale、alpha、rotate 切换Activity动画 控件位置调整
    iPhone代码片段收集(持续更新)
    Activity之间的相互调用与传递参数
    android如何拍照以及返回拍的图片(经过验证的实际例子)
    Android API :SMS短信服务处理和获取联系人
    实现Android的消息通知栏
    iPhone开发 调用摄像头进行拍照等操作
    Android模拟 HTTP multipart/formdata 请求协议信息实现图片上传
  • 原文地址:https://www.cnblogs.com/mushan/p/12266446.html
Copyright © 2011-2022 走看看