• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * [The "BSD licence"]
3  * Copyright (c) 2010 Ben Gruver (JesusFreke)
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 package org.jf.baksmali.Adaptors;
30 
31 import com.google.common.collect.ImmutableList;
32 import org.jf.baksmali.Adaptors.Debug.DebugMethodItem;
33 import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory;
34 import org.jf.baksmali.baksmaliOptions;
35 import org.jf.dexlib2.AccessFlags;
36 import org.jf.dexlib2.Format;
37 import org.jf.dexlib2.Opcode;
38 import org.jf.dexlib2.ReferenceType;
39 import org.jf.dexlib2.analysis.AnalysisException;
40 import org.jf.dexlib2.analysis.AnalyzedInstruction;
41 import org.jf.dexlib2.analysis.MethodAnalyzer;
42 import org.jf.dexlib2.dexbacked.DexBackedDexFile.InvalidItemIndex;
43 import org.jf.dexlib2.iface.*;
44 import org.jf.dexlib2.iface.debug.DebugItem;
45 import org.jf.dexlib2.iface.instruction.Instruction;
46 import org.jf.dexlib2.iface.instruction.OffsetInstruction;
47 import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
48 import org.jf.dexlib2.iface.reference.MethodReference;
49 import org.jf.dexlib2.util.InstructionOffsetMap;
50 import org.jf.dexlib2.util.InstructionOffsetMap.InvalidInstructionOffset;
51 import org.jf.dexlib2.util.ReferenceUtil;
52 import org.jf.dexlib2.util.SyntheticAccessorResolver;
53 import org.jf.dexlib2.util.TypeUtils;
54 import org.jf.util.ExceptionWithContext;
55 import org.jf.util.IndentingWriter;
56 import org.jf.util.SparseIntArray;
57 
58 import javax.annotation.Nonnull;
59 import java.io.IOException;
60 import java.util.*;
61 
62 public class MethodDefinition {
63     @Nonnull public final ClassDefinition classDef;
64     @Nonnull public final Method method;
65     @Nonnull public final MethodImplementation methodImpl;
66     @Nonnull public final ImmutableList<Instruction> instructions;
67     @Nonnull public final ImmutableList<MethodParameter> methodParameters;
68     public RegisterFormatter registerFormatter;
69 
70     @Nonnull private final LabelCache labelCache = new LabelCache();
71 
72     @Nonnull private final SparseIntArray packedSwitchMap;
73     @Nonnull private final SparseIntArray sparseSwitchMap;
74     @Nonnull private final InstructionOffsetMap instructionOffsetMap;
75 
MethodDefinition(@onnull ClassDefinition classDef, @Nonnull Method method, @Nonnull MethodImplementation methodImpl)76     public MethodDefinition(@Nonnull ClassDefinition classDef, @Nonnull Method method,
77                             @Nonnull MethodImplementation methodImpl) {
78         this.classDef = classDef;
79         this.method = method;
80         this.methodImpl = methodImpl;
81 
82         try {
83             //TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
84 
85             instructions = ImmutableList.copyOf(methodImpl.getInstructions());
86             methodParameters = ImmutableList.copyOf(method.getParameters());
87 
88             packedSwitchMap = new SparseIntArray(0);
89             sparseSwitchMap = new SparseIntArray(0);
90             instructionOffsetMap = new InstructionOffsetMap(instructions);
91 
92             for (int i=0; i<instructions.size(); i++) {
93                 Instruction instruction = instructions.get(i);
94 
95                 Opcode opcode = instruction.getOpcode();
96                 if (opcode == Opcode.PACKED_SWITCH) {
97                     boolean valid = true;
98                     int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
99                     int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
100                     try {
101                         targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD);
102                     } catch (InvalidSwitchPayload ex) {
103                         valid = false;
104                     }
105                     if (valid) {
106                         packedSwitchMap.append(targetOffset, codeOffset);
107                     }
108                 } else if (opcode == Opcode.SPARSE_SWITCH) {
109                     boolean valid = true;
110                     int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i);
111                     int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset();
112                     try {
113                         targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD);
114                     } catch (InvalidSwitchPayload ex) {
115                         valid = false;
116                         // The offset to the payload instruction was invalid. Nothing to do, except that we won't
117                         // add this instruction to the map.
118                     }
119                     if (valid) {
120                         sparseSwitchMap.append(targetOffset, codeOffset);
121                     }
122                 }
123             }
124         }catch (Exception ex) {
125             String methodString;
126             try {
127                 methodString = ReferenceUtil.getMethodDescriptor(method);
128             } catch (Exception ex2) {
129                 throw ExceptionWithContext.withContext(ex, "Error while processing method");
130             }
131             throw ExceptionWithContext.withContext(ex, "Error while processing method %s", methodString);
132         }
133     }
134 
writeEmptyMethodTo(IndentingWriter writer, Method method, baksmaliOptions options)135     public static void writeEmptyMethodTo(IndentingWriter writer, Method method,
136                                           baksmaliOptions options) throws IOException {
137         writer.write(".method ");
138         writeAccessFlags(writer, method.getAccessFlags());
139         writer.write(method.getName());
140         writer.write("(");
141         ImmutableList<MethodParameter> methodParameters = ImmutableList.copyOf(method.getParameters());
142         for (MethodParameter parameter: methodParameters) {
143             writer.write(parameter.getType());
144         }
145         writer.write(")");
146         writer.write(method.getReturnType());
147         writer.write('\n');
148 
149         writer.indent(4);
150         writeParameters(writer, method, methodParameters, options);
151         AnnotationFormatter.writeTo(writer, method.getAnnotations());
152         writer.deindent(4);
153         writer.write(".end method\n");
154     }
155 
writeTo(IndentingWriter writer)156     public void writeTo(IndentingWriter writer) throws IOException {
157         int parameterRegisterCount = 0;
158         if (!AccessFlags.STATIC.isSet(method.getAccessFlags())) {
159             parameterRegisterCount++;
160         }
161 
162         writer.write(".method ");
163         writeAccessFlags(writer, method.getAccessFlags());
164         writer.write(method.getName());
165         writer.write("(");
166         for (MethodParameter parameter: methodParameters) {
167             String type = parameter.getType();
168             writer.write(type);
169             parameterRegisterCount++;
170             if (TypeUtils.isWideType(type)) {
171                 parameterRegisterCount++;
172             }
173         }
174         writer.write(")");
175         writer.write(method.getReturnType());
176         writer.write('\n');
177 
178         writer.indent(4);
179         if (classDef.options.useLocalsDirective) {
180             writer.write(".locals ");
181             writer.printSignedIntAsDec(methodImpl.getRegisterCount() - parameterRegisterCount);
182         } else {
183             writer.write(".registers ");
184             writer.printSignedIntAsDec(methodImpl.getRegisterCount());
185         }
186         writer.write('\n');
187         writeParameters(writer, method, methodParameters, classDef.options);
188 
189         if (registerFormatter == null) {
190             registerFormatter = new RegisterFormatter(classDef.options, methodImpl.getRegisterCount(),
191                     parameterRegisterCount);
192         }
193 
194         AnnotationFormatter.writeTo(writer, method.getAnnotations());
195 
196         writer.write('\n');
197 
198         List<MethodItem> methodItems = getMethodItems();
199         for (MethodItem methodItem: methodItems) {
200             if (methodItem.writeTo(writer)) {
201                 writer.write('\n');
202             }
203         }
204         writer.deindent(4);
205         writer.write(".end method\n");
206     }
207 
findSwitchPayload(int targetOffset, Opcode type)208     public int findSwitchPayload(int targetOffset, Opcode type) {
209         int targetIndex;
210         try {
211             targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset);
212         } catch (InvalidInstructionOffset ex) {
213             throw new InvalidSwitchPayload(targetOffset);
214         }
215 
216         //TODO: does dalvik let you pad with multiple nops?
217         //TODO: does dalvik let a switch instruction point to a non-payload instruction?
218 
219         Instruction instruction = instructions.get(targetIndex);
220         if (instruction.getOpcode() != type) {
221             // maybe it's pointing to a NOP padding instruction. Look at the next instruction
222             if (instruction.getOpcode() == Opcode.NOP) {
223                 targetIndex += 1;
224                 if (targetIndex < instructions.size()) {
225                     instruction = instructions.get(targetIndex);
226                     if (instruction.getOpcode() == type) {
227                         return instructionOffsetMap.getInstructionCodeOffset(targetIndex);
228                     }
229                 }
230             }
231             throw new InvalidSwitchPayload(targetOffset);
232         } else {
233             return targetOffset;
234         }
235     }
236 
writeAccessFlags(IndentingWriter writer, int accessFlags)237     private static void writeAccessFlags(IndentingWriter writer, int accessFlags)
238             throws IOException {
239         for (AccessFlags accessFlag: AccessFlags.getAccessFlagsForMethod(accessFlags)) {
240             writer.write(accessFlag.toString());
241             writer.write(' ');
242         }
243     }
244 
writeParameters(IndentingWriter writer, Method method, List<? extends MethodParameter> parameters, baksmaliOptions options)245     private static void writeParameters(IndentingWriter writer, Method method,
246                                         List<? extends MethodParameter> parameters,
247                                         baksmaliOptions options) throws IOException {
248         boolean isStatic = AccessFlags.STATIC.isSet(method.getAccessFlags());
249         int registerNumber = isStatic?0:1;
250         for (MethodParameter parameter: parameters) {
251             String parameterType = parameter.getType();
252             String parameterName = parameter.getName();
253             Collection<? extends Annotation> annotations = parameter.getAnnotations();
254             if (parameterName != null || annotations.size() != 0) {
255                 writer.write(".param p");
256                 writer.printSignedIntAsDec(registerNumber);
257 
258                 if (parameterName != null && options.outputDebugInfo) {
259                     writer.write(", ");
260                     ReferenceFormatter.writeStringReference(writer, parameterName);
261                 }
262                 writer.write("    # ");
263                 writer.write(parameterType);
264                 writer.write("\n");
265                 if (annotations.size() > 0) {
266                     writer.indent(4);
267                     AnnotationFormatter.writeTo(writer, annotations);
268                     writer.deindent(4);
269                     writer.write(".end param\n");
270                 }
271             }
272 
273             registerNumber++;
274             if (TypeUtils.isWideType(parameterType)) {
275                 registerNumber++;
276             }
277         }
278     }
279 
getLabelCache()280     @Nonnull public LabelCache getLabelCache() {
281         return labelCache;
282     }
283 
getPackedSwitchBaseAddress(int packedSwitchPayloadCodeOffset)284     public int getPackedSwitchBaseAddress(int packedSwitchPayloadCodeOffset) {
285         return packedSwitchMap.get(packedSwitchPayloadCodeOffset, -1);
286     }
287 
getSparseSwitchBaseAddress(int sparseSwitchPayloadCodeOffset)288     public int getSparseSwitchBaseAddress(int sparseSwitchPayloadCodeOffset) {
289         return sparseSwitchMap.get(sparseSwitchPayloadCodeOffset, -1);
290     }
291 
getMethodItems()292     private List<MethodItem> getMethodItems() {
293         ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>();
294 
295         if ((classDef.options.registerInfo != 0) || (classDef.options.deodex && needsAnalyzed())) {
296             addAnalyzedInstructionMethodItems(methodItems);
297         } else {
298             addInstructionMethodItems(methodItems);
299         }
300 
301         addTries(methodItems);
302         if (classDef.options.outputDebugInfo) {
303             addDebugInfo(methodItems);
304         }
305 
306         if (classDef.options.useSequentialLabels) {
307             setLabelSequentialNumbers();
308         }
309 
310         for (LabelMethodItem labelMethodItem: labelCache.getLabels()) {
311             methodItems.add(labelMethodItem);
312         }
313 
314         Collections.sort(methodItems);
315 
316         return methodItems;
317     }
318 
needsAnalyzed()319     private boolean needsAnalyzed() {
320         for (Instruction instruction: methodImpl.getInstructions()) {
321             if (instruction.getOpcode().odexOnly()) {
322                 return true;
323             }
324         }
325         return false;
326     }
327 
addInstructionMethodItems(List<MethodItem> methodItems)328     private void addInstructionMethodItems(List<MethodItem> methodItems) {
329         int currentCodeAddress = 0;
330         for (int i=0; i<instructions.size(); i++) {
331             Instruction instruction = instructions.get(i);
332 
333             MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
334                     currentCodeAddress, instruction);
335 
336             methodItems.add(methodItem);
337 
338             if (i != instructions.size() - 1) {
339                 methodItems.add(new BlankMethodItem(currentCodeAddress));
340             }
341 
342             if (classDef.options.addCodeOffsets) {
343                 methodItems.add(new MethodItem(currentCodeAddress) {
344 
345                     @Override
346                     public double getSortOrder() {
347                         return -1000;
348                     }
349 
350                     @Override
351                     public boolean writeTo(IndentingWriter writer) throws IOException {
352                         writer.write("#@");
353                         writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL);
354                         return true;
355                     }
356                 });
357             }
358 
359             if (!classDef.options.noAccessorComments && (instruction instanceof ReferenceInstruction)) {
360                 Opcode opcode = instruction.getOpcode();
361 
362                 if (opcode.referenceType == ReferenceType.METHOD) {
363                     MethodReference methodReference = null;
364                     try {
365                         methodReference = (MethodReference)((ReferenceInstruction)instruction).getReference();
366                     } catch (InvalidItemIndex ex) {
367                         // just ignore it for now. We'll deal with it later, when processing the instructions
368                         // themselves
369                     }
370 
371                     if (methodReference != null &&
372                             SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodReference.getName())) {
373                         SyntheticAccessorResolver.AccessedMember accessedMember =
374                                 classDef.options.syntheticAccessorResolver.getAccessedMember(methodReference);
375                         if (accessedMember != null) {
376                             methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress));
377                         }
378                     }
379                 }
380             }
381 
382             currentCodeAddress += instruction.getCodeUnits();
383         }
384     }
385 
addAnalyzedInstructionMethodItems(List<MethodItem> methodItems)386     private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) {
387         MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classDef.options.classPath, method,
388                 classDef.options.inlineResolver);
389 
390         AnalysisException analysisException = methodAnalyzer.getAnalysisException();
391         if (analysisException != null) {
392             // TODO: need to keep track of whether any errors occurred, so we can exit with a non-zero result
393             methodItems.add(new CommentMethodItem(
394                     String.format("AnalysisException: %s", analysisException.getMessage()),
395                     analysisException.codeAddress, Integer.MIN_VALUE));
396             analysisException.printStackTrace(System.err);
397         }
398 
399         List<AnalyzedInstruction> instructions = methodAnalyzer.getAnalyzedInstructions();
400 
401         int currentCodeAddress = 0;
402         for (int i=0; i<instructions.size(); i++) {
403             AnalyzedInstruction instruction = instructions.get(i);
404 
405             MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(
406                     this, currentCodeAddress, instruction.getInstruction());
407 
408             methodItems.add(methodItem);
409 
410             if (instruction.getInstruction().getOpcode().format == Format.UnresolvedOdexInstruction) {
411                 methodItems.add(new CommentedOutMethodItem(
412                         InstructionMethodItemFactory.makeInstructionFormatMethodItem(
413                                 this, currentCodeAddress, instruction.getOriginalInstruction())));
414             }
415 
416             if (i != instructions.size() - 1) {
417                 methodItems.add(new BlankMethodItem(currentCodeAddress));
418             }
419 
420             if (classDef.options.addCodeOffsets) {
421                 methodItems.add(new MethodItem(currentCodeAddress) {
422 
423                     @Override
424                     public double getSortOrder() {
425                         return -1000;
426                     }
427 
428                     @Override
429                     public boolean writeTo(IndentingWriter writer) throws IOException {
430                         writer.write("#@");
431                         writer.printUnsignedLongAsHex(codeAddress & 0xFFFFFFFFL);
432                         return true;
433                     }
434                 });
435             }
436 
437             if (classDef.options.registerInfo != 0 &&
438                     !instruction.getInstruction().getOpcode().format.isPayloadFormat) {
439                 methodItems.add(
440                         new PreInstructionRegisterInfoMethodItem(classDef.options.registerInfo,
441                                 methodAnalyzer, registerFormatter, instruction, currentCodeAddress));
442 
443                 methodItems.add(
444                         new PostInstructionRegisterInfoMethodItem(registerFormatter, instruction, currentCodeAddress));
445             }
446 
447             currentCodeAddress += instruction.getInstruction().getCodeUnits();
448         }
449     }
450 
addTries(List<MethodItem> methodItems)451     private void addTries(List<MethodItem> methodItems) {
452         List<? extends TryBlock<? extends ExceptionHandler>> tryBlocks = methodImpl.getTryBlocks();
453         if (tryBlocks.size() == 0) {
454             return;
455         }
456 
457         int lastInstructionAddress = instructionOffsetMap.getInstructionCodeOffset(instructions.size() - 1);
458         int codeSize = lastInstructionAddress + instructions.get(instructions.size() - 1).getCodeUnits();
459 
460         for (TryBlock<? extends ExceptionHandler> tryBlock: tryBlocks) {
461             int startAddress = tryBlock.getStartCodeAddress();
462             int endAddress = startAddress + tryBlock.getCodeUnitCount();
463 
464             if (startAddress >= codeSize) {
465                 throw new RuntimeException(String.format("Try start offset %d is past the end of the code block.",
466                         startAddress));
467             }
468             // Note: not >=. endAddress == codeSize is valid, when the try covers the last instruction
469             if (endAddress > codeSize) {
470                 throw new RuntimeException(String.format("Try end offset %d is past the end of the code block.",
471                         endAddress));
472             }
473 
474             /**
475              * The end address points to the address immediately after the end of the last
476              * instruction that the try block covers. We want the .catch directive and end_try
477              * label to be associated with the last covered instruction, so we need to get
478              * the address for that instruction
479              */
480 
481             int lastCoveredIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(endAddress - 1, false);
482             int lastCoveredAddress = instructionOffsetMap.getInstructionCodeOffset(lastCoveredIndex);
483 
484             for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) {
485                 int handlerAddress = handler.getHandlerCodeAddress();
486                 if (handlerAddress >= codeSize) {
487                     throw new ExceptionWithContext(
488                             "Exception handler offset %d is past the end of the code block.", handlerAddress);
489                 }
490 
491                 //use the address from the last covered instruction
492                 CatchMethodItem catchMethodItem = new CatchMethodItem(classDef.options, labelCache, lastCoveredAddress,
493                         handler.getExceptionType(), startAddress, endAddress, handlerAddress);
494                 methodItems.add(catchMethodItem);
495             }
496         }
497     }
498 
addDebugInfo(final List<MethodItem> methodItems)499     private void addDebugInfo(final List<MethodItem> methodItems) {
500         for (DebugItem debugItem: methodImpl.getDebugItems()) {
501             methodItems.add(DebugMethodItem.build(registerFormatter, debugItem));
502         }
503     }
504 
setLabelSequentialNumbers()505     private void setLabelSequentialNumbers() {
506         HashMap<String, Integer> nextLabelSequenceByType = new HashMap<String, Integer>();
507         ArrayList<LabelMethodItem> sortedLabels = new ArrayList<LabelMethodItem>(labelCache.getLabels());
508 
509         //sort the labels by their location in the method
510         Collections.sort(sortedLabels);
511 
512         for (LabelMethodItem labelMethodItem: sortedLabels) {
513             Integer labelSequence = nextLabelSequenceByType.get(labelMethodItem.getLabelPrefix());
514             if (labelSequence == null) {
515                 labelSequence = 0;
516             }
517             labelMethodItem.setLabelSequence(labelSequence);
518             nextLabelSequenceByType.put(labelMethodItem.getLabelPrefix(), labelSequence + 1);
519         }
520     }
521 
522     public static class LabelCache {
523         protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
524 
LabelCache()525         public LabelCache() {
526         }
527 
internLabel(LabelMethodItem labelMethodItem)528         public LabelMethodItem internLabel(LabelMethodItem labelMethodItem) {
529             LabelMethodItem internedLabelMethodItem = labels.get(labelMethodItem);
530             if (internedLabelMethodItem != null) {
531                 return internedLabelMethodItem;
532             }
533             labels.put(labelMethodItem, labelMethodItem);
534             return labelMethodItem;
535         }
536 
537 
getLabels()538         public Collection<LabelMethodItem> getLabels() {
539             return labels.values();
540         }
541     }
542 
543     public static class InvalidSwitchPayload extends ExceptionWithContext {
544         private final int payloadOffset;
545 
InvalidSwitchPayload(int payloadOffset)546         public InvalidSwitchPayload(int payloadOffset) {
547             super("No switch payload at offset: %d", payloadOffset);
548             this.payloadOffset = payloadOffset;
549         }
550 
getPayloadOffset()551         public int getPayloadOffset() {
552             return payloadOffset;
553         }
554     }
555 }
556