未验证 提交 947b6217 编写于 作者: S Skylot

feat: add option to use dx/d8 for convert java bytecode (#1299)

上级 4cc00bda
......@@ -101,6 +101,7 @@ options:
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
......
......@@ -7,6 +7,7 @@ dependencies {
runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.81'
......
......@@ -125,6 +125,9 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
protected boolean useDx = false;
@Parameter(
names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user_only, none",
......@@ -231,6 +234,7 @@ public class JadxCLIArgs {
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
return args;
}
......@@ -266,6 +270,10 @@ public class JadxCLIArgs {
return fallbackMode;
}
public boolean isUseDx() {
return useDx;
}
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
......
......@@ -39,6 +39,7 @@ public class ConvertToClsSet {
Path output = inputPaths.remove(0);
JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
List<ILoadResult> loadedInputs = new ArrayList<>();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
......
......@@ -85,6 +85,8 @@ public class JadxArgs {
private CommentsLevel commentsLevel = CommentsLevel.INFO;
private boolean useDxInput = false;
public JadxArgs() {
// use default options
}
......@@ -423,6 +425,14 @@ public class JadxArgs {
this.commentsLevel = commentsLevel;
}
public boolean isUseDxInput() {
return useDxInput;
}
public void setUseDxInput(boolean useDxInput) {
this.useDxInput = useDxInput;
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
......@@ -454,6 +464,7 @@ public class JadxArgs {
+ ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ '}';
}
}
......@@ -107,6 +107,7 @@ public final class JadxDecompiler implements Closeable {
reset();
JadxArgsValidator.validate(args);
LOG.info("loading ...");
loadPlugins(args);
loadInputFiles();
root = new RootNode(args);
......@@ -159,6 +160,15 @@ public final class JadxDecompiler implements Closeable {
reset();
}
private void loadPlugins(JadxArgs args) {
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
pluginManager.load();
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
p -> p.getPluginInfo().getPluginId()));
}
}
public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin);
}
......
......@@ -185,14 +185,11 @@ public abstract class IntegrationTest extends TestUtils {
protected JadxDecompiler loadFiles(List<File> inputFiles) {
args.setInputFiles(inputFiles);
boolean useDx = !isJavaInput();
LOG.info(useDx ? "Using dex input" : "Using java input");
args.setUseDxInput(useDx);
JadxDecompiler d = new JadxDecompiler(args);
if (isJavaInput()) {
d.getPluginManager().unload("java-convert");
LOG.info("Using java input");
} else {
d.getPluginManager().unload("java-input");
LOG.info("Using dex input");
}
try {
d.load();
} catch (Exception e) {
......
......@@ -51,7 +51,6 @@ public abstract class BaseExternalTest extends IntegrationTest {
protected JadxDecompiler decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.getPluginManager().unload("java-convert");
jadx.load();
if (clsPatternStr == null) {
......
......@@ -278,6 +278,10 @@ public class JadxSettings extends JadxCLIArgs {
this.fallbackMode = fallbackMode;
}
public void setUseDx(boolean useDx) {
this.useDx = useDx;
}
public void setSkipResources(boolean skipResources) {
this.skipResources = skipResources;
}
......
......@@ -412,6 +412,13 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
JCheckBox useDx = new JCheckBox();
useDx.setSelected(settings.isUseDx());
useDx.addItemListener(e -> {
settings.setUseDx(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox showInconsistentCode = new JCheckBox();
showInconsistentCode.setSelected(settings.isShowInconsistentCode());
showInconsistentCode.addItemListener(e -> {
......@@ -522,6 +529,7 @@ public class JadxSettingsWindow extends JDialog {
other.addRow(NLS.str("preferences.inlineMethods"), inlineMethods);
other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive);
other.addRow(NLS.str("preferences.fallback"), fallback);
other.addRow(NLS.str("preferences.useDx"), useDx);
other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode);
other.addRow(NLS.str("preferences.commentsLevel"), commentsLevel);
return other;
......
......@@ -124,6 +124,7 @@ preferences.language=Sprache
preferences.lineNumbersMode=Editor Zeilennummern-Modus
preferences.check_for_updates=Nach Updates beim Start suchen
preferences.fallback=Zwischencode ausgeben (einfacher Speicherauszug)
#preferences.useDx=Use dx/d8 to convert java bytecode
preferences.showInconsistentCode=Inkonsistenten Code anzeigen
preferences.escapeUnicode=Unicodezeichen escapen
preferences.replaceConsts=Konstanten ersetzen
......
......@@ -124,6 +124,7 @@ preferences.language=Language
preferences.lineNumbersMode=Editor line numbers mode
preferences.check_for_updates=Check for updates on startup
preferences.fallback=Fallback mode (simple dump)
preferences.useDx=Use dx/d8 to convert java bytecode
preferences.showInconsistentCode=Show inconsistent code
preferences.escapeUnicode=Escape unicode
preferences.replaceConsts=Replace constants
......
......@@ -124,6 +124,7 @@ preferences.language=Idioma
#preferences.lineNumbersMode=Editor line numbers mode
preferences.check_for_updates=Buscar actualizaciones al iniciar
preferences.fallback=Modo fallback (simple dump)
#preferences.useDx=Use dx/d8 to convert java bytecode
preferences.showInconsistentCode=Mostrar código inconsistente
preferences.escapeUnicode=Escape unicode
preferences.replaceConsts=Reemplazar constantes
......
......@@ -124,6 +124,7 @@ preferences.language=언어
preferences.lineNumbersMode=편집기 줄 번호 모드
preferences.check_for_updates=시작시 업데이트 확인
preferences.fallback=대체 모드 (단순 덤프)
#preferences.useDx=Use dx/d8 to convert java bytecode
preferences.showInconsistentCode=디컴파일 안된 코드 표시
preferences.escapeUnicode=유니코드 이스케이프
preferences.replaceConsts=상수 바꾸기
......
......@@ -124,6 +124,7 @@ preferences.language=语言
preferences.lineNumbersMode=编辑器行号模式
preferences.check_for_updates=启动时检查更新
preferences.fallback=输出中间代码
#preferences.useDx=Use dx/d8 to convert java bytecode
preferences.showInconsistentCode=显示不一致的代码
preferences.escapeUnicode=将 Unicode 字符转义
preferences.replaceConsts=替换常量
......
......@@ -6,7 +6,7 @@ dependencies {
api(project(":jadx-plugins:jadx-plugins-api"))
implementation(project(":jadx-plugins:jadx-dex-input"))
implementation(files('lib/dx-1.16.jar'))
implementation('com.jakewharton.android.repackaged:dalvik-dx:11.0.0_r3')
implementation('com.android.tools:r8:3.0.73')
implementation 'org.ow2.asm:asm:9.2'
......
......@@ -159,7 +159,7 @@ public class JavaConvertLoader {
try {
DxConverter.run(path, tempDirectory);
} catch (Exception e) {
LOG.warn("DX convert failed, trying D8");
LOG.warn("DX convert failed, trying D8, path: {}", path);
D8Converter.run(path, tempDirectory);
}
......
......@@ -13,7 +13,11 @@ public class JavaConvertPlugin implements JadxInputPlugin {
@Override
public JadxPluginInfo getPluginInfo() {
return new JadxPluginInfo("java-convert", "JavaConvert", "Convert .jar and .class files to dex");
return new JadxPluginInfo(
"java-convert",
"JavaConvert",
"Convert .jar and .class files to dex",
"java-input");
}
@Override
......
......@@ -5,10 +5,20 @@ public class JadxPluginInfo {
private final String name;
private final String description;
/**
* Conflicting plugins should have same 'provides' property, only one will be loaded
*/
private final String provides;
public JadxPluginInfo(String id, String name, String description) {
this.pluginId = id;
this(id, name, description, id);
}
public JadxPluginInfo(String pluginId, String name, String description, String provides) {
this.pluginId = pluginId;
this.name = name;
this.description = description;
this.provides = provides;
}
public String getPluginId() {
......@@ -23,8 +33,12 @@ public class JadxPluginInfo {
return description;
}
public String getProvides() {
return provides;
}
@Override
public String toString() {
return name + " - '" + description + '\'';
return pluginId + ": " + name + " - '" + description + '\'';
}
}
package jadx.api.plugins;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -16,40 +20,142 @@ import jadx.api.plugins.input.JadxInputPlugin;
public class JadxPluginManager {
private static final Logger LOG = LoggerFactory.getLogger(JadxPluginManager.class);
private final Map<Class<? extends JadxPlugin>, JadxPlugin> allPlugins = new HashMap<>();
private final Set<PluginData> allPlugins = new TreeSet<>();
private final Map<String, String> provideSuggestions = new TreeMap<>();
private List<JadxPlugin> resolvedPlugins = Collections.emptyList();
public JadxPluginManager() {
}
/**
* Add suggestion how to resolve conflicting plugins
*/
public void providesSuggestion(String provides, String pluginId) {
provideSuggestions.put(provides, pluginId);
}
public void load() {
ServiceLoader<JadxPlugin> jadxPlugins = ServiceLoader.load(JadxPlugin.class);
for (JadxPlugin jadxPlugin : jadxPlugins) {
register(jadxPlugin);
for (JadxPlugin plugin : jadxPlugins) {
addPlugin(plugin);
}
resolve();
}
public void register(JadxPlugin plugin) {
Objects.requireNonNull(plugin);
LOG.debug("Register plugin: {}", plugin.getPluginInfo().getPluginId());
allPlugins.put(plugin.getClass(), plugin);
PluginData addedPlugin = addPlugin(plugin);
LOG.debug("Register plugin: {}", addedPlugin.getPluginId());
resolve();
}
private PluginData addPlugin(JadxPlugin plugin) {
PluginData pluginData = new PluginData(plugin, plugin.getPluginInfo());
if (!allPlugins.add(pluginData)) {
throw new IllegalArgumentException("Duplicate plugin id: " + pluginData + ", class " + plugin.getClass());
}
return pluginData;
}
public boolean unload(String pluginId) {
return allPlugins.values().removeIf(p -> {
String id = p.getPluginInfo().getPluginId();
boolean result = allPlugins.removeIf(pd -> {
String id = pd.getPluginId();
boolean match = id.equals(pluginId);
if (match) {
LOG.debug("Unload plugin: {}", id);
}
return match;
});
resolve();
return result;
}
public List<JadxPlugin> getAllPlugins() {
return new ArrayList<>(allPlugins.values());
return allPlugins.stream().map(PluginData::getPlugin).collect(Collectors.toList());
}
public List<JadxPlugin> getResolvedPlugins() {
return Collections.unmodifiableList(resolvedPlugins);
}
public List<JadxInputPlugin> getInputPlugins() {
return allPlugins.values().stream()
return resolvedPlugins.stream()
.filter(JadxInputPlugin.class::isInstance)
.map(JadxInputPlugin.class::cast)
.collect(Collectors.toList());
}
private synchronized void resolve() {
Map<String, List<PluginData>> provides = allPlugins.stream()
.collect(Collectors.groupingBy(p -> p.getInfo().getProvides()));
List<PluginData> result = new ArrayList<>(provides.size());
provides.forEach((provide, list) -> {
if (list.size() == 1) {
result.add(list.get(0));
} else {
String suggestion = provideSuggestions.get(provide);
if (suggestion != null) {
list.stream().filter(p -> p.getPluginId().equals(suggestion))
.findFirst()
.ifPresent(result::add);
} else {
PluginData selected = list.get(0);
result.add(selected);
LOG.debug("Select providing '{}' plugin '{}', candidates: {}", provide, selected, list);
}
}
});
Collections.sort(result);
resolvedPlugins = result.stream().map(PluginData::getPlugin).collect(Collectors.toList());
}
private static final class PluginData implements Comparable<PluginData> {
private final JadxPlugin plugin;
private final JadxPluginInfo info;
private PluginData(JadxPlugin plugin, JadxPluginInfo info) {
this.plugin = plugin;
this.info = info;
}
public JadxPlugin getPlugin() {
return plugin;
}
public JadxPluginInfo getInfo() {
return info;
}
public String getPluginId() {
return info.getPluginId();
}
@Override
public int compareTo(@NotNull JadxPluginManager.PluginData o) {
return this.info.getPluginId().compareTo(o.info.getPluginId());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PluginData)) {
return false;
}
PluginData that = (PluginData) o;
return getInfo().getPluginId().equals(that.getInfo().getPluginId());
}
@Override
public int hashCode() {
return info.getPluginId().hashCode();
}
@Override
public String toString() {
return info.getPluginId();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册