• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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