mirror of
https://github.com/skylot/jadx.git
synced 2025-02-23 06:20:50 +00:00
feat: add option to use dx/d8 for convert java bytecode (#1299)
This commit is contained in:
parent
4cc00bdaf2
commit
947b621733
@ -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'
|
||||
|
Binary file not shown.
@ -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 + '\'';
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user