`
RednaxelaFX
  • 浏览: 3014978 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

借助HotSpot SA来反汇编

阅读更多
前一篇日志,再记一些HotSpot中Serviceability Agent(以下简称SA)的有趣用法。
前面提到过,要在Windows上使用SA的话,可以使用Sun JDK 7 build 64或者更新的版本。下面我们用JDK 7 build 96来跑VisualVM 1.3和Groovy 1.7.2的groovysh做个例子。
(要用JDK7来启动groovysh的话,一个简单的办法是在命令行里把JAVA_HOME设置到JDK7的目录上。
另外,要让Groovy使用某些Java参数启动的话,可以设置JAVA_OPTS环境变量)

在启动VisualVM的时候,确保它使用的是JDK7。可以用--jdkhome参数或者在VisualVM安装目录下的etc/visualvm.conf文件中设置jdkhome。启动后在VisualVM主菜单的Tools -> Plugins里,选择Available Plugins选项卡,在列表里可以看到一个称为SAPlugin的插件,把它装上。SAPlugin是VisualVM为SA提供的图形界面插件,可以方便的使用SA的部分封装好的功能。

然后用JDK7来启动一个groovysh,再在VisualVM里通过SAPlugin接进去,可以看到:

SAPlugin还有这样的功能,可以看到被JIT编译过的方法以及解释器的汇编代码:


那SA是不是有内建的反汇编器,可以把任意x86、x64、IA64、SPARC等架构的机器码给反汇编为汇编代码呢?答案是肯定的,有足够方便的现成的API可用。(x64版尚未实现完,暂时还用了)
(顺带提一下:“反汇编”(disassemble)与“反编译”(decompile)是不一样的,请不要弄混了。)

设想有个场景,HotSpot VM突然crash了,并在退出过程中打出了一个hs_err_pidXXXX.log的日志文件。其中开头的部分是:
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6d9039f1, pid=3628, tid=3664
#
# JRE version: 6.0_18-b07
# Java VM: Java HotSpot(TM) Client VM (16.0-b13 mixed mode, sharing windows-x86 )
# Problematic frame:
# V  [jvm.dll+0x1039f1]
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread (0x0084c000):  JavaThread "main" [_thread_in_vm, id=3664, stack(0x008e0000,0x00930000)]

siginfo: ExceptionCode=0xc0000005, reading address 0x00000008

Registers:
EAX=0x00000000, EBX=0x0084c000, ECX=0x00000008, EDX=0x0092e134
ESP=0x0092dbf8, EBP=0x0092dc3c, ESI=0x00000000, EDI=0x0084c8ec
EIP=0x6d9039f1, EFLAGS=0x00010246

Top of Stack: (sp=0x0092dbf8)
0x0092dbf8:   0084c110 0092e134 0092e134 0092e134
0x0092dc08:   04506fef 0092dc30 1000781f 00000000
0x0092dc18:   00000001 00000000 0092e054 0084c000
0x0092dc28:   00000000 00000002 0084c110 f910dbcd
0x0092dc38:   0084c000 0084c110 62f01296 0084c8e8
0x0092dc48:   0092e134 62f0f17c 00000049 00000000
0x0092dc58:   00000000 00000000 00000000 00000000
0x0092dc68:   00000000 00000000 00000000 00000000 

Instructions: (pc=0x6d9039f1)
0x6d9039e1:   8b f0 eb 07 89 51 08 89 30 8b f0 8b 00 8d 48 08
0x6d9039f1:   8b 01 53 ff 50 2c 8b 43 04 85 c0 0f 85 8f 00 00 

可以看到HotSpot遇到了访问异常(EXCEPTION_ACCESS_VIOLATION)。访问异常有很多时候是由本地代码中的空指针访问而引起的,这里是否也是如此呢?
至少得看看出错的指令长啥样吧。可以看到日志里有一块写着“Instructions”,有两行十六进制表示的数据跟在后面。这就是出错位置前后的机器码,其中第二行开头处是出错指令的开头位置。

借助SA里内建的反汇编器,我们可以写段代码来看看日志里出错的机器码到底是什么:
import sun.jvm.hotspot.asm.CPUHelper;
import sun.jvm.hotspot.asm.Disassembler;
import sun.jvm.hotspot.asm.Instruction;
import sun.jvm.hotspot.asm.InstructionVisitor;
import sun.jvm.hotspot.asm.SymbolFinder;
import sun.jvm.hotspot.asm.x86.X86Helper;

/**
 * @author sajia
 *
 */
public class TestX86Disassembler {
    /**
     * @param args
     */
    public static void main(String[] args) {
        CodeSnippet code = makeSampleCode();
        
        CPUHelper cpuHelper = new X86Helper();
        Disassembler dasm = cpuHelper.createDisassembler(code.startPc, code.code);
        StringBuilder buf = new StringBuilder();
        dasm.decode(new RawCodeVisitor(buf));
        String dasmStr = buf.toString();
        
        System.out.println(dasmStr);
    }
    
    private static CodeSnippet makeSampleCode() {
        CodeSnippet code = new CodeSnippet();
        code.startPc = 0x6d9039f1;
        code.code = new byte[] {
            (byte) 0x8b, (byte) 0x01,
            (byte) 0x53,
            (byte) 0xff, (byte) 0x50, (byte) 0x2c,
            (byte) 0x8b, (byte) 0x43, (byte) 0x04,
            (byte) 0x85, (byte) 0xc0,
            (byte) 0x0f, (byte) 0x85, (byte) 0x8f, (byte) 0x00, (byte) 0x00
        };
        return code;
    }
    
    private static class CodeSnippet {
        long startPc;
        byte[] code;
    }
    
    private static class RawCodeVisitor implements InstructionVisitor {
        private final StringBuilder buf;
        private final SymbolFinder symFinder = new DummySymbolFinder();
        
        public RawCodeVisitor(StringBuilder buf) {
            this.buf = buf;
        }

        @Override
        public void prologue() {
            // do nothing
        }

        @Override
        public void visit(long currentPc, Instruction instr) {
            buf.append("0x")
                .append(Long.toHexString(currentPc))
                .append(":  ")
                .append(instr.asString(currentPc, symFinder))
                .append("\n");
        }
        
        @Override
        public void epilogue() {
            // do nothing
        }
    }
    
    public static class DummySymbolFinder implements SymbolFinder {
        public String getSymbolFor(long address) {
            return "0x" + Long.toHexString(address);
        }
    }
}

确保$JAVA_HOME/lib/sa-jdi.jar在classpath上,运行该程序可以看到输出为:
0x6d9039f1:  movl	%eax, [%ecx]
0x6d9039f3:  pushl	%ebx
0x6d9039f4:  call	44[%eax]
0x6d9039f7:  movl	%eax, 4[%ebx]
0x6d9039fa:  testl	%eax, %eax
0x6d9039fc:  jne	0x6d903a91

可以看到在地址0x6d9039f1的指令是mov eax, [ecx],对ECX寄存器有一个间接读访问,而从日志上可以看到此时ECX寄存器的值为0x00000008。在32位Windows XP上0x00000000-0x0000FFFF是保护区域,尝试对该区域读写会引发访问异常。这里程序试图从0x00000008读取一个DWORD赋值给EAX,印证了它是日志中访问异常的事发地。当然,真正导致该问题的代码肯定在更前面的地方,但确认事发地的状况也算是个好的开始。

SA反汇编出来的汇编代码语法既不是AT&T系的(GNU as用这种),也不是Intel系的(MASMNASM用这种),感觉有那么点怪。
与Intel系的相似点是参数顺序,目标在前,源在后;间接引用以方括号表示。
与AT&T系相似的是寄存器带有%前缀,而且间接引用的偏移量写在括号的前面;指令带有宽度后缀。
总之需要点时间来习惯…

反汇编器这部分的实现与SA的其它相对独立,要抽出来单独用也没什么问题。有需要的话抽个出来改造成纯MASM语法输出或许会有用。

Alright,今天就废话到这里~
  • 大小: 199.8 KB
  • 大小: 205.1 KB
分享到:
评论
18 楼 RednaxelaFX 2014-10-25  
Ferdiknight 写道
R大,HSDB有什么命令是可以拿到对应方法的机器码数据的吗,比如某个方法地址是0x35xxxxx,pc=0x2bxxxxx,然后mem 0x35xxxxx size 这个得到的是不是对应这个方法的native code 的内容啊?

看help就知道了。如果你的HSDB正常启动了的话(sa.js正确加载了),那么“dis”命令就是把机器指令反汇编的。它的格式是
dis address [length]

其中address是机器指令的起始地址。length是想反汇编的数据的长度。

你问的问题的话pc那个值才是机器码相关的。

还有个比较新的disassemble命令也行。这个接收的参数是CodeBlob或者nmethod的起始地址。

注意:在JDK-6879063: SA should use hsdis for disassembly之后,SA会用hsdis插件来反汇编,这样只要支持hsdis的平台都能在SA里看反汇编;在它之前SA只能在x86(32位)上反汇编。
17 楼 Ferdiknight 2014-10-24  
R大,HSDB有什么命令是可以拿到对应方法的机器码数据的吗,比如某个方法地址是0x35xxxxx,pc=0x2bxxxxx,然后mem 0x35xxxxx size 这个得到的是不是对应这个方法的native code 的内容啊?
16 楼 RednaxelaFX 2014-10-23  
Ferdiknight 写道
今天在新机器上搞了个1.3.8,用的jdk7u55 ,为啥只显示oop的地址了,不显示klass的地址了-_- 不幸福,这个是跟saplugin有关,还是跟jdk的sa-jdi.jar有关系啊

跟sa-jdi.jar的实现有关,而跟SAPlugin没关系。JDK7u的SA有些bug使得metadata字段不一定会显示出来。
15 楼 Ferdiknight 2014-10-22  
今天在新机器上搞了个1.3.8,用的jdk7u55 ,为啥只显示oop的地址了,不显示klass的地址了-_- 不幸福,这个是跟saplugin有关,还是跟jdk的sa-jdi.jar有关系啊
14 楼 RednaxelaFX 2014-03-19  
zking3 写道
R,我晚上下载了好几个版本的VisualVM,然后添加SAPlugin,但是重启后,选项卡那里还是没有,没有那个蓝色的小图标SAPlugin。试了好几个版本都没。

您用的VisualVM是不是很新的,或者说是很新的JDK自带的?
如果是的话,试试用stand-alone的VisualVM。如果不行的话试试1.3.1之前的版本。
13 楼 zking3 2014-03-16  
R,我晚上下载了好几个版本的VisualVM,然后添加SAPlugin,但是重启后,选项卡那里还是没有,没有那个蓝色的小图标SAPlugin。试了好几个版本都没。
12 楼 RednaxelaFX 2011-10-29  
bluesleaf 写道
撒迦同学,我用的win7,JDK 1.7.0_01,visualvm 1.3.3,jdkhome也指定了,安装完SA插件,程序也是用JDK7跑的,但打开进程就是看不到SA Plugin的tab,visual gc的tab也可以看到,不知道是为啥呢

据说VisualVM 1.3.x都有点诡异?回头我找台Windows的机器来试试
11 楼 bluesleaf 2011-10-29  
撒迦同学,我用的win7,JDK 1.7.0_01,visualvm 1.3.3,jdkhome也指定了,安装完SA插件,程序也是用JDK7跑的,但打开进程就是看不到SA Plugin的tab,visual gc的tab也可以看到,不知道是为啥呢
10 楼 igotti 2011-06-16  
RednaxelaFX 写道
igotti 写道
哦,话说翻译的有点不给力啊。只能先“读书不求甚解”式的把前6章看一遍。

翻译怎样个不给力法?我没读过中文版的,一直不知道这本的翻译质量如何

没读过英文版,且中文版目前只读了前两章。这只是一种感觉,抑或是这本书本身的难度对阅

读造成的影响。
9 楼 RednaxelaFX 2011-06-16  
igotti 写道
哦,话说翻译的有点不给力啊。只能先“读书不求甚解”式的把前6章看一遍。

翻译怎样个不给力法?我没读过中文版的,一直不知道这本的翻译质量如何
8 楼 igotti 2011-06-16  
计算机组织与结构相关的内容如果有比较好的基础的话,对理解虚拟机也会有帮助。
毕竟要虚拟机要模拟的东西就是某种体系结构。
不过我觉得读不懂的话完全不需要着急。记得我是08年左右买到的那本书的影印版,前后已经读了很多遍了,花了颇长时间才渐渐觉得里面的内容变得易懂了。
哦,话说翻译的有点不给力啊。只能先“读书不求甚解”式的把前6章看一遍。
7 楼 RednaxelaFX 2011-06-16  
igotti 写道
最近在读虚拟机:系统与进程的通用平台,发现很多知识点对我来说比较朦胧,好想知道,但是又不能全然领悟书中的观点。是不是知识储备不够?需要补充哪方面的知识?

计算机组织与结构相关的内容如果有比较好的基础的话,对理解虚拟机也会有帮助。
毕竟要虚拟机要模拟的东西就是某种体系结构。
不过我觉得读不懂的话完全不需要着急。记得我是08年左右买到的那本书的影印版,前后已经读了很多遍了,花了颇长时间才渐渐觉得里面的内容变得易懂了。
6 楼 igotti 2011-06-16  
RednaxelaFX 写道
igotti 写道
为什么安装saplugin的时候提示实用程序 API 版本 太低。 我是用jdkhome选项启动的visualvm。

你用的JDK版本是什么?这篇的开头就写了在Windows上的话一定要用JDK7才行。我的建议是安装一个新版本的JDK7,并且去下载一个单独的VisualVM(因为现在JDK7里的VisualVM有点小问题所以去下单独的版本吧)。

哦,正在下载visualvm。

那啥顺道再问个问题:

最近在读虚拟机:系统与进程的通用平台,发现很多知识点对我来说比较朦胧,好想知道,但是又不能全然领悟书中的观点。是不是知识储备不够?需要补充哪方面的知识?
5 楼 RednaxelaFX 2011-06-16  
igotti 写道
为什么安装saplugin的时候提示实用程序 API 版本 太低。 我是用jdkhome选项启动的visualvm。

你用的JDK版本是什么?这篇的开头就写了在Windows上的话一定要用JDK7才行。我的建议是安装一个新版本的JDK7,并且去下载一个单独的VisualVM(因为现在JDK7里的VisualVM有点小问题所以去下单独的版本吧)。
4 楼 igotti 2011-06-16  
为什么安装saplugin的时候提示实用程序 API 版本 太低。 我是用jdkhome选项启动的visualvm。
3 楼 lwwin 2010-09-14  
好吧- -其实想着反得- -打字就偏偏少了一个字……
2 楼 RednaxelaFX 2010-08-04  
lwwin 写道
就是可以用它汇编- -?水水没怎么用过GNU系列的,那种符号感觉更贴近GNU的=0=!

是用它“反汇编”,不是“汇编”……这玩儿只能从机器码转换到汇编,没法从汇编转换到机器码啦。
1 楼 lwwin 2010-08-04  
就是可以用它汇编- -?水水没怎么用过GNU系列的,那种符号感觉更贴近GNU的=0=!

一时间前面几个名字没看懂……

相关推荐

Global site tag (gtag.js) - Google Analytics