-
-
[原创动画] [源码分析] --脱壳中, DEX 内存加载 和 磁盘路径方式加载
-
发表于: 2025-6-10 13:38 241
-
在脱壳中, 两种 DEX 内存加载 和 SD卡方式加载, 源代码分析.
InMemoryDexClassLoader 和 DexClassLoader 的流程的分析
动画采用4K录制,建议4k显示器观看.
分为两个部分, 为了视频的清晰, 暂时提供 ftp下载.
文件1, 2025-06-10_在脱壳中, 两种 DEX 内存加载 和 SD卡方式加载, 源代码分析(1).mkv
文件2, 2025-06-10_在脱壳中, 两种 DEX 内存加载 和 SD卡方式加载, 源代码分析(2).mkv
举例子, 有个同学问了你一个问题 , 就是老师说, 你搞懂了吗?
--- 回答, 搞懂了!
然后,老师说, 你把这个题,给那个同学讲一讲.
--- 于是, 思路不是太清晰, 好像懂了, 好像又没有懂.
---- 所以, 当你给别人真正 讲明白了的时候, 才开始更换角色思考. ==> 日语, 版本, 英语版本.
本篇目涉及两个,问题, 一个是课件, 一个是 通过 Android Studio 和 CLion 分别阅读 AOSP8.1 的 Java层源码和 CPP 层的源码.
过程中,带着繁琐的跳转, 没有人能够一次通过. 都是多遍通过.
在 2025 , 几乎不会没有人不用 AI , AI 什么都懂 ,但是不代表,你懂得, 所以, 只有你懂了, 才是真格. 没有严谨的 分析考究AI提供的依据 , AI 就是胡说!
使用它, 抱着最 朴素的观念, 就是证伪! (AI就是错的.)
InMemoryDexClassLoader 源码分析
???? 调用流程总览
InMemoryDexClassLoader (Java) └── BaseDexClassLoader (Java) └── DexPathList (Java) └── makeInMemoryDexElements (Java) └── DexFile (Java) └── DexFile.openInMemoryDexFile (Java) └── `DexFile.DexFile_createCookieWithDirectBuffer 或 `DexFile_createCookieWithArray (Native) └── CreateSingleDexFileCookie (Native) └── DexFile::CreateDexFile (Native) └──DexFile::Open (Native) └── DexFile::OpenCommon (Native) └── DexFile::DexFile 构造函数 (Native)
详细调用链与源码位置
1. InMemoryDexClassLoader
(Java 层)
位置:
libcore/dalvik/src/main/java/dalvik/system/InMemoryDexClassLoader.java
作用:Java 层入口,允许从
ByteBuffer
加载 DEX 数据。调用:构造函数中调用父类
BaseDexClassLoader
的构造函数。
1 | public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) |
2. BaseDexClassLoader
构造函数 (Java)
位置:
libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
这里调用了父类构造函数,跟进到父类BaseDexClassLoader中作用:初始化
DexPathList
,并将其赋值给成员变量pathList
。12345public
BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
// TODO We should support giving this a library search path maybe.
super
(parent);
this
.pathList =
new
DexPathList(
this
, dexFiles);
}
3. DexPathList
构造函数 (Java)
位置:
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
作用:解析
dexFiles
,并调用makeInMemoryDexElements
方法生成dexElements
。123456public
DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) {
...
this
.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions);
...
}
4. makeInMemoryDexElements
方法 (Java)
位置:
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
作用:遍历
dexPath
中的文件,调用loadDexFile
方法加载每个.dex
文件。123456789101112131415161718private
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;
}
5. DexFile
构造方法 (Java)
位置:
libcore/dalvik/src/main/java/dalvik/system/DexFile.java
作用:调用
openInMemoryDexFile
方法加载Dex
文件到内存。12345DexFile(ByteBuffer buf)
throws
IOException {
// 构造函数,接受 ByteBuffer 作为参数,可能会抛出 IOException 异常
mCookie = openInMemoryDexFile(buf);
// 调用 openInMemoryDexFile 方法加载 Dex 文件到内存并返回 cookie // 这样做可以避免磁盘 I/O,提升加载速度
mInternalCookie = mCookie;
// 将 mCookie 赋值给 mInternalCookie // 这样做是为了保持内部状态的一致性,方便后续操作
mFileName =
null
;
// 初始化文件名为 null // 初始化为空,表示当前没有文件路径信息,可能是内存中的 Dex 文件
}
6. DexFile.openInMemoryDexFile
方法 (Java)
位置:
libcore/dalvik/src/main/java/dalvik/system/DexFile.java
作用: 根据 ByteBuffer 类型决定使用 是direct buffer 还是 array buffer, 通过 createCookieWithDirectBuffer 方法,或者 createCookieWithArray 方法
1234567891011121314151617181920// 定义静态方法,用于打开内存中的 dex 文件,并根据 ByteBuffer 类型决定使用何种方式处理 // 通过提供不同的实现方式,支持 direct buffer 和 array buffer
private
static
Object openInMemoryDexFile(ByteBuffer buf)
throws
IOException {
// 判断 ByteBuffer 是否是 direct 类型的缓冲区
if
(buf.isDirect()) {
// 如果是 direct 缓冲区,使用直接内存创建 Cookie 对象
return
createCookieWithDirectBuffer(buf, buf.position(), buf.limit());
// 如果不是 direct 类型的缓冲区
}
else
{
// 使用数组创建 Cookie对象,通过 buf.array() 直接访问底层数组,用于非 direct 缓冲区
return
createCookieWithArray(buf.array(), buf.position(), buf.limit());
}
}
// 声明一个Native方法,使用直接缓冲区(ByteBuffer)创建 Cookie 对象
// 使用直接缓冲区可以提高 I/O 操作的效率,避免了缓冲区的复制
private
static
native
Object createCookieWithDirectBuffer(ByteBuffer buf,
int
start,
int
end);
// 声明一个Native方法,使用字节数组(byte[])创建 Cookie 对象
// 使用字节数组更为常见,适用于大多数情况下的内存操作
private
static
native
Object createCookieWithArray(
byte
[] buf,
int
start,
int
end);
7. DexFile. DexFile_createCookieWithDirectBuffer 或
DexFile_createCookieWithArray 方法 (Native)
位置:
art/runtime/native/dalvik_system_DexFile.cc
Native查询: control + N 检索 clion 中的符号, symbol , 或者 使用 control + shift + F , 全文检索.
Clion中,查询Native 函数, Java 文件名 + "_" + JNI函数名 "DexFile_createCookieWithDirectBuffer "
作用: 两种处理的分支是一样的, 只不过形态不一样. 都是走到最后一步,CreateSingleDexFileCookie( )
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364// 定义了一个 JNI 方法,用于通过直接缓冲区创建 dex 文件 cookie ==================>
static
jobject DexFile_createCookieWithDirectBuffer(JNIEnv* env,
jclass,
// jclass 参数是类的引用,通常用于静态方法
jobject buffer,
// 直接缓冲区对象 ,--> 直接对应 Java 第一参数
jint start,
// 起始偏移量
jint end) {
// 结束偏移量
// 获取直接缓冲区的地址并转换为 uint8_t 类型的指针 // 这是为了直接操作内存,避免复制数据
uint8_t* base_address =
reinterpret_cast
<uint8_t*>(env->GetDirectBufferAddress(buffer));
if
(base_address == nullptr) {
// 如果直接缓冲区的地址为空
ScopedObjectAccess soa(env);
// 创建 ScopedObjectAccess 对象,确保在 JNI 环境中操作时持有对象引用
ThrowWrappedIOException(
"dexFileBuffer not direct"
);
// 抛出异常,提示缓冲区不是直接缓冲区
return
0;
// 返回 0 表示创建失败
}
// 分配内存映射,创建 dex 文件的内存映射对象 // 通过内存映射提高对大文件的处理效率
std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end));
if
(dex_mem_map == nullptr) {
// 如果内存映射失败
DCHECK(Thread::Current()->IsExceptionPending());
// 确保异常已经被抛出
return
0;
// 返回 0 表示失败
}
// 计算数据的长度
size_t
length =
static_cast
<
size_t
>(end - start);
// 将缓冲区中的数据复制到内存映射的起始位置 // 使用 memcpy 提高效率,避免逐字节处理 ==================>
// ★ 此处的 dex_mem_map->Begin() 就是 dex 文件的起始地址 , length 是 dex 文件的长度
memcpy
(dex_mem_map->Begin(), base_address, length);
// 创建 dex 文件 cookie,并返回 =================> // ★ 核心跳转, 到最后就是一个 Memory Map.
return
CreateSingleDexFileCookie(env, std::move(dex_mem_map));
}
// 定义了另一个 JNI 方法,通过字节数组创建 dex 文件 cookie // ===========>=// DexFile 中 createCookieWithArray 函数对应的 C++ 函数
static
jobject DexFile_createCookieWithArray(JNIEnv* env,
jclass,
// jclass 参数是类的引用,通常用于静态方法
jbyteArray buffer,
// 字节数组缓冲区 --> 直接对应java 第一参数.
jint start,
// 起始偏移量
jint end) {
// 结束偏移量
// 分配内存映射,创建 dex 文件的内存映射对象 // 内存映射提供了对内存区域的有效访问
std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end));
if
(dex_mem_map == nullptr) {
// 如果内存映射失败
DCHECK(Thread::Current()->IsExceptionPending());
// 确保异常已经被抛出
return
0;
// 返回 0 表示失败
}
// 获取内存映射的起始位置,并转换为 jbyte* 类型
auto
destination =
reinterpret_cast
<jbyte*>(dex_mem_map.get()->Begin());
// 从字节数组中获取指定范围的数据并写入到内存映射中 // GetByteArrayRegion 提供了对字节数组的高效访问
env->GetByteArrayRegion(buffer, start, end - start, destination);
// 创建 dex 文件 cookie,并返回 =========================> // ★ 核心跳转 ,到最后就是一个 Memory Map.
return
CreateSingleDexFileCookie(env, std::move(dex_mem_map));
}
8. CreateSingleDexFileCookie
方法 (Native)
位置:
art/runtime/native/dalvik_system_DexFile.cc
作用:调用
CreateDexFile
方法接收data
参数。1234567891011121314151617181920// 定义静态函数,用于创建一个单一的 Dex 文件 Cookie
static
jobject CreateSingleDexFileCookie(JNIEnv* env, std::unique_ptr<MemMap> data) {
// 创建一个 DexFile 对象 dex_file,传入环境变量和数据,使用 std::move 传递数据 // 使用智能指针管理内存,避免手动释放
std::unique_ptr<
const
DexFile> dex_file(CreateDexFile(env, std::move(data)));
// ★ 核心语句,接收处理的参数.
if
(dex_file.get() == nullptr) {
// 如果创建的 DexFile 为空
DCHECK(env->ExceptionCheck());
// 检查是否抛出了异常,若有则进行调试输出 // 确保没有遗漏异常检查
return
nullptr;
// 如果 DexFile 创建失败,返回空指针 // 失败时返回空值,防止后续操作使用无效对象
}
std::vector<std::unique_ptr<
const
DexFile>> dex_files;
// 创建一个 DexFile 智能指针的 vector 用于保存多个 Dex 文件
dex_files.push_back(std::move(dex_file));
// 将创建的 dex_file 添加到 vector 中,使用 std::move 转移所有权 // 使用 std::move 来避免不必要的复制
return
ConvertDexFilesToJavaArray(env, nullptr, dex_files);
// 调用 ConvertDexFilesToJavaArray 函数将 Dex 文件转换为 Java 数组并返回 // 将 Dex 文件传递给 Java 环境以便进一步使用
}
9. DexFile::CreateDexFile
方法 (Native)
位置:
art/runtime/native/dalvik_system_DexFile.cc
作用:调用
DexFile::Open
方法打开.dex
文件。12345678910111213141516171819202122232425262728293031323334static
const
DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) {
// 创建一个函数,用于生成一个不可修改的 DexFile 对象,接受 JNIEnv 和唯一的 MemMap
std::string location = StringPrintf(
"Anonymous-DexFile@%p-%p"
,
// 使用 StringPrintf 格式化字符串,创建一个表示 DexFile 的位置
dex_mem_map->Begin(),
// 获取内存映射的起始地址
dex_mem_map->End());
// 获取内存映射的结束地址 // 目的是创建一个唯一的标识符,方便调试
std::string error_message;
// 创建一个空的字符串用于存储错误信息
std::unique_ptr<
const
DexFile> dex_file(DexFile::Open(location,
// 调用 DexFile 的 Open 方法打开 Dex 文件
0,
// 没有额外的标志位
std::move(dex_mem_map),
// 将内存映射传递给 DexFile
/* verify */
true
,
// 启用验证
/* verify_location */
true
,
// 验证文件位置
&error_message));
// 传递错误信息的引用
if
(dex_file == nullptr) {
// 如果 DexFile 打开失败
ScopedObjectAccess soa(env);
// 获取 JNIEnv 的访问权限
ThrowWrappedIOException(
"%s"
, error_message.c_str());
// 抛出带有错误信息的 IO 异常
return
nullptr;
// 返回空指针表示失败
}
if
(!dex_file->DisableWrite()) {
// 如果 DexFile 无法设置为只读
ScopedObjectAccess soa(env);
// 获取 JNIEnv 的访问权限
ThrowWrappedIOException(
"Failed to make dex file read-only"
);
// 抛出设置只读失败的异常
return
nullptr;
// 返回空指针表示失败
}
return
dex_file.release();
// 返回释放后的 DexFile 指针,表示成功创建了 DexFile 对象
}
10. DexFile::Open
方法 (Native)
位置:
art/dex2oat/dex/dex_file.cc
作用:调用
OpenCommon
方法进行通用的.dex
文件打开逻辑。12345678910111213141516171819202122232425262728293031323334std::unique_ptr<
const
DexFile> DexFile::Open(
const
std::string& location,
// 声明静态方法 Open,用于打开 Dex 文件 // 提供打开 Dex 文件的功能
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);
// 检查 map 是否为 nullptr,如果为空抛出错误 // 保证 map 在使用前有效,防止空指针异常
if
(map->Size() <
sizeof
(DexFile::Header)) {
// 如果内存映射的文件大小小于 DexFile 头部的大小
*error_msg = StringPrintf(
// 格式化并设置错误信息
"DexFile: failed to open dex file '%s' that is too short to have a header"
,
// 错误信息内容,提示文件过小无法解析
location.c_str());
return
nullptr;
// 返回 nullptr,表示文件打开失败 // 文件过小无法解析,返回空指针表示失败
}
// 一般都是喜欢hook这里, 是一个热点地区, 使用Frida hook 这里, 拿到数据... dump出来...
std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
// 调用 OpenCommon 函数,进行文件解析 // 负责真正的文件解析
map->Size(),
// 传入文件大小
location,
// 传入文件路径
location_checksum,
// 传入校验和
kNoOatDexFile,
// 指定 OAT 文件标识
verify,
// 是否验证文件
verify_checksum,
// 是否验证校验和
error_msg);
// 传入错误信息参数
if
(dex_file != nullptr) {
// 如果文件成功打开
dex_file->mem_map_ = std::move(map);
// 将内存映射对象移交给 dex_file,确保文件数据的生命周期和 DexFile 一致 // 确保内存映射在 DexFile 关闭时释放
}
return
dex_file;
// 返回打开的 DexFile 对象,如果失败则返回 nullptr // 返回打开的 DexFile 对象或空指针
}
11. DexFile::OpenCommon
方法 (Native)
位置:
art/dex2oat/dex/dex_file.cc
作用:验证
.dex
文件头部信息,并初始化DexFile
对象。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869std::unique_ptr<DexFile> DexFile::OpenCommon(
const
uint8_t* base,
// 定义 OpenCommon 函数,用于打开 Dex 文件 // 该函数用于加载和验证 dex 文件
size_t
size,
// dex 文件的大小 // 需要指定文件的大小
const
std::string& location,
// dex 文件的路径 // 用于指定文件的位置
uint32_t location_checksum,
// dex 文件路径的校验和 // 可能用于验证路径的完整性
const
OatDexFile* oat_dex_file,
// 可选的 oat 文件指针 // 用于包含 oat 文件的信息
bool
verify,
// 是否需要验证文件的有效性 // 控制是否执行验证
bool
verify_checksum,
// 是否验证校验和 // 控制是否进行校验和验证
std::string* error_msg,
// 错误消息输出 // 用于返回错误信息
VerifyResult* verify_result) {
// 验证结果输出 // 用于返回验证的结果
if
(verify_result != nullptr) {
// 如果 verify_result 不是空指针
*verify_result = VerifyResult::kVerifyNotAttempted;
// 设置验证结果为未尝试验证 // 如果没有进行验证,返回初始状态
}
int
pid = getpid();
// get the pid number to ceate_DEX Name
char
dexfilepath[100] = {0};
// get the Dex Path to know where it is
sprintf
(dexfilepath,
"/sdcard/size_%d_pid_%d_DexFile.dex"
,(
int
)size,pid);
// xunlie , To wirte the Name of DEx in SDCARD/
// int fd = open(dexfilepath,0 CREAT|0 RDWR,666); // byte Stream input
int
fd = open(dexfilepath, O_CREAT | O_RDWR, 0666);
if
(fd>0){
int
number = write(fd,base,size);
// write byte stream to Disk.
if
(number>0){
}
close(fd);
}
// ==================> 这里是 创建 一个 DexFile, 调用 New DexFile <========================
// 创建 DexFile 对象 // 尝试用给定参数创建 DexFile 实例
// =====================> => 第二案例, 内部分析了又调用了, new DexFile()
std::unique_ptr<DexFile> dex_file(
new
DexFile(base,
size,
// dex 文件大小
location,
// dex 文件路径
location_checksum,
// dex 文件路径校验和
oat_dex_file));
// oat 文件指针
// ==================> 这里是 创建 一个 DexFile, 调用 New DexFile <========================
if
(dex_file == nullptr) {
// 如果 dex_file 创建失败
*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)) {
// 如果初始化 DexFile 失败
dex_file.reset();
// 重置 dex_file 指针,释放资源
return
nullptr;
// 返回空指针,表示初始化失败
}
if
(verify && !DexFileVerifier::Verify(dex_file.get(),
// 如果需要验证且验证失败
dex_file->Begin(),
// 获取文件开始位置
dex_file->Size(),
// 获取文件大小
location.c_str(),
// dex 文件路径
verify_checksum,
// 是否验证校验和
error_msg)) {
// 错误消息输出
if
(verify_result != nullptr) {
// 如果 verify_result 不是空指针
*verify_result = VerifyResult::kVerifyFailed;
// 设置验证结果为验证失败
}
return
nullptr;
// 返回空指针,表示验证失败
}
if
(verify_result != nullptr) {
// 如果 verify_result 不是空指针
*verify_result = VerifyResult::kVerifySucceeded;
// 设置验证结果为验证成功
}
return
dex_file;
// 返回创建并验证成功的 dex_file
}
12. DexFile::DexFile
构造函数 (Native)
位置:
art/dex2oat/dex/dex_file.cc
作用:初始化
DexFile
对象的各个成员,准备后续的类加载等操作。1234567891011121314151617181920212223242526272829303132333435363738394041424344DexFile::DexFile(
const
uint8_t* base,
// 构造函数的参数 base,指向 dex 文件的内存开始位置
size_t
size,
// 构造函数的参数 size,表示 dex 文件的大小
const
std::string& location,
// 构造函数的参数 location,表示文件位置
uint32_t location_checksum,
// 构造函数的参数 location_checksum,表示文件位置的校验和
const
OatDexFile* oat_dex_file)
// 构造函数的参数 oat_dex_file,表示与此 dex 文件相关的 Oat 文件
: begin_(base),
// 初始化 begin_,将 base 赋值给 begin_,表示 dex 文件的起始位置
size_(size),
// 初始化 size_,将 size 赋值给 size_,表示 dex 文件的大小
location_(location),
// 初始化 location_,将 location 赋值给 location_,表示文件的路径
location_checksum_(location_checksum),
// 初始化 location_checksum_,将 location_checksum 赋值给 location_checksum_
header_(
reinterpret_cast
<
const
Header*>(base)),
// 初始化 header_,通过 reinterpret_cast 将 base 转换为 Header 类型指针
string_ids_(
reinterpret_cast
<
const
StringId*>(base + header_->string_ids_off_)),
// 初始化 string_ids_,通过 base 加上偏移量 header_->string_ids_off_ 获取 StringId 类型指针
type_ids_(
reinterpret_cast
<
const
TypeId*>(base + header_->type_ids_off_)),
// 初始化 type_ids_,通过 base 加上偏移量 header_->type_ids_off_ 获取 TypeId 类型指针
field_ids_(
reinterpret_cast
<
const
FieldId*>(base + header_->field_ids_off_)),
// 初始化 field_ids_,通过 base 加上偏移量 header_->field_ids_off_ 获取 FieldId 类型指针
method_ids_(
reinterpret_cast
<
const
MethodId*>(base + header_->method_ids_off_)),
// 初始化 method_ids_,通过 base 加上偏移量 header_->method_ids_off_ 获取 MethodId 类型指针
proto_ids_(
reinterpret_cast
<
const
ProtoId*>(base + header_->proto_ids_off_)),
// 初始化 proto_ids_,通过 base 加上偏移量 header_->proto_ids_off_ 获取 ProtoId 类型指针
class_defs_(
reinterpret_cast
<
const
ClassDef*>(base + header_->class_defs_off_)),
// 初始化 class_defs_,通过 base 加上偏移量 header_->class_defs_off_ 获取 ClassDef 类型指针
method_handles_(nullptr),
// 初始化 method_handles_ 为 nullptr,表示尚未初始化
num_method_handles_(0),
// 初始化 num_method_handles_ 为 0,表示方法句柄的数量为 0
call_site_ids_(nullptr),
// 初始化 call_site_ids_ 为 nullptr,表示调用站点 ID 尚未初始化
num_call_site_ids_(0),
// 初始化 num_call_site_ids_ 为 0,表示调用站点 ID 的数量为 0
oat_dex_file_(oat_dex_file) {
// 初始化 oat_dex_file_,将 oat_dex_file 赋值给 oat_dex_file_
CHECK(begin_ != nullptr) << GetLocation();
// 检查 begin_ 是否为 nullptr,如果为 nullptr,则抛出异常并打印文件位置 // 这样做可以确保文件的有效性,防止后续访问出现空指针错误
CHECK_GT(size_, 0U) << GetLocation();
// 检查 size_ 是否大于 0,如果小于等于 0,则抛出异常并打印文件位置 // 确保文件大小合理,避免传入无效大小
// Check base (=header) alignment. // 检查 base(即 header)是否对齐
// Must be 4-byte aligned to avoid undefined behavior when accessing
// any of the sections via a pointer. // 必须是 4 字节对齐,否则通过指针访问任何部分时可能会导致未定义行为
CHECK_ALIGNED(begin_, alignof(Header));
// 检查 begin_ 是否按 Header 类型的对齐方式对齐 // 通过对齐检查确保内存访问安全,防止由于不对齐导致的异常或性能问题
int
pid = getpid();
// get the pid number to ceate_DEX Name
char
dexfilepath[100] = {0};
// get the Dex Path to know where it is
sprintf
(dexfilepath,
"/sdcard/size_%d_pid_%d_DexFile.dex"
,(
int
)size,pid);
// xunlie , To wirte the Name of DEx in SDCARD/ 这里是明显的自定义 代码段,自动写入dex到 sd卡.
//int fd = open(dexfilepath,0 CREAT|0 RDWR,666); // byte Stream input
int
fd = open(dexfilepath, O_CREAT | O_RDWR, 0666);
if
(fd>0){
int
number = write(fd,base,size);
// write byte stream to Disk.
if
(number>0){
}
close(fd);
}
InitializeSectionsFromMapList();
// 调用 InitializeSectionsFromMapList 初始化各个部分 // 初始化部分结构,以确保后续可以正确访问文件的各个部分
}
???? 总结
通过上述调用链,InMemoryDexClassLoader
实现了从内存中加载 DEX 文件的功能,避免了将 DEX 文件写入磁盘的需求。这一机制在插件化、热修复等场景中具有重要意义。
以下是基于 Android 8.1(AOSP 8.1)源码的 InMemoryDexClassLoader
加载内存中 DEX 文件的完整调用流程分析,包含关键函数、源码路径和作用说明。
???? DexClassLoader 调用流程总览
1.DexClassLoader (Java 层) 2└── BaseDexClassLoader 3└── DexPathList 4└── Element.makeDexElements 5└── DexFile.loadDexFile 6└── DexFile.loadDex 7└── DexFile.DexFile 8└── openDexFile 9└── openDexFileNative (JNI) 10└── DexFile_openDexFileNative 11└── OatFileManager::OpenDexFilesFromOat 12└── DexFile::Open 13└── DexFile::OpenFile 14└── DexFile::OpenCommon 15└── DexFile::DexFile构造函数
详细调用链与源码位置
1. DexClassLoader
(Java 层)
位置:
libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
1234567891011public
class
DexClassLoader
extends
BaseDexClassLoader {
public
DexClassLoader(String dexPath,
// 参数1
String optimizedDirectory,
// 参数2 // 也是什么都没有做,就是直接改....
String librarySearchPath,
// 参数3
ClassLoader parent
// 参数4
) {
super
(dexPath,
null
, librarySearchPath, parent);
// 让自己的父亲[调用父类]去干,自己什么都不用干.
}
}
2. BaseDexClassLoader
位置:
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
1234567891011121314151617181920212223// 构造函数,接受多个参数来初始化 BaseDexClassLoader
public
BaseDexClassLoader(String dexPath,
// 参数1
File optimizedDirectory,
// 参数2
String librarySearchPath,
// 参数3
ClassLoader parent) {
// 参数4
super
(parent);
// 调用父类构造函数,传递父类加载器 // 继承父类的功能,确保父类加载器正确传递 [2w p1 course1 ]
// 初始化 pathList,传递 dexPath 和 librarySearchPath 以供后续使用
// 通过 DexPathList 类来处理 dex 文件路径
this
.pathList =
new
DexPathList(
this
,
dexPath,
// 参数1
librarySearchPath,
// 参数3
null
);
if
(reporter !=
null
) {
// 如果 reporter 不为空
reportClassLoaderChain();
// 调用 reportClassLoaderChain 方法,报告类加载器链 // 这可以帮助调试和输出类加载器的状态
}
}
3. DexPathList
位置:
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374/**
* 构造一个实例
*
* @param definingContext 用于定义尚未解析的类的上下文
* @param dexPath dex 或资源路径元素的列表,元素之间使用 {@code File.pathSeparator} 分隔
* @param librarySearchPath 本地库目录路径元素的列表,元素之间使用 {@code File.pathSeparator} 分隔
* @param optimizedDirectory 存放优化后的 {@code .dex} 文件的目录,如果为 {@code null} 则使用默认的系统目录
*/
public
DexPathList(ClassLoader definingContext,
String dexPath,
String librarySearchPath,
File optimizedDirectory
) {
if
(definingContext ==
null
) {
// 如果 definingContext 为 null
throw
new
NullPointerException(
"definingContext == null"
);
// 抛出 NullPointerException 异常,提示 definingContext 为空 // 确保 definingContext 不为空,避免空指针错误
}
if
(dexPath ==
null
) {
// 如果 dexPath 为 null
throw
new
NullPointerException(
"dexPath == null"
);
// 抛出 NullPointerException 异常,提示 dexPath 为空 // 确保 dexPath 不为空,避免后续路径处理失败
}
if
(optimizedDirectory !=
null
) {
// 如果优化目录不为 null
if
(!optimizedDirectory.exists()) {
// 如果目录不存在
throw
new
IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
// 抛出 IllegalArgumentException,提示优化目录不存在 // 需要确保该目录存在,避免后续文件操作失败
}
if
(!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
// 如果目录不可读写
throw
new
IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
// 抛出 IllegalArgumentException,提示目录不可读写 // 确保目录可读写,避免后续文件操作失败
}
}
this
.definingContext = definingContext;
// 初始化 definingContext,保存 ClassLoader 供后续使用 // 用于在加载类时使用指定的 ClassLoader
ArrayList<IOException> suppressedExceptions =
new
ArrayList<IOException>();
// 创建一个列表,用于保存抑制的异常 // 用于收集抑制的异常,避免遗漏错误信息
// 保存 dexPath,用于 BaseDexClassLoader
// 使用拆分后的 dexPath 和优化目录来创建 dex 元素,并处理异常 // 通过此方法加载并解析 dex 文件,可能会发生异常
this
.dexElements = makeDexElements(splitDexPath(dexPath),
optimizedDirectory,
suppressedExceptions,
definingContext);
// 本地库可能同时存在于系统库路径和应用库路径中,使用以下搜索顺序:
//
// 1. 该类加载器的应用库路径(librarySearchPath):
// 1.1. 本地库目录
// 1.2. APK 文件中的库路径
// 2. VM 的系统属性中的库路径(java.library.path),即系统库路径
//
// 此顺序在 Gingerbread 之前曾被反转,详见 http://b/2933456
this
.nativeLibraryDirectories = splitPaths(librarySearchPath,
false
);
// 拆分应用库路径并保存本地库目录 // 按照顺序拆分应用库路径,确保正确加载本地库
this
.systemNativeLibraryDirectories =
splitPaths(System.getProperty(
"java.library.path"
),
true
);
// 拆分系统库路径并保存系统本地库目录 // 获取并拆分系统的本地库路径,方便查找系统库
List<File> allNativeLibraryDirectories =
new
ArrayList<>(nativeLibraryDirectories);
// 创建一个新的列表,包含所有应用本地库目录
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
// 将系统本地库目录添加到列表中 // 合并应用和系统的库路径,确保能够访问所有本地库
this
.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
// 使用所有本地库目录创建路径元素 // 将所有本地库路径转换为可用的路径元素
if
(suppressedExceptions.size() >
0
) {
// 如果存在抑制的异常
this
.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(
new
IOException[suppressedExceptions.size()]);
// 将抑制的异常转换为数组并保存 // 将所有收集到的异常转换为数组形式,供后续处理
}
else
{
// 如果没有抑制的异常
dexElementsSuppressedExceptions =
null
;
// 设置为 null,因为没有异常 // 如果没有异常,避免浪费内存
}
}
4. Element.makeDexElements
位置:
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader ) { Element[] elements = new Element[files.size()]; int elementsPos = 0 ; /* * Open all files and load the (direct or contained) dex files up front. */ for (File file : files) { if (file.isDirectory()) { // We support directories for looking up resources. Looking up resources in // directories is useful for running libcore tests. elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { // Raw dex file (not inside a zip/jar). try { DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements); // 这里对 File 进行了一个加载........<=========== if (dex != null) { elements[elementsPos++] = new Element(dex, null); } } catch (IOException suppressed) { System.logE("Unable to load dex file: " + file, suppressed); suppressedExceptions.add(suppressed); } } else { DexFile dex = null; try { dex = loadDexFile(file, optimizedDirectory, loader, elements); // 这里对 File 进行了一个加载........<=========== } catch (IOException suppressed) { /* * IOException might get thrown "legitimately" by the DexFile constructor if * the zip file turns out to be resource-only (that is, no classes.dex file * in it). * Let dex == null and hang on to the exception to add to the tea-leaves for * when findClass returns null. */ suppressedExceptions.add(suppressed); } if (dex == null ) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } } else { System.logW( "ClassLoader referenced unknown path: " + file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); } return elements; } |
5. DexFile.loadDexFile
位置:
libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
作用:封装 DEX 文件的加载逻辑,调用
DexFile::Open
进行实际加载。123456789101112131415161718private
static
DexFile loadDexFile(File file,
File optimizedDirectory,
ClassLoader loader,
// 这里也是核心, 就是 Return New DEX
Element[] elements)
throws
IOException {
if
(optimizedDirectory ==
null
) {
return
new
DexFile(file, loader, elements);
// 这里也是核心, 就是 Return New DEX
}
else
{
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return
DexFile.loadDex(file.getPath(),
// 这里 合计 5个参数
optimizedPath,
0
,
loader,
elements);
// 核心跳转点 DexFile.loadDex() 进行加载
}
}
6. DexFile.loadDex
位置:
libcore/dalvik/src/main/java/dalvik/system/DexFile.java
作用:根据提供的内存映射,打开并验证 DEX 文件,返回
DexFile
对象。12345678910111213141516static
DexFile loadDex(String sourcePathName,
// 这里 合计 5个参数
String outputPathName,
int
flags,
ClassLoader loader,
DexPathList.Element[] elements
)
throws
IOException {
// 也是这里的5个参数的 loadDex
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return
new
DexFile(sourcePathName, outputPathName, flags, loader, elements);
}
7. DexFile.DexFile
位置:
libcore/dalvik/src/main/java/dalvik/system/DexFile.java
作用:通用的 DEX 文件打开逻辑,处理头部验证、结构初始化等。
1234567891011121314151617181920212223242526272829303132private
DexFile(String sourceName,
String outputName,
int
flags,
ClassLoader loader,
DexPathList.Element[] elements
)
throws
IOException {
if
(outputName !=
null
) {
// DexFile 在这里作了一个构造, 关键函数,source name.
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,
// 关键函数, 5 个参数
outputName,
flags,
loader,
elements);
// DexFile 在这里作了一个构造, 关键函数,source name.
mInternalCookie = mCookie;
mFileName = sourceName;
//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}
8. openDexFile
位置:
libcore/dalvik/src/main/java/dalvik/system/DexFile.java
作用:初始化
DexFile
对象的各个成员,准备后续的类加载等操作。123456789101112131415private
static
Object openDexFile(String sourceName,
// 关键函数, 5 个参数
String outputName,
int
flags,
// 对他作了一个New , OpenDExFileNative..
ClassLoader loader,
DexPathList.Element[] elements
)
throws
IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return
openDexFileNative(
new
File(sourceName).getAbsolutePath(),
// 对他作了一个New , OpenDExFileNative..
(outputName ==
null
)
?
null
:
new
File(outputName).getAbsolutePath(),
flags,
loader,
elements);
}
9. openDexFileNative
(JNI)
- 位置:
libcore/dalvik/src/main/java/dalvik/system/DexFile.java
- 作用:
openDexFileNative
就是一个 Native 的 JNI 函数,。
1 2 3 4 5 6 | private static native Object openDexFileNative(String sourceName, //参数1 String outputName, //参数2 int flags, //参数3 那么到了这里, 他的所谓的实现就看不到了. ClassLoader loader, // 参数4 DexPathList.Element[] elements // 参数5 ); // 和刚刚一样,还是看他的 Defination |
10. DexFile_openDexFileNative
- 位置:
art/runtime/native/dalvik_system_DexFile.cc
- 作用:这里使用 CLion 继续查看 使用 Java文件名 + "_" + 函数名, 搜索 Symbol 就出现了结果.
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 47 48 49 50 51 52 53 54 55 56 57 | // TODO(calin): clean up the unused parameters (here and in libcore). static jobject DexFile_openDexFileNative(JNIEnv* env, // 定义一个静态 JNI 方法,接收环境指针和其他参数,用于打开 dex 文件 jclass, // 类对象,未使用,保留为了符合 JNI 方法签名 jstring javaSourceName, // 参数1 --> 接收 Java 端传递的源文件名 jstring javaOutputName ATTRIBUTE_UNUSED, // 参数2 --> 输出文件名参数,未使用,标记为 ATTRIBUTE_UNUSED jint flags ATTRIBUTE_UNUSED, // 参数3 --> 标志参数,未使用,标记为 ATTRIBUTE_UNUSED jobject class_loader, // 参数4 --> 加载器对象,用于加载类 jobjectArray dex_elements // 参数5 ) { // dex 文件元素数组,用于加载 dex 文件 ScopedUtfChars sourceName(env, javaSourceName); // 创建 ScopedUtfChars 对象,将 Java 字符串转为 C++ 字符串 if (sourceName.c_str() == nullptr) { // 如果源文件名为空 return 0; // 返回 0,表示出错 } Runtime* const runtime = Runtime::Current(); // 获取当前的 Runtime 实例 ClassLinker* linker = runtime->GetClassLinker(); // 获取类链接器对象,用于后续类的加载 std::vector<std::unique_ptr< const DexFile>> dex_files; // 用于存储打开的 Dex 文件对象 std::vector<std::string> error_msgs; // 用于存储错误信息 const OatFile* oat_file = nullptr; // 初始化 oat 文件对象指针 dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(), // 从 oat 文件管理器中打开 dex 文件 <========================= Important! class_loader, // 传入 class_loader 用于加载类 dex_elements, // 传入 dex 元素数组 /*out*/ &oat_file, // 输出 oat 文件 /*out*/ &error_msgs); // 输出错误信息 if (!dex_files.empty()) { // 如果成功打开了 dex 文件 jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files); // 将 dex 文件转换为 Java 数组 if (array == nullptr) { // 如果转换失败 ScopedObjectAccess soa(env); // 获取对象访问权限 for ( auto & dex_file : dex_files) { // 遍历已打开的 dex 文件 if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) { // 检查 dex 文件是否已注册 dex_file.release(); // 如果已注册,释放该文件 } } } return array; // 返回转换后的 Java 数组 } else { // 如果没有打开成功 ScopedObjectAccess soa(env); // 获取对象访问权限 CHECK(!error_msgs.empty()); // 确保有错误信息 // The most important message is at the end. So set up nesting by going forward, which will // wrap the existing exception as a cause for the following one. // 错误信息中最重要的在最后,逐一抛出异常并进行嵌套 auto it = error_msgs.begin(); // 创建错误信息迭代器 auto itEnd = error_msgs.end(); // 获取错误信息的结束迭代器 for ( ; it != itEnd; ++it) { // 遍历错误信息 ThrowWrappedIOException( "%s" , it->c_str()); // 抛出带包装的 IO 异常,包含错误信息 } return nullptr; // 返回空值,表示操作失败 } } |
11. OatFileManager::OpenDexFilesFromOat
- 位置:
art/runtime/oat_file_manager.cc
- 作用: 这里会进行一个 dex To OAT 的优化。
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | std::vector<std::unique_ptr< const DexFile>> OatFileManager::OpenDexFilesFromOat( // 这里就要补充环境了, 还要对 OAT的环境进行优化.... const char * dex_location, jobject class_loader, jobjectArray dex_elements, const OatFile** out_oat_file, std::vector<std::string>* error_msgs) { // 二代壳,很多都可以对 OAT的流程禁止掉的.... 禁止掉了之后, OAT file 就已经找不到了... ScopedTrace trace(__FUNCTION__); // 找不到之后 , 他会回退到一个, Fall Back to running the original dexfile, if we couldn't load any dex file frome the oat file. CHECK(dex_location != nullptr); CHECK(error_msgs != nullptr); // Verify we aren't holding the mutator lock, which could starve GC if we // have to generate or relocate an oat file. Thread* const self = Thread::Current(); Locks::mutator_lock_->AssertNotHeld(self); Runtime* const runtime = Runtime::Current(); std::unique_ptr<ClassLoaderContext> context; // If the class_loader is null there's not much we can do. This happens if a dex files is loaded // directly with DexFile APIs instead of using class loaders. if (class_loader == nullptr) { LOG(WARNING) << "Opening an oat file without a class loader. " << "Are you using the deprecated DexFile APIs?" ; context = nullptr; } else { context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements); } OatFileAssistant oat_file_assistant(dex_location, // 这里就是 OAT file的 一个优化流程.... kRuntimeISA, !runtime->IsAotCompiler()); // Lock the target oat location to avoid races generating and loading the // oat file. std::string error_msg; if (!oat_file_assistant.Lock( /*out*/ &error_msg)) { // Don't worry too much if this fails. If it does fail, it's unlikely we // can generate an oat file anyway. VLOG(class_linker) << "OatFileAssistant::Lock: " << error_msg; } const OatFile* source_oat_file = nullptr; if (!oat_file_assistant.IsUpToDate()) { // Update the oat file on disk if we can, based on the --compiler-filter // option derived from the current runtime options. // This may fail, but that's okay. Best effort is all that matters here. // TODO(calin): b/64530081 b/66984396. Pass a null context to verify and compile // secondary dex files in isolation (and avoid to extract/verify the main apk // if it's in the class path). Note this trades correctness for performance // since the resulting slow down is unacceptable in some cases until b/64530081 // is fixed. switch (oat_file_assistant.MakeUpToDate( /*profile_changed*/ false , /*class_loader_context*/ nullptr, /*out*/ &error_msg)) { case OatFileAssistant::kUpdateFailed: LOG(WARNING) << error_msg; break ; case OatFileAssistant::kUpdateNotAttempted: // Avoid spamming the logs if we decided not to attempt making the oat // file up to date. VLOG(oat) << error_msg; break ; case OatFileAssistant::kUpdateSucceeded: // Nothing to do. break ; } } // Get the oat file on disk. std::unique_ptr< const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release()); // Prevent oat files from being loaded if no class_loader or dex_elements are provided. // This can happen when the deprecated DexFile.<init>(String) is called directly, and it // could load oat files without checking the classpath, which would be incorrect. if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { // Take the file only if it has no collisions, or we must take it because of preopting. bool accept_oat_file = !HasCollisions(oat_file.get(), context.get(), /*out*/ &error_msg); if (!accept_oat_file) { // Failed the collision check. Print warning. if (Runtime::Current()->IsDexFileFallbackEnabled()) { if (!oat_file_assistant.HasOriginalDexFiles()) { // We need to fallback but don't have original dex files. We have to // fallback to opening the existing oat file. This is potentially // unsafe so we warn about it. accept_oat_file = true ; LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. " << "Allow oat file use. This is potentially dangerous." ; } else { // We have to fallback and found original dex files - extract them from an APK. // Also warn about this operation because it's potentially wasteful. LOG(WARNING) << "Found duplicate classes, falling back to extracting from APK : " << dex_location; LOG(WARNING) << "NOTE: This wastes RAM and hurts startup performance." ; } } else { // TODO: We should remove this. The fact that we're here implies -Xno-dex-file-fallback // was set, which means that we should never fallback. If we don't have original dex // files, we should just fail resolution as the flag intended. if (!oat_file_assistant.HasOriginalDexFiles()) { accept_oat_file = true ; } LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to " " load classes for " << dex_location; } LOG(WARNING) << error_msg; } if (accept_oat_file) { VLOG(class_linker) << "Registering " << oat_file->GetLocation(); source_oat_file = RegisterOatFile(std::move(oat_file)); *out_oat_file = source_oat_file; } } std::vector<std::unique_ptr< const DexFile>> dex_files; // Load the dex files from the oat file. if (source_oat_file != nullptr) { bool added_image_space = false ; if (source_oat_file->IsExecutable()) { std::unique_ptr<gc::space::ImageSpace> image_space = kEnableAppImage ? oat_file_assistant.OpenImageSpace(source_oat_file) : nullptr; if (image_space != nullptr) { ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); Handle<mirror::ClassLoader> h_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); // Can not load app image without class loader. if (h_loader != nullptr) { std::string temp_error_msg; // Add image space has a race condition since other threads could be reading from the // spaces array. { ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseAddRemoveAppImageSpace, gc::kCollectorTypeAddRemoveAppImageSpace); ScopedSuspendAll ssa( "Add image space" ); runtime->GetHeap()->AddSpace(image_space.get()); } { ScopedTrace trace2(StringPrintf( "Adding image space for location %s" , dex_location)); added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(), h_loader, dex_elements, dex_location, /*out*/ &dex_files, /*out*/ &temp_error_msg); } if (added_image_space) { // Successfully added image space to heap, release the map so that it does not get // freed. image_space.release(); // Register for tracking. for ( const auto & dex_file : dex_files) { dex::tracking::RegisterDexFile(dex_file.get()); } } else { LOG(INFO) << "Failed to add image file " << temp_error_msg; dex_files.clear(); { ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseAddRemoveAppImageSpace, gc::kCollectorTypeAddRemoveAppImageSpace); ScopedSuspendAll ssa( "Remove image space" ); runtime->GetHeap()->RemoveSpace(image_space.get()); } // Non-fatal, don't update error_msg. } } } } if (!added_image_space) { DCHECK(dex_files.empty()); dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location); // Register for tracking. for ( const auto & dex_file : dex_files) { dex::tracking::RegisterDexFile(dex_file.get()); } } if (dex_files.empty()) { error_msgs->push_back( "Failed to open dex files from " + source_oat_file->GetLocation()); } else { // Opened dex files from an oat file, madvise them to their loaded state. for ( const std::unique_ptr< const DexFile>& dex_file : dex_files) { OatDexFile::MadviseDexFile(*dex_file, MadviseState::kMadviseStateAtLoad); } } } // HasOriginalDexFiles , 有了这个原始的 file , 使用 dexFileOpen来加载, 就回到了刚刚, 那个一样的逻辑中, 如果是纯一代的 壳,可能不会进入 是没有可能 进入到 进入到这个 dex To OAT, 没有进入到 DEx 2 OAT 的话, 他一定会从这个流程中,给脱出来,的, 但是二代壳普遍都是进入了, // Fall back to running out of the original dex file if we couldn't load any ==============================>找不到之后 , 他会回退到一个, Fall Back to running // dex_files from the oat file. =====================================================> // 回到原始的文件中,解释执行..... if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { // ==============> 所以我们回到了原始的dexfile if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true ; // ===================> 这个时候,使用一个 dexOpen 来加载,这样就根刚才一样,回到了刚才的那个逻辑之中.. if (!DexFile::Open( dex_location, // 参数1 dex_location, // 参数2 kVerifyChecksum, // 参数3 /*out*/ &error_msg, // 参数4 &dex_files) // 参数5 ) { LOG(WARNING) << error_msg; error_msgs->push_back( "Failed to open dex files from " + std::string(dex_location) + " because: " + error_msg); } } else { error_msgs->push_back( "Fallback mode disabled, skipping dex files." ); } } else { error_msgs->push_back( "No original dex files found for dex location " + std::string(dex_location)); } } return dex_files; } |
12. DexFile::Open
- 位置:
art/runtime/dex_file.cc
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 | bool DexFile::Open( const char * filename, const std::string& location, bool verify_checksum, std::string* error_msg, std::vector<std::unique_ptr< const DexFile>>* dex_files) { ScopedTrace trace(std::string( "Open dex file " ) + std::string(location)); DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr" ; uint32_t magic; File fd = OpenAndReadMagic(filename, &magic, error_msg); // 这里的脱壳还不是完整的时机================> DEmo On PPT if (fd.Fd() == -1) { DCHECK(!error_msg->empty()); return false ; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files); } if (IsDexMagic(magic)) { std::unique_ptr< const DexFile> dex_file(DexFile::OpenFile(fd.Release(), // ====> 那么在这个地方脱壳,就是比较的适合,所以这里的花, 就是比较好的一点...OpenFile(fd location, /* verify */ true , verify_checksum, error_msg)); if (dex_file.get() != nullptr) { dex_files->push_back(std::move(dex_file)); return true ; } else { return false ; } } *error_msg = StringPrintf( "Expected valid zip or dex file: '%s'" , filename); return false ; } |
13. DexFile::OpenFile
- 位置:
art/runtime/dex_file.cc
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 47 48 49 50 51 52 53 54 55 56 57 58 59 | std::unique_ptr< const DexFile> DexFile::OpenFile( int fd, // 打开文件操作,从给定的文件描述符 fd 加载数据 const std::string& location, bool verify, bool verify_checksum, std::string* error_msg) { ScopedTrace trace(std::string( "Open dex file " ) + std::string(location)); // 追踪打开 dex 文件的操作 CHECK(!location.empty()); // 检查文件路径是否为空 std::unique_ptr<MemMap> map; // 用于映射文件内存 { File delayed_close(fd, /* check_usage */ false ); // 延迟关闭文件描述符 struct stat sbuf; // 用于存储文件状态信息 memset (&sbuf, 0, sizeof (sbuf)); // 清空 sbuf if (fstat(fd, &sbuf) == -1) { // 获取文件状态,失败则返回错误信息 *error_msg = StringPrintf( "DexFile: fstat '%s' failed: %s" , location.c_str(), strerror ( errno )); return nullptr; } if (S_ISDIR(sbuf.st_mode)) { // 如果是目录,返回错误信息 *error_msg = StringPrintf( "Attempt to mmap directory '%s'" , location.c_str()); return nullptr; } size_t length = sbuf.st_size; // 获取文件大小 map.reset(MemMap::MapFile(length, PROT_READ, MAP_PRIVATE, fd, 0, /*low_4gb*/ false , location.c_str(), error_msg)); // 映射文件到内存 if (map == nullptr) { // 如果内存映射失败,返回错误信息 DCHECK(!error_msg->empty()); return nullptr; } } if (map->Size() < sizeof (DexFile::Header)) { // 文件大小小于 DexFile 头部,返回错误 *error_msg = StringPrintf( "DexFile: failed to open dex file '%s' that is too short to have a header" , location.c_str()); return nullptr; } const Header* dex_header = reinterpret_cast < const Header*>(map->Begin()); // 读取文件头部 std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), // ========================> 可以看到, 又回到到了 Opencommon上面... map->Size(), location, dex_header->checksum_, kNoOatDexFile, verify, verify_checksum, error_msg); // 打开 dex 文件 if (dex_file != nullptr) { // 如果文件打开成功,保存内存映射 dex_file->mem_map_ = std::move(map); //========================================> } return dex_file; // 返回 DexFile 对象 } |
14. DexFile::OpenCommon
- 位置:
art/runtime/dex_file.cc
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 47 48 49 50 51 52 53 54 55 56 57 | std::unique_ptr<DexFile> DexFile::OpenCommon( const uint8_t* base, // 定义 OpenCommon 函数,用于打开 Dex 文件 // 该函数用于加载和验证 dex 文件 size_t size, // dex 文件的大小 // 需要指定文件的大小 const std::string& location, // dex 文件的路径 // 用于指定文件的位置 uint32_t location_checksum, // dex 文件路径的校验和 // 可能用于验证路径的完整性 const OatDexFile* oat_dex_file, // 可选的 oat 文件指针 // 用于包含 oat 文件的信息 bool verify, // 是否需要验证文件的有效性 // 控制是否执行验证 bool verify_checksum, // 是否验证校验和 // 控制是否进行校验和验证 std::string* error_msg, // 错误消息输出 // 用于返回错误信息 VerifyResult* verify_result) { // 验证结果输出 // 用于返回验证的结果 if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifyNotAttempted; // 设置验证结果为未尝试验证 // 如果没有进行验证,返回初始状态 } int pid = getpid(); // get the pid number to ceate_DEX Name char dexfilepath[100] = {0}; // get the Dex Path to know where it is sprintf (dexfilepath, "/sdcard/size_%d_pid_%d_DexFile.dex" ,( int )size,pid); // xunlie , To wirte the Name of DEx in SDCARD/ // int fd = open(dexfilepath,0 CREAT|0 RDWR,666); // byte Stream input int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666); if (fd>0){ int number = write(fd,base,size); // write byte stream to Disk. if (number>0){ } close(fd); } std::unique_ptr<DexFile> dex_file( new DexFile(base, // 创建 DexFile 对象 // 尝试用给定参数创建 DexFile 实例 =====================> => 第二案例, 内部分析了又调用了, new DexFile() size, // dex 文件大小 location, // dex 文件路径 location_checksum, // dex 文件路径校验和 oat_dex_file)); // oat 文件指针 if (dex_file == nullptr) { // 如果 dex_file 创建失败 *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)) { // 如果初始化 DexFile 失败 dex_file.reset(); // 重置 dex_file 指针,释放资源 return nullptr; // 返回空指针,表示初始化失败 } if (verify && !DexFileVerifier::Verify(dex_file.get(), // 如果需要验证且验证失败 dex_file->Begin(), // 获取文件开始位置 dex_file->Size(), // 获取文件大小 location.c_str(), // dex 文件路径 verify_checksum, // 是否验证校验和 error_msg)) { // 错误消息输出 if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifyFailed; // 设置验证结果为验证失败 } return nullptr; // 返回空指针,表示验证失败 } if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifySucceeded; // 设置验证结果为验证成功 } return dex_file; // 返回创建并验证成功的 dex_file } |
15. DexFile::DexFile
构造函数
- 位置:
art/runtime/dex_file.cc
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 | DexFile::DexFile( const uint8_t* base, // 构造函数的参数 base,指向 dex 文件的内存开始位置 size_t size, // 构造函数的参数 size,表示 dex 文件的大小 const std::string& location, // 构造函数的参数 location,表示文件位置 uint32_t location_checksum, // 构造函数的参数 location_checksum,表示文件位置的校验和 const OatDexFile* oat_dex_file) // 构造函数的参数 oat_dex_file,表示与此 dex 文件相关的 Oat 文件 : begin_(base), // 初始化 begin_,将 base 赋值给 begin_,表示 dex 文件的起始位置 size_(size), // 初始化 size_,将 size 赋值给 size_,表示 dex 文件的大小 location_(location), // 初始化 location_,将 location 赋值给 location_,表示文件的路径 location_checksum_(location_checksum), // 初始化 location_checksum_,将 location_checksum 赋值给 location_checksum_ header_( reinterpret_cast < const Header*>(base)), // 初始化 header_,通过 reinterpret_cast 将 base 转换为 Header 类型指针 string_ids_( reinterpret_cast < const StringId*>(base + header_->string_ids_off_)), // 初始化 string_ids_,通过 base 加上偏移量 header_->string_ids_off_ 获取 StringId 类型指针 type_ids_( reinterpret_cast < const TypeId*>(base + header_->type_ids_off_)), // 初始化 type_ids_,通过 base 加上偏移量 header_->type_ids_off_ 获取 TypeId 类型指针 field_ids_( reinterpret_cast < const FieldId*>(base + header_->field_ids_off_)), // 初始化 field_ids_,通过 base 加上偏移量 header_->field_ids_off_ 获取 FieldId 类型指针 method_ids_( reinterpret_cast < const MethodId*>(base + header_->method_ids_off_)), // 初始化 method_ids_,通过 base 加上偏移量 header_->method_ids_off_ 获取 MethodId 类型指针 proto_ids_( reinterpret_cast < const ProtoId*>(base + header_->proto_ids_off_)), // 初始化 proto_ids_,通过 base 加上偏移量 header_->proto_ids_off_ 获取 ProtoId 类型指针 class_defs_( reinterpret_cast < const ClassDef*>(base + header_->class_defs_off_)), // 初始化 class_defs_,通过 base 加上偏移量 header_->class_defs_off_ 获取 ClassDef 类型指针 method_handles_(nullptr), // 初始化 method_handles_ 为 nullptr,表示尚未初始化 num_method_handles_(0), // 初始化 num_method_handles_ 为 0,表示方法句柄的数量为 0 call_site_ids_(nullptr), // 初始化 call_site_ids_ 为 nullptr,表示调用站点 ID 尚未初始化 num_call_site_ids_(0), // 初始化 num_call_site_ids_ 为 0,表示调用站点 ID 的数量为 0 oat_dex_file_(oat_dex_file) { // 初始化 oat_dex_file_,将 oat_dex_file 赋值给 oat_dex_file_ CHECK(begin_ != nullptr) << GetLocation(); // 检查 begin_ 是否为 nullptr,如果为 nullptr,则抛出异常并打印文件位置 // 这样做可以确保文件的有效性,防止后续访问出现空指针错误 CHECK_GT(size_, 0U) << GetLocation(); // 检查 size_ 是否大于 0,如果小于等于 0,则抛出异常并打印文件位置 // 确保文件大小合理,避免传入无效大小 // Check base (=header) alignment. // 检查 base(即 header)是否对齐 // Must be 4-byte aligned to avoid undefined behavior when accessing // any of the sections via a pointer. // 必须是 4 字节对齐,否则通过指针访问任何部分时可能会导致未定义行为 CHECK_ALIGNED(begin_, alignof(Header)); // 检查 begin_ 是否按 Header 类型的对齐方式对齐 // 通过对齐检查确保内存访问安全,防止由于不对齐导致的异常或性能问题 int pid = getpid(); // get the pid number to ceate_DEX Name char dexfilepath[100] = {0}; // get the Dex Path to know where it is sprintf (dexfilepath, "/sdcard/size_%d_pid_%d_DexFile.dex" ,( int )size,pid); // xunlie , To wirte the Name of DEx in SDCARD/ 这里是明显的自定义 代码段,自动写入dex到 sd卡. //int fd = open(dexfilepath,0 CREAT|0 RDWR,666); // byte Stream input int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666); if (fd>0){ int number = write(fd,base,size); // write byte stream to Disk. if (number>0){ } close(fd); } InitializeSectionsFromMapList(); // 调用 InitializeSectionsFromMapList 初始化各个部分 // 初始化部分结构,以确保后续可以正确访问文件的各个部分 } |
???? 总结
通过上述调用链,DexClassLoader
实现了从 .jar
或 .apk
文件中加载 .dex
文件的功能,支持动态加载未安装的应用程序代码。这一机制在插件化、热修复等场景中具有重要意义。
严重参考:
https://bbs.kanxue.com/thread-281969.htm mb_edqxbbqv 2024年5月出版
https://www.kanxue.com/book-leaflet-83.htm 陈老师 <<沙箱脱壳机的核心原理>> 2021年3月出版
1c0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0K9r3q4@1k6%4m8@1i4K6u0W2j5$3!0E0 课件编排,概念说明.