1 背景
同事反映说,京东电器界面打开加载慢,我就趁机抓了下trace分析一下这个界面。
通过trace,我们可以看到打卡过程主要是下面三个部分耗时较多,第一部分是打卡dex文件(注意这是一个二级页面,理论上不需要加载dex文件,难道是插件化机制?),第二部分里面很多verifyclass,第三部分是加载x5webview。
1.1 插件加载
我们看下第一部分,OpenDexFilesFromOat(/data/app/~~ls6d4SRFl1SZ2RFZBY1KCA==/com.jingdong.app.mall-myJ8jZF4jcpYW2rLgrWQNA==/lib/arm64/libcom.jd.lib.babel.so)
看到后缀,不禁想问,这个是加载的so? 不过,咋可能呢,障眼法吧,后缀名是so,但是其实是apk吧?
我们将京东的apk拖出来,反编译看下。
adb shell pm path com.jingdong.app.mall
package:/data/app/~~ls6d4SRFl1SZ2RFZBY1KCA==/com.jingdong.app.mall-myJ8jZF4jcpYW2rLgrWQNA==/base.apk
adb pull /data/app/~~ls6d4SRFl1SZ2RFZBY1KCA==/com.jingdong.app.mall-myJ8jZF4jcpYW2rLgrWQNA==/base.apk jingdong.apk
我们找到libcom.jd.lib.babel.so这个文件,用010editor打开发现需要用dex脚本解析。
继续用jadx反编译一下,发现确实是个插件apk,并且资源id是0x58开头
京东插件加载用了什么框架呢?通过反编译京东的apk和搜索,发现他们是用了自研的框架叫aura,这个框架四大组件的代理,资源的处理,系统接口的hook,由于代码混淆了我们就先不研究了,不过从截图可以大致认为,这个框架自定义了classloader。
因为用了自定义的classloader,所以这个插件只能应用自己加载,系统是没法替应用去做的。
1.2 verifyclass
通过trace可以看到,插件里class加载的时候,都需要去verifyclass,那什么是verifyclass呢?我们咨询下最近比较火的chagtgpt老师:
在 ART 虚拟机中,VerifyClass 会在编译期和加载期进行检查,以确保类文件的完整性和正确性。如果类文件未通过验证,ART 虚拟机将不会加载该类文件,并向操作系统报告错误。
1.2.1 为啥加载主apk非插件的文件,不会发生verifyclass呢?
android/art/runtime/oat_file_manager.cc
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
const char* dex_location,
jobject class_loader,
jobjectArray dex_elements,
const OatFile** out_oat_file,
std::vector<std::string>* error_msgs) {
......
std::vector<std::unique_ptr<const DexFile>> dex_files;
std::unique_ptr<ClassLoaderContext> context(
ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements));
// 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?";
} else if (context != nullptr) {
// 京东主apk加载的时候走这里
OatFileAssistant oat_file_assistant(dex_location,
kRuntimeISA,
context.get(),
runtime->GetOatFilesExecutable(),
only_use_system_oat_files_);
}
.......
}
京东主apk加载的时候可以走到OatFileAssistant相关逻辑,加载oat文件,但是插件无法走到这里,因为
std::unique_ptr<ClassLoaderContext> context(
ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements));
这里获取的context为null。
android/art/runtime/class_loader_context.cc
std::unique_ptr<ClassLoaderContext> ClassLoaderContext::CreateContextForClassLoader(
jobject class_loader,
jobjectArray dex_elements) {
ScopedTrace trace(__FUNCTION__);
if (class_loader == nullptr) {
return nullptr;
}
ScopedObjectAccess soa(Thread::Current());
StackHandleScope<2> hs(soa.Self());
Handle<mirror::ClassLoader> h_class_loader =
hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader));
Handle<mirror::ObjectArray<mirror::Object>> h_dex_elements =
hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(dex_elements));
std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext(/*owns_the_dex_files=*/ false));
if (!result->CreateInfoFromClassLoader(
soa, h_class_loader, h_dex_elements, nullptr, /*is_shared_library=*/ false)) {
return nullptr;
}
return result;
}
bool ClassLoaderContext::CreateInfoFromClassLoader(
ScopedObjectAccessAlreadyRunnable& soa,
Handle<mirror::ClassLoader> class_loader,
Handle<mirror::ObjectArray<mirror::Object>> dex_elements,
ClassLoaderInfo* child_info,
bool is_shared_library)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (ClassLinker::IsBootClassLoader(soa, class_loader.Get())) {
// Nothing to do for the boot class loader as we don't add its dex files to the context.
return true;
}
ClassLoaderContext::ClassLoaderType type;
if (IsPathOrDexClassLoader(soa, class_loader)) {
type = kPathClassLoader;
} else if (IsDelegateLastClassLoader(soa, class_loader)) {
type = kDelegateLastClassLoader;
} else if (IsInMemoryDexClassLoader(soa, class_loader)) {
type = kInMemoryDexClassLoader;
} else {
//京东插件加载使用了自定义的classloader,会走到这里,最后无法加载oat文件。
LOG(WARNING) << "Unsupported class loader";
return false;
}
逻辑比较简单,最关键是是:京东插件加载使用了自定义的classloader,会走到return false分支,最后无法加载oat文件,最后回到dex文件加载,从而进行verifyclass的执行。 主apk可以使用oat文件加载,oat文件中class和method已经校验过,写入到了oat文件中,所以加载的时候就不需要执行verifyclass了。 如果主apk没有oat文件呢?他加载dex文件也会verifyclass的。
1.2.2 verifyclass流程
verifyclass流程是什么样子的
1.2.3 如何规避verifyclass呢?
(1)插件classloader使用系统的DexClassloader,而非自定义的classloader
(2)hook runtime.cc中的verify_, 让verify_ = verifier::VerifyMode::kNone;
// If kNone, verification is disabled. kEnable by default.
verifier::VerifyMode verify_;