系列
- Arthas入门篇
- Arthas功能介绍
- Arthas 启动过程分析
- Arthas使用Idea调试
- Arthas Command处理流程
- Arthas类查找和反编译原理
- Arthas内存动态编译原理
- Arthas动态重新加载类
- Arthas导出加载类
开篇
- Arthas支持通过类相关的操作命令,包括sc、sm、jad等。
- sc(Search-Class)命令搜索出所有已经加载到 JVM 中的 Class 信息。
- sm(Search-Method)命令搜索出所有已经加载了 Class 信息的方法信息。
- jad命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码。
这篇文章是分析上述类相关操作的底层实现原理包含Instrumentation和CFR。
SC实现原理
public class SearchClassCommand extends AnnotatedCommand {
public void process(final CommandProcess process) {
// 1、核心是获取Instrumentation对象
Instrumentation inst = process.session().getInstrumentation();
// 2、SearchUtils负责查找对应的class
List> matchedClasses = new ArrayList>(SearchUtils.searchClass(inst, classPattern, isRegEx, hashCode));
// 3、对查询结果进行下排序
Collections.sort(matchedClasses, new Comparator>() {
@Override
public int compare(Class> c1, Class> c2) {
return StringUtils.classname(c1).compareTo(StringUtils.classname(c2));
}
});
affect.rCnt(matchedClasses.size());
process.appendResult(new RowAffectModel(affect));
process.end();
}
}
public class SearchUtils {
public static Set> searchClass(Instrumentation inst, Matcher classNameMatcher, int limit) {
if (classNameMatcher == null) {
return Collections.emptySet();
}
final Set> matches = new HashSet>();
// 通过inst.getAllLoadedClasses获取所有加载的类
for (Class> clazz : inst.getAllLoadedClasses()) {
if (classNameMatcher.matching(clazz.getName())) {
matches.add(clazz);
}
if (matches.size() >= limit) {
break;
}
}
return matches;
}
- java Instrumentation指的是可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序。这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等。
- Arthas的SC命令就是通过Instrumentation的getAllLoadedClasses来实现类的查找。
SM实现原理
public class SearchMethodCommand extends AnnotatedCommand {
public void process(CommandProcess process) {
RowAffect affect = new RowAffect();
Instrumentation inst = process.session().getInstrumentation();
Matcher methodNameMatcher = methodNameMatcher();
// 1、通过Instrumentation查找对应的类
Set> matchedClasses = SearchUtils.searchClass(inst, classPattern, isRegEx, hashCode);
for (Class> clazz : matchedClasses) {
try {
// 2、遍历类的构造函数
for (Constructor> constructor : clazz.getDeclaredConstructors()) {
if (!methodNameMatcher.matching("")) {
continue;
}
MethodVO methodInfo = ClassUtils.createMethodInfo(constructor, clazz, isDetail);
process.appendResult(new SearchMethodModel(methodInfo, isDetail));
affect.rCnt(1);
}
// 3、遍历所有的方法
for (Method method : clazz.getDeclaredMethods()) {
if (!methodNameMatcher.matching(method.getName())) {
continue;
}
MethodVO methodInfo = ClassUtils.createMethodInfo(method, clazz, isDetail);
process.appendResult(new SearchMethodModel(methodInfo, isDetail));
affect.rCnt(1);
}
} catch (Error e) {
}
}
process.appendResult(new RowAffectModel(affect));
process.end();
}
}
- Arthas的SM命令首先通过Instrumentation的getAllLoadedClasses来实现类的查找。
- Arthas的SM命令其次通过反射查找对应的方法。
JAD实现原理
public class JadCommand extends AnnotatedCommand {
public void process(CommandProcess process) {
RowAffect affect = new RowAffect();
Instrumentation inst = process.session().getInstrumentation();
// 1、通过Instrumentation查找对应的类
Set> matchedClasses = SearchUtils.searchClassOnly(inst, classPattern, isRegEx, code);
try {
ExitStatus status = null;
if (matchedClasses == null || matchedClasses.isEmpty()) {
status = processNoMatch(process);
} else if (matchedClasses.size() > 1) {
status = processMatches(process, matchedClasses);
} else { // matchedClasses size is 1
// find inner classes.
Set> withInnerClasses = SearchUtils.searchClassOnly(inst, matchedClasses.iterator().next().getName() + "$*", false, code);
if(withInnerClasses.isEmpty()) {
withInnerClasses = matchedClasses;
}
// 2、执行类的反编译核心操作
status = processExactMatch(process, affect, inst, matchedClasses, withInnerClasses);
}
if (!this.sourceOnly) {
process.appendResult(new RowAffectModel(affect));
}
CommandUtils.end(process, status);
} catch (Throwable e){
}
}
private ExitStatus processExactMatch(CommandProcess process, RowAffect affect, Instrumentation inst, Set> matchedClasses, Set> withInnerClasses) {
Class> c = matchedClasses.iterator().next();
Set> allClasses = new HashSet>(withInnerClasses);
allClasses.add(c);
try {
// 1、创建ClassDumpTransformer对象
ClassDumpTransformer transformer = new ClassDumpTransformer(allClasses);
// 2、执行retransformClasses收集待反编译的类文件
InstrumentationUtils.retransformClasses(inst, transformer, allClasses);
Map, File> classFiles = transformer.getDumpResult();
File classFile = classFiles.get(c);
// 3、执行反编译的动作
Pair> decompileResult = Decompiler.decompileWithMappings(classFile.getAbsolutePath(), methodName, hideUnicode, lineNumber);
// 省略无关代码
return ExitStatus.success();
} catch (Throwable t) {
}
}
}
- 通过Instrumentation的getAllLoadedClasses来实现类的查找。
- 创建ClassDumpTransformer并通过retransformClasses保存原始字节码。
- 通过decompileWithMappings实现字节码的反编译。
class ClassDumpTransformer implements ClassFileTransformer {
private Set> classesToEnhance;
private Map, File> dumpResult;
private File arthasLogHome;
private File directory;
public ClassDumpTransformer(Set> classesToEnhance) {
this(classesToEnhance, null);
}
public ClassDumpTransformer(Set> classesToEnhance, File directory) {
this.classesToEnhance = classesToEnhance;
this.dumpResult = new HashMap, File>();
this.arthasLogHome = new File(LogUtil.loggingDir());
this.directory = directory;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
if (classesToEnhance.contains(classBeingRedefined)) {
dumpClassIfNecessary(classBeingRedefined, classfileBuffer);
}
return null;
}
public Map, File> getDumpResult() {
return dumpResult;
}
private void dumpClassIfNecessary(Class> clazz, byte[] data) {
String className = clazz.getName();
ClassLoader classLoader = clazz.getClassLoader();
String classDumpDir = "classdump";
// 创建类所在的包路径
File dumpDir = null;
if (directory != null) {
dumpDir = directory;
} else {
dumpDir = new File(arthasLogHome, classDumpDir);
}
if (!dumpDir.mkdirs() && !dumpDir.exists()) {
logger.warn("create dump directory:{} failed.", dumpDir.getAbsolutePath());
return;
}
String fileName;
if (classLoader != null) {
fileName = classLoader.getClass().getName() + "-" + Integer.toHexString(classLoader.hashCode()) +
File.separator + className.replace(".", File.separator) + ".class";
} else {
fileName = className.replace(".", File.separator) + ".class";
}
File dumpClassFile = new File(dumpDir, fileName);
// 将类字节码写入文件
try {
FileUtils.writeByteArrayToFile(dumpClassFile, data);
dumpResult.put(clazz, dumpClassFile);
} catch (IOException e) {
}
}
}
- ClassDumpTransformer是自定义的Transformer对象,retransformClasses会进行调用。
- ClassDumpTransformer的dumpClassIfNecessary负责保存原始的字节码。
public class InstrumentationUtils {
public static void retransformClasses(Instrumentation inst, ClassFileTransformer transformer,
Set> classes) {
try {
inst.addTransformer(transformer, true);
for (Class> clazz : classes) {
try {
inst.retransformClasses(clazz);
} catch (Throwable e) {
String errorMsg = "retransformClasses class error, name: " + clazz.getName();
logger.error(errorMsg, e);
}
}
} finally {
inst.removeTransformer(transformer);
}
}
}
- InstrumentationUtils负责执行原始字节码的收集,通过retransformClasses来实现。
public class Decompiler {
public static Pair> decompileWithMappings(String classFilePath,
String methodName, boolean hideUnicode, boolean printLineNumber) {
final StringBuilder sb = new StringBuilder(8192);
final NavigableMap lineMapping = new TreeMap();
OutputSinkFactory mySink = new OutputSinkFactory() {
@Override
public List getSupportedSinks(SinkType sinkType, Collection collection) {
return Arrays.asList(SinkClass.STRING, SinkClass.DECOMPILED, SinkClass.DECOMPILED_MULTIVER,
SinkClass.EXCEPTION_MESSAGE, SinkClass.LINE_NUMBER_MAPPING);
}
@Override
public Sink getSink(final SinkType sinkType, final SinkClass sinkClass) {
return new Sink() {
@Override
public void write(T sinkable) {
// skip message like: Analysing type demo.MathGame
if (sinkType == SinkType.PROGRESS) {
return;
}
if (sinkType == SinkType.LINENUMBER) {
LineNumberMapping mapping = (LineNumberMapping) sinkable;
NavigableMap classFileMappings = mapping.getClassFileMappings();
NavigableMap mappings = mapping.getMappings();
if (classFileMappings != null && mappings != null) {
for (Entry entry : mappings.entrySet()) {
Integer srcLineNumber = classFileMappings.get(entry.getKey());
lineMapping.put(entry.getValue(), srcLineNumber);
}
}
return;
}
sb.append(sinkable);
}
};
}
};
HashMap options = new HashMap();
options.put("showversion", "false");
options.put("hideutf", String.valueOf(hideUnicode));
options.put("trackbytecodeloc", "true");
if (!StringUtils.isBlank(methodName)) {
options.put("methodname", methodName);
}
CfrDriver driver = new CfrDriver.Builder().withOptions(options).withOutputSink(mySink).build();
List toAnalyse = new ArrayList();
toAnalyse.add(classFilePath);
driver.analyse(toAnalyse);
String resultCode = sb.toString();
if (printLineNumber && !lineMapping.isEmpty()) {
resultCode = addLineNumber(resultCode, lineMapping);
}
return Pair.make(resultCode, lineMapping);
}
}
- 通过CFR-api实现字节码的反编译。
总结
- 类的查询或方法的查找基于Instrumentation来实现的。
- 类反编译是基于CFR来实现的,提供CFR-api实现。
- 认识 JavaAgent --获取目标进程已加载的所有类