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 {
}
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'
......
package jadx.api;
import java.io.BufferedWriter;
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.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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.LoggerFactory;
......@@ -26,6 +44,8 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
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.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
......@@ -290,6 +310,31 @@ public final class JadxDecompiler {
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() {
return root;
}
......
......@@ -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() {
cls.unload();
}
......
package jadx.core.dex.nodes;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
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.Field;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
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.codegen.CodeWriter;
......@@ -35,8 +38,6 @@ import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
......@@ -53,6 +54,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
// store decompiled code
private CodeWriter code;
// store smali
private String smali;
// store parent for inner classes or 'this' otherwise
private ClassNode parentClass;
......@@ -482,6 +485,14 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
return code;
}
public void setSmali(String smali) {
this.smali = smali;
}
public String getSmali() {
return smali;
}
public ProcessState getState() {
return state;
}
......
package jadx.core.utils.files;
import java.nio.file.Path;
import com.android.dex.Dex;
public class DexFile {
private final InputFile inputFile;
private final String name;
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.name = name;
this.dexBuf = dexBuf;
this.path = path;
}
public String getName() {
......@@ -21,6 +25,10 @@ public class DexFile {
return dexBuf;
}
public Path getPath() {
return path;
}
public InputFile getInputFile() {
return inputFile;
}
......
......@@ -54,7 +54,7 @@ public class InputFile {
String fileName = file.getName();
if (fileName.endsWith(".dex")) {
addDexFile(new Dex(file));
addDexFile(fileName, new Dex(file), file.toPath());
return;
}
if (fileName.endsWith(".smali")) {
......@@ -62,12 +62,12 @@ public class InputFile {
SmaliOptions options = new SmaliOptions();
options.outputDexFile = output.toAbsolutePath().toString();
Smali.assemble(options, file.getAbsolutePath());
addDexFile(new Dex(output.toFile()));
addDexFile("", new Dex(output.toFile()), output);
return;
}
if (fileName.endsWith(".class")) {
for (Dex dex : loadFromClassFile(file)) {
addDexFile(dex);
for (Path path : loadFromClassFile(file)) {
addDexFile(path);
}
return;
}
......@@ -81,8 +81,8 @@ public class InputFile {
return;
}
if (fileName.endsWith(".jar")) {
for (Dex dex : loadFromJar(file.toPath())) {
addDexFile(dex);
for (Path path : loadFromJar(file.toPath())) {
addDexFile(path);
}
return;
}
......@@ -98,12 +98,16 @@ public class InputFile {
LOG.warn("No dex files found in {}", file);
}
private void addDexFile(Dex dexBuf) {
addDexFile("", dexBuf);
private void addDexFile(Path path) throws IOException {
addDexFile("", path);
}
private void addDexFile(String fileName, Dex dexBuf) {
dexFiles.add(new DexFile(this, fileName, dexBuf));
private void addDexFile(String fileName, Path path) throws IOException {
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 {
......@@ -125,9 +129,9 @@ public class InputFile {
|| entryName.endsWith(instantRunDexSuffix)) {
switch (ext) {
case ".dex":
Dex dexBuf = makeDexBuf(entryName, inputStream);
if (dexBuf != null) {
addDexFile(entryName, dexBuf);
Path path = makeDexBuf(entryName, inputStream);
if (path != null) {
addDexFile(entryName, path);
index++;
}
break;
......@@ -136,8 +140,8 @@ public class InputFile {
index++;
Path jarFile = FileUtils.createTempFile(entryName);
Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING);
for (Dex dex : loadFromJar(jarFile)) {
addDexFile(entryName, dex);
for (Path p : loadFromJar(jarFile)) {
addDexFile(entryName, p);
}
break;
......@@ -164,28 +168,26 @@ public class InputFile {
}
@Nullable
private Dex makeDexBuf(String entryName, InputStream inputStream) {
private Path makeDexBuf(String entryName, InputStream inputStream) {
try {
return new Dex(inputStream);
Path path = FileUtils.createTempFile(".dex");
Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
return path;
} catch (Exception e) {
LOG.error("Failed to load file: {}, error: {}", entryName, e.getMessage(), e);
return null;
}
}
private static List<Dex> loadFromJar(Path jar) throws DecodeException {
private static List<Path> loadFromJar(Path jar) throws DecodeException {
JavaToDex j2d = new JavaToDex();
try {
LOG.info("converting to dex: {} ...", jar.getFileName());
List<byte[]> byteList = j2d.convert(jar);
if (byteList.isEmpty()) {
List<Path> pathList = j2d.convert(jar);
if (pathList.isEmpty()) {
throw new JadxException("Empty dx output");
}
List<Dex> dexList = new ArrayList<>(byteList.size());
for (byte[] b : byteList) {
dexList.add(new Dex(b));
}
return dexList;
return pathList;
} catch (Exception e) {
throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e);
} finally {
......@@ -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");
try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(outFile))) {
String clsName = AsmUtils.getNameFromClassFile(file);
......
......@@ -37,7 +37,7 @@ public class JavaToDex {
private String dxErrors;
public List<byte[]> convert(Path jar) throws JadxException {
public List<Path> convert(Path jar) throws JadxException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream errOut = new ByteArrayOutputStream()) {
DxContext context = new DxContext(out, errOut);
......@@ -51,14 +51,14 @@ public class JavaToDex {
if (result != 0) {
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)) {
for (Path child : ds) {
list.add(Files.readAllBytes(child));
Files.delete(child);
list.add(child);
child.toFile().deleteOnExit();
}
}
Files.delete(dir);
dir.toFile().deleteOnExit();
return list;
} catch (Exception e) {
throw new JadxException("dx exception: " + e.getMessage(), e);
......
......@@ -82,6 +82,11 @@ public class JClass extends JLoadableNode {
}
@Override
public String getSmali() {
return cls.getSmali();
}
@Override
public String getSyntaxName() {
return SyntaxConstants.SYNTAX_STYLE_JAVA;
}
......
......@@ -28,6 +28,10 @@ public abstract class JNode extends DefaultMutableTreeNode {
return null;
}
public String getSmali() {
return null;
}
public String getSyntaxName() {
return SyntaxConstants.SYNTAX_STYLE_NONE;
}
......
package jadx.gui.ui.codearea;
import javax.swing.*;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
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.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.ContentPanel;
import jadx.gui.ui.TabbedPane;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
public final class CodePanel extends ContentPanel {
......@@ -17,22 +22,36 @@ public final class CodePanel extends ContentPanel {
private final SearchBar searchBar;
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) {
super(panel, jnode);
codeArea = new CodeArea(this);
smaliArea = new SmaliArea(this);
searchBar = new SearchBar(codeArea);
scrollPane = new JScrollPane(codeArea);
codeScrollPane = new JScrollPane(codeArea);
smaliScrollPane = new JScrollPane(smaliArea);
initLineNumbers();
setLayout(new BorderLayout());
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());
Utils.addKeyBinding(codeArea, key, "SearchAction", new SearchAction());
areaTabbedPane.addChangeListener(e -> {
if (areaTabbedPane.getSelectedComponent() == smaliScrollPane) {
smaliArea.load();
}
});
}
private void initLineNumbers() {
......@@ -40,7 +59,7 @@ public final class CodePanel extends ContentPanel {
if (codeArea.getDocument().getLength() <= 100_000) {
LineNumbers numbers = new LineNumbers(codeArea);
numbers.setUseSourceLines(isUseSourceLines());
scrollPane.setRowHeaderView(numbers);
codeScrollPane.setRowHeaderView(numbers);
}
}
......@@ -89,7 +108,4 @@ public final class CodePanel extends ContentPanel {
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
tabs.close=Close
tabs.closeOthers=Close Others
tabs.closeAll=Close All
tabs.code=Code
tabs.smali=Smali
nav.back=Back
nav.forward=Forward
......
......@@ -44,6 +44,8 @@ tabs.copy_class_name=Copy Name
tabs.close=Cerrar
tabs.closeOthers=Cerrar otros
tabs.closeAll=Cerrar todo
#tabs.code=
#tabs.smali=
nav.back=Atrás
nav.forward=Adelante
......
......@@ -44,6 +44,8 @@ tabs.copy_class_name=复制类名
tabs.close=关闭
tabs.closeOthers=关闭其他文件
tabs.closeAll=全部关闭
#tabs.code=
#tabs.smali=
nav.back=后退
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