• 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 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
createCodeFragment(TextWithImports item, PsiElement context, Project project)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
isContextAccepted(PsiElement contextElement)85     public boolean isContextAccepted(PsiElement contextElement) {
86         if (contextElement == null) {
87             return false;
88         }
89         return contextElement.getLanguage() == SmaliLanguage.INSTANCE;
90     }
91 
92     @Override
createPresentationCodeFragment(TextWithImports item, PsiElement context, Project project)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 
getFileType()103     @Override public LanguageFileType getFileType() {
104         return SmaliFileType.INSTANCE;
105     }
106 
getEvaluatorBuilder()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