一、前言 在之前分析dex文件加载流程中(dex文件加载流程(一)(二)(三) ),我们分析过了DexClassLoader和PathClassLoader这两个类加载器加载dex的流程。它们两个都是用来记载文件dex的,但是在Android 8新增了InMemoryDexClassLoader,专门用于加载内存dex,接下来我们就根据源码来剖析内存dex的加载流程。
二、InMemoryDexClassLoader加载内存dex的流程 2.1 InMemoryDexClassLoader 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public final class InMemoryDexClassLoader extends BaseDexClassLoader { public InMemoryDexClassLoader (ByteBuffer[] dexBuffers, ClassLoader parent) { super (dexBuffers, parent); } public InMemoryDexClassLoader (ByteBuffer dexBuffer, ClassLoader parent) { this (new ByteBuffer [] { dexBuffer }, parent); } }
InMemoryDexClassLoader继承自BaseDexClassLoader,是专门用来加载内存dex的,其中第一个构造方法接受ByteBuffer数组,因此是可以支持多dex的 。它的方法都在父类中实现,其中对应的构造方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public BaseDexClassLoader (ByteBuffer[] dexFiles, ClassLoader parent) { super (parent); this .pathList = new DexPathList (this , dexFiles); }
在该方法中,会创建一个DexPathList对象。
2.2 DexPathList实例化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public DexPathList (ClassLoader definingContext, ByteBuffer[] dexFiles) { if (definingContext == null ) { throw new NullPointerException ("definingContext == null" ); } if (dexFiles == null ) { throw new NullPointerException ("dexFiles == null" ); } if (Arrays.stream(dexFiles).anyMatch(v -> v == null )) { throw new NullPointerException ("dexFiles contains a null Buffer!" ); } this .definingContext = definingContext; this .nativeLibraryDirectories = Collections.emptyList(); this .systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path" ), true ); this .nativeLibraryPathElements = makePathElements(this .systemNativeLibraryDirectories); ArrayList<IOException> suppressedExceptions = new ArrayList <IOException>(); this .dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions); if (suppressedExceptions.size() > 0 ) { this .dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException [suppressedExceptions.size()]); } else { dexElementsSuppressedExceptions = null ; } }
值得注意的是,它在这里调用的是makeInMemoryDexElements()
方法,而在DexClassLoader和PathClassLoader流程中,调用的是makeDexElements()
方法。
2.3 makeInMemoryDexElements() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles, List<IOException> suppressedExceptions) { Element[] elements = new Element [dexFiles.length]; int elementPos = 0 ; for (ByteBuffer buf : dexFiles) { try { DexFile dex = new DexFile (buf); elements[elementPos++] = new Element (dex); } catch (IOException suppressed) { System.logE("Unable to load dex file: " + buf, suppressed); suppressedExceptions.add(suppressed); } } if (elementPos != elements.length) { elements = Arrays.copyOf(elements, elementPos); } return elements; }
这个方法主要是遍历内存dex数组,给每个内存dex创建一个DexFile对象,并封装成Element对象。
2.4 DexFile实例化 1 2 3 4 5 6 DexFile(ByteBuffer buf) throws IOException { mCookie = openInMemoryDexFile(buf); mInternalCookie = mCookie; mFileName = null ; }
调用openInMemoryDexFile()
方法,返回的cookie是用于操作dex文件的唯一凭证,可以理解为”身份码”。
2.5 openInMemoryDexFile() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 private static Object openInMemoryDexFile (ByteBuffer buf) throws IOException { if (buf.isDirect()) { return createCookieWithDirectBuffer(buf, buf.position(), buf.limit()); } else { return createCookieWithArray(buf.array(), buf.position(), buf.limit()); } }private static native Object createCookieWithDirectBuffer (ByteBuffer buf, int start, int end) ;private static native Object createCookieWithArray (byte [] buf, int start, int end) ;
最终调用两个native层的方法,它们都在art/runtime/native/dalvik_system_DexFile.cc
中实现。
2.5.1 createCookieWithDirectBuffer() createCookieWithDirectBuffer()
对应DexFile_createCookieWithDirectBuffer()
方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static jobject DexFile_createCookieWithDirectBuffer (JNIEnv* env, jclass, jobject buffer, jint start, jint end) { uint8_t * base_address = reinterpret_cast <uint8_t *>(env->GetDirectBufferAddress (buffer)); if (base_address == nullptr ) { ScopedObjectAccess soa (env) ; ThrowWrappedIOException ("dexFileBuffer not direct" ); return 0 ; } std::unique_ptr<MemMap> dex_mem_map (AllocateDexMemoryMap(env, start, end)) ; if (dex_mem_map == nullptr ) { DCHECK (Thread::Current ()->IsExceptionPending ()); return 0 ; } size_t length = static_cast <size_t >(end - start); memcpy (dex_mem_map->Begin (), base_address, length); return CreateSingleDexFileCookie (env, std::move (dex_mem_map)); }
主要是将java层的数据转换成native层的数据,最后调用CreateSingleDexFileCookie()
方法。
2.5.2 createCookieWithArray() createCookieWithArray()
对应DexFile_createCookieWithArray()
方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static jobject DexFile_createCookieWithArray (JNIEnv* env, jclass, jbyteArray buffer, jint start, jint end) { std::unique_ptr<MemMap> dex_mem_map (AllocateDexMemoryMap(env, start, end)) ; if (dex_mem_map == nullptr ) { DCHECK (Thread::Current ()->IsExceptionPending ()); return 0 ; } auto destination = reinterpret_cast <jbyte*>(dex_mem_map.get ()->Begin ()); env->GetByteArrayRegion (buffer, start, end - start, destination); return CreateSingleDexFileCookie (env, std::move (dex_mem_map)); }
同样最终也是调用CreateSingleDexFileCookie()
方法。
2.6 CreateSingleDexFileCookie() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static jobject CreateSingleDexFileCookie (JNIEnv* env, std::unique_ptr<MemMap> data) { std::unique_ptr<const DexFile> dex_file (CreateDexFile(env, std::move(data))) ; if (dex_file.get () == nullptr ) { DCHECK (env->ExceptionCheck ()); return nullptr ; } std::vector<std::unique_ptr<const DexFile>> dex_files; dex_files.push_back (std::move (dex_file)); return ConvertDexFilesToJavaArray (env, nullptr , dex_files); }
主要是调用CreateDexFile()
方法为内存dex创建DexFile对象,然后移入DexFile数组中,最后调用ConvertDexFilesToJavaArray()
方法将DexFile数组转成Java数组。这里我们主要关注CreateDexFile()
方法。
2.7 CreateDexFile() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static const DexFile* CreateDexFile (JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) { std::string location = StringPrintf ("Anonymous-DexFile@%p-%p" , dex_mem_map->Begin (), dex_mem_map->End ()); std::string error_message; std::unique_ptr<const DexFile> dex_file (DexFile::Open(location, 0 , std::move(dex_mem_map), true , true , &error_message)) ; if (dex_file == nullptr ) { ScopedObjectAccess soa (env) ; ThrowWrappedIOException ("%s" , error_message.c_str ()); return nullptr ; } if (!dex_file->DisableWrite ()) { ScopedObjectAccess soa (env) ; ThrowWrappedIOException ("Failed to make dex file read-only" ); return nullptr ; } return dex_file.release (); }
主要是调用DexFile::Open()
方法,之后的流程就跟DexClassLoader和PathClassLoader一样了,可以详见dex文件加载流程(三) ,本文就不再详细分析了。
2.8 DexFile::Open() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 std::unique_ptr<const DexFile> DexFile::Open (const std::string& location, uint32_t location_checksum, std::unique_ptr<MemMap> map, bool verify, bool verify_checksum, std::string* error_msg) { ScopedTrace trace (std::string("Open dex file from mapped-memory " ) + location) ; CHECK (map.get () != nullptr ); if (map->Size () < sizeof (DexFile::Header)) { *error_msg = StringPrintf ( "DexFile: failed to open dex file '%s' that is too short to have a header" , location.c_str ()); return nullptr ; } std::unique_ptr<DexFile> dex_file = OpenCommon (map->Begin (), map->Size (), location, location_checksum, kNoOatDexFile, verify, verify_checksum, error_msg); if (dex_file != nullptr ) { dex_file->mem_map_.reset (map.release ()); } return dex_file; }
主要是调用OpenCommon()
方法。
2.9 OpenCommon() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 std::unique_ptr<DexFile> DexFile::OpenCommon (const uint8_t * base, size_t size, const std::string& location, uint32_t location_checksum, const OatDexFile* oat_dex_file, bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr ) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file (new DexFile(base, size, location, location_checksum, oat_dex_file)) ; if (dex_file == nullptr ) { *error_msg = StringPrintf ("Failed to open dex file '%s' from memory: %s" , location.c_str (), error_msg->c_str ()); return nullptr ; } if (!dex_file->Init (error_msg)) { dex_file.reset (); return nullptr ; } if (verify && !DexFileVerifier::Verify (dex_file.get (), dex_file->Begin (), dex_file->Size (), location.c_str (), verify_checksum, error_msg)) { if (verify_result != nullptr ) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr ; } if (verify_result != nullptr ) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
从这里可以看出,不管是DexClassLoader、PathClassLoader,还是InMemoryDexClassLoader,尽管它们的流程可能会不太一样,但最终都会调用OpenCommon()
方法创建dexfile,而且该方法的参数含有dex的起始地址(base)和大小(size),因此这里可以作为一个非常好的脱壳点。
参考:
http://aospxref.com/android-8.0.0_r36