| /* |
| * Copyright 2014, Google Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.jf.smalidea.debugging; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.intellij.debugger.SourcePosition; |
| import com.intellij.debugger.engine.evaluation.*; |
| import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilder; |
| import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator; |
| import com.intellij.debugger.engine.jdi.StackFrameProxy; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.fileTypes.LanguageFileType; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.psi.JavaCodeFragment; |
| import com.intellij.psi.JavaRecursiveElementVisitor; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiLocalVariable; |
| import com.intellij.psi.util.PsiMatchers; |
| import com.sun.jdi.*; |
| import com.sun.tools.jdi.LocalVariableImpl; |
| import com.sun.tools.jdi.LocationImpl; |
| import org.jf.dexlib2.analysis.AnalyzedInstruction; |
| import org.jf.dexlib2.analysis.RegisterType; |
| import org.jf.smalidea.SmaliFileType; |
| import org.jf.smalidea.SmaliLanguage; |
| import org.jf.smalidea.debugging.value.LazyValue; |
| import org.jf.smalidea.psi.impl.SmaliInstruction; |
| import org.jf.smalidea.psi.impl.SmaliMethod; |
| import org.jf.smalidea.util.NameUtils; |
| import org.jf.smalidea.util.PsiUtil; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class SmaliCodeFragmentFactory extends DefaultCodeFragmentFactory { |
| static final Key<List<LazyValue>> SMALI_LAZY_VALUES_KEY = Key.create("_smali_register_value_key_"); |
| |
| @Override |
| public JavaCodeFragment createCodeFragment(TextWithImports item, PsiElement context, Project project) { |
| context = wrapContext(project, context); |
| JavaCodeFragment fragment = super.createCodeFragment(item, context, project); |
| List<LazyValue> lazyValues = context.getUserData(SMALI_LAZY_VALUES_KEY); |
| if (lazyValues != null) { |
| fragment.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues); |
| } |
| return fragment; |
| } |
| |
| @Override |
| public boolean isContextAccepted(PsiElement contextElement) { |
| if (contextElement == null) { |
| return false; |
| } |
| return contextElement.getLanguage() == SmaliLanguage.INSTANCE; |
| } |
| |
| @Override |
| public JavaCodeFragment createPresentationCodeFragment(TextWithImports item, PsiElement context, Project project) { |
| context = wrapContext(project, context); |
| JavaCodeFragment fragment = super.createPresentationCodeFragment(item, context, project); |
| List<LazyValue> lazyValues = context.getUserData(SMALI_LAZY_VALUES_KEY); |
| if (lazyValues != null) { |
| fragment.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues); |
| } |
| return fragment; |
| } |
| |
| @Override public LanguageFileType getFileType() { |
| return SmaliFileType.INSTANCE; |
| } |
| |
| @Override public EvaluatorBuilder getEvaluatorBuilder() { |
| final EvaluatorBuilder builder = super.getEvaluatorBuilder(); |
| return new EvaluatorBuilder() { |
| |
| @Override |
| public ExpressionEvaluator build(PsiElement codeFragment, SourcePosition position) |
| throws EvaluateException { |
| return new SmaliExpressionEvaluator(codeFragment, builder.build(codeFragment, position)); |
| } |
| }; |
| } |
| |
| private PsiElement wrapContext(final Project project, final PsiElement originalContext) { |
| if (project.isDefault()) return originalContext; |
| |
| final List<LazyValue> lazyValues = Lists.newArrayList(); |
| |
| SmaliInstruction currentInstruction = (SmaliInstruction)PsiUtil.searchBackward(originalContext, |
| PsiMatchers.hasClass(SmaliInstruction.class), |
| PsiMatchers.hasClass(SmaliMethod.class)); |
| |
| if (currentInstruction == null) { |
| currentInstruction = (SmaliInstruction)PsiUtil.searchForward(originalContext, |
| PsiMatchers.hasClass(SmaliInstruction.class), |
| PsiMatchers.hasClass(SmaliMethod.class)); |
| if (currentInstruction == null) { |
| return originalContext; |
| } |
| } |
| |
| final SmaliMethod containingMethod = currentInstruction.getParentMethod(); |
| AnalyzedInstruction analyzedInstruction = currentInstruction.getAnalyzedInstruction(); |
| if (analyzedInstruction == null) { |
| return originalContext; |
| } |
| |
| final int firstParameterRegister = containingMethod.getRegisterCount() - |
| containingMethod.getParameterRegisterCount(); |
| |
| final Map<String, String> registerMap = Maps.newHashMap(); |
| StringBuilder variablesText = new StringBuilder(); |
| for (int i=0; i<containingMethod.getRegisterCount(); i++) { |
| int parameterRegisterNumber = i - firstParameterRegister; |
| |
| RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(i); |
| switch (registerType.category) { |
| case RegisterType.UNKNOWN: |
| case RegisterType.UNINIT: |
| case RegisterType.CONFLICTED: |
| case RegisterType.LONG_HI: |
| case RegisterType.DOUBLE_HI: |
| continue; |
| case RegisterType.NULL: |
| case RegisterType.ONE: |
| case RegisterType.INTEGER: |
| variablesText.append("int v").append(i).append(";\n"); |
| registerMap.put("v" + i, "I"); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append("int p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "I"); |
| } |
| break; |
| case RegisterType.BOOLEAN: |
| variablesText.append("boolean v").append(i).append(";\n"); |
| registerMap.put("v" + i, "Z"); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append("boolean p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "Z"); |
| } |
| break; |
| case RegisterType.BYTE: |
| case RegisterType.POS_BYTE: |
| variablesText.append("byte v").append(i).append(";\n"); |
| registerMap.put("v" + i, "B"); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append("byte p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "B"); |
| } |
| break; |
| case RegisterType.SHORT: |
| case RegisterType.POS_SHORT: |
| variablesText.append("short v").append(i).append(";\n"); |
| registerMap.put("v" + i, "S"); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append("short p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "S"); |
| } |
| break; |
| case RegisterType.CHAR: |
| variablesText.append("char v").append(i).append(";\n"); |
| registerMap.put("v" + i, "C"); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append("char p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "C"); |
| } |
| break; |
| case RegisterType.FLOAT: |
| variablesText.append("float v").append(i).append(";\n"); |
| registerMap.put("v" + i, "F"); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append("float p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "F"); |
| } |
| break; |
| case RegisterType.LONG_LO: |
| variablesText.append("long v").append(i).append(";\n"); |
| registerMap.put("v" + i, "J"); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append("long p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "J"); |
| } |
| break; |
| case RegisterType.DOUBLE_LO: |
| variablesText.append("double v").append(i).append(";\n"); |
| registerMap.put("v" + i, "D"); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append("double p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "D"); |
| } |
| break; |
| case RegisterType.UNINIT_REF: |
| case RegisterType.UNINIT_THIS: |
| case RegisterType.REFERENCE: |
| String smaliType = registerType.type.getType(); |
| String javaType = NameUtils.smaliToJavaType(smaliType); |
| variablesText.append(javaType).append(" v").append(i).append(";\n"); |
| registerMap.put("v" + i, smaliType); |
| if (parameterRegisterNumber >= 0) { |
| variablesText.append(javaType).append(" p").append(parameterRegisterNumber).append(";\n"); |
| registerMap.put("p" + parameterRegisterNumber, "Ljava/lang/Object;"); |
| } |
| break; |
| } |
| } |
| final TextWithImportsImpl textWithImports = new TextWithImportsImpl(CodeFragmentKind.CODE_BLOCK, |
| variablesText.toString(), "", getFileType()); |
| |
| final JavaCodeFragment codeFragment = super.createCodeFragment(textWithImports, originalContext, project); |
| |
| codeFragment.accept(new JavaRecursiveElementVisitor() { |
| @Override |
| public void visitLocalVariable(final PsiLocalVariable variable) { |
| final String name = variable.getName(); |
| if (name != null && registerMap.containsKey(name)) { |
| int registerNumber = Integer.parseInt(name.substring(1)); |
| if (name.charAt(0) == 'p') { |
| registerNumber += ApplicationManager.getApplication().runReadAction(new Computable<Integer>() { |
| @Override public Integer compute() { |
| return containingMethod.getRegisterCount() - |
| containingMethod.getParameterRegisterCount(); |
| } |
| }); |
| } |
| LazyValue lazyValue = LazyValue.create(containingMethod, project, registerNumber, |
| registerMap.get(name)); |
| variable.putUserData(CodeFragmentFactoryContextWrapper.LABEL_VARIABLE_VALUE_KEY, lazyValue); |
| lazyValues.add(lazyValue); |
| } |
| } |
| }); |
| |
| int offset = variablesText.length() - 1; |
| |
| final PsiElement newContext = codeFragment.findElementAt(offset); |
| if (newContext != null) { |
| newContext.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues); |
| return newContext; |
| } |
| return originalContext; |
| } |
| |
| public static Value evaluateRegister(EvaluationContext context, final SmaliMethod smaliMethod, |
| final int registerNum, final String type) throws EvaluateException { |
| |
| final StackFrameProxy frameProxy = context.getSuspendContext().getFrameProxy(); |
| if (frameProxy == null) { |
| return null; |
| } |
| |
| VirtualMachine vm = frameProxy.getStackFrame().virtualMachine(); |
| Location currentLocation = frameProxy.location(); |
| if (currentLocation == null) { |
| return null; |
| } |
| |
| Method method = currentLocation.method(); |
| |
| try { |
| final Constructor<LocalVariableImpl> localVariableConstructor = LocalVariableImpl.class.getDeclaredConstructor( |
| VirtualMachine.class, Method.class, Integer.TYPE, Location.class, Location.class, String.class, |
| String.class, String.class); |
| localVariableConstructor.setAccessible(true); |
| |
| Constructor<LocationImpl> locationConstructor = LocationImpl.class.getDeclaredConstructor( |
| VirtualMachine.class, Method.class, Long.TYPE); |
| locationConstructor.setAccessible(true); |
| |
| int methodSize = 0; |
| for (SmaliInstruction instruction: smaliMethod.getInstructions()) { |
| methodSize += instruction.getInstructionSize(); |
| } |
| Location endLocation = method.locationOfCodeIndex((methodSize/2) - 1); |
| |
| LocalVariable localVariable = localVariableConstructor.newInstance(vm, |
| method, |
| mapRegister(frameProxy.getStackFrame().virtualMachine(), smaliMethod, registerNum), |
| method.locationOfCodeIndex(0), |
| endLocation, |
| String.format("v%d", registerNum), type, null); |
| |
| return frameProxy.getStackFrame().getValue(localVariable); |
| } catch (NoSuchMethodException e) { |
| return null; |
| } catch (InstantiationException e) { |
| return null; |
| } catch (IllegalAccessException e) { |
| return null; |
| } catch (InvocationTargetException e) { |
| return null; |
| } |
| } |
| |
| private static int mapRegister(final VirtualMachine vm, final SmaliMethod smaliMethod, final int register) { |
| if (vm.version().equals("1.5.0")) { |
| return mapRegisterForDalvik(smaliMethod, register); |
| } else { |
| return mapRegisterForArt(smaliMethod, register); |
| } |
| } |
| |
| private static int mapRegisterForArt(final SmaliMethod smaliMethod, final int register) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() { |
| @Override public Integer compute() { |
| |
| int totalRegisters = smaliMethod.getRegisterCount(); |
| int parameterRegisters = smaliMethod.getParameterRegisterCount(); |
| |
| if (smaliMethod.getModifierList().hasModifierProperty("static")) { |
| return register; |
| } |
| |
| // For ART, the parameter registers are rotated to the front |
| if (register >= (totalRegisters - parameterRegisters)) { |
| return register - (totalRegisters - parameterRegisters); |
| } |
| return register + parameterRegisters; |
| } |
| }); |
| } |
| |
| private static int mapRegisterForDalvik(final SmaliMethod smaliMethod, final int register) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() { |
| @Override public Integer compute() { |
| if (smaliMethod.getModifierList().hasModifierProperty("static")) { |
| return register; |
| } |
| |
| int totalRegisters = smaliMethod.getRegisterCount(); |
| int parameterRegisters = smaliMethod.getParameterRegisterCount(); |
| |
| // For dalvik, p0 is mapped to register 1, and register 0 is mapped to register 1000 |
| if (register == (totalRegisters - parameterRegisters)) { |
| return 0; |
| } |
| if (register == 0) { |
| return 1000; |
| } |
| return register; |
| } |
| }); |
| } |
| } |
| |