From dc507df175c615458deb976346208bb9310fcd16 Mon Sep 17 00:00:00 2001 From: hengyunabc Date: Wed, 10 Feb 2021 14:20:09 +0800 Subject: [PATCH] jad command support print line numbers. #1 #1695 --- .../core/command/klass100/JadCommand.java | 11 ++- .../taobao/arthas/core/util/Decompiler.java | 74 +++++++++++++++++-- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java index ce62103e0..3884b7683 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java @@ -21,6 +21,7 @@ import com.taobao.arthas.core.util.InstrumentationUtils; import com.taobao.arthas.core.util.SearchUtils; import com.taobao.arthas.core.util.affect.RowAffect; import com.taobao.middleware.cli.annotations.Argument; +import com.taobao.middleware.cli.annotations.DefaultValue; import com.taobao.middleware.cli.annotations.Description; import com.taobao.middleware.cli.annotations.Name; import com.taobao.middleware.cli.annotations.Option; @@ -58,6 +59,7 @@ public class JadCommand extends AnnotatedCommand { private String classLoaderClass; private boolean isRegEx = false; private boolean hideUnicode = false; + private boolean lineNumber; /** * jad output source code only @@ -107,6 +109,13 @@ public class JadCommand extends AnnotatedCommand { this.sourceOnly = sourceOnly; } + @Option(longName = "lineNumber") + @DefaultValue("true") + @Description("Output source code contins line number, default value true") + public void setLineNumber(boolean lineNumber) { + this.lineNumber = lineNumber; + } + @Override public void process(CommandProcess process) { RowAffect affect = new RowAffect(); @@ -168,7 +177,7 @@ public class JadCommand extends AnnotatedCommand { Map, File> classFiles = transformer.getDumpResult(); File classFile = classFiles.get(c); - String source = Decompiler.decompile(classFile.getAbsolutePath(), methodName, hideUnicode); + String source = Decompiler.decompile(classFile.getAbsolutePath(), methodName, hideUnicode, lineNumber); if (source != null) { source = pattern.matcher(source).replaceAll(""); } else { diff --git a/core/src/main/java/com/taobao/arthas/core/util/Decompiler.java b/core/src/main/java/com/taobao/arthas/core/util/Decompiler.java index 3707a90ba..07beba6c6 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/Decompiler.java +++ b/core/src/main/java/com/taobao/arthas/core/util/Decompiler.java @@ -5,9 +5,13 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NavigableMap; import org.benf.cfr.reader.api.CfrDriver; import org.benf.cfr.reader.api.OutputSinkFactory; +import org.benf.cfr.reader.api.SinkReturns.LineNumberMapping; /** * @@ -20,24 +24,31 @@ public class Decompiler { return decompile(classFilePath, methodName, false); } + public static String decompile(String classFilePath, String methodName, boolean hideUnicode) { + return decompile(classFilePath, methodName, hideUnicode, true); + } + /** * @param classFilePath * @param methodName * @param hideUnicode * @return */ - public static String decompile(String classFilePath, String methodName, boolean hideUnicode) { - final StringBuilder result = new StringBuilder(8192); + public static String decompile(String classFilePath, String methodName, boolean hideUnicode, + boolean printLineNumber) { + final StringBuilder sb = new StringBuilder(8192); + + final Map lineMapping = new HashMap(); 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.EXCEPTION_MESSAGE, SinkClass.LINE_NUMBER_MAPPING); } @Override - public Sink getSink(final SinkType sinkType, SinkClass sinkClass) { + public Sink getSink(final SinkType sinkType, final SinkClass sinkClass) { return new Sink() { @Override public void write(T sinkable) { @@ -45,7 +56,19 @@ public class Decompiler { if (sinkType == SinkType.PROGRESS) { return; } - result.append(sinkable); + 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); } }; } @@ -58,6 +81,7 @@ public class Decompiler { */ options.put("showversion", "false"); options.put("hideutf", String.valueOf(hideUnicode)); + options.put("trackbytecodeloc", "true"); if (!StringUtils.isBlank(methodName)) { options.put("methodname", methodName); } @@ -67,7 +91,45 @@ public class Decompiler { toAnalyse.add(classFilePath); driver.analyse(toAnalyse); - return result.toString(); + String result = sb.toString(); + if (printLineNumber && !lineMapping.isEmpty()) { + result = addLineNumber(result, lineMapping); + } + return result; + } + + private static String addLineNumber(String src, Map lineMapping) { + int maxLineNumber = 0; + for (Integer value : lineMapping.values()) { + if (value != null && value > maxLineNumber) { + maxLineNumber = value; + } + } + + String formatStr = "/*%2d*/ "; + String emptyStr = " "; + + StringBuilder sb = new StringBuilder(); + String[] lines = src.split("\\R"); + + if (maxLineNumber >= 100) { + formatStr = "/*%3d*/ "; + emptyStr = " "; + } else if (maxLineNumber >= 1000) { + formatStr = "/*%4d*/ "; + emptyStr = " "; + } + for (int i = 0; i < lines.length; ++i) { + Integer srcLineNumber = lineMapping.get(i + 1); + if (srcLineNumber != null) { + sb.append(String.format(formatStr, srcLineNumber)); + } else { + sb.append(emptyStr); + } + sb.append(lines[i]).append("\n"); + } + + return sb.toString(); } }