结合JDK 6.0 Attach / JDK 5.0 intrumentation 实现对jvm中class的修改

注意:这里仅仅提供一个最简单的修改入门课程。让大家先attach 和 intrumentation 有一个比较清晰的认识。

也希望大家如果是一点经验都没有的话。最好能够根据此篇博文顺序耐心 从头到尾的 进行练习 然后再进行个人的创作和修改。这么建议是帮助大家节省学习新知识的时间。参考时间:完成练习代码并进行测试不应该超过半小时,等你的程序运行起来之后再去思考为什么是这样的。我们学习的路途中很多时候是 先有果,后有因的一个顺序。这也是为什么基本很多框架会有helloworld例子的原因。

在学习前请思考如下问题:

1、你觉得一台装有JDK并跑了很多个java 进程的计算机上,会有多少个java虚拟机呢?

 

从jdk 6.0 后sun jdk 中 有一个工具包叫 tool.jar 提供了 attach 功能需要的类。你能够在 %java_home%/ lib 目录下找到。

在eclipse 中如果想要使用需要你build path 加入依赖中才能够使用。(很抱歉我不会使用IDE)

一、第一部分代码 :打印出你当前机子上的所有虚拟机描述(不包含JDK1.5版本前的 虚拟机)

这里解答了第一个问题。可能比较蠢,反正在我还没有接触虚拟机的时候。我一直以为一台机子就只有一台java 虚拟机。

package com.zhu.attachagent;

import java.util.List;

import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor;

/ 往 jvm中注入代码的注入器 @author Felix */ public class VirtualMachineInjector {

public static void main(String[] args) {
        // 列表出 当前系统运行中的 jvm ,只能够列表出 jdk1.6及其以上的jvm。
        // jdk1.5以前的虚拟机 你通过 jconsole 能够看到进程号,但是无法进行连接
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor virtualMachineDescriptor : list) {
            System.out.println("Pid : " + virtualMachineDescriptor.id());
            System.out.println("DisplayName :" + virtualMachineDescriptor.displayName());
        }
    }
}

当前我的运行结果: 


二、接下来进行对第一部分的代码进行修改,并添加剩下其他需要使用的class的代码。

然后进行对目标 jvm中的已经加载的class的字节码内存进行替换

1.VirtualMachineInjector 

package com.zhu.attachagent;

import java.util.List;

import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor;

/ 往 jvm中注入代码的注入器 @author Felix */ public class VirtualMachineInjector {

public static void main(String[] args) {
        // 列表出 当前系统运行中的 jvm ,只能够列表出 jdk1.6及其以上的jvm。
        // jdk1.5以前的虚拟机 你通过 jconsole 能够看到进程号,但是无法进行连接
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor virtualMachineDescriptor : list) {
            System.out.println("Pid : " + virtualMachineDescriptor.id());
            System.out.println("DisplayName :" + virtualMachineDescriptor.displayName());
        }
    }
}

2.ToBeTransform 即将被替换的类

package com.zhu.attachagent;

/**
 * 将被重新转换的一个类
 * @author Felix
 *
 */
public class ToBeTransform {
    public void test() {
        System.out.println(1);
    }
}

3.MainTest 运行ToBeTransform 的 主进程类

package com.zhu.attachagent;

/**
 * 测试类 测试我们的jvm中的class 是否被修改
 * @author Felix
 *
 */
public class MainTest {
    public static void main(String[] args) throws InterruptedException {
        while (true) {
            ToBeTransform t = new ToBeTransform();
            t.test();
            Thread.sleep(3000);
        }
    }
}

4.修改ToBeTransform 如下后,从获取IDE或者Eclipse帮你编译好的ToBeTransform.class 放入D盘下。一般在bin或者target(maven项目)目录下。存放好D盘后,请将代码修改为System.out.println(1).

package com.zhu.attachagent;

/**
 * 将被重新转换的一个类
 * @author Felix
 *
 */
public class ToBeTransform {
    public void test() {
        System.out.println(2);
    }
}

5.Agent 类中 agentmain 方法不是随意写的,是按照规定来写的还有一个约定俗成的方法是premain

package com.zhu.attachagent;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class Agent{
    public static void agentmain(String args, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
         //打印传入的参数 就是刚才传入的一个jvm pid
         System.out.println("Pid" + args);
         inst.addTransformer(new MyTransformer(), true);
         inst.retransformClasses(Class.forName("com.zhu.attachagent.ToBeTransform"));
    }
}

6.MyTransformer 这是一个对字节码替换很重要的一个类 实现了ClassFileTransformer接口

package com.zhu.attachagent;

import java.io.File;
import java.io.FileInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;


public class MyTransformer implements ClassFileTransformer{

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("正在修改的类: " + className);
        System.out.println("修改前的字节码大小: " + classfileBuffer.length);

        // 将你修改过得ToBeTransform.class 放入下面位置等待被读取
        File file = new File("D:\\ToBeTransform.class");
        byte [] data = new byte[(int) file.length()];
        try (FileInputStream fis = new FileInputStream(file);){
            fis.read(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("修改后的字节码大小: " + data.length);
        return data;
    }

}


7.MANIFEST.MF

请注意文件的书写格式为 key: value  然后 请注意观察:1.后面一定要有一个空格  2.最后一行要空一行。这个是规范要求。具体为啥还未研究。

Manifest-Version: 1.0
Agent-Class: com.zhu.attachagent.Agent 
Can-Redine-Classes: true

Can-Retransform-Classes: true

三、将 Agent 和 MyTransformer 打成agent.jar 

1


2.


3.


4.


5.


四、到这里你就完成了所有的编程和环境处理。接下来就是进行测试啦

1、先运行MainTest方法 查看 console 控制台的输出情况 打印 为 1

2. 运行VirtualMachineInjector 进行 对目标类修改情况

从上面能够看出我们已经成功了修改了我们想要修改的类。而且现在console 打印编程了 2.

现在请大家思考如果 我把 MainTest 中的换成 下面 的代码。会对接下来有什么影响。


package com.zhu.attachagent;

/**
 * 测试类 测试我们的jvm中的class 是否被修改
 * @author Felix
 *
 */
public class MainTest {
    public static void main(String[] args) throws InterruptedException {
        while (true) {
            new ToBeTransform().test();;
            Thread.sleep(3000);
        }
    }
}
 



 

 

 

 

 

 

 

 

总结:

替换完后,结果显示的是不是一样呢?为什么是这样请 深入了解 java 虚拟机中 的知识吧。

1.现在是不是想要看看tool.jar 的源代码了呢?苦于找不到代码对吗?来使用下面的功能让你能够在eclipse 中反编译代码

http://blog.youkuaiyun.com/sinat_35608637/article/details/73222583

如何以上不行请使用这个。

https://jingyan.baidu.com/article/fc07f9896da51512ffe5198a.html

2.在这里我们在MyTransformer 中的 是将一个我们修改后的class 文件读取并完全替换了原本 jvm 启动后自动加载的class 文件。

这是一个比较简单的实现。如果我们需要对一个类中某个方法进行定向的修改的话,需要你进一步的学习asm 技术 。当然 javassit 也行。不过个人觉得学习asm 技术虽然很难,

但是能够帮助你了解很多jvm 的知识。

3。注意这里这个功能 对 jdk 1.5 之前的程序是没有任何作用的。

4. 32位的 jvm 进程 与 64 的jvm 进程之间是无法进行相互 attach 的这里也需要注意。

5. 你可以对MANIFEST.ME文件进行修改,删除其中的某些配置然后再进行打包后。进行重新测试看看是否有什么问题存在。

6. 请思考一下 jdk1.6 编译的程序 是否能够注入 其他版本的 1.7 或1.8 编译的程序吗?


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值