📅  最后修改于: 2023-12-03 14:42:17.851000             🧑  作者: Mango
javap
是Java开发中一个非常重要的工具,它可以将class文件中的字节码反汇编成易于理解的文本形式,方便程序员进行代码阅读和调试。本文介绍如何利用Java自己创建一个类似的javap
工具。
public class Javap {
public static void main(String[] args) {
// 解析命令行参数
...
// 获取class文件字节码
...
// 反汇编并输出
...
}
}
使用args4j
库来解析命令行参数,它可以让我们非常方便地定义和解析命令行参数。下面是一个例子:
public class Javap {
@Option(name = "-v", usage = "Verbose mode")
private boolean verbose;
@Argument(index = 0, required = true, usage = "Class name")
private String className;
public static void main(String[] args) {
Javap javap = new Javap();
CmdLineParser parser = new CmdLineParser(javap);
try {
parser.parseArgument(args);
} catch (CmdLineException e) {
System.err.println(e.getMessage());
parser.printUsage(System.err);
System.exit(1);
}
// 保存命令行参数
javap.saveCommandLineArguments(args);
// ...
}
}
使用ClassLoader
来加载class文件,并利用java.lang.instrument.ClassDefinition
类将class文件的字节码转换成字节数组。下面是一个例子:
public class Javap {
private byte[] getClassBytes(String className) throws IOException {
Class<?> clazz = Class.forName(className);
URL classUrl = clazz.getResource('/' + className.replace('.', '/') + ".class");
URLConnection connection = classUrl.openConnection();
try (InputStream input = connection.getInputStream()) {
return IOUtils.toByteArray(input);
}
}
public static void main(String[] args) {
// ...
byte[] classBytes = javap.getClassBytes(javap.className);
// ...
}
}
使用Java提供的javax.tools.ToolProvider
类加载javax.tools.JavaCompiler
,并使用javax.tools.JavaFileManager
来提供反汇编服务。下面是一个例子:
public class Javap {
private void execute() throws Exception {
// ...
// 获取Java编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 获取Java文件管理器,用于提供反汇编服务
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
// 将class文件字节码转换成JavaFileObject对象
JavaFileObject fileObject = new SimpleJavaFileObject(URI.create(className.replaceAll("\\.", "/") + Kind.CLASS.extension), Kind.CLASS) {
@Override
public InputStream openInputStream() throws IOException {
return new ByteArrayInputStream(classBytes);
}
};
// 获取反汇编器
ForwardingJavaFileManager<JavaFileManager> manager = new ForwardingJavaFileManager<>(fileManager) {
@Override
public ClassLoader getClassLoader(Location location) {
return new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 反汇编后Java代码
String javaCode = getJavaCode(name);
if (javaCode != null) {
// 输出Java代码
System.out.println(javaCode);
}
return super.loadClass(name);
}
};
}
};
// 编译Java字符串(只有一个空类),触发反汇编
compiler.getTask(null, manager, null, null, null, Collections.singletonList(new JavaStringObject(className)), Collections.singletonList(fileObject)).call();
// ...
}
}
下面是完整的代码:
import com.sun.codemodel.internal.JExpr;
import com.sun.codemodel.internal.JExpression;
import com.sun.codemodel.internal.JFormatter;
import com.sun.codemodel.internal.JInvocation;
import com.sun.codemodel.internal.JOp;
import com.sun.codemodel.internal.JType;
import com.sun.codemodel.internal.JVar;
import com.sun.tools.javac.file.BaseFileObject;
import com.sun.tools.javac.util.ListBuffer;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import javax.lang.model.SourceVersion;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class Javap {
@Option(name = "-v", usage = "Verbose mode")
private boolean verbose;
@Argument(index = 0, required = true, usage = "Class name")
private String className;
private List<String> classpath = new ArrayList<>();
private List<String> commandLineArguments = new ArrayList<>();
private byte[] classBytes;
public static void main(String[] args) {
Javap javap = new Javap();
CmdLineParser parser = new CmdLineParser(javap);
try {
parser.parseArgument(args);
} catch (CmdLineException e) {
System.err.println(e.getMessage());
parser.printUsage(System.err);
System.exit(1);
}
// 保存命令行参数
javap.saveCommandLineArguments(args);
try {
// 获取class文件字节码
javap.classBytes = javap.getClassBytes(javap.className);
// 反汇编并输出Java代码
javap.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
private void saveCommandLineArguments(String[] args) {
Collections.addAll(commandLineArguments, args);
}
private void execute() throws Exception {
// 输出完整命令行参数
if (verbose) {
System.out.println(String.join(" ", commandLineArguments));
}
// 获取Java编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 获取Java文件管理器,用于提供反汇编服务
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
// 将class文件字节码转换成JavaFileObject对象
JavaFileObject fileObject = new SimpleJavaFileObject(URI.create(className.replaceAll("\\.", "/") + Kind.CLASS.extension), Kind.CLASS) {
@Override
public InputStream openInputStream() throws IOException {
return new ByteArrayInputStream(classBytes);
}
};
// 获取反汇编器
ForwardingJavaFileManager<JavaFileManager> manager = new ForwardingJavaFileManager<>(fileManager) {
@Override
public ClassLoader getClassLoader(Location location) {
return new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 反汇编后Java代码
String javaCode = getJavaCode(name);
if (javaCode != null) {
// 输出Java代码
System.out.println(javaCode);
}
return super.loadClass(name);
}
};
}
};
// 编译Java字符串(只有一个空类),触发反汇编
compiler.getTask(null, manager, null, null, null, Collections.singletonList(new JavaStringObject(className)), Collections.singletonList(fileObject)).call();
}
private byte[] getClassBytes(String className) throws IOException {
Class<?> clazz = Class.forName(className);
URL classUrl = clazz.getResource('/' + className.replace('.', '/') + ".class");
URLConnection connection = classUrl.openConnection();
try (InputStream input = connection.getInputStream()) {
return IOUtils.toByteArray(input);
}
}
private String getJavaCode(String name) throws ClassNotFoundException {
// 只反汇编指定类
if (!name.equals(className)) {
return null;
}
// 获取反汇编后Java代码的输出流
StringWriter out = new StringWriter();
PrintWriter writer = new PrintWriter(out);
// 设置反汇编选项
List<String> options = new ArrayList<>();
options.add("-p");
// 创建反汇编器
com.sun.tools.javap.Main.Result result = com.sun.tools.javap.Main.run(options.toArray(new String[0]), new String[]{className}, writer, writer);
writer.flush();
// 输出反汇编成功
if (resultOK(result, options)) {
return formatJavaCode(out.toString());
} else {
return null;
}
}
private boolean resultOK(com.sun.tools.javap.Main.Result result, List<String> options) {
if (result != com.sun.tools.javap.Main.Result.OK) {
if (verbose) {
System.err.printf("Error executing javap with options %s: %s%n", options, result);
}
return false;
} else {
return true;
}
}
private String formatJavaCode(String javaCode) {
StringBuilder sb = new StringBuilder();
sb.append("```java\n");
sb.append(javaCode);
sb.append("```");
return sb.toString();
}
private static class JavaStringObject extends BaseFileObject {
private final String name;
protected JavaStringObject(String className) {
super(URI.create(className.replaceAll("\\.", "/") + ".java"), Kind.SOURCE);
this.name = StringUtils.substringAfterLast(className, ".");
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return "public class " + name + " {}";
}
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(new byte[0]);
}
@Override
public OutputStream openOutputStream() {
return new OutputStream() {
@Override
public void write(int b) throws IOException {
// ignore
}
};
}
@Override
public String getName() {
return name;
}
}
private static class CmdLineParser extends PosixParser {
private final Object bean;
public CmdLineParser(Object bean) {
this.bean = bean;
}
public void parseArgument(String[] args) throws CmdLineException {
CommandLine cmd = this.parse(buildOptions(), args, false);
for (Field field : this.bean.getClass().getDeclaredFields()) {
Option option = field.getAnnotation(Option.class);
Argument argument = field.getAnnotation(Argument.class);
if (option != null) {
String name = field.getName();
name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
if (!StringUtils.isEmpty(option.name())) {
name = option.name();
}
Object value;
Class<?> type = field.getType();
if (type.equals(boolean.class)) {
value = cmd.hasOption(name);
} else {
value = cmd.getOptionValue(name);
}
try {
field.setAccessible(true);
field.set(this.bean, value);
} catch (IllegalAccessException e) {
throw new CmdLineException(e);
}
} else if (argument != null) {
String value = cmd.getArgs()[argument.index()];
try {
field.setAccessible(true);
field.set(this.bean, value);
} catch (IllegalAccessException e) {
throw new CmdLineException(e);
}
} else {
// ignore
}
}
}
public void printUsage(PrintWriter writer) {
HelpFormatter formatter = new HelpFormatter();
formatter.printUsage(writer, 80, "javap", buildOptions());
}
private Options buildOptions() {
Options options = new Options();
for (Field field : this.bean.getClass().getDeclaredFields()) {
Option option = field.getAnnotation(Option.class);
if (option != null) {
String name = field.getName();
name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
if (!StringUtils.isEmpty(option.name())) {
name = option.name();
}
String argName = option.usage().split(" ")[0];
options.addOption(name, !field.getType().equals(boolean.class), option.usage(), argName);
} else {
Argument argument = field.getAnnotation(Argument.class);
if (argument != null) {
options.addOption(null, true, argument.usage(), argument.metaVar());
} else {
// ignore
}
}
}
return options;
}
}
}
编译Javap.java,并在命令行中运行:
java Javap com.example.TestClass
将会输出反汇编后的Java代码。
javap
是Java开发中一个非常实用的工具,通过Java创建自己的javap工具,不仅可以对字节码进行反汇编,而且还可以灵活地处理反汇编后的Java代码,为代码阅读和调试提供了便利。