|
20 | 20 | import brut.util.OS; |
21 | 21 | import com.android.tools.smali.baksmali.Baksmali; |
22 | 22 | import com.android.tools.smali.baksmali.BaksmaliOptions; |
23 | | -import com.android.tools.smali.dexlib2.DexFileFactory; |
24 | 23 | import com.android.tools.smali.dexlib2.analysis.InlineMethodResolver; |
25 | 24 | import com.android.tools.smali.dexlib2.dexbacked.DexBackedDexFile; |
26 | 25 | import com.android.tools.smali.dexlib2.dexbacked.DexBackedOdexFile; |
27 | | -import com.android.tools.smali.dexlib2.iface.DexFile; |
28 | | -import com.android.tools.smali.dexlib2.iface.MultiDexContainer; |
| 26 | +import com.android.tools.smali.dexlib2.dexbacked.ZipDexContainer; |
29 | 27 |
|
30 | 28 | import java.io.File; |
31 | 29 | import java.io.IOException; |
32 | | -import java.util.HashSet; |
| 30 | +import java.util.Map; |
33 | 31 | import java.util.Set; |
| 32 | +import java.util.TreeMap; |
| 33 | +import java.util.concurrent.ConcurrentHashMap; |
| 34 | +import java.util.concurrent.atomic.AtomicInteger; |
34 | 35 |
|
35 | 36 | public class SmaliDecoder { |
36 | | - private final File mApkFile; |
| 37 | + private final ZipDexContainer mDexContainer; |
37 | 38 | private final boolean mDebugMode; |
38 | 39 | private final Set<String> mDexFiles; |
39 | | - private int mInferredApiLevel; |
| 40 | + private final AtomicInteger mInferredApiLevel; |
40 | 41 |
|
41 | | - public SmaliDecoder(File apkFile, boolean debugMode) { |
42 | | - mApkFile = apkFile; |
| 42 | + public SmaliDecoder(File apkFile, boolean debugMode) throws AndrolibException { |
| 43 | + mDexContainer = new ZipDexContainer(apkFile, null); |
| 44 | + // ZipDexContainer is lazily initialized and not thread-safe. Eagerly initialize on the constructing thread. |
| 45 | + try { |
| 46 | + mDexContainer.getEntry(""); |
| 47 | + } catch (IOException ex) { |
| 48 | + throw new AndrolibException("Could not open apk file: " + apkFile, ex); |
| 49 | + } |
43 | 50 | mDebugMode = debugMode; |
44 | | - mDexFiles = new HashSet<>(); |
| 51 | + mDexFiles = ConcurrentHashMap.newKeySet(); |
| 52 | + mInferredApiLevel = new AtomicInteger(); |
45 | 53 | } |
46 | 54 |
|
47 | 55 | public Set<String> getDexFiles() { |
48 | 56 | return mDexFiles; |
49 | 57 | } |
50 | 58 |
|
51 | 59 | public int getInferredApiLevel() { |
52 | | - return mInferredApiLevel; |
| 60 | + return mInferredApiLevel.get(); |
53 | 61 | } |
54 | 62 |
|
55 | | - public void decode(String dexName, File smaliDir) throws AndrolibException { |
| 63 | + public void decode(String dexName, File outDir) throws AndrolibException { |
56 | 64 | try { |
57 | | - BaksmaliOptions options = new BaksmaliOptions(); |
58 | | - options.deodex = false; |
59 | | - options.implicitReferences = false; |
60 | | - options.parameterRegisters = true; |
61 | | - options.localsDirective = true; |
62 | | - options.sequentialLabels = true; |
63 | | - options.debugInfo = mDebugMode; |
64 | | - options.codeOffsets = false; |
65 | | - options.accessorComments = false; |
66 | | - options.registerInfo = 0; |
67 | | - options.inlineResolver = null; |
68 | | - |
69 | | - // Set jobs automatically. |
70 | | - int jobs = Runtime.getRuntime().availableProcessors(); |
71 | | - if (jobs > 6) { |
72 | | - jobs = 6; |
| 65 | + // Fetch the requested dex file from the dex container. |
| 66 | + ZipDexContainer.DexEntry<DexBackedDexFile> dexEntry = mDexContainer.getEntry(dexName); |
| 67 | + if (dexEntry == null) { |
| 68 | + throw new AndrolibException("Could not find file: " + dexName); |
73 | 69 | } |
74 | 70 |
|
75 | | - // Create the container. |
76 | | - MultiDexContainer<? extends DexBackedDexFile> container = DexFileFactory.loadDexContainer(mApkFile, null); |
77 | | - |
78 | | - // If we have 1 item, ignore the passed file. Pull the DexFile we need. |
79 | | - MultiDexContainer.DexEntry<? extends DexBackedDexFile> dexEntry = |
80 | | - container.getDexEntryNames().size() == 1 |
81 | | - ? container.getEntry(container.getDexEntryNames().get(0)) |
82 | | - : container.getEntry(dexName); |
| 71 | + // Add the requested dex file. |
| 72 | + Map<Integer, DexBackedDexFile> dexFiles = new TreeMap<>(); |
| 73 | + dexFiles.put(1, dexEntry.getDexFile()); |
83 | 74 |
|
84 | | - // Double-check the passed param exists. |
85 | | - if (dexEntry == null) { |
86 | | - dexEntry = container.getEntry(container.getDexEntryNames().get(0)); |
87 | | - assert dexEntry != null; |
88 | | - } |
| 75 | + // Add additional dex files if it's a multi-dex container. |
| 76 | + for (String dexEntryName : mDexContainer.getDexEntryNames()) { |
| 77 | + if (dexEntryName.equals(dexName)) { |
| 78 | + continue; |
| 79 | + } |
89 | 80 |
|
90 | | - DexBackedDexFile dexFile = dexEntry.getDexFile(); |
| 81 | + String prefix = dexName + "/"; |
| 82 | + if (!dexEntryName.startsWith(prefix)) { |
| 83 | + continue; |
| 84 | + } |
91 | 85 |
|
92 | | - if (dexFile.supportsOptimizedOpcodes()) { |
93 | | - throw new AndrolibException("Could not disassemble an odex file without deodexing it."); |
| 86 | + int dexNum; |
| 87 | + try { |
| 88 | + dexNum = Integer.parseInt(dexEntryName.substring(prefix.length())); |
| 89 | + } catch (NumberFormatException ignored) { |
| 90 | + continue; |
| 91 | + } |
| 92 | + if (dexNum > 1) { |
| 93 | + dexFiles.put(dexNum, mDexContainer.getEntry(dexEntryName).getDexFile()); |
| 94 | + } |
94 | 95 | } |
95 | 96 |
|
96 | | - if (dexFile instanceof DexBackedOdexFile) { |
97 | | - options.inlineResolver = InlineMethodResolver.createInlineMethodResolver( |
98 | | - ((DexBackedOdexFile) dexFile).getOdexVersion()); |
99 | | - } |
| 97 | + // Decode the dex files into separate folders. |
| 98 | + for (Map.Entry<Integer, DexBackedDexFile> entry : dexFiles.entrySet()) { |
| 99 | + int dexNum = entry.getKey(); |
| 100 | + DexBackedDexFile dexFile = entry.getValue(); |
100 | 101 |
|
101 | | - OS.mkdir(smaliDir); |
102 | | - Baksmali.disassembleDexFile(dexFile, smaliDir, jobs, options); |
| 102 | + if (dexFile.supportsOptimizedOpcodes()) { |
| 103 | + throw new AndrolibException("Cannot disassemble an odex file without deodexing it: " + dexName); |
| 104 | + } |
103 | 105 |
|
104 | | - synchronized (mDexFiles) { |
105 | | - int apiLevel = dexFile.getOpcodes().api; |
106 | | - if (mInferredApiLevel == 0 || mInferredApiLevel > apiLevel) { |
107 | | - mInferredApiLevel = apiLevel; |
| 106 | + String dirName = "smali"; |
| 107 | + if (dexNum > 1 || !dexName.equals("classes.dex")) { |
| 108 | + dirName += "_" + dexName.substring(0, dexName.lastIndexOf('.')).replace('/', '@'); |
| 109 | + if (dexNum > 1) { |
| 110 | + dirName += dexNum; |
| 111 | + } |
108 | 112 | } |
109 | 113 |
|
110 | | - mDexFiles.add(dexName); |
| 114 | + decodeFile(dexFile, new File(outDir, dirName)); |
111 | 115 | } |
| 116 | + |
| 117 | + mDexFiles.add(dexName); |
112 | 118 | } catch (IOException ex) { |
113 | 119 | throw new AndrolibException("Could not baksmali file: " + dexName, ex); |
114 | 120 | } |
115 | 121 | } |
| 122 | + |
| 123 | + private void decodeFile(DexBackedDexFile dexFile, File smaliDir) { |
| 124 | + int jobs = Math.min(Runtime.getRuntime().availableProcessors(), 6); |
| 125 | + |
| 126 | + BaksmaliOptions options = new BaksmaliOptions(); |
| 127 | + options.parameterRegisters = true; |
| 128 | + options.localsDirective = true; |
| 129 | + options.sequentialLabels = true; |
| 130 | + options.debugInfo = mDebugMode; |
| 131 | + options.codeOffsets = false; |
| 132 | + options.accessorComments = false; |
| 133 | + options.allowOdex = false; |
| 134 | + options.deodex = false; |
| 135 | + options.implicitReferences = false; |
| 136 | + options.normalizeVirtualMethods = false; |
| 137 | + options.registerInfo = 0; |
| 138 | + |
| 139 | + if (dexFile instanceof DexBackedOdexFile) { |
| 140 | + options.inlineResolver = InlineMethodResolver.createInlineMethodResolver( |
| 141 | + ((DexBackedOdexFile) dexFile).getOdexVersion()); |
| 142 | + } |
| 143 | + |
| 144 | + OS.mkdir(smaliDir); |
| 145 | + Baksmali.disassembleDexFile(dexFile, smaliDir, jobs, options); |
| 146 | + |
| 147 | + int apiLevel = dexFile.getOpcodes().api; |
| 148 | + mInferredApiLevel.updateAndGet(cur -> (cur == 0 || cur > apiLevel) ? apiLevel : cur); |
| 149 | + } |
116 | 150 | } |
0 commit comments