Andorid DexClassLoader的创建过程解析(基于5.0)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Andorid DexClassLoader的创建过程解析(基于5.0)相关的知识,希望对你有一定的参考价值。

android插件框架时,经常会用到dex的动态加载,就要直接或间接的使用DexClassLoader,在new DexClassLoader的时候Android系统做了很多工作,下面我们详细分析一下:

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}
public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
  ......
@Override
protected URL findResource(String name) { return pathList.findResource(name); } @Override protected Enumeration<URL> findResources(String name) { return pathList.findResources(name); } @Override public String findLibrary(String name) { return pathList.findLibrary(name); }
  ......
}

看到关键步骤了,设置完parent的ClassLoader之后,创建了DexPathList对象pathList,可以看到,很多操作都是直接委托给pathList的,我们看下这个对象里面做了什么。

这里涉及到两个目录,optimizedDirectory和libraryPath,我们先从简单的入手,先看看DexPathList是如何处理libraryPath

final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";/** List of native library directories. */
    private final File[] nativeLibraryDirectories;public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {

        ......

        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }

    private static File[] splitLibraryPath(String path) {
        // Native libraries may exist in both the system and
        // application library paths, and we use this search order:
        //
        //   1. this class loader‘s library path for application libraries
        //   2. the VM‘s library path from the system property for system libraries
        //
        // This order was reversed prior to Gingerbread; see http://b/2933456.
        ArrayList<File> result = splitPaths(path, System.getProperty("java.library.path"), true);
        return result.toArray(new File[result.size()]);
    }

    private static ArrayList<File> splitPaths(String path1, String path2,
            boolean wantDirectories) {
        ArrayList<File> result = new ArrayList<File>();

        splitAndAdd(path1, wantDirectories, result);
        splitAndAdd(path2, wantDirectories, result);
        return result;
    }

    private static void splitAndAdd(String searchPath, boolean directoriesOnly,
            ArrayList<File> resultList) {
        if (searchPath == null) {
            return;
        }
        for (String path : searchPath.split(":")) {
            try {
                StructStat sb = Libcore.os.stat(path);
                if (!directoriesOnly || S_ISDIR(sb.st_mode)) {
                    resultList.add(new File(path));
                }
            } catch (ErrnoException ignored) {
            }
        }
    }

}

从上面代码可以看到,系统会将用户传进的目录和默认的系统lib目录(System.getProperty("java.library.path"))通过“:”分割后,存进一个File数组中。

这就是DexClassLoader对libraryPath的所有操作,所以可以看到,和optimizePath不一样,并没有对so文件解压。

接下来,我们看看DexClassLoader初始化时候对optimizePath做了什么

 还是从DexPathList入手

final class DexPathList {

    /** class definition context */
    private final ClassLoader definingContext;

    private final Element[] dexElements;

    private final IOException[] dexElementsSuppressedExceptions;

    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        ......
        //check optimized directory
        ......

        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);
        //save Exceptions
        ......
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }

    private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                             ArrayList<IOException> suppressedExceptions) {
        ......

                if (name.endsWith(DEX_SUFFIX)) {
                    // Raw dex file (not inside a zip/jar).
                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException ex) {
                        System.logE("Unable to load dex file: " + file, ex);
                    }
                } else {
                    zip = file;
                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException suppressed) {
                        suppressedExceptions.add(suppressed);
                    }
                }
           ......

    private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }

}    

除了一些目录检查,异常处理,从构造函数一步一步调用到了loadDexFile,从而进入到DexFile中,我们继续。

public final class DexFile {

    public DexFile(File file) throws IOException {
        this(file.getPath());
    }

    public DexFile(String fileName) throws IOException {
        mCookie = openDexFile(fileName, null, 0);
        mFileName = fileName;
        guard.open("close");
        //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
    }

    private DexFile(String sourceName, String outputName, int flags) throws IOException {
        if (outputName != null) {
            try {
                String parent = new File(outputName).getParent();
                if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
                    throw new IllegalArgumentException("Optimized data directory " + parent
                            + " is not owned by the current user. Shared storage cannot protect"
                            + " your application from code injection attacks.");
                }
            } catch (ErrnoException ignored) {
                // assume we‘ll fail with a more contextual error later
            }
        }

        mCookie = openDexFile(sourceName, outputName, flags);
        mFileName = sourceName;
        guard.open("close");
        //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
    }

    static public DexFile loadDex(String sourcePathName, String outputPathName,
        int flags) throws IOException {
        return new DexFile(sourcePathName, outputPathName, flags);
    }

    private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
        // Use absolute paths to enable the use of relative paths when testing on host.
        return openDexFileNative(new File(sourceName).getAbsolutePath(),
                                 (outputName == null) ? null : new File(outputName).getAbsolutePath(),
                                 flags);
    }
}

可以看到,最终走到openDexFileNative这个native方法中,这个方法的native实现在art/runtime/native/dalvik_system_DexFile.cc中,看代码:

static jlong DexFile_openDexFileNative(JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
  ......
  bool success = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs,
                                             dex_files.get());
  ......
}

方法OpenDexFilesFromOat的实现在art/runtime/class_linker.cc中,这个方法太长,我先把全部内容贴上来,

技术分享
  1 bool ClassLinker::OpenDexFilesFromOat(const char* dex_location, const char* oat_location,
  2                                       std::vector<std::string>* error_msgs,
  3                                       std::vector<std::unique_ptr<const DexFile>>* dex_files) {
  4   // 1) Check whether we have an open oat file.
  5   // This requires a dex checksum, use the "primary" one.
  6   uint32_t dex_location_checksum;
  7   uint32_t* dex_location_checksum_pointer = &dex_location_checksum;
  8   bool have_checksum = true;
  9   std::string checksum_error_msg;
 10   if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) {
 11     // This happens for pre-opted files since the corresponding dex files are no longer on disk.
 12     dex_location_checksum_pointer = nullptr;
 13     have_checksum = false;
 14   }
 15 
 16   bool needs_registering = false;
 17 
 18   const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location,
 19                                                                  dex_location_checksum_pointer);
 20   std::unique_ptr<const OatFile> open_oat_file(
 21       oat_dex_file != nullptr ? oat_dex_file->GetOatFile() : nullptr);
 22 
 23   // 2) If we do not have an open one, maybe there‘s one on disk already.
 24 
 25   // In case the oat file is not open, we play a locking game here so
 26   // that if two different processes race to load and register or generate
 27   // (or worse, one tries to open a partial generated file) we will be okay.
 28   // This is actually common with apps that use DexClassLoader to work
 29   // around the dex method reference limit and that have a background
 30   // service running in a separate process.
 31   ScopedFlock scoped_flock;
 32 
 33   if (open_oat_file.get() == nullptr) {
 34     if (oat_location != nullptr) {
 35       // Can only do this if we have a checksum, else error.
 36       if (!have_checksum) {
 37         error_msgs->push_back(checksum_error_msg);
 38         return false;
 39       }
 40 
 41       std::string error_msg;
 42 
 43       // We are loading or creating one in the future. Time to set up the file lock.
 44       if (!scoped_flock.Init(oat_location, &error_msg)) {
 45         error_msgs->push_back(error_msg);
 46         return false;
 47       }
 48 
 49       // TODO Caller specifically asks for this oat_location. We should honor it. Probably?
 50       open_oat_file.reset(FindOatFileInOatLocationForDexFile(dex_location, dex_location_checksum,
 51                                                              oat_location, &error_msg));
 52 
 53       if (open_oat_file.get() == nullptr) {
 54         std::string compound_msg = StringPrintf("Failed to find dex file ‘%s‘ in oat location ‘%s‘: %s",
 55                                                 dex_location, oat_location, error_msg.c_str());
 56         VLOG(class_linker) << compound_msg;
 57         error_msgs->push_back(compound_msg);
 58       }
 59     } else {
 60       // TODO: What to lock here?
 61       bool obsolete_file_cleanup_failed;
 62       open_oat_file.reset(FindOatFileContainingDexFileFromDexLocation(dex_location,
 63                                                                       dex_location_checksum_pointer,
 64                                                                       kRuntimeISA, error_msgs,
 65                                                                       &obsolete_file_cleanup_failed));
 66       // There‘s no point in going forward and eventually try to regenerate the
 67       // file if we couldn‘t remove the obsolete one. Mostly likely we will fail
 68       // with the same error when trying to write the new file.
 69       // TODO: should we maybe do this only when we get permission issues? (i.e. EACCESS).
 70       if (obsolete_file_cleanup_failed) {
 71         return false;
 72       }
 73     }
 74     needs_registering = true;
 75   }
 76 
 77   // 3) If we have an oat file, check all contained multidex files for our dex_location.
 78   // Note: LoadMultiDexFilesFromOatFile will check for nullptr in the first argument.
 79   bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
 80                                               dex_location_checksum_pointer,
 81                                               false, error_msgs, dex_files);
 82   if (success) {
 83     const OatFile* oat_file = open_oat_file.release();  // Avoid deleting it.
 84     if (needs_registering) {
 85       // We opened the oat file, so we must register it.
 86       RegisterOatFile(oat_file);
 87     }
 88     // If the file isn‘t executable we failed patchoat but did manage to get the dex files.
 89     return oat_file->IsExecutable();
 90   } else {
 91     if (needs_registering) {
 92       // We opened it, delete it.
 93       open_oat_file.reset();
 94     } else {
 95       open_oat_file.release();  // Do not delete open oat files.
 96     }
 97   }
 98 
 99   // 4) If it‘s not the case (either no oat file or mismatches), regenerate and load.
100 
101   // Need a checksum, fail else.
102   if (!have_checksum) {
103     error_msgs->push_back(checksum_error_msg);
104     return false;
105   }
106 
107   // Look in cache location if no oat_location is given.
108   std::string cache_location;
109   if (oat_location == nullptr) {
110     // Use the dalvik cache.
111     const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));
112     cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str());
113     oat_location = cache_location.c_str();
114   }
115 
116   bool has_flock = true;
117   // Definitely need to lock now.
118   if (!scoped_flock.HasFile()) {
119     std::string error_msg;
120     if (!scoped_flock.Init(oat_location, &error_msg)) {
121       error_msgs->push_back(error_msg);
122       has_flock = false;
123     }
124   }
125 
126   if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {
127     // Create the oat file.
128     open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
129                                                     oat_location, error_msgs));
130   }
131 
132   // Failed, bail.
133   if (open_oat_file.get() == nullptr) {
134     std::string error_msg;
135     // dex2oat was disabled or crashed. Add the dex file in the list of dex_files to make progress.
136     DexFile::Open(dex_location, dex_location, &error_msg, dex_files);
137     error_msgs->push_back(error_msg);
138     return false;
139   }
140 
141   // Try to load again, but stronger checks.
142   success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
143                                          dex_location_checksum_pointer,
144                                          true, error_msgs, dex_files);
145   if (success) {
146     RegisterOatFile(open_oat_file.release());
147     return true;
148   } else {
149     return false;
150   }
151 }
class_linker.cc的方法ClassLinker::OpenDexFilesFromOat

我们按步骤分析,首先会先获取这个dex文件的Checksum值:

  if (!DexFile::GetChecksum(dex_location, dex_location_checksum_pointer, &checksum_error_msg)) {
    // This happens for pre-opted files since the corresponding dex files are no longer on disk.
    dex_location_checksum_pointer = nullptr;
    have_checksum = false;
  }

这个checksum的值存放在dex_location_checksum_pointer指向的地址,然后通过这个dex_location和checksum查找oat文件是否已经生成,并且加载到内存,

  const OatFile::OatDexFile* oat_dex_file = FindOpenedOatDexFile(oat_location, dex_location, dex_location_checksum_pointer);

进入FindOpenedOatDexFile()函数,

const OatFile::OatDexFile* ClassLinker::FindOpenedOatDexFile(const char* oat_location,
                                                             const char* dex_location,
                                                             const uint32_t* dex_location_checksum) {
  ReaderMutexLock mu(Thread::Current(), dex_lock_);
  for (const OatFile* oat_file : oat_files_) {
    DCHECK(oat_file != nullptr);

    if (oat_location != nullptr) {
      if (oat_file->GetLocation() != oat_location) {
        continue;
      }
    }

    const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location,
                                                                      dex_location_checksum,
                                                                      false);
    if (oat_dex_file != nullptr) {
      return oat_dex_file;
    }
  }
  return nullptr;
}

从代码看出,先根据oat的文件路径,查找内存里是否加载了同路径的oat文件,有的话执行oat_file->GetOatDexFile(),

技术分享
 1 const OatFile::OatDexFile* OatFile::GetOatDexFile(const char* dex_location,
 2                                                   const uint32_t* dex_location_checksum,
 3                                                   bool warn_if_not_found) const {
 4   // NOTE: We assume here that the canonical location for a given dex_location never
 5   // changes. If it does (i.e. some symlink used by the filename changes) we may return
 6   // an incorrect OatDexFile. As long as we have a checksum to check, we shall return
 7   // an identical file or fail; otherwise we may see some unpredictable failures.
 8 
 9   // TODO: Additional analysis of usage patterns to see if this can be simplified
10   // without any performance loss, for example by not doing the first lock-free lookup.
11 
12   const OatFile::OatDexFile* oat_dex_file = nullptr;
13   StringPiece key(dex_location);
14   // Try to find the key cheaply in the oat_dex_files_ map which holds dex locations
15   // directly mentioned in the oat file and doesn‘t require locking.
16   auto primary_it = oat_dex_files_.find(key);
17   if (primary_it != oat_dex_files_.end()) {
18     oat_dex_file = primary_it->second;
19     DCHECK(oat_dex_file != nullptr);
20   } else {
21     // This dex_location is not one of the dex locations directly mentioned in the
22     // oat file. The correct lookup is via the canonical location but first see in
23     // the secondary_oat_dex_files_ whether we‘ve looked up this location before.
24     MutexLock mu(Thread::Current(), secondary_lookup_lock_);
25     auto secondary_lb = secondary_oat_dex_files_.lower_bound(key);
26     if (secondary_lb != secondary_oat_dex_files_.end() && key == secondary_lb->first) {
27       oat_dex_file = secondary_lb->second;  // May be nullptr.
28     } else {
29       // We haven‘t seen this dex_location before, we must check the canonical location.
30       std::string dex_canonical_location = DexFile::GetDexCanonicalLocation(dex_location);
31       if (dex_canonical_location != dex_location) {
32         StringPiece canonical_key(dex_canonical_location);
33         auto canonical_it = oat_dex_files_.find(canonical_key);
34         if (canonical_it != oat_dex_files_.end()) {
35           oat_dex_file = canonical_it->second;
36         }  // else keep nullptr.
37       }  // else keep nullptr.
38 
39       // Copy the key to the string_cache_ and store the result in secondary map.
40       string_cache_.emplace_back(key.data(), key.length());
41       StringPiece key_copy(string_cache_.back());
42       secondary_oat_dex_files_.PutBefore(secondary_lb, key_copy, oat_dex_file);
43     }
44   }
45   if (oat_dex_file != nullptr &&
46       (dex_location_checksum == nullptr ||
47        oat_dex_file->GetDexFileLocationChecksum() == *dex_location_checksum)) {
48     return oat_dex_file;
49   }
50 
51   if (warn_if_not_found) {
52     std::string dex_canonical_location = DexFile::GetDexCanonicalLocation(dex_location);
53     std::string checksum("<unspecified>");
54     if (dex_location_checksum != NULL) {
55       checksum = StringPrintf("0x%08x", *dex_location_checksum);
56     }
57     LOG(WARNING) << "Failed to find OatDexFile for DexFile " << dex_location
58                  << " ( canonical path " << dex_canonical_location << ")"
59                  << " with checksum " << checksum << " in OatFile " << GetLocation();
60     if (kIsDebugBuild) {
61       for (const OatDexFile* odf : oat_dex_files_storage_) {
62         LOG(WARNING) << "OatFile " << GetLocation()
63                      << " contains OatDexFile " << odf->GetDexFileLocation()
64                      << " (canonical path " << odf->GetCanonicalDexFileLocation() << ")"
65                      << " with checksum 0x" << std::hex << odf->GetDexFileLocationChecksum();
66       }
67     }
68   }
69 
70   return NULL;
71 }
oat_file.cc的OatFile::GetOatDexFile函数

简单说一下GetOatDexFile函数做了什么,系统每次成功生成oat后,都会将dex文件的路径(注意这里是dex文件,不是oat文件)和对应的OatDexFile对象以key/value的形式保存到oat_dex_files_中,这里有两点注意,第一,缓存的key值是原dex或apk的路径,不是oat的;第二,之所以一个oat对象会用一个map做缓存,是因为会有multidex的情况存在。

首次运行,内存中没有已打开的oat对象,从磁盘查找,

      open_oat_file.reset(FindOatFileInOatLocationForDexFile(dex_location, dex_location_checksum,
                                                             oat_location, &error_msg));

      if (open_oat_file.get() == nullptr) {
        std::string compound_msg = StringPrintf("Failed to find dex file ‘%s‘ in oat location ‘%s‘: %s",
                                                dex_location, oat_location, error_msg.c_str());
        VLOG(class_linker) << compound_msg;
        error_msgs->push_back(compound_msg);
      }

FindOatFileInOatLocationForDexFile()函数会尝试打开目标oat文件,并对其checksum,oat_offset,patch_delta做校验,如果oat文件存在且内容正确,则

  bool success = LoadMultiDexFilesFromOatFile(open_oat_file.get(), dex_location,
                                              dex_location_checksum_pointer,
                                              false, error_msgs, dex_files);
  if (success) {
    const OatFile* oat_file = open_oat_file.release();  // Avoid deleting it.
    if (needs_registering) {
      // We opened the oat file, so we must register it.
      RegisterOatFile(oat_file);
    }
    // If the file isn‘t executable we failed patchoat but did manage to get the dex files.
    return oat_file->IsExecutable();
  }

LoadMultiDexFilesFromOatFile()是将多个dex文件(例如classes1.dex,classes2.dex)映射成DexFile对象,并添加到一个vector数据结构dex_files中,之后调用RegisterOatFile()函数将内存中的oat对象注册到oat_files_中,然后整个流程就跑完了。

如果oat文件不存在,或者文件不匹配,则重新创建dex的oat文件,并加载,

 // Look in cache location if no oat_location is given.
  std::string cache_location;
  if (oat_location == nullptr) {
    // Use the dalvik cache.
   // GetDalvikCacheOrDie()函数在art/runtime/utils.cc中
     // 这句意味着如果没有指定oat的存放目录,则使用类似/data/dalvik-cache目录
    const std::string dalvik_cache(GetDalvikCacheOrDie(GetInstructionSetString(kRuntimeISA)));
    cache_location = GetDalvikCacheFilenameOrDie(dex_location, dalvik_cache.c_str());
    oat_location = cache_location.c_str();
  }

  // 对文件上锁
  bool has_flock = true;
  // Definitely need to lock now.
  if (!scoped_flock.HasFile()) {
    std::string error_msg;
    if (!scoped_flock.Init(oat_location, &error_msg)) {
      error_msgs->push_back(error_msg);
      has_flock = false;
    }
  }

  if (Runtime::Current()->IsDex2OatEnabled() && has_flock && scoped_flock.HasFile()) {
    // Create the oat file.
    // 如果dex2oat工具可用,则生成oat文件
    open_oat_file.reset(CreateOatFileForDexLocation(dex_location, scoped_flock.GetFile()->Fd(),
                                                    oat_location, error_msgs));
  }

由于代码比较简单,直接写在注释里了,接下来看下oat是怎么生成的,也就是CreateOatFileForDexLocation()函数做了什么,

const OatFile* ClassLinker::CreateOatFileForDexLocation(const char* dex_location,
                                                        int fd, const char* oat_location,
                                                        std::vector<std::string>* error_msgs) {
  std::string error_msg;
  if (!GenerateOatFile(dex_location, fd, oat_location, &error_msg)) {
    CHECK(!error_msg.empty());
    error_msgs->push_back(error_msg);
    return nullptr;
  }
  ......

  return oat_file.release();
}

真正生成oat是在GenerateOatFile()函数中,

 1 bool ClassLinker::GenerateOatFile(const char* dex_filename,
 2                                   int oat_fd,
 3                                   const char* oat_cache_filename,
 4                                   std::string* error_msg) {
 5   Locks::mutator_lock_->AssertNotHeld(Thread::Current());  // Avoid starving GC.

以上是关于Andorid DexClassLoader的创建过程解析(基于5.0)的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向启动 DEX 字节码中的 Activity 组件 ( 在 PathClassLoader 和 BootClassLoader 之间插入 DexClassLoader )

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

创建第一个Andorid程序

使用Android Studio创建Andorid模拟器

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | ART 虚拟机下 DexClassLoader 类加载器脱壳点总结 )