概述

这部分可以略过。

编译

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-synth-demo-before.png

redex优化后:

redex-synth-demo-after.png

可见:

先简单描述一下内部类访问外部类成员的原理。

内部类中访问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.gzredex-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.configdefault.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的向量。相互关系如下图:

redex-DexStoresVector-2-DexClass.png

# 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);
    }
  }
  ...
}

By @hyongbai 共15973个字

本文链接 http://yourbay.me/all-about-tech/2020/05/12/redex-1-arch/