Commit 031582dd authored by Ahmed Ashour's avatar Ahmed Ashour Committed by skylot

feat(gui): show smali (#197) (PR #635)

parent 745c52e8
...@@ -13,7 +13,7 @@ dependencies { ...@@ -13,7 +13,7 @@ dependencies {
} }
compile 'com.google.guava:guava:27.1-jre' compile 'com.google.guava:guava:27.1-jre'
testCompile 'org.smali:baksmali:2.2.7' compile 'org.smali:baksmali:2.2.7'
testCompile 'org.apache.commons:commons-lang3:3.8.1' testCompile 'org.apache.commons:commons-lang3:3.8.1'
......
package jadx.api; package jadx.api;
import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.baksmali.Baksmali;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.util.IndentingWriter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -26,6 +44,8 @@ import jadx.core.dex.visitors.IDexTreeVisitor; ...@@ -26,6 +44,8 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode; import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject; import jadx.core.export.ExportGradleProject;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.DexFile;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.InputFile; import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser; import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver; import jadx.core.xmlgen.ResourcesSaver;
...@@ -290,6 +310,31 @@ public final class JadxDecompiler { ...@@ -290,6 +310,31 @@ public final class JadxDecompiler {
ProcessClass.process(cls, passes, true); ProcessClass.process(cls, passes, true);
} }
void generateSmali(ClassNode cls) {
Path path = cls.dex().getDexFile().getPath();
String className = cls.getAlias().makeRawFullName();
className = 'L' + className.replace('.', '/') + ';';
try (InputStream in = Files.newInputStream(path)) {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), Opcodes.getDefault());
boolean decompiled = false;
for (DexBackedClassDef classDef : dexFile.getClasses()) {
if (classDef.getType().equals(className)) {
ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef);
StringWriter sw = new StringWriter();
classDefinition.writeTo(new IndentingWriter(sw));
cls.setSmali(sw.toString());
decompiled = true;
break;
}
}
if (!decompiled) {
LOG.error("Failed to find smali class {}", className);
}
} catch (IOException e) {
LOG.error("Error generating smali", e);
}
}
RootNode getRoot() { RootNode getRoot() {
return root; return root;
} }
......
...@@ -64,6 +64,16 @@ public final class JavaClass implements JavaNode { ...@@ -64,6 +64,16 @@ public final class JavaClass implements JavaNode {
} }
} }
public synchronized String getSmali() {
if (decompiler == null) {
return null;
}
if (cls.getSmali() == null) {
decompiler.generateSmali(cls);
}
return cls.getSmali();
}
public synchronized void unload() { public synchronized void unload() {
cls.unload(); cls.unload();
} }
......
package jadx.core.dex.nodes; package jadx.core.dex.nodes;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.ClassData; import com.android.dex.ClassData;
import com.android.dex.ClassData.Field; import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method; import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef; import com.android.dex.ClassDef;
import com.android.dex.Dex; import com.android.dex.Dex;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.codegen.CodeWriter; import jadx.core.codegen.CodeWriter;
...@@ -35,8 +38,6 @@ import jadx.core.dex.nodes.parser.StaticValuesParser; ...@@ -35,8 +38,6 @@ import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
...@@ -53,6 +54,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { ...@@ -53,6 +54,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
// store decompiled code // store decompiled code
private CodeWriter code; private CodeWriter code;
// store smali
private String smali;
// store parent for inner classes or 'this' otherwise // store parent for inner classes or 'this' otherwise
private ClassNode parentClass; private ClassNode parentClass;
...@@ -482,6 +485,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { ...@@ -482,6 +485,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return code; return code;
} }
public void setSmali(String smali) {
this.smali = smali;
}
public String getSmali() {
return smali;
}
public ProcessState getState() { public ProcessState getState() {
return state; return state;
} }
......
package jadx.core.utils.files; package jadx.core.utils.files;
import java.nio.file.Path;
import com.android.dex.Dex; import com.android.dex.Dex;
public class DexFile { public class DexFile {
private final InputFile inputFile; private final InputFile inputFile;
private final String name; private final String name;
private final Dex dexBuf; private final Dex dexBuf;
private final Path path;
public DexFile(InputFile inputFile, String name, Dex dexBuf) { public DexFile(InputFile inputFile, String name, Dex dexBuf, Path path) {
this.inputFile = inputFile; this.inputFile = inputFile;
this.name = name; this.name = name;
this.dexBuf = dexBuf; this.dexBuf = dexBuf;
this.path = path;
} }
public String getName() { public String getName() {
...@@ -21,6 +25,10 @@ public class DexFile { ...@@ -21,6 +25,10 @@ public class DexFile {
return dexBuf; return dexBuf;
} }
public Path getPath() {
return path;
}
public InputFile getInputFile() { public InputFile getInputFile() {
return inputFile; return inputFile;
} }
......
...@@ -54,7 +54,7 @@ public class InputFile { ...@@ -54,7 +54,7 @@ public class InputFile {
String fileName = file.getName(); String fileName = file.getName();
if (fileName.endsWith(".dex")) { if (fileName.endsWith(".dex")) {
addDexFile(new Dex(file)); addDexFile(fileName, new Dex(file), file.toPath());
return; return;
} }
if (fileName.endsWith(".smali")) { if (fileName.endsWith(".smali")) {
...@@ -62,12 +62,12 @@ public class InputFile { ...@@ -62,12 +62,12 @@ public class InputFile {
SmaliOptions options = new SmaliOptions(); SmaliOptions options = new SmaliOptions();
options.outputDexFile = output.toAbsolutePath().toString(); options.outputDexFile = output.toAbsolutePath().toString();
Smali.assemble(options, file.getAbsolutePath()); Smali.assemble(options, file.getAbsolutePath());
addDexFile(new Dex(output.toFile())); addDexFile("", new Dex(output.toFile()), output);
return; return;
} }
if (fileName.endsWith(".class")) { if (fileName.endsWith(".class")) {
for (Dex dex : loadFromClassFile(file)) { for (Path path : loadFromClassFile(file)) {
addDexFile(dex); addDexFile(path);
} }
return; return;
} }
...@@ -81,8 +81,8 @@ public class InputFile { ...@@ -81,8 +81,8 @@ public class InputFile {
return; return;
} }
if (fileName.endsWith(".jar")) { if (fileName.endsWith(".jar")) {
for (Dex dex : loadFromJar(file.toPath())) { for (Path path : loadFromJar(file.toPath())) {
addDexFile(dex); addDexFile(path);
} }
return; return;
} }
...@@ -98,12 +98,16 @@ public class InputFile { ...@@ -98,12 +98,16 @@ public class InputFile {
LOG.warn("No dex files found in {}", file); LOG.warn("No dex files found in {}", file);
} }
private void addDexFile(Dex dexBuf) { private void addDexFile(Path path) throws IOException {
addDexFile("", dexBuf); addDexFile("", path);
} }
private void addDexFile(String fileName, Dex dexBuf) { private void addDexFile(String fileName, Path path) throws IOException {
dexFiles.add(new DexFile(this, fileName, dexBuf)); addDexFile(fileName, new Dex(Files.readAllBytes(path)), path);
}
private void addDexFile(String fileName, Dex dexBuf, Path path) {
dexFiles.add(new DexFile(this, fileName, dexBuf, path));
} }
private boolean loadFromZip(String ext) throws IOException, DecodeException { private boolean loadFromZip(String ext) throws IOException, DecodeException {
...@@ -125,9 +129,9 @@ public class InputFile { ...@@ -125,9 +129,9 @@ public class InputFile {
|| entryName.endsWith(instantRunDexSuffix)) { || entryName.endsWith(instantRunDexSuffix)) {
switch (ext) { switch (ext) {
case ".dex": case ".dex":
Dex dexBuf = makeDexBuf(entryName, inputStream); Path path = makeDexBuf(entryName, inputStream);
if (dexBuf != null) { if (path != null) {
addDexFile(entryName, dexBuf); addDexFile(entryName, path);
index++; index++;
} }
break; break;
...@@ -136,8 +140,8 @@ public class InputFile { ...@@ -136,8 +140,8 @@ public class InputFile {
index++; index++;
Path jarFile = FileUtils.createTempFile(entryName); Path jarFile = FileUtils.createTempFile(entryName);
Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING); Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING);
for (Dex dex : loadFromJar(jarFile)) { for (Path p : loadFromJar(jarFile)) {
addDexFile(entryName, dex); addDexFile(entryName, p);
} }
break; break;
...@@ -164,28 +168,26 @@ public class InputFile { ...@@ -164,28 +168,26 @@ public class InputFile {
} }
@Nullable @Nullable
private Dex makeDexBuf(String entryName, InputStream inputStream) { private Path makeDexBuf(String entryName, InputStream inputStream) {
try { try {
return new Dex(inputStream); Path path = FileUtils.createTempFile(".dex");
Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
return path;
} catch (Exception e) { } catch (Exception e) {
LOG.error("Failed to load file: {}, error: {}", entryName, e.getMessage(), e); LOG.error("Failed to load file: {}, error: {}", entryName, e.getMessage(), e);
return null; return null;
} }
} }
private static List<Dex> loadFromJar(Path jar) throws DecodeException { private static List<Path> loadFromJar(Path jar) throws DecodeException {
JavaToDex j2d = new JavaToDex(); JavaToDex j2d = new JavaToDex();
try { try {
LOG.info("converting to dex: {} ...", jar.getFileName()); LOG.info("converting to dex: {} ...", jar.getFileName());
List<byte[]> byteList = j2d.convert(jar); List<Path> pathList = j2d.convert(jar);
if (byteList.isEmpty()) { if (pathList.isEmpty()) {
throw new JadxException("Empty dx output"); throw new JadxException("Empty dx output");
} }
List<Dex> dexList = new ArrayList<>(byteList.size()); return pathList;
for (byte[] b : byteList) {
dexList.add(new Dex(b));
}
return dexList;
} catch (Exception e) { } catch (Exception e) {
throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e); throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e);
} finally { } finally {
...@@ -195,7 +197,7 @@ public class InputFile { ...@@ -195,7 +197,7 @@ public class InputFile {
} }
} }
private static List<Dex> loadFromClassFile(File file) throws IOException, DecodeException { private static List<Path> loadFromClassFile(File file) throws IOException, DecodeException {
Path outFile = FileUtils.createTempFile(".jar"); Path outFile = FileUtils.createTempFile(".jar");
try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(outFile))) { try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(outFile))) {
String clsName = AsmUtils.getNameFromClassFile(file); String clsName = AsmUtils.getNameFromClassFile(file);
......
...@@ -37,7 +37,7 @@ public class JavaToDex { ...@@ -37,7 +37,7 @@ public class JavaToDex {
private String dxErrors; private String dxErrors;
public List<byte[]> convert(Path jar) throws JadxException { public List<Path> convert(Path jar) throws JadxException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream(); try (ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream errOut = new ByteArrayOutputStream()) { ByteArrayOutputStream errOut = new ByteArrayOutputStream()) {
DxContext context = new DxContext(out, errOut); DxContext context = new DxContext(out, errOut);
...@@ -51,14 +51,14 @@ public class JavaToDex { ...@@ -51,14 +51,14 @@ public class JavaToDex {
if (result != 0) { if (result != 0) {
throw new JadxException("Java to dex conversion error, code: " + result); throw new JadxException("Java to dex conversion error, code: " + result);
} }
List<byte[]> list = new ArrayList<>(); List<Path> list = new ArrayList<>();
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) { try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
for (Path child : ds) { for (Path child : ds) {
list.add(Files.readAllBytes(child)); list.add(child);
Files.delete(child); child.toFile().deleteOnExit();
} }
} }
Files.delete(dir); dir.toFile().deleteOnExit();
return list; return list;
} catch (Exception e) { } catch (Exception e) {
throw new JadxException("dx exception: " + e.getMessage(), e); throw new JadxException("dx exception: " + e.getMessage(), e);
......
...@@ -82,6 +82,11 @@ public class JClass extends JLoadableNode { ...@@ -82,6 +82,11 @@ public class JClass extends JLoadableNode {
} }
@Override @Override
public String getSmali() {
return cls.getSmali();
}
@Override
public String getSyntaxName() { public String getSyntaxName() {
return SyntaxConstants.SYNTAX_STYLE_JAVA; return SyntaxConstants.SYNTAX_STYLE_JAVA;
} }
......
...@@ -28,6 +28,10 @@ public abstract class JNode extends DefaultMutableTreeNode { ...@@ -28,6 +28,10 @@ public abstract class JNode extends DefaultMutableTreeNode {
return null; return null;
} }
public String getSmali() {
return null;
}
public String getSyntaxName() { public String getSyntaxName() {
return SyntaxConstants.SYNTAX_STYLE_NONE; return SyntaxConstants.SYNTAX_STYLE_NONE;
} }
......
package jadx.gui.ui.codearea; package jadx.gui.ui.codearea;
import javax.swing.*; import java.awt.BorderLayout;
import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource; import jadx.gui.treemodel.JResource;
import jadx.gui.ui.ContentPanel; import jadx.gui.ui.ContentPanel;
import jadx.gui.ui.TabbedPane; import jadx.gui.ui.TabbedPane;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils; import jadx.gui.utils.Utils;
public final class CodePanel extends ContentPanel { public final class CodePanel extends ContentPanel {
...@@ -17,22 +22,36 @@ public final class CodePanel extends ContentPanel { ...@@ -17,22 +22,36 @@ public final class CodePanel extends ContentPanel {
private final SearchBar searchBar; private final SearchBar searchBar;
private final CodeArea codeArea; private final CodeArea codeArea;
private final JScrollPane scrollPane; private final SmaliArea smaliArea;
private final JScrollPane codeScrollPane;
private final JScrollPane smaliScrollPane;
private JTabbedPane areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
public CodePanel(TabbedPane panel, JNode jnode) { public CodePanel(TabbedPane panel, JNode jnode) {
super(panel, jnode); super(panel, jnode);
codeArea = new CodeArea(this); codeArea = new CodeArea(this);
smaliArea = new SmaliArea(this);
searchBar = new SearchBar(codeArea); searchBar = new SearchBar(codeArea);
scrollPane = new JScrollPane(codeArea); codeScrollPane = new JScrollPane(codeArea);
smaliScrollPane = new JScrollPane(smaliArea);
initLineNumbers(); initLineNumbers();
setLayout(new BorderLayout()); setLayout(new BorderLayout());
add(searchBar, BorderLayout.NORTH); add(searchBar, BorderLayout.NORTH);
add(scrollPane);
areaTabbedPane.add(codeScrollPane, NLS.str("tabs.code"));
areaTabbedPane.add(smaliScrollPane, NLS.str("tabs.smali"));
add(areaTabbedPane);
KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, Utils.ctrlButton()); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_F, Utils.ctrlButton());
Utils.addKeyBinding(codeArea, key, "SearchAction", new SearchAction()); Utils.addKeyBinding(codeArea, key, "SearchAction", new SearchAction());
areaTabbedPane.addChangeListener(e -> {
if (areaTabbedPane.getSelectedComponent() == smaliScrollPane) {
smaliArea.load();
}
});
} }
private void initLineNumbers() { private void initLineNumbers() {
...@@ -40,7 +59,7 @@ public final class CodePanel extends ContentPanel { ...@@ -40,7 +59,7 @@ public final class CodePanel extends ContentPanel {
if (codeArea.getDocument().getLength() <= 100_000) { if (codeArea.getDocument().getLength() <= 100_000) {
LineNumbers numbers = new LineNumbers(codeArea); LineNumbers numbers = new LineNumbers(codeArea);
numbers.setUseSourceLines(isUseSourceLines()); numbers.setUseSourceLines(isUseSourceLines());
scrollPane.setRowHeaderView(numbers); codeScrollPane.setRowHeaderView(numbers);
} }
} }
...@@ -89,7 +108,4 @@ public final class CodePanel extends ContentPanel { ...@@ -89,7 +108,4 @@ public final class CodePanel extends ContentPanel {
return codeArea; return codeArea;
} }
JScrollPane getScrollPane() {
return scrollPane;
}
} }
package jadx.gui.ui.codearea;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import jadx.gui.treemodel.JNode;
public final class SmaliArea extends RSyntaxTextArea {
private static final long serialVersionUID = 1334485631870306494L;
private final JNode node;
SmaliArea(CodePanel panel) {
node = panel.getNode();
setEditable(false);
}
void load() {
if (getText().isEmpty()) {
setText(node.getSmali());
}
}
}
...@@ -44,6 +44,8 @@ tabs.copy_class_name=Copy Name ...@@ -44,6 +44,8 @@ tabs.copy_class_name=Copy Name
tabs.close=Close tabs.close=Close
tabs.closeOthers=Close Others tabs.closeOthers=Close Others
tabs.closeAll=Close All tabs.closeAll=Close All
tabs.code=Code
tabs.smali=Smali
nav.back=Back nav.back=Back
nav.forward=Forward nav.forward=Forward
......
...@@ -44,6 +44,8 @@ tabs.copy_class_name=Copy Name ...@@ -44,6 +44,8 @@ tabs.copy_class_name=Copy Name
tabs.close=Cerrar tabs.close=Cerrar
tabs.closeOthers=Cerrar otros tabs.closeOthers=Cerrar otros
tabs.closeAll=Cerrar todo tabs.closeAll=Cerrar todo
#tabs.code=
#tabs.smali=
nav.back=Atrás nav.back=Atrás
nav.forward=Adelante nav.forward=Adelante
......
...@@ -44,6 +44,8 @@ tabs.copy_class_name=复制类名 ...@@ -44,6 +44,8 @@ tabs.copy_class_name=复制类名
tabs.close=关闭 tabs.close=关闭
tabs.closeOthers=关闭其他文件 tabs.closeOthers=关闭其他文件
tabs.closeAll=全部关闭 tabs.closeAll=全部关闭
#tabs.code=
#tabs.smali=
nav.back=后退 nav.back=后退
nav.forward=前进 nav.forward=前进
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment