Home | History | Annotate | Download | only in debugging
      1 /*
      2  * Copyright 2014, Google Inc.
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 package org.jf.smalidea.debugging;
     33 
     34 import com.google.common.collect.Lists;
     35 import com.google.common.collect.Maps;
     36 import com.intellij.debugger.SourcePosition;
     37 import com.intellij.debugger.engine.evaluation.*;
     38 import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilder;
     39 import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
     40 import com.intellij.debugger.engine.jdi.StackFrameProxy;
     41 import com.intellij.openapi.application.ApplicationManager;
     42 import com.intellij.openapi.fileTypes.LanguageFileType;
     43 import com.intellij.openapi.project.Project;
     44 import com.intellij.openapi.util.Computable;
     45 import com.intellij.openapi.util.Key;
     46 import com.intellij.psi.JavaCodeFragment;
     47 import com.intellij.psi.JavaRecursiveElementVisitor;
     48 import com.intellij.psi.PsiElement;
     49 import com.intellij.psi.PsiLocalVariable;
     50 import com.intellij.psi.util.PsiMatchers;
     51 import com.sun.jdi.*;
     52 import com.sun.tools.jdi.LocalVariableImpl;
     53 import com.sun.tools.jdi.LocationImpl;
     54 import org.jf.dexlib2.analysis.AnalyzedInstruction;
     55 import org.jf.dexlib2.analysis.RegisterType;
     56 import org.jf.smalidea.SmaliFileType;
     57 import org.jf.smalidea.SmaliLanguage;
     58 import org.jf.smalidea.debugging.value.LazyValue;
     59 import org.jf.smalidea.psi.impl.SmaliInstruction;
     60 import org.jf.smalidea.psi.impl.SmaliMethod;
     61 import org.jf.smalidea.util.NameUtils;
     62 import org.jf.smalidea.util.PsiUtil;
     63 
     64 import javax.annotation.Nullable;
     65 import java.lang.reflect.Constructor;
     66 import java.lang.reflect.InvocationTargetException;
     67 import java.util.List;
     68 import java.util.Map;
     69 
     70 public class SmaliCodeFragmentFactory extends DefaultCodeFragmentFactory {
     71     static final Key<List<LazyValue>> SMALI_LAZY_VALUES_KEY = Key.create("_smali_register_value_key_");
     72 
     73     @Override
     74     public JavaCodeFragment createCodeFragment(TextWithImports item, PsiElement context, Project project) {
     75         context = wrapContext(project, context);
     76         JavaCodeFragment fragment = super.createCodeFragment(item, context, project);
     77         List<LazyValue> lazyValues = context.getUserData(SMALI_LAZY_VALUES_KEY);
     78         if (lazyValues != null) {
     79             fragment.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues);
     80         }
     81         return fragment;
     82     }
     83 
     84     @Override
     85     public boolean isContextAccepted(PsiElement contextElement) {
     86         if (contextElement == null) {
     87             return false;
     88         }
     89         return contextElement.getLanguage() == SmaliLanguage.INSTANCE;
     90     }
     91 
     92     @Override
     93     public JavaCodeFragment createPresentationCodeFragment(TextWithImports item, PsiElement context, Project project) {
     94         context = wrapContext(project, context);
     95         JavaCodeFragment fragment = super.createPresentationCodeFragment(item, context, project);
     96         List<LazyValue> lazyValues = context.getUserData(SMALI_LAZY_VALUES_KEY);
     97         if (lazyValues != null) {
     98             fragment.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues);
     99         }
    100         return fragment;
    101     }
    102 
    103     @Override public LanguageFileType getFileType() {
    104         return SmaliFileType.INSTANCE;
    105     }
    106 
    107     @Override public EvaluatorBuilder getEvaluatorBuilder() {
    108         final EvaluatorBuilder builder = super.getEvaluatorBuilder();
    109         return new EvaluatorBuilder() {
    110 
    111             @Override
    112             public ExpressionEvaluator build(PsiElement codeFragment, SourcePosition position)
    113                     throws EvaluateException {
    114                 return new SmaliExpressionEvaluator(codeFragment, builder.build(codeFragment, position));
    115             }
    116         };
    117     }
    118 
    119     private PsiElement wrapContext(final Project project, final PsiElement originalContext) {
    120         if (project.isDefault()) return originalContext;
    121 
    122         final List<LazyValue> lazyValues = Lists.newArrayList();
    123 
    124         SmaliInstruction currentInstruction = (SmaliInstruction)PsiUtil.searchBackward(originalContext,
    125                 PsiMatchers.hasClass(SmaliInstruction.class),
    126                 PsiMatchers.hasClass(SmaliMethod.class));
    127 
    128         if (currentInstruction == null) {
    129             currentInstruction = (SmaliInstruction)PsiUtil.searchForward(originalContext,
    130                     PsiMatchers.hasClass(SmaliInstruction.class),
    131                     PsiMatchers.hasClass(SmaliMethod.class));
    132             if (currentInstruction == null) {
    133                 return originalContext;
    134             }
    135         }
    136 
    137         final SmaliMethod containingMethod = currentInstruction.getParentMethod();
    138         AnalyzedInstruction analyzedInstruction = currentInstruction.getAnalyzedInstruction();
    139         if (analyzedInstruction == null) {
    140             return originalContext;
    141         }
    142 
    143         final int firstParameterRegister = containingMethod.getRegisterCount() -
    144                 containingMethod.getParameterRegisterCount();
    145 
    146         final Map<String, String> registerMap = Maps.newHashMap();
    147         StringBuilder variablesText = new StringBuilder();
    148         for (int i=0; i<containingMethod.getRegisterCount(); i++) {
    149             int parameterRegisterNumber = i - firstParameterRegister;
    150 
    151             RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(i);
    152             switch (registerType.category) {
    153                 case RegisterType.UNKNOWN:
    154                 case RegisterType.UNINIT:
    155                 case RegisterType.CONFLICTED:
    156                 case RegisterType.LONG_HI:
    157                 case RegisterType.DOUBLE_HI:
    158                     continue;
    159                 case RegisterType.NULL:
    160                 case RegisterType.ONE:
    161                 case RegisterType.INTEGER:
    162                     variablesText.append("int v").append(i).append(";\n");
    163                     registerMap.put("v" + i, "I");
    164                     if (parameterRegisterNumber >= 0) {
    165                         variablesText.append("int p").append(parameterRegisterNumber).append(";\n");
    166                         registerMap.put("p" + parameterRegisterNumber, "I");
    167                     }
    168                     break;
    169                 case RegisterType.BOOLEAN:
    170                     variablesText.append("boolean v").append(i).append(";\n");
    171                     registerMap.put("v" + i, "Z");
    172                     if (parameterRegisterNumber >= 0) {
    173                         variablesText.append("boolean p").append(parameterRegisterNumber).append(";\n");
    174                         registerMap.put("p" + parameterRegisterNumber, "Z");
    175                     }
    176                     break;
    177                 case RegisterType.BYTE:
    178                 case RegisterType.POS_BYTE:
    179                     variablesText.append("byte v").append(i).append(";\n");
    180                     registerMap.put("v" + i, "B");
    181                     if (parameterRegisterNumber >= 0) {
    182                         variablesText.append("byte p").append(parameterRegisterNumber).append(";\n");
    183                         registerMap.put("p" + parameterRegisterNumber, "B");
    184                     }
    185                     break;
    186                 case RegisterType.SHORT:
    187                 case RegisterType.POS_SHORT:
    188                     variablesText.append("short v").append(i).append(";\n");
    189                     registerMap.put("v" + i, "S");
    190                     if (parameterRegisterNumber >= 0) {
    191                         variablesText.append("short p").append(parameterRegisterNumber).append(";\n");
    192                         registerMap.put("p" + parameterRegisterNumber, "S");
    193                     }
    194                     break;
    195                 case RegisterType.CHAR:
    196                     variablesText.append("char v").append(i).append(";\n");
    197                     registerMap.put("v" + i, "C");
    198                     if (parameterRegisterNumber >= 0) {
    199                         variablesText.append("char p").append(parameterRegisterNumber).append(";\n");
    200                         registerMap.put("p" + parameterRegisterNumber, "C");
    201                     }
    202                     break;
    203                 case RegisterType.FLOAT:
    204                     variablesText.append("float v").append(i).append(";\n");
    205                     registerMap.put("v" + i, "F");
    206                     if (parameterRegisterNumber >= 0) {
    207                         variablesText.append("float p").append(parameterRegisterNumber).append(";\n");
    208                         registerMap.put("p" + parameterRegisterNumber, "F");
    209                     }
    210                     break;
    211                 case RegisterType.LONG_LO:
    212                     variablesText.append("long v").append(i).append(";\n");
    213                     registerMap.put("v" + i, "J");
    214                     if (parameterRegisterNumber >= 0) {
    215                         variablesText.append("long p").append(parameterRegisterNumber).append(";\n");
    216                         registerMap.put("p" + parameterRegisterNumber, "J");
    217                     }
    218                     break;
    219                 case RegisterType.DOUBLE_LO:
    220                     variablesText.append("double v").append(i).append(";\n");
    221                     registerMap.put("v" + i, "D");
    222                     if (parameterRegisterNumber >= 0) {
    223                         variablesText.append("double p").append(parameterRegisterNumber).append(";\n");
    224                         registerMap.put("p" + parameterRegisterNumber, "D");
    225                     }
    226                     break;
    227                 case RegisterType.UNINIT_REF:
    228                 case RegisterType.UNINIT_THIS:
    229                 case RegisterType.REFERENCE:
    230                     String smaliType = registerType.type.getType();
    231                     String javaType = NameUtils.smaliToJavaType(smaliType);
    232                     variablesText.append(javaType).append(" v").append(i).append(";\n");
    233                     registerMap.put("v" + i, smaliType);
    234                     if (parameterRegisterNumber >= 0) {
    235                         variablesText.append(javaType).append(" p").append(parameterRegisterNumber).append(";\n");
    236                         registerMap.put("p" + parameterRegisterNumber, "Ljava/lang/Object;");
    237                     }
    238                     break;
    239             }
    240         }
    241         final TextWithImportsImpl textWithImports = new TextWithImportsImpl(CodeFragmentKind.CODE_BLOCK,
    242                 variablesText.toString(), "", getFileType());
    243 
    244         final JavaCodeFragment codeFragment = super.createCodeFragment(textWithImports, originalContext, project);
    245 
    246         codeFragment.accept(new JavaRecursiveElementVisitor() {
    247             @Override
    248             public void visitLocalVariable(final PsiLocalVariable variable) {
    249                 final String name = variable.getName();
    250                 if (name != null && registerMap.containsKey(name)) {
    251                     int registerNumber = Integer.parseInt(name.substring(1));
    252                     if (name.charAt(0) == 'p') {
    253                         registerNumber += ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
    254                             @Override public Integer compute() {
    255                                 return containingMethod.getRegisterCount() -
    256                                         containingMethod.getParameterRegisterCount();
    257                             }
    258                         });
    259                     }
    260                     LazyValue lazyValue = LazyValue.create(containingMethod, project, registerNumber,
    261                             registerMap.get(name));
    262                     variable.putUserData(CodeFragmentFactoryContextWrapper.LABEL_VARIABLE_VALUE_KEY, lazyValue);
    263                     lazyValues.add(lazyValue);
    264                 }
    265             }
    266         });
    267 
    268         int offset = variablesText.length() - 1;
    269 
    270         final PsiElement newContext = codeFragment.findElementAt(offset);
    271         if (newContext != null) {
    272             newContext.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues);
    273             return newContext;
    274         }
    275         return originalContext;
    276     }
    277 
    278     @Nullable
    279     public static Value evaluateRegister(EvaluationContext context, final SmaliMethod smaliMethod,
    280                                          final int registerNum, final String type) throws EvaluateException {
    281 
    282         if (registerNum >= smaliMethod.getRegisterCount()) {
    283             return null;
    284         }
    285 
    286         final StackFrameProxy frameProxy = context.getSuspendContext().getFrameProxy();
    287         if (frameProxy == null) {
    288             return null;
    289         }
    290 
    291         VirtualMachine vm = frameProxy.getStackFrame().virtualMachine();
    292         Location currentLocation = frameProxy.location();
    293         if (currentLocation == null) {
    294             return null;
    295         }
    296 
    297         Method method = currentLocation.method();
    298 
    299         try {
    300             final Constructor<LocalVariableImpl> localVariableConstructor = LocalVariableImpl.class.getDeclaredConstructor(
    301                     VirtualMachine.class, Method.class, Integer.TYPE, Location.class, Location.class, String.class,
    302                     String.class, String.class);
    303             localVariableConstructor.setAccessible(true);
    304 
    305             Constructor<LocationImpl> locationConstructor = LocationImpl.class.getDeclaredConstructor(
    306                     VirtualMachine.class, Method.class, Long.TYPE);
    307             locationConstructor.setAccessible(true);
    308 
    309             int methodSize = 0;
    310             for (SmaliInstruction instruction: smaliMethod.getInstructions()) {
    311                 methodSize += instruction.getInstructionSize();
    312             }
    313             Location endLocation = null;
    314             for (int endCodeIndex = (methodSize/2) - 1; endCodeIndex >= 0; endCodeIndex--) {
    315                 endLocation = method.locationOfCodeIndex(endCodeIndex);
    316                 if (endLocation != null) {
    317                     break;
    318                 }
    319             }
    320             if (endLocation == null) {
    321                 return null;
    322             }
    323 
    324             LocalVariable localVariable = localVariableConstructor.newInstance(vm,
    325                     method,
    326                     mapRegister(frameProxy.getStackFrame().virtualMachine(), smaliMethod, registerNum),
    327                     method.location(),
    328                     endLocation,
    329                     String.format("v%d", registerNum), type, null);
    330 
    331             return frameProxy.getStackFrame().getValue(localVariable);
    332         } catch (NoSuchMethodException e) {
    333             return null;
    334         } catch (InstantiationException e) {
    335             return null;
    336         } catch (IllegalAccessException e) {
    337             return null;
    338         } catch (InvocationTargetException e) {
    339             return null;
    340         }
    341     }
    342 
    343     private static int mapRegister(final VirtualMachine vm, final SmaliMethod smaliMethod, final int register) {
    344         if (vm.version().equals("1.5.0")) {
    345             return mapRegisterForDalvik(smaliMethod, register);
    346         } else {
    347             return mapRegisterForArt(smaliMethod, register);
    348         }
    349     }
    350 
    351     private static int mapRegisterForArt(final SmaliMethod smaliMethod, final int register) {
    352         return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
    353             @Override public Integer compute() {
    354 
    355                 int totalRegisters = smaliMethod.getRegisterCount();
    356                 int parameterRegisters = smaliMethod.getParameterRegisterCount();
    357 
    358                 if (smaliMethod.getModifierList().hasModifierProperty("static")) {
    359                     return register;
    360                 }
    361 
    362                 // For ART, the parameter registers are rotated to the front
    363                 if (register >= (totalRegisters - parameterRegisters)) {
    364                     return register - (totalRegisters - parameterRegisters);
    365                 }
    366                 return register + parameterRegisters;
    367             }
    368         });
    369     }
    370 
    371     private static int mapRegisterForDalvik(final SmaliMethod smaliMethod, final int register) {
    372         return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
    373             @Override public Integer compute() {
    374                 if (smaliMethod.getModifierList().hasModifierProperty("static")) {
    375                     return register;
    376                 }
    377 
    378                 int totalRegisters = smaliMethod.getRegisterCount();
    379                 int parameterRegisters = smaliMethod.getParameterRegisterCount();
    380 
    381                 // For dalvik, p0 is mapped to register 1, and register 0 is mapped to register 1000
    382                 if (register == (totalRegisters - parameterRegisters)) {
    383                     return 0;
    384                 }
    385                 if (register == 0) {
    386                     return 1000;
    387                 }
    388                 return register;
    389             }
    390         });
    391     }
    392 }
    393 
    394