Arthas支持通过类相关的操作命令,包括sc、sm、jad等。
sc(Search-Class)
命令搜索出所有已经加载到 JVM 中的 Class 信息。
sm(Search-Method)
命令搜索出所有已经加载了 Class 信息的方法信息。
jad
命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码。
这篇文章是分析上述类相关操作的底层实现原理包含Instrumentation和CFR。
public class SearchClassCommand extends AnnotatedCommand {
public void process(final CommandProcess process) {
// 1、核心是获取Instrumentation对象
Instrumentation inst = process.session().getInstrumentation();
// 2、SearchUtils负责查找对应的class
List<Class<?>> matchedClasses = new ArrayList<Class<?>>(SearchUtils.searchClass(inst, classPattern, isRegEx, hashCode));
// 3、对查询结果进行下排序
Collections.sort(matchedClasses, new Comparator<Class<?>>() {
@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<Class<?>> searchClass(Instrumentation inst, Matcher<String> classNameMatcher, int limit) {
if (classNameMatcher == null) {
return Collections.emptySet();
}
final Set<Class<?>> matches = new HashSet<Class<?>>();
// 通过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来实现类的查找。
public class SearchMethodCommand extends AnnotatedCommand {
public void process(CommandProcess process) {
RowAffect affect = new RowAffect();
Instrumentation inst = process.session().getInstrumentation();
Matcher<String> methodNameMatcher = methodNameMatcher();
// 1、通过Instrumentation查找对应的类
Set<Class<?>> matchedClasses = SearchUtils.searchClass(inst, classPattern, isRegEx, hashCode);
for (Class<?> clazz : matchedClasses) {
try {
// 2、遍历类的构造函数
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (!methodNameMatcher.matching("<init>")) {
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命令其次通过反射查找对应的方法。
public class JadCommand extends AnnotatedCommand {
public void process(CommandProcess process) {
RowAffect affect = new RowAffect();
Instrumentation inst = process.session().getInstrumentation();
// 1、通过Instrumentation查找对应的类
Set<Class<?>> 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<Class<?>> 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<Class<?>> matchedClasses, Set<Class<?>> withInnerClasses) {
Class<?> c = matchedClasses.iterator().next();
Set<Class<?>> allClasses = new HashSet<Class<?>>(withInnerClasses);
allClasses.add(c);
try {
// 1、创建ClassDumpTransformer对象
ClassDumpTransformer transformer = new ClassDumpTransformer(allClasses);
// 2、执行retransformClasses收集待反编译的类文件
InstrumentationUtils.retransformClasses(inst, transformer, allClasses);
Map<Class<?>, File> classFiles = transformer.getDumpResult();
File classFile = classFiles.get(c);
// 3、执行反编译的动作
Pair<String,NavigableMap<Integer,Integer>> 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<Class<?>> classesToEnhance;
private Map<Class<?>, File> dumpResult;
private File arthasLogHome;
private File directory;
public ClassDumpTransformer(Set<Class<?>> classesToEnhance) {
this(classesToEnhance, null);
}
public ClassDumpTransformer(Set<Class<?>> classesToEnhance, File directory) {
this.classesToEnhance = classesToEnhance;
this.dumpResult = new HashMap<Class<?>, 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<Class<?>, 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<Class<?>> 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<String, NavigableMap<Integer, Integer>> decompileWithMappings(String classFilePath,
String methodName, boolean hideUnicode, boolean printLineNumber) {
final StringBuilder sb = new StringBuilder(8192);
final NavigableMap<Integer, Integer> lineMapping = new TreeMap<Integer, Integer>();
OutputSinkFactory mySink = new OutputSinkFactory() {
@Override
public List<SinkClass> getSupportedSinks(SinkType sinkType, Collection<SinkClass> collection) {
return Arrays.asList(SinkClass.STRING, SinkClass.DECOMPILED, SinkClass.DECOMPILED_MULTIVER,
SinkClass.EXCEPTION_MESSAGE, SinkClass.LINE_NUMBER_MAPPING);
}
@Override
public <T> Sink<T> getSink(final SinkType sinkType, final SinkClass sinkClass) {
return new Sink<T>() {
@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<Integer, Integer> classFileMappings = mapping.getClassFileMappings();
NavigableMap<Integer, Integer> mappings = mapping.getMappings();
if (classFileMappings != null && mappings != null) {
for (Entry<Integer, Integer> entry : mappings.entrySet()) {
Integer srcLineNumber = classFileMappings.get(entry.getKey());
lineMapping.put(entry.getValue(), srcLineNumber);
}
}
return;
}
sb.append(sinkable);
}
};
}
};
HashMap<String, String> options = new HashMap<String, String>();
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<String> toAnalyse = new ArrayList<String>();
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 --获取目标进程已加载的所有类
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_21383435/article/details/123977626
内容来源于网络,如有侵权,请联系作者删除!