feat(gui): improve smali printer to show bytecode (#1114) (PR #1126)

* improve smali printer to show bytecode
* set insnStart position before start decoding
* swithed line 62 and line 63, to get the proper bytes, insnStart must to be set before start to decode.

Co-authored-by: tobias <tobias.hotmail.com>
This commit is contained in:
LBJ-the-GOAT
2021-03-02 21:02:56 +08:00
committed by GitHub
parent 3a69ac23c0
commit 650863836c
20 changed files with 1174 additions and 18 deletions
@@ -576,6 +576,29 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
sb.append(this.clsData.getDisassembledCode());
}
public String getSmaliV2() {
StringBuilder sb = new StringBuilder();
getSmaliV2(sb);
sb.append(System.lineSeparator());
Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
getInnerAndInlinedClassesRecursive(allInlinedClasses);
for (ClassNode innerClass : allInlinedClasses) {
innerClass.getSmaliV2(sb);
sb.append(System.lineSeparator());
}
return sb.toString();
}
private void getSmaliV2(StringBuilder sb) {
if (this.clsData == null) {
sb.append(String.format("###### Class %s is created by jadx", getFullName()));
return;
}
sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName()));
sb.append(System.lineSeparator());
sb.append(this.clsData.getDisassembledCodeV2());
}
public ProcessState getState() {
return state;
}
@@ -54,6 +54,7 @@ public class JadxSettings extends JadxCLIArgs {
private boolean checkForUpdates = false;
private List<Path> recentProjects = new ArrayList<>();
private String fontStr = "";
private String smaliFontStr = "";
private String editorThemePath = "";
private LangLocale langLocale = NLS.defaultLocale();
private boolean autoStartJobs = false;
@@ -68,6 +69,7 @@ public class JadxSettings extends JadxCLIArgs {
private int srhResourceSkipSize = 1000;
private String srhResourceFileExt = ".xml|.html|.js|.json|.txt";
private boolean keepCommonDialogOpen = false;
private boolean smaliAreaShowBytecode = false;
/**
* UI setting: the width of the tree showing the classes, resources, ...
@@ -377,6 +379,27 @@ public class JadxSettings extends JadxCLIArgs {
}
}
public Font getSmaliFont() {
if (smaliFontStr.isEmpty()) {
return DEFAULT_FONT;
}
try {
return FontUtils.loadByStr(smaliFontStr);
} catch (Exception e) {
LOG.warn("Failed to load font: {} for smali, reset to default", smaliFontStr, e);
setSmaliFont(DEFAULT_FONT);
return DEFAULT_FONT;
}
}
public void setSmaliFont(@Nullable Font font) {
if (font == null) {
this.smaliFontStr = "";
} else {
this.smaliFontStr = FontUtils.convertToStr(font);
}
}
public void setLogLevel(LogHelper.LogLevelEnum level) {
this.logLevel = level;
}
@@ -430,6 +453,14 @@ public class JadxSettings extends JadxCLIArgs {
return keepCommonDialogOpen;
}
public void setSmaliAreaShowBytecode(boolean yes) {
smaliAreaShowBytecode = yes;
}
public boolean getSmaliAreaShowBytecode() {
return smaliAreaShowBytecode;
}
private void upgradeSettings(int fromVersion) {
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
if (fromVersion == 0) {
@@ -7,8 +7,7 @@ import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Collection;
import java.util.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
@@ -292,6 +291,7 @@ public class JadxSettingsWindow extends JDialog {
private SettingsGroup makeEditorGroup() {
JButton fontBtn = new JButton(NLS.str("preferences.select_font"));
JButton smaliFontBtn = new JButton(NLS.str("preferences.select_smali_font"));
EditorTheme[] editorThemes = EditorTheme.getAllThemes();
JComboBox<EditorTheme> themesCbx = new JComboBox<>(editorThemes);
@@ -311,6 +311,7 @@ public class JadxSettingsWindow extends JDialog {
SettingsGroup group = new SettingsGroup(NLS.str("preferences.editor"));
JLabel fontLabel = group.addRow(getFontLabelStr(), fontBtn);
group.addRow(NLS.str("preferences.theme"), themesCbx);
JLabel smaliFontLabel = group.addRow(getSmaliFontLabelStr(), smaliFontBtn);
fontBtn.addMouseListener(new MouseAdapter() {
@Override
@@ -327,6 +328,22 @@ public class JadxSettingsWindow extends JDialog {
}
}
});
smaliFontBtn.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
JFontChooser fontChooser = new JPreferredFontChooser();
fontChooser.setSelectedFont(settings.getSmaliFont());
int result = fontChooser.showDialog(JadxSettingsWindow.this);
if (result == JFontChooser.OK_OPTION) {
Font font = fontChooser.getSelectedFont();
LOG.debug("Selected Font: {} for smali", font);
settings.setSmaliFont(font);
mainWindow.loadSettings();
smaliFontLabel.setText(getSmaliFontLabelStr());
}
}
});
return group;
}
@@ -336,6 +353,12 @@ public class JadxSettingsWindow extends JDialog {
return NLS.str("preferences.font") + ": " + font.getFontName() + ' ' + fontStyleName + ' ' + font.getSize();
}
private String getSmaliFontLabelStr() {
Font font = settings.getSmaliFont();
String fontStyleName = FontUtils.convertFontStyleToString(font.getStyle());
return NLS.str("preferences.smali_font") + ": " + font.getFontName() + ' ' + fontStyleName + ' ' + font.getSize();
}
private SettingsGroup makeDecompilationGroup() {
JCheckBox fallback = new JCheckBox();
fallback.setSelected(settings.isFallbackMode());
@@ -578,4 +601,42 @@ public class JadxSettingsWindow extends JDialog {
add(Box.createVerticalGlue());
}
}
private static class JPreferredFontChooser extends JFontChooser {
private static final String[] PREFERRED_FONTS = new String[] {
"Monospaced", "Consolas", "Courier", "Courier New",
"Lucida Sans Typewriter", "Lucida Console",
"SimSun", "SimHei",
};
private String[] filteredFonts;
@Override
protected String[] getFontFamilies() {
if (filteredFonts == null) {
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Set<String> fontSet = new HashSet<>();
Collections.addAll(fontSet, env.getAvailableFontFamilyNames());
ArrayList<String> found = new ArrayList<>(PREFERRED_FONTS.length);
for (String font : PREFERRED_FONTS) {
if (fontSet.contains(font)) {
found.add(font);
}
}
if (found.size() == PREFERRED_FONTS.length) {
filteredFonts = PREFERRED_FONTS;
} else if (found.size() > 0) {
filteredFonts = new String[found.size()];
for (int i = 0; i < found.size(); i++) {
filteredFonts[i] = found.get(i);
}
} else {
// this machine is crazy.
LOG.warn("Can't found any preferred fonts for smali, use all available.");
filteredFonts = env.getAvailableFontFamilyNames();
}
}
return filteredFonts;
}
}
}
@@ -107,6 +107,10 @@ public class JClass extends JLoadableNode {
return cls.getSmali();
}
public String getSmaliV2() {
return cls.getClassNode().getSmaliV2();
}
@Override
public String getSyntaxName() {
return SyntaxConstants.SYNTAX_STYLE_JAVA;
@@ -55,9 +55,10 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
contentPanel.getTabbedPane().getOpenTabs().values().forEach(v -> {
if (v instanceof AbstractCodeContentPanel) {
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) v).getCodeArea();
codeArea.setLineWrap(wrap);
if (codeArea.isVisible()) {
codeArea.repaint();
setCodeAreaLineWrap(codeArea, wrap);
if (v instanceof ClassCodeContentPanel) {
codeArea = ((ClassCodeContentPanel) v).getSmaliCodeArea();
setCodeAreaLineWrap(codeArea, wrap);
}
}
});
@@ -116,6 +117,13 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
});
}
private void setCodeAreaLineWrap(AbstractCodeArea codeArea, boolean wrap) {
codeArea.setLineWrap(wrap);
if (codeArea.isVisible()) {
codeArea.repaint();
}
}
private String highlightCaretWord(String lastText, int pos) {
String text = getWordByPosition(pos);
if (StringUtils.isEmpty(text)) {
@@ -1,26 +1,106 @@
package jadx.gui.ui.codearea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import org.fife.ui.rsyntaxtextarea.*;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.TextNode;
import jadx.gui.ui.ContentPanel;
import jadx.gui.utils.NLS;
public final class SmaliArea extends AbstractCodeArea {
private static final long serialVersionUID = 1334485631870306494L;
private final JNode textNode;
private SmaliV2Style smaliV2Style;
private boolean curVersion = false;
private final JCheckBoxMenuItem cbUseSmaliV2;
SmaliArea(ContentPanel contentPanel) {
super(contentPanel);
this.textNode = new TextNode(node.getName());
setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
cbUseSmaliV2 = new JCheckBoxMenuItem(NLS.str("popup.bytecode_col"), shouldUseSmaliPrinterV2());
cbUseSmaliV2.setAction(new AbstractAction(NLS.str("popup.bytecode_col")) {
@Override
public void actionPerformed(ActionEvent e) {
boolean usingV2 = shouldUseSmaliPrinterV2();
JadxSettings settings = getContentPanel().getTabbedPane().getMainWindow().getSettings();
settings.setSmaliAreaShowBytecode(!usingV2);
contentPanel.getTabbedPane().getOpenTabs().values().forEach(v -> {
if (v instanceof ClassCodeContentPanel) {
((ClassCodeContentPanel) v).getSmaliCodeArea().refresh();
}
});
settings.sync();
}
});
getPopupMenu().add(cbUseSmaliV2);
if (shouldUseSmaliPrinterV2()) {
loadV2Style();
}
}
@Override
public Font getFont() {
if (smaliV2Style != null && shouldUseSmaliPrinterV2()) {
return smaliV2Style.getFont();
}
return super.getFont();
}
@Override
public Font getFontForTokenType(int type) {
if (shouldUseSmaliPrinterV2()) {
return smaliV2Style.getFont();
}
return super.getFontForTokenType(type);
}
private boolean shouldUseSmaliPrinterV2() {
return getContentPanel().getTabbedPane().getMainWindow().getSettings().getSmaliAreaShowBytecode();
}
private void loadV2Style() {
if (smaliV2Style == null) {
smaliV2Style = new SmaliV2Style(this);
addPropertyChangeListener(SYNTAX_SCHEME_PROPERTY, evt -> {
if (smaliV2Style.refreshTheme() && shouldUseSmaliPrinterV2()) {
setSyntaxScheme(smaliV2Style);
}
});
}
setSyntaxScheme(smaliV2Style);
}
@Override
public void load() {
if (getText().isEmpty()) {
setText(node.getSmali());
boolean useSmaliV2 = shouldUseSmaliPrinterV2();
if (useSmaliV2 != cbUseSmaliV2.getState()) {
cbUseSmaliV2.setState(useSmaliV2);
}
if (getText().isEmpty() || curVersion != useSmaliV2) {
curVersion = useSmaliV2;
if (!useSmaliV2) {
if (getSyntaxScheme() == smaliV2Style) {
Theme theme = getContentPanel().getTabbedPane().getMainWindow().getEditorTheme();
setSyntaxScheme(theme.scheme);
}
setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
setText(node.getSmali());
} else {
loadV2Style();
setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_ASSEMBLER_6502);
setText(((JClass) node).getSmaliV2());
}
setCaretPosition(0);
}
}
@@ -35,4 +115,57 @@ public final class SmaliArea extends AbstractCodeArea {
// this area contains only smali without other node attributes
return textNode;
}
private static class SmaliV2Style extends SyntaxScheme {
SmaliArea smaliArea;
Theme curTheme;
public SmaliV2Style(SmaliArea smaliArea) {
super(true);
this.smaliArea = smaliArea;
curTheme = smaliArea.getContentPanel().getTabbedPane().getMainWindow().getEditorTheme();
updateTheme();
}
public Font getFont() {
return smaliArea.getContentPanel().getTabbedPane().getMainWindow().getSettings().getSmaliFont();
}
public boolean refreshTheme() {
Theme theme = smaliArea.getContentPanel().getTabbedPane().getMainWindow().getEditorTheme();
boolean refresh = theme != curTheme;
if (refresh) {
curTheme = theme;
updateTheme();
}
return refresh;
}
private void updateTheme() {
Style[] mainStyles = curTheme.scheme.getStyles();
Style[] styles = new Style[mainStyles.length];
for (int i = 0; i < mainStyles.length; i++) {
Style mainStyle = mainStyles[i];
if (mainStyle == null) {
styles[i] = new Style();
} else {
// font will be hijacked by getFont & getFontForTokenType,
// so it doesn't need to be set here.
styles[i] = new Style(mainStyle.foreground, mainStyle.background, null);
}
}
setStyles(styles);
}
@Override
public void restoreDefaults(Font baseFont) {
restoreDefaults(baseFont, true);
}
@Override
public void restoreDefaults(Font baseFont, boolean fontStyles) {
// Note: it's a hook for continue using the editor theme, better don't remove it.
}
}
}
@@ -115,9 +115,11 @@ preferences.excludedPackages.editDialog=<html>Liste der durch Leerzeichen getren
preferences.cfg=Methoden generieren CFG-Grafiken (im 'Punkt'-Format)
preferences.raw_cfg=RAW CFG-Grafiken generieren
preferences.font=Schrift ändern
#preferences.smali_font=
preferences.theme=Thema ändern
preferences.start_jobs=Autom. Hintergrunddekompilierung starten
preferences.select_font=Ändern
#preferences.select_smali_font=
preferences.deobfuscation_on=Deobfuscation aktivieren
preferences.deobfuscation_force=Deobfuscationskartendatei umschreiben erzwingen
preferences.deobfuscation_min_len=Minimale Namenlänge
@@ -154,6 +156,7 @@ msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert n
#msg.rename_node_disabled=
#msg.rename_node_failed=
#popup.bytecode_col=
#popup.line_wrap=
popup.undo=Rückgängig
popup.redo=Wiederholen
@@ -115,9 +115,11 @@ preferences.excludedPackages.editDialog=<html>List of space separated package na
preferences.cfg=Generate methods CFG graphs (in 'dot' format)
preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Editor font
preferences.smali_font=Smali Editor font
preferences.theme=Editor theme
preferences.start_jobs=Auto start background decompilation
preferences.select_font=Change
preferences.select_smali_font=Change
preferences.deobfuscation_on=Enable deobfuscation
preferences.deobfuscation_force=Force rewrite deobfuscation map file
preferences.deobfuscation_min_len=Minimum name length
@@ -154,6 +156,7 @@ msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not ex
msg.rename_node_disabled=Can't rename this node
msg.rename_node_failed=Can't rename %s
popup.bytecode_col=Show Bytecode
popup.line_wrap=Line Wrap
popup.undo=Undo
popup.redo=Redo
@@ -115,9 +115,11 @@ preferences.threads=Número de hilos a procesar
preferences.cfg=Generar methods CFG graphs (in 'dot' format)
preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Fuente del editor
#preferences.smali_font=
preferences.theme=Tema del editor
preferences.start_jobs=Inicio autom. descompilación de fondo
preferences.select_font=Seleccionar
#preferences.select_smali_font=
preferences.deobfuscation_on=Activar desobfuscación
preferences.deobfuscation_force=Forzar reescritura del fichero de ofuscación
preferences.deobfuscation_min_len=Longitud mínima del nombre
@@ -154,6 +156,7 @@ msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivar
#msg.rename_node_disabled=
#msg.rename_node_failed=
#popup.bytecode_col=
#popup.line_wrap=
popup.undo=Deshacer
popup.redo=Rehacer
@@ -115,9 +115,11 @@ preferences.excludedPackages.editDialog=<html>RAM 절약을 위해 디컴파일
preferences.cfg=메소드 CFG 그래프 생성 ('dot' 포맷)
preferences.raw_cfg=RAW CFG 그래프 생성
preferences.font=에디터 글씨체
#preferences.smali_font=
preferences.theme=에디터 테마
preferences.start_jobs=백그라운드에서 디컴파일 자동 시작
preferences.select_font=변경
#preferences.select_smali_font=
preferences.deobfuscation_on=난독 해제 활성화
preferences.deobfuscation_force=난독 해제 맵 파일 다시 쓰기
preferences.deobfuscation_min_len=최소 이름 길이
@@ -154,6 +156,7 @@ msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클
msg.rename_node_disabled=이 노드의 이름을 바꿀 수 없습니다.
msg.rename_node_failed=%s의 이름을 바꿀 수 없습니다.
#popup.bytecode_col=
popup.line_wrap=줄 바꿈
popup.undo=실행 취소
popup.redo=다시 실행
@@ -115,9 +115,11 @@ preferences.excludedPackages.editDialog=<html>将不被解压或索引的以空
preferences.cfg=生成方法的 CFG 图(以 .dot 格式保存)
preferences.raw_cfg=生成原始的 CFG 图
preferences.font=编辑器字体
#preferences.smali_font=
preferences.theme=编辑器主题
preferences.start_jobs=自动进行后台反编译
preferences.select_font=更改
#preferences.select_smali_font=
preferences.deobfuscation_on=启用反混淆
preferences.deobfuscation_force=强制覆盖反混淆映射文件
preferences.deobfuscation_min_len=最小命名长度
@@ -154,6 +156,7 @@ msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
#msg.rename_node_disabled=
#msg.rename_node_failed=
#popup.bytecode_col=
#popup.line_wrap=
popup.undo=撤销
popup.redo=重做
@@ -2,9 +2,7 @@ package jadx.plugins.input.dex.insns;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.*;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.InsnIndexType;
import jadx.api.plugins.input.insns.Opcode;
@@ -21,6 +19,7 @@ public class DexInsnData implements InsnData {
private boolean decoded;
private int opcodeUnit;
private int length;
private int insnStart;
private int offset;
private int[] argsReg = new int[5];
@@ -49,6 +48,11 @@ public class DexInsnData implements InsnData {
return offset;
}
@Override
public int getFileOffset() {
return insnStart;
}
@Override
public Opcode getOpcode() {
DexInsnInfo info = this.insnInfo;
@@ -58,6 +62,11 @@ public class DexInsnData implements InsnData {
return info.getApiOpcode();
}
@Override
public byte[] getByteCode() {
return externalReader.getByteCode(insnStart, length * 2); // a unit is 2 bytes
}
@Override
public int getRawOpcodeUnit() {
return opcodeUnit;
@@ -118,6 +127,20 @@ public class DexInsnData implements InsnData {
return externalReader.getCallSite(index, secondExtReader);
}
/**
* Currently, protoIndex is either being stored at index or target, index for const-method-type,
* target for invoke-polymorphic(/range)
*/
@Override
public IMethodProto getIndexAsProto(int protoIndex) {
return externalReader.getMethodProto(protoIndex);
}
@Override
public IMethodHandle getIndexAsMethodHandle() {
return externalReader.getMethodHandle(index);
}
@Nullable
@Override
public ICustomPayload getPayload() {
@@ -144,6 +167,10 @@ public class DexInsnData implements InsnData {
this.length = length;
}
public void setInsnStart(int start) {
this.insnStart = start;
}
public void setLiteral(long literal) {
this.literal = literal;
}
@@ -230,4 +230,60 @@ public class DexOpcodes {
public static final int PACKED_SWITCH_PAYLOAD = 0x0100;
public static final int SPARSE_SWITCH_PAYLOAD = 0x0200;
public static final int FILL_ARRAY_DATA_PAYLOAD = 0x0300;
public static final String MNE_UNUSED = "(unused)";
public static final String[] MNEMONICS = new String[] {
"nop", "move", "move/from16", "move/16", "move-wide",
"move-wide/from16", "move-wide/16", "move-object", "move-object/from16", "move-object/16",
"move-result", "move-result-wide", "move-result-object", "move-exception", "return-void",
"return", "return-wide", "return-object", "const/4", "const/16",
"const", "const/high16", "const-wide/16", "const-wide/32", "const-wide",
"const-wide/high16", "const-string", "const-string/jumbo", "const-class", "monitor-enter",
"monitor-exit", "check-cast", "instance-of", "array-length", "new-instance",
"new-array", "filled-new-array", "filled-new-array/range", "fill-array-data", "throw",
"goto", "goto/16", "goto/32", "packed-switch", "sparse-switch",
"cmpl-float", "cmpg-float", "cmpl-double", "cmpg-double", "cmp-long",
"if-eq", "if-ne", "if-lt", "if-ge", "if-gt",
"if-le", "if-eqz", "if-nez", "if-ltz", "if-gez",
"if-gtz", "if-lez", "(unused)", "(unused)", "(unused)",
"(unused)", "(unused)", "(unused)", "aget", "aget-wide",
"aget-object", "aget-boolean", "aget-byte", "aget-char", "aget-short",
"aput", "aput-wide", "aput-object", "aput-boolean", "aput-byte",
"aput-char", "aput-short", "iget", "iget-wide", "iget-object",
"iget-boolean", "iget-byte", "iget-char", "iget-short", "iput",
"iput-wide", "iput-object", "iput-boolean", "iput-byte", "iput-char",
"iput-short", "sget", "sget-wide", "sget-object", "sget-boolean",
"sget-byte", "sget-char", "sget-short", "sput", "sput-wide",
"sput-object", "sput-boolean", "sput-byte", "sput-char", "sput-short",
"invoke-virtual", "invoke-super", "invoke-direct", "invoke-static", "invoke-interface",
"(unused)", "invoke-virtual/range", "invoke-super/range", "invoke-direct/range", "invoke-static/range",
"invoke-interface/range", "(unused)", "(unused)", "neg-int", "not-int",
"neg-long", "not-long", "neg-float", "neg-double", "int-to-long",
"int-to-float", "int-to-double", "long-to-int", "long-to-float", "long-to-double",
"float-to-int", "float-to-long", "float-to-double", "double-to-int", "double-to-long",
"double-to-float", "int-to-byte", "int-to-char", "int-to-short", "add-int",
"sub-int", "mul-int", "div-int", "rem-int", "and-int",
"or-int", "xor-int", "shl-int", "shr-int", "ushr-int",
"add-long", "sub-long", "mul-long", "div-long", "rem-long",
"and-long", "or-long", "xor-long", "shl-long", "shr-long",
"ushr-long", "add-float", "sub-float", "mul-float", "div-float",
"rem-float", "add-double", "sub-double", "mul-double", "div-double",
"rem-double", "add-int/2addr", "sub-int/2addr", "mul-int/2addr", "div-int/2addr",
"rem-int/2addr", "and-int/2addr", "or-int/2addr", "xor-int/2addr", "shl-int/2addr",
"shr-int/2addr", "ushr-int/2addr", "add-long/2addr", "sub-long/2addr", "mul-long/2addr",
"div-long/2addr", "rem-long/2addr", "and-long/2addr", "or-long/2addr", "xor-long/2addr",
"shl-long/2addr", "shr-long/2addr", "ushr-long/2addr", "add-float/2addr", "sub-float/2addr",
"mul-float/2addr", "div-float/2addr", "rem-float/2addr", "add-double/2addr", "sub-double/2addr",
"mul-double/2addr", "div-double/2addr", "rem-double/2addr", "add-int/lit16", "rsub-int",
"mul-int/lit16", "div-int/lit16", "rem-int/lit16", "and-int/lit16", "or-int/lit16",
"xor-int/lit16", "add-int/lit8", "rsub-int/lit8", "mul-int/lit8", "div-int/lit8",
"rem-int/lit8", "and-int/lit8", "or-int/lit8", "xor-int/lit8", "shl-int/lit8",
"shr-int/lit8", "ushr-int/lit8", "(unused)", "(unused)", "(unused)",
"(unused)", "(unused)", "(unused)", "(unused)", "(unused)",
"(unused)", "(unused)", "(unused)", "(unused)", "(unused)",
"(unused)", "(unused)", "(unused)", "(unused)", "(unused)",
"(unused)", "(unused)", "(unused)", "(unused)", "(unused)",
"invoke-polymorphic", "invoke-polymorphic/range", "invoke-custom", "invoke-custom/range", "const-method-handle",
"const-method-type" };
}
@@ -13,6 +13,7 @@ import jadx.api.plugins.input.data.IMethodData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.plugins.input.dex.sections.annotations.AnnotationsParser;
import jadx.plugins.input.dex.smali.SmaliPrinter;
import jadx.plugins.input.dex.utils.SmaliUtils;
public class DexClassData implements IClassData {
@@ -193,6 +194,11 @@ public class DexClassData implements IClassData {
return SmaliUtils.getSmaliCode(dexBuf, getClassDefOffset());
}
@Override
public String getDisassembledCodeV2() {
return SmaliPrinter.printClass(this);
}
@Override
public String toString() {
return getType();
@@ -59,8 +59,10 @@ public class DexCodeReader implements ICodeReader {
int size = in.readInt();
int offset = 0; // in code units (2 byte)
while (offset < size) {
int insnStart = in.getAbsPos();
int opcodeUnit = in.readUShort();
DexInsnInfo insnInfo = DexInsnInfo.get(opcodeUnit);
insnData.setInsnStart(insnStart);
insnData.setOffset(offset);
insnData.setInsnInfo(insnInfo);
insnData.setOpcodeUnit(opcodeUnit);
@@ -45,6 +45,14 @@ public class SectionReader {
return new SectionReader(this, off);
}
public byte[] getByteCode(int start, int len) {
int pos = buf.position();
buf.position(start);
byte[] bytes = readByteArray(len);
buf.position(pos);
return bytes;
}
private static ByteBuffer duplicate(ByteBuffer baseBuffer, int off) {
ByteBuffer dupBuf = baseBuffer.duplicate();
dupBuf.order(ByteOrder.LITTLE_ENDIAN);
@@ -1,10 +1,21 @@
package jadx.plugins.input.dex.smali;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.ICodeReader;
import java.util.*;
import java.util.Map.Entry;
import jadx.api.plugins.input.data.*;
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.InsnIndexType;
import jadx.plugins.input.dex.insns.DexOpcodes;
import jadx.plugins.input.dex.insns.payloads.DexSwitchPayload;
import jadx.plugins.input.dex.sections.DexFieldData;
import jadx.plugins.input.dex.sections.DexMethodData;
import jadx.plugins.input.dex.sections.DexMethodRef;
import static jadx.api.plugins.input.data.AccessFlagsScope.FIELD;
import static jadx.api.plugins.input.data.AccessFlagsScope.METHOD;
// TODO: not finished
@@ -37,4 +48,762 @@ public class SmaliPrinter {
codeWriter.startLine(".end method");
return codeWriter.getCode();
}
public static String printClass(IClassData cls) {
SmaliCodeWriter smali = new SmaliCodeWriter();
smali.startLine("Class: " + cls.getType())
.startLine("AccessFlags: " + AccessFlags.format(cls.getAccessFlags(), AccessFlagsScope.CLASS))
.startLine("SuperType: " + cls.getSuperType())
.startLine("Interfaces: " + cls.getInterfacesTypes())
.startLine("SourceFile: " + cls.getSourceFile());
if (cls.getAnnotations().size() > 0) {
smali.startLine().startLine("# annotations");
printAnnotations(smali, cls.getAnnotations());
}
List<Entry<DexFieldData, List<IAnnotation>>> flds = new ArrayList<>();
cls.visitFieldsAndMethods(
f -> {
DexFieldData fld = new DexFieldData(null);
fld.setParentClassType(f.getParentClassType());
fld.setAccessFlags(f.getAccessFlags());
fld.setName(f.getName());
fld.setType(f.getType());
flds.add(new AbstractMap.SimpleEntry<>(fld, f.getAnnotations()));
},
m -> {
if (!flds.isEmpty()) {
printField(smali, flds, cls.getStaticFieldInitValues());
flds.clear();
smali.startLine("# methods");
}
printMethod(smali, m);
});
if (!flds.isEmpty()) { // in case there are no methods.
printField(smali, flds, cls.getStaticFieldInitValues());
flds.clear();
}
return smali.getCode();
}
private static void printField(SmaliCodeWriter smali,
List<Entry<DexFieldData, List<IAnnotation>>> flds,
List<EncodedValue> staticFieldInitValues) {
int staticIdx = 0;
int accessColWidth = 0;
int nameColWidth = 0;
List<String> accesses = new ArrayList<>(flds.size());
for (Entry<DexFieldData, List<IAnnotation>> fld : flds) { // calc width of cols
String temp = fld.getKey().getName();
if (temp.length() > nameColWidth) {
nameColWidth = temp.length();
}
temp = AccessFlags.format(fld.getKey().getAccessFlags(), FIELD);
accesses.add(temp);
if (temp.length() > accessColWidth) {
accessColWidth = temp.length();
}
}
smali.startLine().startLine("# fields");
String whites = new String(new byte[Math.max(accessColWidth, nameColWidth)]).replace("\0", " ");
for (int i = 0; i < flds.size(); i++) {
smali.startLine();
Entry<DexFieldData, List<IAnnotation>> fld = flds.get(i);
String access = accesses.get(i);
int pad = accessColWidth - access.length();
if (pad > 0) {
access += whites.substring(0, pad);
}
smali.add(".field ").add(access);
String name = fld.getKey().getName();
pad = nameColWidth - name.length();
if (pad > 0) {
name += whites.substring(0, pad);
}
smali.add(name).add(" ");
smali.add(": ").add(fld.getKey().getType());
if ((fld.getKey().getAccessFlags() & AccessFlags.STATIC) != 0) { // static field
if (staticIdx < staticFieldInitValues.size()) {
smali.add(" # init val = ");
printEncodedValue(smali, staticFieldInitValues.get(staticIdx++), false);
}
}
smali.incIndent();
printAnnotations(smali, fld.getValue());
smali.decIndent();
}
smali.startLine();
}
private static void printMethod(SmaliCodeWriter smali, IMethodData mth) {
smali.startLine()
.startLine(mth.isDirect() ? "# direct method" : " # virtual method")
.startLine(".method ");
printMethodDef(smali, mth);
smali.incIndent();
ICodeReader codeReader = mth.getCodeReader();
if (codeReader != null) {
smali.startLine(".registers ")
.add(codeReader.getRegistersCount())
.startLine();
Map<Integer, String> paramMap = formatMthParamInfo(mth, smali, codeReader);
if (paramMap.size() > 0) {
smali.startLine();
}
SmaliGen smaliGen = new SmaliGen(paramMap, codeReader.getDebugInfo(), true, true);
codeReader.visitInstructions(insn -> {
insn.decode();
smaliGen.format(insn);
});
smaliGen.gen(smali);
}
smali.decIndent();
smali.startLine(".end method");
}
private static void printMethodDef(SmaliCodeWriter smali, IMethodData mth) {
smali.add(AccessFlags.format(mth.getAccessFlags(), METHOD));
IMethodRef methodRef = mth.getMethodRef();
methodRef.load();
smali.add(methodRef.getName());
smali.add('(').addArgs(methodRef.getArgTypes()).add(')');
smali.add(methodRef.getReturnType());
if (mth.getAnnotations().size() > 0) {
smali.incIndent();
printAnnotations(smali, mth.getAnnotations());
smali.decIndent();
smali.startLine();
}
}
private static Map<Integer, String> formatMthParamInfo(IMethodData mth, SmaliCodeWriter smali, ICodeReader codeReader) {
List<String> types = mth.getMethodRef().getArgTypes();
if (types.size() == 0) {
return Collections.emptyMap();
}
int i = 0;
int paramCount = 0;
int paramStart = isStaticMethod(mth) ? 0 : 1;
int regNum = getParamStartRegNum(mth);
Map<Integer, String> paramMap = new HashMap<>(types.size());
IDebugInfo dbgInfo = codeReader.getDebugInfo();
if (dbgInfo != null) {
for (ILocalVar var : dbgInfo.getLocalVars()) {
if (var.getStartOffset() == -1) {
smali.startLine(String.format(".param p%d, \"%s\":%s",
paramStart + i, var.getName(), var.getType()));
paramMap.put(regNum + i, "p" + (paramStart + i));
paramCount++;
i += 1;
if (isWideType(var.getType())) {
paramMap.put(regNum + i, "p" + (paramStart + i));
i += 1;
}
}
}
if (paramCount + 1 == types.size()) {
return paramMap;
}
}
for (; paramCount < types.size(); paramCount++) {
String type = types.get(paramCount);
smali.startLine(String.format(".param p%d, \"\":%s", paramStart + i, type));
paramMap.put(regNum + i, "p" + (paramStart + i));
i += 1;
if (isWideType(type)) {
paramMap.put(regNum + i, "p" + (paramStart + i));
i += 1;
}
}
return paramMap;
}
private static int getParamStartRegNum(IMethodData mth) {
ICodeReader codeReader = mth.getCodeReader();
if (codeReader != null) {
int startNum = codeReader.getRegistersCount();
if (startNum > 0) {
for (String argType : mth.getMethodRef().getArgTypes()) {
if (isWideType(argType)) {
startNum -= 2;
} else {
startNum -= 1;
}
}
if (!isStaticMethod(mth)) {
startNum--;
}
return startNum;
}
}
return -1;
}
private static boolean isWideType(String type) {
return type.equals("D") || type.equals("J");
}
private static boolean isStaticMethod(IMethodData mth) {
return (mth.getAccessFlags() & AccessFlags.STATIC) != 0;
}
private static void printAnnotations(SmaliCodeWriter smali, List<IAnnotation> annoList) {
if (annoList.size() > 0) {
for (int i = 0; i < annoList.size(); i++) {
smali.startLine();
printAnnotation(smali, annoList.get(i));
if (i != annoList.size() - 1) {
smali.startLine();
}
}
}
}
private static void printAnnotation(SmaliCodeWriter smali, IAnnotation anno) {
smali.add(".annotation")
.add(" ");
AnnotationVisibility vby = anno.getVisibility();
if (vby != null) {
smali.add(vby.toString().toLowerCase()).add(" ");
}
smali.add(anno.getAnnotationClass());
anno.getValues().forEach((k, v) -> {
smali.incIndent();
smali.startLine(k).add(" = ");
printEncodedValue(smali, v, true);
smali.decIndent();
});
smali.startLine(".end annotation");
}
private static void printEncodedValue(SmaliCodeWriter smali, EncodedValue value, boolean wrapArray) {
switch (value.getType()) {
case ENCODED_ARRAY:
smali.add("{");
if (wrapArray) {
smali.incIndent();
smali.startLine();
}
List<EncodedValue> values = (List<EncodedValue>) value.getValue();
for (int i = 0; i < values.size(); i++) {
printEncodedValue(smali, values.get(i), wrapArray);
if (i != values.size() - 1) {
smali.add(",");
if (wrapArray) {
smali.startLine();
} else {
smali.add(" ");
}
}
}
if (wrapArray) {
smali.decIndent();
smali.startLine("}");
}
break;
case ENCODED_STRING:
smali.add("\"").add(value.getValue()).add("\"");
break;
case ENCODED_NULL:
smali.add("null");
break;
case ENCODED_ANNOTATION:
printAnnotation(smali, (IAnnotation) value.getValue());
break;
default:
smali.add(value.getValue());
}
}
private static final int CODE_OFFSET_COLUMN_WIDTH = 4;
private static final int BYTECODE_COLUMN_WIDTH = 20 + 3; // 3 for ellipses.
private static final String FMT_BYTECODE_COL = "%-" + (BYTECODE_COLUMN_WIDTH - 3) + "s";
private static final int INSN_COL_WIDTH = "const-method-handle".length();
private static final String FMT_INSN_COL = "%-" + INSN_COL_WIDTH + "s";
private static final String FMT_FILE_OFFSET = "%08x:";
private static final String FMT_CODE_OFFSET = "%04x:";
private static final String FMT_TARGET_OFFSET = "%04x";
private static final String FMT_GOTO = ":goto_" + FMT_TARGET_OFFSET;
private static final String FMT_COND = ":cond_" + FMT_TARGET_OFFSET;
private static final String FMT_DATA = ":data_" + FMT_TARGET_OFFSET;
private static final String FMT_P_SWITCH = ":p_switch_" + FMT_TARGET_OFFSET;
private static final String FMT_S_SWITCH = ":s_switch_" + FMT_TARGET_OFFSET;
private static final String FMT_P_SWITCH_CASE = ":p_case_" + FMT_TARGET_OFFSET;
private static final String FMT_S_SWITCH_CASE = ":s_case_" + FMT_TARGET_OFFSET;
private static final String FMT_GOTO_TAG = "goto_" + FMT_TARGET_OFFSET + ":";
private static final String FMT_COND_TAG = "cond_" + FMT_TARGET_OFFSET + ":";
private static final String FMT_DATA_TAG = "data_" + FMT_TARGET_OFFSET + ":";
private static final String FMT_P_SWITCH_TAG = "p_switch_" + FMT_TARGET_OFFSET + ":";
private static final String FMT_S_SWITCH_TAG = "s_switch_" + FMT_TARGET_OFFSET + ":";
private static final String FMT_P_SWITCH_CASE_TAG = "p_case_" + FMT_TARGET_OFFSET + ":";
private static final String FMT_S_SWITCH_CASE_TAG = "s_case_" + FMT_TARGET_OFFSET + ":";
public static class SmaliGen {
static class SmaliLine {
Object line;
List<Entry<String, String>> tips = Collections.emptyList();
void setLine(String str) {
line = str;
}
void addLine(String str) {
if (!(line instanceof List)) {
line = new ArrayList<String>();
}
((ArrayList<String>) this.line).add(str);
}
void addLineTip(String tip, String extra) {
if (tips.isEmpty()) {
tips = new ArrayList<>();
}
tips.add(new AbstractMap.SimpleEntry<>(tip, extra));
}
private void fmtLineTip(int lineOffset, SmaliCodeWriter smali) {
for (Entry<String, String> tip : tips) {
int start = Math.max(0, lineOffset - tip.getKey().length());
if (start > 0) {
smali.add(new String(new byte[start]).replace("\0", " "));
}
smali.add(tip.getKey() + tip.getValue()).startLine();
}
}
private void gen(int lineOffset, SmaliCodeWriter smali) {
fmtLineTip(lineOffset, smali);
if (line instanceof List) {
int size = ((List<String>) line).size();
for (int i = 0; i < size; i++) {
smali.add(((List<String>) line).get(i));
if (i != size - 1) {
smali.startLine();
}
}
} else {
smali.add(line);
}
}
}
StringBuilder lineWriter = new StringBuilder(50);
Map<Integer, SmaliLine> targetMap = new HashMap<>();
Map<Integer, Integer> payloadOffsetMap = new HashMap<>();
Map<Integer, String> paramMap;
List<SmaliLine> smaliList = new ArrayList<>();
boolean fileOffset;
boolean bytecode;
boolean hasDbgInfo;
/**
* @param fileOffset adds file offset column to smali output
* @param bytecode adds bytecode column to smali output
*/
public SmaliGen(Map<Integer, String> paramMap, IDebugInfo dbgInfo,
boolean fileOffset, boolean bytecode) {
this.fileOffset = fileOffset;
this.bytecode = bytecode;
this.paramMap = paramMap;
this.hasDbgInfo = dbgInfo != null;
if (hasDbgInfo) {
fmtDbgInfo(dbgInfo);
}
}
private boolean isParamReg(int regNum) {
return paramMap.containsKey(regNum);
}
private String getRegName(int regNum) {
String text = paramMap.get(regNum);
if (text == null || text.isEmpty()) {
return "v" + regNum;
}
return text;
}
public void gen(SmaliCodeWriter smali) {
removeDupTips();
int lineOffset = getInsnColStart();
for (SmaliLine smaliLine : smaliList) {
smali.startLine();
smaliLine.gen(lineOffset, smali);
}
}
public void format(InsnData insnData) {
SmaliLine line = targetMap.computeIfAbsent(insnData.getOffset(), k -> new SmaliLine());
smaliList.add(line);
fmt(insnData, line);
}
private void fmt(InsnData insn, SmaliLine line) {
fmtCols(insn);
if (!fmtPayloadInsn(insn, line)) {
fmtInsn(insn);
line.line = lineWriter.toString();
}
lineWriter.delete(0, lineWriter.length());
}
private void fmtDbgInfo(IDebugInfo dbgInfo) {
dbgInfo.getSourceLineMapping().forEach((codeOffset, srcLine) -> {
if (codeOffset > -1) {
SmaliLine line = targetMap.computeIfAbsent(codeOffset, k -> new SmaliLine());
line.addLineTip(String.format(".line %d", srcLine), "");
}
});
for (ILocalVar localVar : dbgInfo.getLocalVars()) {
if (localVar.getStartOffset() > -1) {
SmaliLine line = targetMap.computeIfAbsent(localVar.getStartOffset(), k -> new SmaliLine());
line.addLineTip(String.format(".local v%d", localVar.getRegNum()),
String.format(", \"%s\":%s", localVar.getName(), localVar.getType()));
}
if (localVar.getEndOffset() > -1) {
if (isParamReg(localVar.getRegNum())) {
return; // no need to add .end local for parameters.
}
SmaliLine line = targetMap.computeIfAbsent(localVar.getEndOffset(), k -> new SmaliLine());
line.addLineTip(String.format(".end local v%d", localVar.getRegNum()),
String.format(" # \"%s\":%s", localVar.getName(), localVar.getType()));
}
}
}
private void fmtInsn(InsnData insn) {
int opcode = insn.getRawOpcodeUnit();
opcode = opcode & 0xff;
String mne = DexOpcodes.MNEMONICS[opcode];
lineWriter.append(String.format(FMT_INSN_COL, mne)).append(" ");
fmtRegs(opcode, insn, lineWriter);
if (hasTarget(opcode)) {
if (isGotoIns(opcode)) {
lineWriter.append(String.format(FMT_GOTO, insn.getTarget()));
addTarget(FMT_GOTO_TAG, insn.getTarget());
return;
}
lineWriter.append(", ");
if (isConditionIns(opcode)) {
lineWriter.append(String.format(FMT_COND, insn.getTarget()));
addTarget(FMT_COND_TAG, insn.getTarget());
} else if (opcode == DexOpcodes.PACKED_SWITCH) {
payloadOffsetMap.put(insn.getTarget(), insn.getOffset());
lineWriter.append(String.format(FMT_P_SWITCH, insn.getTarget()));
addTarget(FMT_P_SWITCH_TAG, insn.getTarget());
} else if (opcode == DexOpcodes.SPARSE_SWITCH) {
payloadOffsetMap.put(insn.getTarget(), insn.getOffset());
lineWriter.append(String.format(FMT_S_SWITCH, insn.getTarget()));
addTarget(FMT_S_SWITCH_TAG, insn.getTarget());
} else {
lineWriter.append(String.format(FMT_DATA, insn.getTarget()));
addTarget(FMT_DATA_TAG, insn.getTarget());
}
return;
}
if (isInvokeIns(opcode)) {
lineWriter.append(", ").append(method(insn));
return;
}
if (insn.getIndexType() == InsnIndexType.TYPE_REF) {
lineWriter.append(", ").append(type(insn));
return;
}
if (insn.getIndexType() == InsnIndexType.FIELD_REF) {
lineWriter.append(", ").append(field(insn));
return;
}
if (insn.getIndexType() == InsnIndexType.STRING_REF) {
lineWriter.append(", ").append(str(insn));
return;
}
if (hasLiteral(opcode)) {
lineWriter.append(", ").append(literal(insn, opcode));
return;
}
if (opcode == DexOpcodes.CONST_METHOD_HANDLE) {
lineWriter.append(", ").append(methodHandle(insn));
return;
}
if (opcode == DexOpcodes.CONST_METHOD_TYPE) {
lineWriter.append(", ").append(proto(insn, insn.getIndex()));
return;
}
}
private void addTarget(String fmtTag, int target) {
addTarget(fmtTag, target, "");
}
private void addTarget(String fmtTag, int target, String extraTip) {
targetMap.computeIfAbsent(target, k -> new SmaliLine())
.addLineTip(String.format(fmtTag, target), extraTip);
}
private void fmtRegs(int opcode, InsnData insn, StringBuilder smali) {
boolean appendBrace = isRegList(opcode);
if (appendBrace) {
smali.append("{");
}
if (isRangeRegIns(opcode)) {
smali.append(getRegName(insn.getReg(0)))
.append(" .. ")
.append(getRegName(insn.getReg(insn.getRegsCount() - 1)));
} else if (insn.getRegsCount() > 0) {
for (int i = 0; i < insn.getRegsCount(); i++) {
if (i > 0) {
smali.append(", ");
}
smali.append(getRegName(insn.getReg(i)));
}
}
if (appendBrace) {
smali.append("}");
}
}
private boolean fmtPayloadInsn(InsnData insn, SmaliLine line) {
int opcode = insn.getRawOpcodeUnit();
if (opcode == DexOpcodes.PACKED_SWITCH_PAYLOAD) {
lineWriter.append("packed-switch-payload");
line.addLine(lineWriter.toString());
DexSwitchPayload payload = (DexSwitchPayload) insn.getPayload();
if (payload != null) {
fmtSwitchPayload(FMT_P_SWITCH_CASE, FMT_P_SWITCH_CASE_TAG, line, payload, insn.getOffset());
}
return true;
}
if (opcode == DexOpcodes.SPARSE_SWITCH_PAYLOAD) {
lineWriter.append("sparse-switch-payload");
line.addLine(lineWriter.toString());
DexSwitchPayload payload = (DexSwitchPayload) insn.getPayload();
if (payload != null) {
fmtSwitchPayload(FMT_S_SWITCH_CASE, FMT_S_SWITCH_CASE_TAG, line, payload, insn.getOffset());
}
return true;
}
if (opcode == DexOpcodes.FILL_ARRAY_DATA_PAYLOAD) {
lineWriter.append("fill-array-data-payload");
line.setLine(lineWriter.toString());
return true;
}
return false;
}
private void fmtSwitchPayload(String fmtTarget, String fmtTag, SmaliLine line,
DexSwitchPayload payload, int curOffset) {
int lineStart = getInsnColStart();
lineStart += CODE_OFFSET_COLUMN_WIDTH + 1 + 1; // plus 1s for space and the ':'
String basicIndent = new String(new byte[lineStart]).replace("\0", " ");
String indent = SmaliCodeWriter.INDENT_STR + basicIndent;
int[] keys = payload.getKeys();
int[] targets = payload.getTargets();
int opcodeOffset = payloadOffsetMap.get(curOffset);
for (int i = 0; i < keys.length; i++) {
int target = opcodeOffset + targets[i];
line.addLine(String.format("%scase %d: -> " + fmtTarget, indent, keys[i], target));
addTarget(fmtTag, target, String.format(" # case %d", keys[i]));
}
line.addLine(basicIndent + ".end payload");
}
private void removeDupTips() {
List<Entry<Integer, Entry<String, String>>> dbgLines = null; // line num: tip
if (hasDbgInfo) {
dbgLines = new ArrayList<>();
}
for (int i = 0; i < smaliList.size(); i++) {
SmaliLine line = smaliList.get(i);
Map<String, Integer> tipSet = Collections.emptyMap(); // tip: reference count
for (Iterator<Entry<String, String>> it = line.tips.iterator(); it.hasNext();) {
Entry<String, String> tip = it.next();
if (hasDbgInfo && removeDupSourceLine(tip, i, dbgLines)) { // debug info source line.
it.remove();
continue;
}
if (tipSet.containsKey(tip.getKey())) { // remove dup tips like cond_:/goto_:.
it.remove();
tipSet.computeIfPresent(tip.getKey(), (k, v) -> v + 1);
} else {
if (tipSet.isEmpty()) {
tipSet = new HashMap<>();
}
tipSet.computeIfAbsent(tip.getKey(), k -> 1);
}
}
tipSet.forEach((k, v) -> {
if (v > 1) {
for (int j = 0; j < line.tips.size(); j++) {
if (line.tips.get(j).getKey().equals(k)) {
line.tips.set(j, new AbstractMap.SimpleEntry<>(k, " # " + v + " refs"));
}
}
}
});
}
}
private boolean removeDupSourceLine(Entry<String, String> tip, int i,
List<Entry<Integer, Entry<String, String>>> dbgLines) {
boolean removeIt = false;
if (tip.getKey().startsWith(".line ")) { // debug info source line.
if (dbgLines.size() > 0) {
Entry<Integer, Entry<String, String>> entry = dbgLines.get(dbgLines.size() - 1);
if (i - entry.getKey() == 1 && entry.getValue().getKey().equals(tip.getKey())) {
removeIt = true; // duplicated.
}
}
dbgLines.add(new AbstractMap.SimpleEntry<>(i, tip));
}
return removeIt;
}
private int getInsnColStart() {
int start = 0;
if (fileOffset) {
start += 8 + 1 + 1; // plus 1s for space and the ':'
}
if (bytecode) {
start += BYTECODE_COLUMN_WIDTH + 1; // plus 1 for space
}
return start;
}
private void fmtCols(InsnData insn) {
if (fileOffset) {
lineWriter.append(String.format(FMT_FILE_OFFSET + " ", insn.getFileOffset()));
}
if (bytecode) {
formatByteCode(lineWriter, insn.getByteCode());
lineWriter.append(" ");
lineWriter.append(String.format(FMT_CODE_OFFSET + " ", insn.getOffset()));
}
}
private static void formatByteCode(StringBuilder smali, byte[] bytes) {
int maxLen = Math.min(bytes.length, 4 * 2); // limit to 4 units
StringBuilder inHex = new StringBuilder();
for (int i = 0; i < maxLen; i++) {
int temp = ((bytes[i++] & 0xff) << 8) | (bytes[i] & 0xff);
inHex.append(String.format("%04x ", temp));
}
smali.append(String.format(FMT_BYTECODE_COL, inHex));
if (maxLen < bytes.length) {
smali.append("...");
} else {
smali.append(" ");
}
}
private static String literal(InsnData insn, int opcode) {
long it = insn.getLiteral();
String tip = "";
if (it > Integer.MAX_VALUE) {
if (isWideIns(opcode)) {
tip = " # double: " + Double.longBitsToDouble(it);
} else if (opcode == DexOpcodes.CONST_HIGH16) {
tip = " # float: " + Float.intBitsToFloat((int) it);
}
} else if (it <= 0) {
return "" + it + tip;
}
return "0x" + Long.toHexString(it) + tip;
}
private static String str(InsnData insn) {
return String.format("\"%s\" # string@%04x",
insn.getIndexAsString()
.replace("\n", "\\n")
.replace("\t", "\\t"),
insn.getIndex());
}
private static String type(InsnData insn) {
return String.format("%s # type@%04x", insn.getIndexAsType(), insn.getIndex());
}
private static String field(InsnData insn) {
return String.format("%s # field@%04x", insn.getIndexAsField().toString(), insn.getIndex());
}
private static String method(InsnData insn) {
int rawOpcodeUnit = insn.getRawOpcodeUnit();
int opcode = rawOpcodeUnit & 0xFF;
if (opcode == DexOpcodes.INVOKE_CUSTOM || opcode == DexOpcodes.INVOKE_CUSTOM_RANGE) {
insn.getIndexAsCallSite().load();
return String.format("%s # call_site@%04x", insn.getIndexAsCallSite().toString(), insn.getIndex());
}
IMethodRef mthRef = insn.getIndexAsMethod();
mthRef.load();
if (opcode == DexOpcodes.INVOKE_POLYMORPHIC || opcode == DexOpcodes.INVOKE_POLYMORPHIC_RANGE) {
return String.format("%s, %s # method@%04x, proto@%04x",
mthRef.toString(), insn.getIndexAsProto(insn.getTarget()).toString(),
insn.getIndex(), insn.getTarget());
}
return String.format("%s # method@%04x", mthRef.toString(), insn.getIndex());
}
private static String proto(InsnData insn, int protoIndex) {
return String.format("%s # proto@%04x", insn.getIndexAsProto(protoIndex).toString(), protoIndex);
}
private static String methodHandle(InsnData insn) {
return String.format("%s # method_handle@%04x",
insn.getIndexAsMethodHandle().toString(), insn.getIndex());
}
private static boolean isGotoIns(int opcode) {
return opcode >= DexOpcodes.GOTO && opcode <= DexOpcodes.GOTO_32;
}
private static boolean isInvokeIns(int opcode) {
return (opcode >= DexOpcodes.INVOKE_VIRTUAL && opcode <= DexOpcodes.INVOKE_INTERFACE)
|| (opcode >= DexOpcodes.INVOKE_VIRTUAL_RANGE && opcode <= DexOpcodes.INVOKE_INTERFACE_RANGE)
|| (opcode >= DexOpcodes.INVOKE_POLYMORPHIC && opcode <= DexOpcodes.INVOKE_CUSTOM_RANGE);
}
private static boolean isRangeRegIns(int opcode) {
if (opcode >= DexOpcodes.INVOKE_VIRTUAL_RANGE && opcode <= DexOpcodes.INVOKE_INTERFACE_RANGE) {
return true;
}
switch (opcode) {
case DexOpcodes.FILLED_NEW_ARRAY_RANGE:
case DexOpcodes.INVOKE_CUSTOM_RANGE:
case DexOpcodes.INVOKE_POLYMORPHIC_RANGE:
return true;
}
return false;
}
private static boolean isWideIns(int opcode) {
return (opcode >= DexOpcodes.CONST_WIDE_16 && opcode <= DexOpcodes.CONST_WIDE_HIGH16);
}
private static boolean hasLiteral(int opcode) {
return (opcode >= DexOpcodes.CONST_4 && opcode <= DexOpcodes.CONST_WIDE_HIGH16)
|| (opcode >= DexOpcodes.ADD_INT_LIT16 && opcode <= DexOpcodes.USHR_INT_LIT8);
}
private static boolean isConditionIns(int opcode) {
return opcode >= DexOpcodes.IF_EQ && opcode <= DexOpcodes.IF_LEZ;
}
private static boolean hasTarget(int opcode) {
return (opcode >= DexOpcodes.IF_EQ && opcode <= DexOpcodes.IF_LEZ)
|| (opcode >= DexOpcodes.GOTO && opcode <= DexOpcodes.SPARSE_SWITCH)
|| (opcode == DexOpcodes.FILL_ARRAY_DATA);
}
private static boolean isRegList(int opcode) {
return isInvokeIns(opcode)
|| (opcode >= DexOpcodes.FILLED_NEW_ARRAY && opcode <= DexOpcodes.FILLED_NEW_ARRAY_RANGE);
}
}
}
@@ -31,4 +31,6 @@ public interface IClassData {
List<IAnnotation> getAnnotations();
String getDisassembledCode();
String getDisassembledCodeV2();
}
@@ -34,4 +34,9 @@ public class FieldRefHandle implements IMethodHandle {
public void load() {
// already loaded
}
@Override
public String toString() {
return type + ": " + fieldRef;
}
}
@@ -2,19 +2,21 @@ package jadx.api.plugins.input.insns;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.ICallSite;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodRef;
import jadx.api.plugins.input.data.*;
import jadx.api.plugins.input.insns.custom.ICustomPayload;
public interface InsnData {
void decode();
int getOffset();
int getOffset(); // offset within method
int getFileOffset(); // offset within dex file
Opcode getOpcode();
byte[] getByteCode();
InsnIndexType getIndexType();
int getRawOpcodeUnit();
@@ -39,6 +41,10 @@ public interface InsnData {
ICallSite getIndexAsCallSite();
IMethodProto getIndexAsProto(int protoIndex);
IMethodHandle getIndexAsMethodHandle();
@Nullable
ICustomPayload getPayload();
}