changeset 104:61cef1662035

Redirect types
author Thinker K.F. Li <thinker@codemud.net>
date Thu, 28 Jul 2011 00:06:54 +0800
parents 8a53e6f7f517
children f14c32108164
files paraspace/dalvik_opcodes.py paraspace/dexfile.py paraspace/injection.py paraspace/tests/dexfile_test.py
diffstat 4 files changed, 200 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/paraspace/dalvik_opcodes.py	Wed Jul 27 12:09:19 2011 +0800
+++ b/paraspace/dalvik_opcodes.py	Thu Jul 28 00:06:54 2011 +0800
@@ -1270,6 +1270,13 @@
     return decoded_insns
 
 
+## \brief Encode a list of opcode vectors to a instruction block.
+def encode_opcode_vectors(op_vectors):
+    insns = [encode_inst(op_vector) for op_vector in op_vectors]
+    insns_blk = ''.join(insns)
+    return insns_blk
+
+
 del name
 del _names
 del _opcode
--- a/paraspace/dexfile.py	Wed Jul 27 12:09:19 2011 +0800
+++ b/paraspace/dexfile.py	Thu Jul 28 00:06:54 2011 +0800
@@ -1031,7 +1031,7 @@
 
 class _DEX_TypeList(composite):
     num = uint32
-    typeItems = array('num', uint16)
+    typeItems = array('num', depend_idx('DEXFile.typeIds')(uint16))
 
     child_names = 'num typeItems'.split()
     pass
@@ -1693,7 +1693,29 @@
     ## \brief Return name string of a linked class definition item
     @staticmethod
     def get_classdef_name(classdef):
-        return classdef.classIdx.descriptorIdx.stringDataOff.data.data
+        return DEXFile_linked.get_typeid_name(classdef.classIdx)
+
+    ## \brief Return name string of a linked type ID item.
+    @staticmethod
+    def get_typeid_name(typeid):
+        return typeid.descriptorIdx.stringDataOff.data.data
+
+    ## \brief Get index of given type ID.
+    def get_idx_typeid(self, typeid):
+        return self.typeIds.items.index(typeid)
+
+    ## \brief Find type ID item with given name.
+    def find_typeid_name(self, name):
+        for typeid in self.typeIds.items:
+            typeid_name = DEXFile_linked.get_typeid_name(typeid)
+            if typeid_name == name:
+                return typeid
+            pass
+        pass
+    
+    ## \brief Return type ID item with given index.
+    def find_typeid_idx(self, idx):
+        return self.typeIds.items[idx]
     
     def find_class_name(self, name):
         for classdef in self.classDefs.items:
@@ -1703,6 +1725,15 @@
             pass
         raise ValueError, 'can not find class definition for \'%s\'' % (name)
 
+    ## \brief Return a class definition corresponding for give type ID.
+    def find_class_typeid(self, typeid):
+        for classdef in self.classDefs.items:
+            if classdef.classIdx == typeid:
+                return classdef
+            pass
+        raise ValueError, \
+            'can not find class definition for typeid %s' % (repr(typeid))
+
     ## \brief Update size of map items.
     #
     # Corresponding data lists of maps may be changed, it should be updated
@@ -1769,6 +1800,61 @@
                 return wmethod
             pass
         pass
+
+    ## \brief Get name of given method ID.
+    @staticmethod
+    def get_methodid_name(methoid):
+        return methoid.nameIdx.stringDataOff.data.data
+
+    ## \brief Find the method ID item of given index.
+    def find_methodid_idx(self, idx):
+        methodid = self.methodIds.items[idx]
+        return methodid
+
+    ## \brief Find a method definition with an index to method ID.
+    def find_method_idx(self, idx):
+        methodid = self.find_methodid_idx(idx)
+        method_name = DEXFile_linked.get_methoid_name(methodid)
+        method_proto = methodid.protoIdx
+        method_typeid = methodid.classIdx
+        classdef = self.find_class_typeid(method_typeid)
+        
+        method = self.find_method_name_proto(method, method_proto, classdef)
+        
+        return method
+
+    ## \brief Test if prototype of two methods are compatible.
+    @staticmethod
+    def _proto_is_compatible(proto1, proto2):
+        if proto1.returnTypeIdx != proto2.returnTypeIdx:
+            return False
+        typelist1 = proto1.parametersOffRef.value
+        typelist2 = proto2.parametersOffRef.value
+        if len(typelist1.typeItems.items) != len(typelist2.typeItems.items):
+            return False
+
+        for typeid1, typeid2 in map(None,
+                                    typelist1.typeItems.items,
+                                    typelist2.typeItems.items):
+            if typeid1 != typeid2:
+                return False
+            pass
+        return True
+
+    def find_method_name_proto(self, method_name, proto, classdef):
+        if not classdef.classDataOffRef.is_true:
+            return
+
+        classdata = classdef.classDataOffRef.value
+        for wmethod in classdata.directMethods.items + \
+                classdata.virtualMethods.items:
+            wmethod_name = DEXFile_linked.get_method_name(wmethod)
+            if method_name != wmethod_name:
+                continue
+            if DEXFile_linked._proto_is_compatible(wmethod.protoIdx, proto):
+                return wmethod
+            pass
+        raise ValueError, 'can not find a method for given name and prototype'
     pass
 
 
--- a/paraspace/injection.py	Wed Jul 27 12:09:19 2011 +0800
+++ b/paraspace/injection.py	Thu Jul 28 00:06:54 2011 +0800
@@ -209,3 +209,79 @@
 def dexfile_insert_class(dex, class_def):
     clone = _clone_classdef(dex, class_def)
     return clone
+
+
+def method_redirect_types(dex, method, redirect_map):
+    from paraspace.dalvik_opcodes import decode_insn_blk, all_opcodes
+    from paraspace.dalvik_opcodes import encode_opcode_vectors
+    from paraspace.dexfile import DEXFile_linked
+    
+    if not method.codeOffRef.is_true:
+        return
+    
+    code = method.codeOffRef.value
+    insns_blk = code.insns.data
+    op_vectors = decode_insn_blk(insns_blk)
+
+    def redirect(opcode, args):
+        if opcode == all_opcodes.OP_NEW_INSTANCE:
+            typeidx = args[1]
+            if typeidx in redirect_map:
+                to_type = redirect_map[typeidx]
+                return opcode, (args[0], to_type)
+            pass
+        elif opcode == all_opcodes.OP_INVOKE_DIRECT:
+            methodidx = args[2]
+            methodid = dex.find_methodid_idx(methodidx)
+            method_typeid = methodid.classIdx
+            method_typeidx = dex.get_idx_typeid(method_typeidx)
+            if method_typeidx not in redirect_map:
+                return opcode, args
+            
+            new_method_typeidx = redirect_map[method_typeidx]
+            new_method_typeid = dex.find_typeid_idx(new_method_typeidx)
+            classdef = dex.find_class_typeid(new_method_typeid)
+            method_name = DEXFile_linked.get_methodid_name(methodid)
+            method_proto = methodid.protoIdx
+
+            try:
+                new_method = dex.find_method_name_proto(method_name,
+                                                        method_proto,
+                                                        classdef)
+            except:
+                return opcode, args
+            new_method_idx = dex.get_index_method(new_method)
+            return opcode, (args[0], args[1], new_method_idx,
+                            args[3], args[4], args[5], args[6])
+        return opcode, args
+    
+    new_op_vectors = [redirect(opcode, args) for opcode, args in op_vectors]
+    new_insns_blk = encode_opcode_vectors(new_op_vectors) 
+    
+    code.insns.data = new_insns_blk
+    pass
+
+
+def class_redirect_types(dex, classdef, redirect_map):
+    if not classdef.classDataOffRef.is_true:
+        return
+    
+    classdata = classdef.classDataOffRef.value
+    for method in classdata.directMethods.items:
+        method_redirect_types(dex, method, redirect_map)
+        pass
+    for method in classdata.virtualMethods.items:
+        method_redirect_types(dex, method, redirect_map)
+        pass
+    pass
+
+
+def dexfile_redirect_types(dex, redirect_map, excludes=set([])):
+    for classdef in dex.classDefs.items:
+        typeid = classdef.classIdx
+        idx = dex.get_index_typeid(typeid)
+        if idx in excludes:
+            continue
+        class_redirect_types(dex, classdef, redirect_map)
+        pass
+    pass
--- a/paraspace/tests/dexfile_test.py	Wed Jul 27 12:09:19 2011 +0800
+++ b/paraspace/tests/dexfile_test.py	Thu Jul 28 00:06:54 2011 +0800
@@ -401,3 +401,32 @@
     method_name = dexfile.DEXFile_linked.get_method_name(fakefile_cstr)
     assert method_name == '<init>'
     pass
+
+
+def find_typeid_test():
+    from paraspace.dex_deptracker import prepare_dep_decls
+    
+    _install_dexfile_4_deptracker()
+    
+    all_dep_decls = prepare_dep_decls()
+    
+    srcdir = os.path.dirname(__file__)
+    srcroot = os.path.join(srcdir, '..', '..')
+    
+    fakefile_fn = os.path.join(srcroot, 'data', 'fakefile.dex')
+    fakefile_dex = dexfile.DEXFile.open(fakefile_fn)
+    fakefile_linked = \
+        dexfile.DEXFile_linked. \
+        build_dependencies(fakefile_dex, all_dep_decls)
+    
+    File_typeid = fakefile_linked.find_typeid_name('Ljava/io/File;')
+    assert File_typeid
+
+    File_name = dexfile.DEXFile_linked.get_typeid_name(File_typeid)
+    assert File_name == 'Ljava/io/File;'
+
+    idx = fakefile_linked.get_idx_typeid(File_typeid)
+    assert idx >= 0
+    File_typeid_idx = fakefile_linked.find_typeid_idx(idx)
+    assert File_typeid == File_typeid_idx
+    pass