1. 通灵之术

在《火影忍者》中,通灵之术,属于时空间忍术的一种。

那么,“通灵之术”,在Java领域,代表什么意思呢?就是将正在运行的JVM当中的class进行导出。

本文的主要目的:借助于Java Agent将class文件从JVM当中导出。

2. 准备工作

开发环境:

  • JDK版本:Java 8
  • 开发工具:记事本或vi

创建文件目录结构:准备一个prepare.sh文件

#!/bin/bashmkdir -p application/{src,out}/sample/
touch application/src/sample/{HelloWorld.java,Program.java}mkdir -p java-agent/{src,out}/
touch java-agent/src/{ClassDumpAgent.java,ClassDumpTransformer.java,ClassDumpUtils.java,manifest.txt}mkdir -p tools-attach/{src,out}/
touch tools-attach/src/Attach.java

目录结构:(编译之前)

java-agent-summoning-jutsu
├─── application
│    └─── src
│         └─── sample
│              ├─── HelloWorld.java
│              └─── Program.java
├─── java-agent
│    └─── src
│         ├─── ClassDumpAgent.java
│         ├─── ClassDumpTransformer.java
│         ├─── ClassDumpUtils.java
│         └─── manifest.txt
└─── tools-attach└─── src└─── Attach.java

目录结构:(编译之后)

java-agent-summoning-jutsu
├─── application
│    ├─── out
│    │    └─── sample
│    │         ├─── HelloWorld.class
│    │         └─── Program.class
│    └─── src
│         └─── sample
│              ├─── HelloWorld.java
│              └─── Program.java
├─── java-agent
│    ├─── out
│    │    ├─── ClassDumpAgent.class
│    │    ├─── classdumper.jar
│    │    ├─── ClassDumpTransformer.class
│    │    ├─── ClassDumpUtils.class
│    │    └─── manifest.txt
│    └─── src
│         ├─── ClassDumpAgent.java
│         ├─── ClassDumpTransformer.java
│         ├─── ClassDumpUtils.java
│         └─── manifest.txt
└─── tools-attach├─── out│    └─── Attach.class└─── src└─── Attach.java

3. Application

3.1. HelloWorld.java

package sample;public class HelloWorld {public static int add(int a, int b) {return a + b;}public static int sub(int a, int b) {return a - b;}
}

3.2. Program.java

package sample;import java.lang.management.ManagementFactory;
import java.util.Random;
import java.util.concurrent.TimeUnit;public class Program {public static void main(String[] args) throws Exception {// (1) print process idString nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();System.out.println(nameOfRunningVM);// (2) count downint count = 600;for (int i = 0; i < count; i++) {String info = String.format("|%03d| %s remains %03d seconds", i, nameOfRunningVM, (count - i));System.out.println(info);Random rand = new Random(System.currentTimeMillis());int a = rand.nextInt(10);int b = rand.nextInt(10);boolean flag = rand.nextBoolean();String message;if (flag) {message = String.format("a + b = %d", HelloWorld.add(a, b));}else {message = String.format("a - b = %d", HelloWorld.sub(a, b));}System.out.println(message);TimeUnit.SECONDS.sleep(1);}}
}

3.3. 编译和运行

进行编译:

# 进行编译
$ cd application/
$ javac src/sample/*.java -d out/

运行结果:

$ cd out/
$ java sample.Program
5556@LenovoWin7
|000| 5556@LenovoWin7 remains 600 seconds
a - b = 6
|001| 5556@LenovoWin7 remains 599 seconds
a - b = -4
...

4. Java Agent

曾经有一篇文章《Retrieving .class files from a running app》,最初是发表在Sun公司的网站,后来转移到了Oracle的网站,再后来就从Oracle网站消失了。

Sometimes it is better to dump .class files of generated/modified classes for off-line debugging -
for example, we may want to view such classes using tools like jclasslib.

4.1. 类

ClassDumpAgent

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.util.ArrayList;
import java.util.List;/*** This is a java.lang.instrument agent to dump .class files* from a running Java application.*/
public class ClassDumpAgent {public static void premain(String agentArgs, Instrumentation inst) {agentmain(agentArgs, inst);}public static void agentmain(String agentArgs, Instrumentation inst) {System.out.println("agentArgs: " + agentArgs);ClassDumpUtils.parseArgs(agentArgs);inst.addTransformer(new ClassDumpTransformer(), true);// by the time we are attached, the classes to be// dumped may have been loaded already.// So, check for candidates in the loaded classes.Class[] classes = inst.getAllLoadedClasses();List<Class> candidates = new ArrayList<>();for (Class c : classes) {String className = c.getName();// 第一步,排除法:不考虑JDK自带的类if (className.startsWith("java")) continue;if (className.startsWith("javax")) continue;if (className.startsWith("jdk")) continue;if (className.startsWith("sun")) continue;if (className.startsWith("com.sun")) continue;// 第二步,筛选法:只留下感兴趣的类(正则表达式匹配)boolean isModifiable = inst.isModifiableClass(c);boolean isCandidate = ClassDumpUtils.isCandidate(className);if (isModifiable && isCandidate) {candidates.add(c);}// 不重要:打印调试信息String message = String.format("[DEBUG] Loaded Class: %s ---> Modifiable: %s, Candidate: %s", className, isModifiable, isCandidate);System.out.println(message);}try {// 第三步,将具体的class进行dump操作// if we have matching candidates, then retransform those classes// so that we will get callback to transform.if (!candidates.isEmpty()) {inst.retransformClasses(candidates.toArray(new Class[0]));// 不重要:打印调试信息String message = String.format("[DEBUG] candidates size: %d", candidates.size());System.out.println(message);}}catch (UnmodifiableClassException ignored) {}}
}

ClassDumpTransformer

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;public class ClassDumpTransformer implements ClassFileTransformer {public byte[] transform(ClassLoader loader,String className,Class redefinedClass,ProtectionDomain protDomain,byte[] classBytes) {// check and dump .class fileif (ClassDumpUtils.isCandidate(className)) {ClassDumpUtils.dumpClass(className, classBytes);}// we don't mess with .class file, just return nullreturn null;}}

ClassDumpUtils

import java.io.File;
import java.io.FileOutputStream;
import java.util.regex.Pattern;public class ClassDumpUtils {// directory where we would write .class filesprivate static String dumpDir;// classes with name matching this pattern will be dumpedprivate static Pattern classes;// parse agent args of the form arg1=value1,arg2=value2public static void parseArgs(String agentArgs) {if (agentArgs != null) {String[] args = agentArgs.split(",");for (String arg : args) {String[] tmp = arg.split("=");if (tmp.length == 2) {String name = tmp[0];String value = tmp[1];if (name.equals("dumpDir")) {dumpDir = value;}else if (name.equals("classes")) {classes = Pattern.compile(value);}}}}if (dumpDir == null) {dumpDir = ".";}if (classes == null) {classes = Pattern.compile(".*");}System.out.println("[DEBUG] dumpDir: " + dumpDir);System.out.println("[DEBUG] classes: " + classes);}public static boolean isCandidate(String className) {// ignore array classesif (className.charAt(0) == '[') {return false;}// convert the class name to external nameclassName = className.replace('/', '.');// check for name pattern matchreturn classes.matcher(className).matches();}public static void dumpClass(String className, byte[] classBuf) {try {// create package directories if neededclassName = className.replace("/", File.separator);StringBuilder buf = new StringBuilder();buf.append(dumpDir);buf.append(File.separatorChar);int index = className.lastIndexOf(File.separatorChar);if (index != -1) {String pkgPath = className.substring(0, index);buf.append(pkgPath);}String dir = buf.toString();new File(dir).mkdirs();// write .class fileString fileName = dumpDir + File.separator + className + ".class";FileOutputStream fos = new FileOutputStream(fileName);fos.write(classBuf);fos.close();System.out.println("[DEBUG] FileName: " + fileName);}catch (Exception ex) {ex.printStackTrace();}}}

4.2. manifest.txt

Premain-Class: ClassDumpAgent
Agent-Class: ClassDumpAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

注意:在结尾处添加一个空行。

4.3. 编译和打包

第一步,进行编译:

$ javac src/ClassDump*.java -d ./out

在Windows操作系统,如果遇到如下错误:

错误: 编码GBK的不可映射字符

可以添加-encoding选项:

javac -encoding UTF-8 src/ClassDump*.java -d ./out

第二步,生成Jar文件:

$ cp src/manifest.txt out/
$ cd out/
$ jar -cvfm classdumper.jar manifest.txt ClassDump*.class

5. Tools Attach

将一个Agent Jar与一个正在运行的Application建立联系,需要用到Attach机制:

Agent Jar ---> Tools Attach ---> Application(JVM)

与Attach机制相关的类,定义在tools.jar文件:

JDK_HOME/lib/tools.jar

5.1. Attach

import com.sun.tools.attach.VirtualMachine;/*** Simple attach-on-demand client tool* that loads the given agent into the given Java process.*/
public class Attach {public static void main(String[] args) throws Exception {if (args.length < 2) {System.out.println("usage: java Attach <pid> <agent-jar-full-path> [<agent-args>]");System.exit(1);}// JVM is identified by process id (pid).VirtualMachine vm = VirtualMachine.attach(args[0]);String agentArgs = (args.length > 2) ? args[2] : null;// load a specified agent onto the JVMvm.loadAgent(args[1], agentArgs);vm.detach();}
}

5.2. 编译

# 编译(Linux)
$ javac -cp "${JAVA_HOME}/lib/tools.jar":. src/Attach.java -d out/# 编译(MINGW64)
$ javac -cp "${JAVA_HOME}/lib/tools.jar"\;. src/Attach.java -d out/# 编译(Windows)
$ javac -cp "%JAVA_HOME%/lib/tools.jar";. src/Attach.java -d out/

5.3. 运行

# 运行(Linux)
java -cp "${JAVA_HOME}/lib/tools.jar":. Attach <pid> <full-path-of-classdumper.jar> dumpDir=<dir>,classes=<name-pattern># 运行(MINGW64)
java -cp "${JAVA_HOME}/lib/tools.jar"\;. Attach <pid> <full-path-of-classdumper.jar> dumpDir=<dir>,classes=<name-pattern># 运行(Windows)
java -cp "%JAVA_HOME%/lib/tools.jar";. Attach <pid> <full-path-of-classdumper.jar> dumpDir=<dir>,classes=<name-pattern>

示例:

java -cp "${JAVA_HOME}/lib/tools.jar"\;. Attach <pid> \
D:/tmp/java-agent-summoning-jutsu/java-agent/out/classdumper.jar \
dumpDir=D:/tmp/java-agent-summoning-jutsu/dump,classes=sample\.HelloWorld

6. 总结

本文内容总结如下:

  • 第一点,主要功能。从功能的角度来讲,是如何从一个正在运行的JVM当中将某一个class文件导出的磁盘上。
  • 第二点,实现方式。从实现方式上来说,是借助于Java Agent和正则表达式(区配类名)来实现功能。
  • 第三点,注意事项。在Java 8的环境下,想要将Agent Jar加载到一个正在运行的JVM当中,需要用到tools.jar

当然,将class文件从运行的JVM当中导出,只是Java Agent功能当中的一个小部分,想要更多的了解Java Agent的内容,可以学习《Java Agent基础篇》。

Java Agent:通灵之术相关推荐

  1. 写那么多年Java,还不知道啥是Java agent 的必须看一下!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者信息:张帅,花名洵澈,国际化中台事业部高级开发工程师,负责物流 ...

  2. 我的天,你工作5年了,连Java agent都不知道...

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 # 引言 在本篇文章中,我会通过几个简单的程序来说明 agent ...

  3. java实现扫地agent_如何实现java agent?分享java agent的使用案例

    java agent如何实现?1.实现java agent需要实现premain方法:2.必须在MANIFEST.MF文件中有Premain-Class. 在字节码这个层面对类和方法进行修改的技术,能 ...

  4. 说实话,你工作5年,不知道什么是Java agent技术,让我很吃惊...

    注:本文定义-在函数执行前后增加对应的逻辑的操作统称为MOCK. 引子 在某天与QA同学进行沟通时,发现QA同学有针对某个方法调用时,有让该方法停止一段时间的需求,我对这部分的功能实现非常好奇,因此决 ...

  5. 通过使用Byte Buddy,便捷地创建Java Agent

    Java agent是在另外一个Java应用("目标"应用)启动之前要执行的Java程序,这样agent就有机会修改目标应用或者应用所运行的环境.在本文中,我们将会从基础内容开始, ...

  6. idea git 过滤target_IDEA + maven 零基础构建 java agent 项目

    Java Agent(java 探针)虽说在 jdk1.5 之后就有了,但是对于绝大多数的业务开发 javaer 来说,这个东西还是比较神奇和陌生的:虽说在实际的业务开发中,很少会涉及到 agent ...

  7. Java agent初探

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群"获取公众号专属群聊入口 来源:阿里巴巴中间件 在本篇文章中,我会通过几个简单的程 ...

  8. java 字节码增强原理_深入浅出Java探针技术1--基于java agent的字节码增强案例

    Java agent又叫做Java 探针,本文将从以下四个问题出发来深入浅出了解下Java agent 一.什么是java agent? Java agent是在JDK1.5引入的,是一种可以动态修改 ...

  9. JVM插桩之二:Java agent基础原理

    Javaagent只要作用在class被加载之前对其加载,插入我们需要添加的字节码. Javaagent面向的是我们java程序员,而且agent都是用java编写的,不需要太多的c/c++编程基础, ...

最新文章

  1. Spring aop 实现异常拦截
  2. 在Eclipse中使用JUnit4进行单元测试(中级篇)
  3. 关于量子计算机论文,终于,科学家们找到了只有量子计算机才能解决的问题
  4. 实现数据排序的几种方法
  5. linux CentOS7最小化安装环境静默安装Oracle11GR2数据库(上传安装包并解压_05)
  6. es6分享——变量的解构赋值
  7. c语言程序设计行列式,新手作品:行列式计算C语言版
  8. python求最大值代码的方式_python使用分治法实现求解最大值的方法
  9. python数据库去重_python redis连接 有序集合去重的代码
  10. ServletConfig讲解
  11. TEST ON 平安夜
  12. 【ML小结3】线性回归与逻辑回归、softmax回归
  13. vue-methods三种调用的形势
  14. java swing高仿qq聊天_GitHub - sxei/myqq: Java版SWing“高”仿QQ即时通聊天系统
  15. 如何理解「朝闻道,夕死可矣」?
  16. 模拟卷Leetcode【普通】198. 打家劫舍
  17. dimission letter exmaple
  18. 2.4G无线芯片NRF24L01 驱动源码及详解
  19. 泰山OFFICE技术讲座:字符宽度、中文标宽、字符间距
  20. windows首次登录自动输入密码

热门文章

  1. 两个EXcel的关联表格进行合并详细步骤
  2. 深度解析:云计算三种服务模式
  3. 财务年终总结怎么写?技巧在这里
  4. ICMP----ping报文格式
  5. linux修改用户密码命令
  6. 单片机c语言怎么实现按键松开,灯还保持松开前的状态,单片机C语言程序设计:K1-K4按键状态显示...
  7. 人脸检测中现有的人脸数据库
  8. 【机器学习小常识】“分类” 与 “回归”的概念及区别详解
  9. 刨根问底Objective-C Runtime(1)- Self Super
  10. 网络渗透测试实验三——XSS和SQL注入