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