fixes #396, update to smali v1.4.2

This commit is contained in:
Connor Tumbleson 2013-02-16 07:32:39 -06:00
parent 881bb56b4d
commit 626d7e7ecd
9 changed files with 153 additions and 58 deletions

View File

@ -1,4 +1,6 @@
v1.5.3 (TBA)
-Updated to smali/baksmali to v1.4.2
-Fixed (issue #396) - Correctly handle android:debuggable while in debug mode.
v1.5.2 (Released February 2 - 2013) Codename: Bug Fixes
-Fixed (issue #299) - output smali filename errors to screen during rebuild instead of filestream

View File

@ -44,8 +44,6 @@ import static org.jf.dexlib.ClassDataItem.EncodedField;
import static org.jf.dexlib.ClassDataItem.EncodedMethod;
public class ClassPath {
public static boolean dontLoadClassPath = false;
private static ClassPath theClassPath = null;
/**
@ -263,10 +261,6 @@ public class ClassPath {
@Nonnull
public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef) {
if (dontLoadClassPath) {
return null;
}
ClassDef classDef = theClassPath.classDefs.get(classType);
if (classDef == null) {
//if it's an array class, try to create it
@ -543,7 +537,7 @@ public class ClassPath {
}
public ClassDef getSuperclass() {
throw unresolvedValidationException();
return theClassPath.javaLangObjectClassDef;
}
public int getClassDepth() {
@ -599,6 +593,10 @@ public class ClassPath {
private final int classDepth;
// classes can only be public or package-private. Internally, any private/protected inner class is actually
// package-private.
private final boolean isPublic;
private final VirtualMethod[] vtable;
//this maps a method name of the form method(III)Ljava/lang/String; to an integer
@ -641,6 +639,7 @@ public class ClassPath {
implementedInterfaces.add(ClassPath.getClassDef("Ljava/lang/Cloneable;"));
implementedInterfaces.add(ClassPath.getClassDef("Ljava/io/Serializable;"));
isInterface = false;
isPublic = true;
vtable = superclass.vtable;
methodLookup = superclass.methodLookup;
@ -658,6 +657,7 @@ public class ClassPath {
this.superclass = null;
implementedInterfaces = null;
isInterface = false;
isPublic = true;
vtable = null;
methodLookup = null;
instanceFields = null;
@ -671,6 +671,7 @@ public class ClassPath {
this.superclass = ClassPath.getClassDef("Ljava/lang/Object;");
implementedInterfaces = new TreeSet<ClassDef>();
isInterface = false;
isPublic = true;
vtable = superclass.vtable;
methodLookup = superclass.methodLookup;
@ -685,6 +686,7 @@ public class ClassPath {
protected ClassDef(UnresolvedClassInfo classInfo) {
classType = classInfo.classType;
isPublic = classInfo.isPublic;
isInterface = classInfo.isInterface;
superclass = loadSuperclass(classInfo);
@ -738,6 +740,10 @@ public class ClassPath {
return this.isInterface;
}
public boolean isPublic() {
return this.isPublic;
}
public boolean extendsClass(ClassDef superclassDef) {
if (superclassDef == null) {
return false;
@ -1226,6 +1232,7 @@ public class ClassPath {
private static class UnresolvedClassInfo {
public final String dexFilePath;
public final String classType;
public final boolean isPublic;
public final boolean isInterface;
public final String superclassType;
public final String[] interfaces;
@ -1239,6 +1246,7 @@ public class ClassPath {
classType = classDefItem.getClassType().getTypeDescriptor();
isPublic = (classDefItem.getAccessFlags() & AccessFlags.PUBLIC.getValue()) != 0;
isInterface = (classDefItem.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
TypeIdItem superclassType = classDefItem.getSuperclass();

View File

@ -64,19 +64,21 @@ public class DeodexUtil {
return inlineMethodResolver.resolveExecuteInline(instruction);
}
public FieldIdItem lookupField(ClassPath.ClassDef classDef, int fieldOffset) {
ClassPath.FieldDef field = classDef.getInstanceField(fieldOffset);
public FieldIdItem lookupField(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass,
int fieldOffset) {
ClassPath.FieldDef field = instanceClass.getInstanceField(fieldOffset);
if (field == null) {
return null;
}
return parseAndResolveField(classDef, field);
return parseAndResolveField(accessingClass, instanceClass, field);
}
private static final Pattern shortMethodPattern = Pattern.compile("([^(]+)\\(([^)]*)\\)(.+)");
public MethodIdItem lookupVirtualMethod(ClassPath.ClassDef classDef, int methodIndex) {
String method = classDef.getVirtualMethod(methodIndex);
public MethodIdItem lookupVirtualMethod(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass,
int methodIndex) {
String method = instanceClass.getVirtualMethod(methodIndex);
if (method == null) {
return null;
}
@ -91,20 +93,20 @@ public class DeodexUtil {
String methodParams = m.group(2);
String methodRet = m.group(3);
if (classDef instanceof ClassPath.UnresolvedClassDef) {
if (instanceClass instanceof ClassPath.UnresolvedClassDef) {
//if this is an unresolved class, the only way getVirtualMethod could have found a method is if the virtual
//method being looked up was a method on java.lang.Object.
classDef = ClassPath.getClassDef("Ljava/lang/Object;");
} else if (classDef.isInterface()) {
classDef = classDef.getSuperclass();
assert classDef != null;
instanceClass = ClassPath.getClassDef("Ljava/lang/Object;");
} else if (instanceClass.isInterface()) {
instanceClass = instanceClass.getSuperclass();
assert instanceClass != null;
}
return parseAndResolveMethod(classDef, methodName, methodParams, methodRet);
return parseAndResolveMethod(accessingClass, instanceClass, methodName, methodParams, methodRet);
}
private MethodIdItem parseAndResolveMethod(ClassPath.ClassDef classDef, String methodName, String methodParams,
String methodRet) {
private MethodIdItem parseAndResolveMethod(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass,
String methodName, String methodParams, String methodRet) {
StringIdItem methodNameItem = StringIdItem.lookupStringIdItem(dexFile, methodName);
if (methodNameItem == null) {
return null;
@ -197,14 +199,14 @@ public class DeodexUtil {
return null;
}
ClassPath.ClassDef methodClassDef = classDef;
ClassPath.ClassDef methodClassDef = definingClass;
do {
TypeIdItem classTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, methodClassDef.getClassType());
if (classTypeItem != null) {
MethodIdItem methodIdItem = MethodIdItem.lookupMethodIdItem(dexFile, classTypeItem, protoItem, methodNameItem);
if (methodIdItem != null) {
if (methodIdItem != null && checkClassAccess(accessingClass, methodClassDef)) {
return methodIdItem;
}
}
@ -214,7 +216,28 @@ public class DeodexUtil {
return null;
}
private FieldIdItem parseAndResolveField(ClassPath.ClassDef classDef, ClassPath.FieldDef field) {
private static boolean checkClassAccess(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass) {
return definingClass.isPublic() ||
getPackage(accessingClass.getClassType()).equals(getPackage(definingClass.getClassType()));
}
private static String getPackage(String classRef) {
int lastSlash = classRef.lastIndexOf('/');
if (lastSlash < 0) {
return "";
}
return classRef.substring(1, lastSlash);
}
/**
*
* @param accessingClass The class that contains the field reference. I.e. the class being deodexed
* @param instanceClass The inferred class type of the object that the field is being accessed on
* @param field The field being accessed
* @return The FieldIdItem of the resolved field
*/
private FieldIdItem parseAndResolveField(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass,
ClassPath.FieldDef field) {
String definingClass = field.definingClass;
String fieldName = field.name;
String fieldType = field.type;
@ -229,7 +252,7 @@ public class DeodexUtil {
return null;
}
ClassPath.ClassDef fieldClass = classDef;
ClassPath.ClassDef fieldClass = instanceClass;
ArrayList<ClassPath.ClassDef> parents = new ArrayList<ClassPath.ClassDef>();
parents.add(fieldClass);
@ -248,7 +271,7 @@ public class DeodexUtil {
}
FieldIdItem fieldIdItem = FieldIdItem.lookupFieldIdItem(dexFile, classTypeItem, fieldTypeItem, fieldNameItem);
if (fieldIdItem != null) {
if (fieldIdItem != null && checkClassAccess(accessingClass, fieldClass)) {
return fieldIdItem;
}
}
@ -283,7 +306,8 @@ public class DeodexUtil {
private void loadMethod(DeodexUtil deodexUtil) {
ClassPath.ClassDef classDef = ClassPath.getClassDef(classType);
this.methodIdItem = deodexUtil.parseAndResolveMethod(classDef, methodName, parameters, returnType);
this.methodIdItem = deodexUtil.parseAndResolveMethod(classDef, classDef, methodName, parameters,
returnType);
}
public String getMethodString() {

View File

@ -3578,7 +3578,14 @@ public class MethodAnalyzer {
return false;
}
FieldIdItem fieldIdItem = deodexUtil.lookupField(objectRegisterType.type, fieldOffset);
ClassPath.ClassDef accessingClass =
ClassPath.getClassDef(this.encodedMethod.method.getContainingClass(), false);
if (accessingClass == null) {
throw new ExceptionWithContext(String.format("Could not find ClassDef for current class: %s",
this.encodedMethod.method.getContainingClass()));
}
FieldIdItem fieldIdItem = deodexUtil.lookupField(accessingClass, objectRegisterType.type, fieldOffset);
if (fieldIdItem == null) {
throw new ValidationException(String.format("Could not resolve the field in class %s at offset %d",
objectRegisterType.type.getClassType(), fieldOffset));
@ -3621,12 +3628,16 @@ public class MethodAnalyzer {
}
MethodIdItem methodIdItem = null;
ClassPath.ClassDef accessingClass =
ClassPath.getClassDef(this.encodedMethod.method.getContainingClass(), false);
if (accessingClass == null) {
throw new ExceptionWithContext(String.format("Could not find ClassDef for current class: %s",
this.encodedMethod.method.getContainingClass()));
}
if (isSuper) {
ClassPath.ClassDef classDef = ClassPath.getClassDef(this.encodedMethod.method.getContainingClass(), false);
assert classDef != null;
if (classDef.getSuperclass() != null) {
methodIdItem = deodexUtil.lookupVirtualMethod(classDef.getSuperclass(), methodIndex);
if (accessingClass.getSuperclass() != null) {
methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, accessingClass.getSuperclass(),
methodIndex);
}
if (methodIdItem == null) {
@ -3634,10 +3645,10 @@ public class MethodAnalyzer {
//of from the superclass (although the superclass method is still what would actually be called).
//And so the MethodIdItem for the superclass method may not be in the dex file. Let's try to get the
//MethodIdItem for the method in the current class instead
methodIdItem = deodexUtil.lookupVirtualMethod(classDef, methodIndex);
methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, accessingClass, methodIndex);
}
} else{
methodIdItem = deodexUtil.lookupVirtualMethod(objectRegisterType.type, methodIndex);
methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, objectRegisterType.type, methodIndex);
}
if (methodIdItem == null) {

View File

@ -40,6 +40,9 @@ public class ByteArrayInput
/** &gt;= 0; current read cursor */
private int cursor;
/* buffer for reading UTF-8 strings */
private char[] buffer = null;
/**
* Constructs an instance with the given data
*
@ -291,7 +294,11 @@ public class ByteArrayInput
//skip the terminating null
cursor++;
return Utf8Utils.utf8BytesToString(data, startPosition, byteCount);
if (buffer == null || buffer.length < byteCount) {
buffer = new char[byteCount];
}
return Utf8Utils.utf8BytesToString(buffer, data, startPosition, byteCount);
}
/** {@inheritDoc} */

View File

@ -68,23 +68,16 @@ public final class Utf8Utils {
return result;
}
private static char[] tempBuffer = null;
/**
* Converts an array of UTF-8 bytes into a string.
*
* This method uses a global buffer to avoid having to allocate one every time, so it is *not* thread-safe
*
* @param buffer a buffer to hold the chars as they are read. Make sure the length of the array is at least 'length'
* @param bytes non-null; the bytes to convert
* @param start the start index of the utf8 string to convert
* @param length the length of the utf8 string to convert, not including any null-terminator that might be present
* @return non-null; the converted string
*/
public static String utf8BytesToString(byte[] bytes, int start, int length) {
if (tempBuffer == null || tempBuffer.length < length) {
tempBuffer = new char[length];
}
char[] chars = tempBuffer;
public static String utf8BytesToString(char[] buffer, byte[] bytes, int start, int length) {
int outAt = 0;
for (int at = start; length > 0; /*at*/) {
@ -157,11 +150,11 @@ public final class Utf8Utils {
return throwBadUtf8(v0, at);
}
}
chars[outAt] = out;
buffer[outAt] = out;
outAt++;
}
return new String(chars, 0, outAt);
return new String(buffer, 0, outAt);
}
/**

View File

@ -36,6 +36,7 @@ options {
@header {
package org.jf.smali;
import com.google.common.collect.ImmutableSortedMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.regex.*;
@ -1616,23 +1617,26 @@ annotation_element returns[StringIdItem elementName, EncodedValue elementValue]
};
subannotation returns[TypeIdItem annotationType, StringIdItem[\] elementNames, EncodedValue[\] elementValues]
: {ArrayList<StringIdItem> elementNamesList = new ArrayList<StringIdItem>();
ArrayList<EncodedValue> elementValuesList = new ArrayList<EncodedValue>();}
: {ImmutableSortedMap.Builder<StringIdItem, EncodedValue> elementBuilder =
ImmutableSortedMap.<StringIdItem, EncodedValue>naturalOrder();}
^(I_SUBANNOTATION
class_type_descriptor
(annotation_element
{
elementNamesList.add($annotation_element.elementName);
elementValuesList.add($annotation_element.elementValue);
elementBuilder.put($annotation_element.elementName, $annotation_element.elementValue);
}
)*
)
{
ImmutableSortedMap<StringIdItem, EncodedValue> elementMap = elementBuilder.build();
$annotationType = $class_type_descriptor.type;
$elementNames = new StringIdItem[elementNamesList.size()];
elementNamesList.toArray($elementNames);
$elementValues = new EncodedValue[elementValuesList.size()];
elementValuesList.toArray($elementValues);
$elementNames = new StringIdItem[elementMap.size()];
$elementValues = new EncodedValue[elementMap.size()];
elementMap.keySet().toArray($elementNames);
elementMap.values().toArray($elementValues);
};
field_literal returns[FieldIdItem value]

View File

@ -228,9 +228,6 @@ public class Androlib {
if (!working.exists()) {
return false;
}
if (flags.get("debug")) {
LOGGER.warning("Debug mode not available.");
}
File stored = new File(appDir, APK_DIRNAME + "/classes.dex");
if (flags.get("forceBuildAll") || isModified(working, stored)) {
LOGGER.info("Copying classes.dex file...");
@ -379,7 +376,13 @@ public class Androlib {
if (!flags.get("forceBuildAll")) {
LOGGER.info("Checking whether resources has changed...");
}
File apkDir = new File(appDir, APK_DIRNAME);
if (flags.get("debug")) {
mAndRes.remove_application_debug(new File(apkDir,"AndroidManifest.xml").getAbsolutePath());
}
if (flags.get("forceBuildAll")
|| isModified(newFiles(APK_MANIFEST_FILENAMES, appDir),
newFiles(APK_MANIFEST_FILENAMES, apkDir))) {

View File

@ -38,6 +38,7 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
@ -46,6 +47,7 @@ import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.apache.commons.io.IOUtils;
import org.xml.sax.SAXException;
@ -151,6 +153,48 @@ final public class AndrolibResources {
}
}
public void remove_application_debug(String filePath)
throws AndrolibException {
// change application:debug to true
try {
DocumentBuilderFactory docFactory = DocumentBuilderFactory
.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.parse(filePath.toString());
Node application = doc.getElementById("application");
// load attr
NamedNodeMap attr = application.getAttributes();
Node debugAttr = attr.getNamedItem("debug");
// remove application:debug
if (debugAttr != null) {
attr.removeNamedItem("debug");
}
// save manifest
TransformerFactory transformerFactory = TransformerFactory
.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File(filePath));
transformer.transform(source, result);
} catch (ParserConfigurationException ex) {
throw new AndrolibException(ex);
} catch (SAXException ex) {
throw new AndrolibException(ex);
} catch (IOException ex) {
throw new AndrolibException(ex);
} catch (TransformerConfigurationException ex) {
throw new AndrolibException(ex);
} catch (TransformerException ex) {
throw new AndrolibException(ex);
}
}
public void adjust_package_manifest(ResTable resTable, String filePath)
throws AndrolibException {
@ -182,7 +226,6 @@ final public class AndrolibResources {
nodeAttr.setNodeValue(packageInfo.get("cur_package"));
// re-save manifest.
// fancy an auto-sort :p
TransformerFactory transformerFactory = TransformerFactory
.newInstance();
Transformer transformer = transformerFactory.newTransformer();