blob: 5e2dd0c913536a3821ad22230dd99a2b34245602 [file] [log] [blame]
/*
* 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;
}
});
}
}