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 java.lang.reflect.Constructor; 65 import java.lang.reflect.InvocationTargetException; 66 import java.util.List; 67 import java.util.Map; 68 69 public class SmaliCodeFragmentFactory extends DefaultCodeFragmentFactory { 70 static final Key<List<LazyValue>> SMALI_LAZY_VALUES_KEY = Key.create("_smali_register_value_key_"); 71 72 @Override createCodeFragment(TextWithImports item, PsiElement context, Project project)73 public JavaCodeFragment createCodeFragment(TextWithImports item, PsiElement context, Project project) { 74 context = wrapContext(project, context); 75 JavaCodeFragment fragment = super.createCodeFragment(item, context, project); 76 List<LazyValue> lazyValues = context.getUserData(SMALI_LAZY_VALUES_KEY); 77 if (lazyValues != null) { 78 fragment.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues); 79 } 80 return fragment; 81 } 82 83 @Override isContextAccepted(PsiElement contextElement)84 public boolean isContextAccepted(PsiElement contextElement) { 85 if (contextElement == null) { 86 return false; 87 } 88 return contextElement.getLanguage() == SmaliLanguage.INSTANCE; 89 } 90 91 @Override createPresentationCodeFragment(TextWithImports item, PsiElement context, Project project)92 public JavaCodeFragment createPresentationCodeFragment(TextWithImports item, PsiElement context, Project project) { 93 context = wrapContext(project, context); 94 JavaCodeFragment fragment = super.createPresentationCodeFragment(item, context, project); 95 List<LazyValue> lazyValues = context.getUserData(SMALI_LAZY_VALUES_KEY); 96 if (lazyValues != null) { 97 fragment.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues); 98 } 99 return fragment; 100 } 101 getFileType()102 @Override public LanguageFileType getFileType() { 103 return SmaliFileType.INSTANCE; 104 } 105 getEvaluatorBuilder()106 @Override public EvaluatorBuilder getEvaluatorBuilder() { 107 final EvaluatorBuilder builder = super.getEvaluatorBuilder(); 108 return new EvaluatorBuilder() { 109 110 @Override 111 public ExpressionEvaluator build(PsiElement codeFragment, SourcePosition position) 112 throws EvaluateException { 113 return new SmaliExpressionEvaluator(codeFragment, builder.build(codeFragment, position)); 114 } 115 }; 116 } 117 118 private PsiElement wrapContext(final Project project, final PsiElement originalContext) { 119 if (project.isDefault()) return originalContext; 120 121 final List<LazyValue> lazyValues = Lists.newArrayList(); 122 123 SmaliInstruction currentInstruction = (SmaliInstruction)PsiUtil.searchBackward(originalContext, 124 PsiMatchers.hasClass(SmaliInstruction.class), 125 PsiMatchers.hasClass(SmaliMethod.class)); 126 127 if (currentInstruction == null) { 128 currentInstruction = (SmaliInstruction)PsiUtil.searchForward(originalContext, 129 PsiMatchers.hasClass(SmaliInstruction.class), 130 PsiMatchers.hasClass(SmaliMethod.class)); 131 if (currentInstruction == null) { 132 return originalContext; 133 } 134 } 135 136 final SmaliMethod containingMethod = currentInstruction.getParentMethod(); 137 AnalyzedInstruction analyzedInstruction = currentInstruction.getAnalyzedInstruction(); 138 if (analyzedInstruction == null) { 139 return originalContext; 140 } 141 142 final int firstParameterRegister = containingMethod.getRegisterCount() - 143 containingMethod.getParameterRegisterCount(); 144 145 final Map<String, String> registerMap = Maps.newHashMap(); 146 StringBuilder variablesText = new StringBuilder(); 147 for (int i=0; i<containingMethod.getRegisterCount(); i++) { 148 int parameterRegisterNumber = i - firstParameterRegister; 149 150 RegisterType registerType = analyzedInstruction.getPreInstructionRegisterType(i); 151 switch (registerType.category) { 152 case RegisterType.UNKNOWN: 153 case RegisterType.UNINIT: 154 case RegisterType.CONFLICTED: 155 case RegisterType.LONG_HI: 156 case RegisterType.DOUBLE_HI: 157 continue; 158 case RegisterType.NULL: 159 case RegisterType.ONE: 160 case RegisterType.INTEGER: 161 variablesText.append("int v").append(i).append(";\n"); 162 registerMap.put("v" + i, "I"); 163 if (parameterRegisterNumber >= 0) { 164 variablesText.append("int p").append(parameterRegisterNumber).append(";\n"); 165 registerMap.put("p" + parameterRegisterNumber, "I"); 166 } 167 break; 168 case RegisterType.BOOLEAN: 169 variablesText.append("boolean v").append(i).append(";\n"); 170 registerMap.put("v" + i, "Z"); 171 if (parameterRegisterNumber >= 0) { 172 variablesText.append("boolean p").append(parameterRegisterNumber).append(";\n"); 173 registerMap.put("p" + parameterRegisterNumber, "Z"); 174 } 175 break; 176 case RegisterType.BYTE: 177 case RegisterType.POS_BYTE: 178 variablesText.append("byte v").append(i).append(";\n"); 179 registerMap.put("v" + i, "B"); 180 if (parameterRegisterNumber >= 0) { 181 variablesText.append("byte p").append(parameterRegisterNumber).append(";\n"); 182 registerMap.put("p" + parameterRegisterNumber, "B"); 183 } 184 break; 185 case RegisterType.SHORT: 186 case RegisterType.POS_SHORT: 187 variablesText.append("short v").append(i).append(";\n"); 188 registerMap.put("v" + i, "S"); 189 if (parameterRegisterNumber >= 0) { 190 variablesText.append("short p").append(parameterRegisterNumber).append(";\n"); 191 registerMap.put("p" + parameterRegisterNumber, "S"); 192 } 193 break; 194 case RegisterType.CHAR: 195 variablesText.append("char v").append(i).append(";\n"); 196 registerMap.put("v" + i, "C"); 197 if (parameterRegisterNumber >= 0) { 198 variablesText.append("char p").append(parameterRegisterNumber).append(";\n"); 199 registerMap.put("p" + parameterRegisterNumber, "C"); 200 } 201 break; 202 case RegisterType.FLOAT: 203 variablesText.append("float v").append(i).append(";\n"); 204 registerMap.put("v" + i, "F"); 205 if (parameterRegisterNumber >= 0) { 206 variablesText.append("float p").append(parameterRegisterNumber).append(";\n"); 207 registerMap.put("p" + parameterRegisterNumber, "F"); 208 } 209 break; 210 case RegisterType.LONG_LO: 211 variablesText.append("long v").append(i).append(";\n"); 212 registerMap.put("v" + i, "J"); 213 if (parameterRegisterNumber >= 0) { 214 variablesText.append("long p").append(parameterRegisterNumber).append(";\n"); 215 registerMap.put("p" + parameterRegisterNumber, "J"); 216 } 217 break; 218 case RegisterType.DOUBLE_LO: 219 variablesText.append("double v").append(i).append(";\n"); 220 registerMap.put("v" + i, "D"); 221 if (parameterRegisterNumber >= 0) { 222 variablesText.append("double p").append(parameterRegisterNumber).append(";\n"); 223 registerMap.put("p" + parameterRegisterNumber, "D"); 224 } 225 break; 226 case RegisterType.UNINIT_REF: 227 case RegisterType.UNINIT_THIS: 228 case RegisterType.REFERENCE: 229 String smaliType = registerType.type.getType(); 230 String javaType = NameUtils.smaliToJavaType(smaliType); 231 variablesText.append(javaType).append(" v").append(i).append(";\n"); 232 registerMap.put("v" + i, smaliType); 233 if (parameterRegisterNumber >= 0) { 234 variablesText.append(javaType).append(" p").append(parameterRegisterNumber).append(";\n"); 235 registerMap.put("p" + parameterRegisterNumber, "Ljava/lang/Object;"); 236 } 237 break; 238 } 239 } 240 final TextWithImportsImpl textWithImports = new TextWithImportsImpl(CodeFragmentKind.CODE_BLOCK, 241 variablesText.toString(), "", getFileType()); 242 243 final JavaCodeFragment codeFragment = super.createCodeFragment(textWithImports, originalContext, project); 244 245 codeFragment.accept(new JavaRecursiveElementVisitor() { 246 @Override 247 public void visitLocalVariable(final PsiLocalVariable variable) { 248 final String name = variable.getName(); 249 if (name != null && registerMap.containsKey(name)) { 250 int registerNumber = Integer.parseInt(name.substring(1)); 251 if (name.charAt(0) == 'p') { 252 registerNumber += ApplicationManager.getApplication().runReadAction(new Computable<Integer>() { 253 @Override public Integer compute() { 254 return containingMethod.getRegisterCount() - 255 containingMethod.getParameterRegisterCount(); 256 } 257 }); 258 } 259 LazyValue lazyValue = LazyValue.create(containingMethod, project, registerNumber, 260 registerMap.get(name)); 261 variable.putUserData(CodeFragmentFactoryContextWrapper.LABEL_VARIABLE_VALUE_KEY, lazyValue); 262 lazyValues.add(lazyValue); 263 } 264 } 265 }); 266 267 int offset = variablesText.length() - 1; 268 269 final PsiElement newContext = codeFragment.findElementAt(offset); 270 if (newContext != null) { 271 newContext.putUserData(SMALI_LAZY_VALUES_KEY, lazyValues); 272 return newContext; 273 } 274 return originalContext; 275 } 276 277 public static Value evaluateRegister(EvaluationContext context, final SmaliMethod smaliMethod, 278 final int registerNum, final String type) throws EvaluateException { 279 280 final StackFrameProxy frameProxy = context.getSuspendContext().getFrameProxy(); 281 if (frameProxy == null) { 282 return null; 283 } 284 285 VirtualMachine vm = frameProxy.getStackFrame().virtualMachine(); 286 Location currentLocation = frameProxy.location(); 287 if (currentLocation == null) { 288 return null; 289 } 290 291 Method method = currentLocation.method(); 292 293 try { 294 final Constructor<LocalVariableImpl> localVariableConstructor = LocalVariableImpl.class.getDeclaredConstructor( 295 VirtualMachine.class, Method.class, Integer.TYPE, Location.class, Location.class, String.class, 296 String.class, String.class); 297 localVariableConstructor.setAccessible(true); 298 299 Constructor<LocationImpl> locationConstructor = LocationImpl.class.getDeclaredConstructor( 300 VirtualMachine.class, Method.class, Long.TYPE); 301 locationConstructor.setAccessible(true); 302 303 int methodSize = 0; 304 for (SmaliInstruction instruction: smaliMethod.getInstructions()) { 305 methodSize += instruction.getInstructionSize(); 306 } 307 Location endLocation = method.locationOfCodeIndex((methodSize/2) - 1); 308 309 LocalVariable localVariable = localVariableConstructor.newInstance(vm, 310 method, 311 mapRegister(frameProxy.getStackFrame().virtualMachine(), smaliMethod, registerNum), 312 method.locationOfCodeIndex(0), 313 endLocation, 314 String.format("v%d", registerNum), type, null); 315 316 return frameProxy.getStackFrame().getValue(localVariable); 317 } catch (NoSuchMethodException e) { 318 return null; 319 } catch (InstantiationException e) { 320 return null; 321 } catch (IllegalAccessException e) { 322 return null; 323 } catch (InvocationTargetException e) { 324 return null; 325 } 326 } 327 328 private static int mapRegister(final VirtualMachine vm, final SmaliMethod smaliMethod, final int register) { 329 if (vm.version().equals("1.5.0")) { 330 return mapRegisterForDalvik(smaliMethod, register); 331 } else { 332 return mapRegisterForArt(smaliMethod, register); 333 } 334 } 335 336 private static int mapRegisterForArt(final SmaliMethod smaliMethod, final int register) { 337 return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() { 338 @Override public Integer compute() { 339 340 int totalRegisters = smaliMethod.getRegisterCount(); 341 int parameterRegisters = smaliMethod.getParameterRegisterCount(); 342 343 if (smaliMethod.getModifierList().hasModifierProperty("static")) { 344 return register; 345 } 346 347 // For ART, the parameter registers are rotated to the front 348 if (register >= (totalRegisters - parameterRegisters)) { 349 return register - (totalRegisters - parameterRegisters); 350 } 351 return register + parameterRegisters; 352 } 353 }); 354 } 355 356 private static int mapRegisterForDalvik(final SmaliMethod smaliMethod, final int register) { 357 return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() { 358 @Override public Integer compute() { 359 if (smaliMethod.getModifierList().hasModifierProperty("static")) { 360 return register; 361 } 362 363 int totalRegisters = smaliMethod.getRegisterCount(); 364 int parameterRegisters = smaliMethod.getParameterRegisterCount(); 365 366 // For dalvik, p0 is mapped to register 1, and register 0 is mapped to register 1000 367 if (register == (totalRegisters - parameterRegisters)) { 368 return 0; 369 } 370 if (register == 0) { 371 return 1000; 372 } 373 return register; 374 } 375 }); 376 } 377 } 378 379