tools/redex-opt/main.cpp (188 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include <boost/filesystem.hpp> #include <boost/program_options.hpp> #include <iostream> #include <json/json.h> #include "DexClass.h" #include "DexLoader.h" #include "PassRegistry.h" #include "RedexContext.h" #include "Timer.h" #include "ToolsCommon.h" namespace { struct Arguments { std::string input_ir_dir; std::string output_ir_dir; std::vector<std::string> pass_names; RedexOptions redex_options; std::string config_file; std::vector<std::string> s_args; std::vector<std::string> j_args; }; Arguments parse_args(int argc, char* argv[]) { namespace po = boost::program_options; po::options_description desc( "Run one pass with dex and IR meta as input and output"); desc.add_options()("help,h", "produce help message"); desc.add_options()("input-ir,i", po::value<std::string>(), "input dex and IR meta directory"); desc.add_options()("output-ir,o", po::value<std::string>(), "output dex and IR meta directory"); desc.add_options()("pass-name,p", po::value<std::vector<std::string>>(), "pass name"); desc.add_options()("config,c", po::value<std::string>(), "A JSON-formatted config file to replace the one from " "{input-ir}/entry.json"); desc.add_options()(",S", po::value<std::vector<std::string>>(), // Accumulation "-Skey=string\n" " \tAdd a string value to the global config, overwriting " "the existing value if any\n" " \te.g. -Smy_param_name=foo\n" "-Spass_name.key=string\n" " \tAdd a string value to a pass" "config, overwriting the existing value if any\n" " \te.g. -SMyPass.config=\"foo bar\""); desc.add_options()( ",J", po::value<std::vector<std::string>>(), // Accumulation "-Jkey=<json value>\n" " \tAdd a json value to the global config, overwriting the existing " "value if any\n" " \te.g. -Jmy_param_name={\"foo\": true}\n" "-JSomePassName.key=<json value>\n" " \tAdd a json value to a pass config, overwriting the existing value " "if any\n" " \te.g. -JMyPass.config=[1, 2, 3]\n" "Note: Be careful to properly escape JSON parameters, e.g., strings must " "be quoted."); po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); po::notify(vm); if (vm.count("help")) { desc.print(std::cout); exit(EXIT_SUCCESS); } Arguments args; if (vm.count("input-ir")) { args.input_ir_dir = vm["input-ir"].as<std::string>(); } if (vm.count("output-ir")) { args.output_ir_dir = vm["output-ir"].as<std::string>(); } if (args.output_ir_dir.empty()) { std::cerr << "output-dir is empty\n"; exit(EXIT_FAILURE); } std::string meta_dir = args.output_ir_dir + "/meta"; boost::filesystem::create_directories(meta_dir); if (!boost::filesystem::is_directory(meta_dir)) { std::cerr << "Could not create " << meta_dir << std::endl; exit(EXIT_FAILURE); } if (vm.count("pass-name")) { args.pass_names = vm["pass-name"].as<std::vector<std::string>>(); } if (vm.count("config")) { args.config_file = vm["config"].as<std::string>(); } if (vm.count("-S")) { args.s_args = vm["-S"].as<std::vector<std::string>>(); } if (vm.count("-J")) { args.j_args = vm["-J"].as<std::vector<std::string>>(); } return args; } Json::Value parse_json_value(const std::string& value_string) { std::istringstream temp_stream(value_string); Json::Value temp_json; temp_stream >> temp_json; return temp_json; } bool add_value_to_config(Json::Value& config, const std::string& key_value, bool is_json) { const size_t equals_idx = key_value.find('='); if (equals_idx == std::string::npos) { return false; } const size_t dot_idx = key_value.find('.'); if (dot_idx != std::string::npos && dot_idx < equals_idx) { // Pass-specific config value specified with -Dpassname.key=value std::string pass = key_value.substr(0, dot_idx); std::string key = key_value.substr(dot_idx + 1, equals_idx - dot_idx - 1); std::string value_string = key_value.substr(equals_idx + 1); if (is_json) { config[pass][key] = parse_json_value(value_string); } else { config[pass][key] = value_string; } } else { // Global config value specified with -Dkey=value std::string key = key_value.substr(0, equals_idx); std::string value_string = key_value.substr(equals_idx + 1); if (is_json) { config[key] = parse_json_value(value_string); } else { config[key] = value_string; } } return true; } /** * Load config file and change the passes list. * entry_data.json is a json file in the following format: * - apk_dir * - dex_list * - redex_options * - config * - jars */ Json::Value process_entry_data(const Json::Value& entry_data, const Arguments& args) { Json::Value config_data = redex::parse_config(entry_data["config"].asString()); // Change passes list in config data. config_data["redex"]["passes"] = Json::arrayValue; Json::Value& passes_list = config_data["redex"]["passes"]; for (const std::string& pass_name : args.pass_names) { passes_list.append(pass_name); } int len = config_data["redex"]["passes"].size(); if (len == 0 || passes_list[len - 1].asString() != "RegAllocPass") { passes_list.append("RegAllocPass"); } // apk_dir if (entry_data.isMember("apk_dir")) { config_data["apk_dir"] = entry_data["apk_dir"].asString(); } // Include -S and -J params. for (auto& key_value : args.s_args) { if (!add_value_to_config(config_data, key_value, false)) { std::cerr << "warning: cannot parse -S" << key_value << std::endl; } } for (auto& key_value : args.j_args) { if (!add_value_to_config(config_data, key_value, true)) { std::cerr << "warning: cannot parse -S" << key_value << std::endl; } } return config_data; } } // namespace int main(int argc, char* argv[]) { Timer opt_timer("Redex-opt"); Arguments args = parse_args(argc, argv); g_redex = new RedexContext(); Json::Value entry_data; DexStoresVector stores; redex::load_all_intermediate(args.input_ir_dir, stores, &entry_data); // Set input dex magic to the first DexStore from the first dex file if (!stores.empty()) { auto first_dex_path = boost::filesystem::path(args.input_ir_dir) / entry_data["dex_list"][0]["list"][0].asString(); auto location = DexLocation::make_location("dex", first_dex_path.string()); stores[0].set_dex_magic(load_dex_magic_from_dex(location)); } if (!args.config_file.empty()) { entry_data["config"] = args.config_file; } args.redex_options.deserialize(entry_data); Json::Value config_data = process_entry_data(entry_data, args); ConfigFiles conf(config_data, args.output_ir_dir); const auto& passes = PassRegistry::get().get_passes(); PassManager manager(passes, config_data, args.redex_options); manager.set_testing_mode(); manager.run_passes(stores, conf); redex::write_all_intermediate(conf, args.output_ir_dir, args.redex_options, stores, entry_data); delete g_redex; return 0; }