Redex源码分析1-整体架构
概述
这部分可以略过。
编译
xcode-select --install
brew install autoconf automake libtool python3
brew install boost jsoncpp
git clone https://github.com/facebook/redex.git
cd redex
autoreconf -ivf && ./configure && make -j4
配置
# config/default.config
{
"redex" : {
# 添加需要使用的Pass
"passes" : [
...
"SynthPass",
"RegAllocPass",
"ReduceGotosPass" # This pass should come at the very end, after all other code transformations that might add gotos
...
]
},
# 单个Pass自身的设置
"RegAllocPass" : {
"live_range_splitting": false
},
...
}
官方介绍: https://fbredex.com/docs/configuring
使用
- 列出所有支持的Pass列表
./redex-all --show-passes
- 使用
redex -c default.config -o tmp/output.apk input.apk
-c
指定配置文件
-o
指定输出路径
- 查看编译log
export TRACE=1
使用redex命令之前, 设置TRACE为1即可打开Trace(1及其以下level的trace信息都会输出), 用于查看一些调试信息。
如果设置为4的话很有可能会因为输出的log过多, 而使编译时间异常长。
官方介绍: https://fbredex.com/docs/usage
redex项目中自带了可以应用一些redex优化的example。比如优化Synthetic的例子, 在examples/Synth/synth-example
目录。
编译成功之后, 执行如下代码即可:
./redex \
examples/Synth/synth-example/build/outputs/apk/synth-example-debug.apk \
-o examples/Synth/synth-example/build/outputs/apk/synth-example-debug-redex.apk
# redex又redex.py和redex-all打包而来, 上面的命令会转化成类似如下的参数:
# /tmp/redex.5GOAeU/redex-all \
# --config config/default.config \
# --proguard-config=examples/Synth/synth-example/proguard-rules.pro \
# --apkdir /tmp/redex.5GOAeU/tmpg03aggp7.redex_extracted_apk \
# --outdir /tmp/redex.5GOAeU/tmphwf8mhgk.redex_dexen \
# /tmp/redex.5GOAeU/tmphwf8mhgk.redex_dexen/dex0/classes.dex
# 同时也可以直接运行redex-all, 只不过最后不会将文件再打包。如下:
# ./redex-all \
# -c config/default.config \
# -p examples/Synth/synth-example/proguard-rules.pro \
# --apkdir examples/apk/redex_extracted_apk \
# --outdir examples/apk/redex_dexen examples/apk/classes.dex
执行之后, 进入到apk目录, 可以看到有如下文件:
➜ apk git:(stable) ✗ ll
total 3.9M
-rw-r--r-- 1 hyb staff 225K May 11 01:57 redex-bytecode-offset-map.txt
-rw-r--r-- 1 hyb staff 1.4M May 11 01:57 redex-class-rename-map.txt
-rw-r--r-- 1 hyb staff 27K May 11 01:57 redex-src-strings-map.txt
-rw-r--r-- 1 hyb staff 15K May 11 01:57 redex-stats.txt
-rw-r--r-- 1 hyb staff 1.1M May 11 01:57 synth-example-debug-redex.apk
-rw-r--r-- 1 hyb staff 1.2M May 11 01:55 synth-example-debug.apk
其中的txt文件是redex编译的中间产物, 包括一些map文件以及统计信息。可以看到redex.apk相对于原文件而言小了大概100KB。
下面对比一下example/Synth这个项目中Alpha.java类编译出来的结果:
redex优化前:
redex优化后:
可见:
先简单描述一下内部类访问外部类成员的原理。
内部类中访问private的alpha, 会在编译时生成对应的静态公有函数access$0000
, 同时它由synthetic标记。表示这是一个由编译器生成的函数。而在Beta的dpubleAlpha函数中则会通过invokeStatic
指令访问这个静态函数。
而经过SynthPass之后, 这个access$0000
函数会被移除, 并且修改alpha为public, 之后通过getStatic
直接访问这个静态成员。
redex运行逻辑
编译完之后, 在redex目录会生成一些文件。
可能一上来会有点迷糊, 不过结合Makefile文件等, 可以得出如下结论:
redex-all
这个文件是redex的真正编译层产物。所有cpp文件最后编译在这个可执行文件中。redex
这个文件bash脚本selfextract.sh
以及redex.tar.gz
组合而成, 严格意义上来说它只是一段bash脚本。具体生成过程见bundle-redex.sh
脚本。redex.tar.gz
由redex-all redex.py pyredex/*.py
等文件压缩而成。redex.py
这个是redex的entry脚本, 通过python运行这个脚本, 脚本中完成对redex-all文件的执行, 以及处理执行后的收尾工作。 比如: 将输入的apk文件解压, 将新生成的dex文件们重新压缩成apk文件并签名等。
当redex
执行时, 会解压自己, 并运行解压出来的redex.py文件, 其执行逻辑见selfextract.sh
脚本。
通过上面等信息可以得出: redex的核心类为tools/redex-all/main.cpp
。
其main函数如下:
# redex-main
// tools/redex-all/main.cpp
int main(int argc, char* argv[]) {
...
std::string stats_output_path;
Json::Value stats;
{
Timer redex_all_main_timer("redex-all main()");
// 全局的Redex上下文
g_redex = new RedexContext();
// 解析命令行中添加参数
Arguments args = parse_args(argc, argv);
RedexContext::set_record_keep_reasons(
args.config.get("record_keep_reasons", false).asBool());
auto pg_config = std::make_unique<keep_rules::ProguardConfiguration>();
// 存储dex文件的列表
DexStoresVector stores;
ConfigFiles conf(args.config, args.out_dir);
std::string apk_dir;
conf.get_json_config().get("apk_dir", "", apk_dir);
const std::string& manifest_filename = apk_dir + "/AndroidManifest.xml";
boost::optional<int32_t> maybe_sdk = get_min_sdk(manifest_filename);
if (maybe_sdk != boost::none) {
args.redex_options.min_sdk = *maybe_sdk;
}
// 预处理逻辑: 根据参数加载dex和配置信息
redex_frontend(conf, args, *pg_config, stores, stats);
// 获取整个Redex中注册了个Pass列表。
auto const& passes = PassRegistry::get().get_passes();
// 创建PassManager, 用于将pass应用到dex上。
PassManager manager(passes, std::move(pg_config), args.config,
args.redex_options);
{
// 运行用户配置了的pass
manager.run_passes(stores, conf);
}
if (args.stop_pass_idx == boost::none) {
// 后处理, 生成处理后的dex文件等。
redex_backend(args.out_dir, conf, manager, stores, stats);
if (args.config.get("emit_class_method_info_map", false).asBool()) {
dump_class_method_info_map(conf.metafile(CLASS_METHOD_INFO_MAP), stores);
}
} else {
redex::write_all_intermediate(conf, args.out_dir, args.redex_options,
stores, args.entry_data);
}
// 从配置中获取统计信息的输出路径, 即stats_output。默认为redex-stats.txt"
stats_output_path = conf.metafile(
args.config.get("stats_output", "redex-stats.txt").asString());
{
// 释放全局上下文
delete g_redex;
}
}
// now that all the timers are done running, we can collect the data
stats["output_stats"]["time_stats"] = get_times();
Json::StyledStreamWriter writer;
{
std::ofstream out(stats_output_path);
writer.write(out, stats);
}
TRACE(MAIN, 1, "Done.");
return 0;
}
- 总结
redex在执行时分为三个阶段:
- redex_frontend
预处理逻辑: 根据参数加载dex和配置信息。
其中加载dex具体是指将dex文件加载为一个DexClasses, 而DexClasses则为dex中所有的DexClass对象的集合。即解析出DexClass的过程。
其中args为命令行阶段redex后面的参数(并且由redex.py加工过), config则为-c
对应的config文件。redex自带了两个config文件, 即config/
目录下的aggressive.config
和default.config
。
- run_passes
PassManager管理着用户激活了的pass项, 并将命令发送给一个个具体的Pass去执行。
Pass列表可以通过config中的”redex/passes”字段配置。
通过redex_frontend
阶段可以很简单知道passes就是解析redex/passes
字段。
./redex-all --show-passes
则列出了redex中支持的所有的Pass们, 这些Pass统一由PassRegistry
管理着。
PassRegistry
很简单的单例, 提供了register_pass
方法用于注册Pass, get_passes
用于获取所有注册了的Pass们。
而Pass则是在其构造函数中直接调用register_pass
将自己注册进去了。
- redex_backend
主要是生成处理后的dex文件等。
# redex_frontend
// tools/redex-all/main.cpp
/**
* Pre processing steps: load dex and configurations
*/
void redex_frontend(ConfigFiles& conf, /* input */
Arguments& args, /* inout */
keep_rules::ProguardConfiguration& pg_config,
DexStoresVector& stores,
Json::Value& stats) {
// 解析参数中的parguard配置文件到pg_config
for (const auto& pg_config_path : args.proguard_config_paths) {
keep_rules::proguard_parser::parse_file(pg_config_path, &pg_config);
}
// 将黑名单从pg_config中移除, 即保留规则。
keep_rules::proguard_parser::remove_blacklisted_rules(&pg_config);
// 从上面解析出来的proguard规则中拿到libraryjars
// proguard规定中允许不混淆指定的jar包(使用`-libraryjars`即可)
// 并解析出其对应的所有jar包的路径添加的library_jars集合中
const auto& pg_libs = pg_config.libraryjars;看ill
args.jar_paths.insert(pg_libs.begin(), pg_libs.end());
std::set<std::string> library_jars;
for (const auto& jar_path : args.jar_paths) {
std::istringstream jar_stream(jar_path);
std::string dependent_jar_path;
while (std::getline(jar_stream, dependent_jar_path, ':')) {
library_jars.emplace(dependent_jar_path);
}
}
// 创建一个用于存储所有class的DexStore实例
DexStore root_store("classes");
// 解析dex文件header的magic数, 并设置到root_store中
// magic为8个字节长, 值为"dex\n035\0"或者"dex\n037\0"
root_store.set_dex_magic(get_dex_magic(args.dex_files));
// 添加到stores向量
stores.emplace_back(std::move(root_store));
// conf就是redex运行时指定的config文件, 这里将其读取json对象
const JsonWrapper& json_config = conf.get_json_config();
dup_classes::read_dup_class_whitelist(json_config);
{ // 解析dex文件列表
dex_stats_t input_totals;
// 每个dex文件都对应一个的redex-stats.txt
std::vector<dex_stats_t> input_dexes_stats;
for (const auto& filename : args.dex_files) {
// 文件必须有效, 判断方式为: 文件名大于4同时必须以dex为后缀
if (filename.size() >= 5 &&
filename.compare(filename.size() - 4, 4, ".dex") == 0) {
// 校验每个文件的magic是否与上面设置的一致。
assert_dex_magic_consistency(stores[0].get_dex_magic(),
load_dex_magic_from_dex(filename.c_str()));
dex_stats_t dex_stats;
// 使用DexLoader从dex文件中解析出class
DexClasses classes =
load_classes_from_dex(filename.c_str(), &dex_stats);
input_totals += dex_stats;
input_dexes_stats.push_back(dex_stats);
// 来自参数的dex文件添加到第一个DexStore对象中, 即上面的root_store。
stores[0].add_classes(std::move(classes));
} else {
// 如果不是dex文件, 那么将其解析为DexMetadata
DexMetadata store_metadata;
store_metadata.parse(filename);
DexStore store(store_metadata);
// 解析metadata中的dex文件列表, for循环中处理dex的逻辑与上面一致
for (const auto& file_path : store_metadata.get_files()) {
assert_dex_magic_consistency(
stores[0].get_dex_magic(),
load_dex_magic_from_dex(file_path.c_str()));
dex_stats_t dex_stats;
DexClasses classes =
load_classes_from_dex(file_path.c_str(), &dex_stats);
input_totals += dex_stats;
input_dexes_stats.push_back(dex_stats);
store.add_classes(std::move(classes));
}
// 每一个metadata都单独对应一个DexStore
stores.emplace_back(std::move(store));
}
}
stats["input_stats"] = get_input_stats(input_totals, input_dexes_stats);
}
// 解析jar文件
Scope external_classes;
args.entry_data["jars"] = Json::arrayValue;
if (!library_jars.empty()) {
Timer t("Load library jars");
for (const auto& library_jar : library_jars) {
// 加载jar文件到external_classes中去。
if (!load_jar_file(library_jar.c_str(), &external_classes)) {
// 加载失败则添加basedirectory组合jar文件的路径再次加载
std::string basedir_path =
pg_config.basedirectory + "/" + library_jar.c_str();
if (!load_jar_file(basedir_path.c_str())) {
// 如果jar文件路径有误或者文件损坏等加载失败, 则抛出异常。
std::cerr << "error: library jar could not be loaded: " << library_jar
<< std::endl;
exit(EXIT_FAILURE);
}
args.entry_data["jars"].append(basedir_path);
} else {
auto abs_path = boost::filesystem::absolute(library_jar);
args.entry_data["jars"].append(abs_path.string());
}
}
}
{
// 使用proguard的map文件反混淆dex文件
for (auto& store : stores) {
apply_deobfuscated_names(store.get_dexen(), conf.get_proguard_map());
}
}
DexStoreClassesIterator it(stores);
Scope scope = build_class_scope(it);
{
bool keep_all_annotation_classes;
json_config.get("keep_all_annotation_classes", true,
keep_all_annotation_classes);
process_proguard_rules(conf.get_proguard_map(), scope, external_classes,
pg_config, keep_all_annotation_classes);
}
{
keep_rules::process_no_optimizations_rules(
conf.get_no_optimizations_annos(), scope);
monitor_count::mark_sketchy_methods_with_no_optimize(scope);
}
{
init_reachable_classes(scope, json_config,
conf.get_no_optimizations_annos());
}
}
DexStoresVector是一个存储DexStore的向量, DexStore则中持有的m_dexen是存储了DexClasses的向量。而DexClasses则是保存单个dex文件中所有的DexClass的向量。相互关系如下图:
# PassManager
void PassManager::run_passes(DexStoresVector& stores, ConfigFiles& conf) {
DexStoreClassesIterator it(stores);
// 生成Scope,
// Scope是当前锁处理的所有dex文件解析出来的所有DexClass
Scope scope = build_class_scope(it);
{
api::LevelChecker::init(m_redex_options.min_sdk, scope);
}
...
// Enable opt decision logging if specified in config.
const Json::Value& opt_decisions_args =
conf.get_json_config()["opt_decisions"];
if (opt_decisions_args.get("enable_logs", false).asBool()) {
opt_metadata::OptDataMapper::get_instance().enable_logs();
}
// 解析config中跟inliner相关的逻辑。
// 主要是三部分: 黑名单的类/方法/注解等。
conf.load(scope);
// 运行所有激活的Pass的eval_pass方法(很多Pass并未重写此方法, 默认为空)。
// m_activated_passes来自PackageManager初始化时读取config中"redex/passes"的配置。
// 如未配置, 则默认为所有注册的pass, 即`m_registered_passes`。
// 具体逻辑见`PassManager::init`方法。
for (size_t i = 0; i < m_activated_passes.size(); ++i) {
Pass* pass = m_activated_passes[i];
m_current_pass_info = &m_pass_info[i];
pass->eval_pass(stores, conf, *this);
m_current_pass_info = nullptr;
}
...
// 运行所有激活Pass的run_pass函数, dex相关的具体逻辑都在这里完成。
for (size_t i = 0; i < m_activated_passes.size(); ++i) {
Pass* pass = m_activated_passes[i];
m_current_pass_info = &m_pass_info[i];
{
ScopedCommandProfiling cmd_prof(
m_profiler_info && m_profiler_info->pass == pass
? boost::make_optional(m_profiler_info->command)
: boost::none);
jemalloc_util::ScopedProfiling malloc_prof(m_malloc_profile_pass == pass);
// pass的实现都是从run_pass方法开始的。
pass->run_pass(stores, conf, *this);
}
...
m_current_pass_info = nullptr;
}
...
}
# redex_backend
// tools/redex-all/main.cpp
/**
* Post processing steps: write dex and collect stats
*/
void redex_backend(const std::string& output_dir,
const ConfigFiles& conf,
PassManager& manager,
DexStoresVector& stores,
Json::Value& stats) {
Timer redex_backend_timer("Redex_backend");
const RedexOptions& redex_options = manager.get_redex_options();
...
const JsonWrapper& json_config = conf.get_json_config();
...
dex_stats_t output_totals;
std::vector<dex_stats_t> output_dexes_stats;
const std::string& line_number_map_filename = conf.metafile(LINE_NUMBER_MAP);
const std::string& debug_line_map_filename = conf.metafile(DEBUG_LINE_MAP);
const std::string& iodi_metadata_filename = conf.metafile(IODI_METADATA);
auto dik = redex_options.debug_info_kind;
bool needs_addresses = dik == DebugInfoKind::NoPositions || is_iodi(dik);
std::unique_ptr<PositionMapper> pos_mapper(PositionMapper::make(
dik == DebugInfoKind::NoCustomSymbolication ? ""
: line_number_map_filename));
std::unordered_map<DexMethod*, uint64_t> method_to_id;
std::unordered_map<DexCode*, std::vector<DebugLineItem>> code_debug_lines;
IODIMetadata iodi_metadata;
std::unique_ptr<PostLowering> post_lowering =
redex_options.redacted ? PostLowering::create() : nullptr;
if (is_iodi(dik)) {
iodi_metadata.mark_methods(stores);
}
// 将DexStoresVector中的所有DexStore遍历出来
for (size_t store_number = 0; store_number < stores.size(); ++store_number) {
auto& store = stores[store_number];
// 遍历DexStore中的存储的所有DexClasses集合。
// 由上面redex_frontend可知, 每个DexClasses其实对应着一个完整的dex文件。
for (size_t i = 0; i < store.get_dexen().size(); i++) {
std::ostringstream ss;
// 组合将要生成的dex文件名。
// output_dir为输出目录。也就是“-o”参数。
// 规则如下:
ss << output_dir << "/" << store.get_name();
if (store.get_name().compare("classes") == 0) {
if (i > 0) {
ss << (i + 1);
}
} else {
ss << (i + 2);
}
ss << ".dex";
// 将当前的DexClasses写入到上面组合出来的dex文件中。
// 具体写入过程将`write_classes_to_dex`方法的实现。先略过。
auto this_dex_stats =
write_classes_to_dex(redex_options,
ss.str(),
&store.get_dexen()[i],
locator_index,
emit_name_based_locators,
store_number,
i,
conf,
pos_mapper.get(),
needs_addresses ? &method_to_id : nullptr,
needs_addresses ? &code_debug_lines : nullptr,
is_iodi(dik) ? &iodi_metadata : nullptr,
stores[0].get_dex_magic(),
post_lowering.get());
...
output_totals += this_dex_stats;
output_dexes_stats.push_back(this_dex_stats);
}
}
...
}