in tools/redex-all/main.cpp [238:554]
Arguments parse_args(int argc, char* argv[]) {
Arguments args;
args.out_dir = ".";
args.config = default_config();
namespace po = boost::program_options;
po::options_description od(k_usage_header);
od.add_options()("help,h", "print this help message");
od.add_options()("reflect-config",
"print a reflection of the config and exit");
od.add_options()("apkdir,a",
// We allow overwrites to most of the options but will take
// only the last one.
po::value<std::vector<std::string>>(),
"directory containing unzipped APK");
od.add_options()("config,c",
po::value<std::vector<std::string>>(),
"JSON-formatted config file");
od.add_options()("outdir,o",
po::value<std::vector<std::string>>(),
"output directory for optimized dexes");
od.add_options()(
// This option value will be accumulated to a vector.
"jarpath,j",
po::value<std::vector<std::string>>(),
"classpath jar");
od.add_options()("proguard-config,p",
po::value<std::vector<std::string>>(), // Accumulation
"ProGuard config file");
od.add_options()("printseeds,q",
po::value<std::vector<std::string>>(),
"file to report seeds computed by redex");
od.add_options()(
"used-js-assets", po::value<std::vector<std::string>>(),
"A JSON file (or files) containing a list of resources used by JS");
od.add_options()("warn,w",
po::value<std::vector<int>>(),
"warning level:\n"
" 0: no warnings\n"
" 1: count of warnings\n"
" 2: full text of warnings");
od.add_options()(
"verify-none-mode",
po::bool_switch(&args.redex_options.verify_none_enabled)
->default_value(false),
"run redex in verify-none mode\n"
" \tThis will activate optimization passes or code in some passes that "
"wouldn't normally operate with verification enabled.");
od.add_options()(
"is-art-build",
po::bool_switch(&args.redex_options.is_art_build)->default_value(false),
"If specified, states that the current build is art specific.\n");
od.add_options()(
"disable-dex-hasher",
po::bool_switch(&args.redex_options.disable_dex_hasher)
->default_value(false),
"If specified, states that the current run disables dex hasher.\n");
od.add_options()(
"redacted",
po::bool_switch(&args.redex_options.redacted)->default_value(false),
"If specified then resulting dex files will have class data placed at"
" the end of the file, i.e. last map item entry just before map list.\n");
od.add_options()(
"arch,A",
po::value<std::vector<std::string>>(),
"Architecture; one of arm/arm64/thumb2/x86_64/x86/mips/mips64");
od.add_options()("enable-instrument-pass",
po::bool_switch(&args.redex_options.instrument_pass_enabled)
->default_value(false),
"If specified, enables InstrumentPass if any.\n");
od.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\"");
od.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.");
od.add_options()("show-passes", "show registered passes");
od.add_options()("dex-files", po::value<std::vector<std::string>>(),
"dex files");
// Development usage only, and Python script will generate the following
// arguments.
od.add_options()("stop-pass", po::value<int>(),
"Stop before pass n and output IR to file");
od.add_options()("output-ir", po::value<std::string>(),
"IR output directory, used with --stop-pass");
od.add_options()("jni-summary",
po::value<std::string>(),
"Path to JNI summary directory of json files.");
po::positional_options_description pod;
pod.add("dex-files", -1);
po::variables_map vm;
try {
po::store(
po::command_line_parser(argc, argv).options(od).positional(pod).run(),
vm);
po::notify(vm);
} catch (std::exception& e) {
std::cerr << e.what() << std::endl << std::endl;
print_usage();
exit(EXIT_FAILURE);
}
// -h, --help handling must be the first.
if (vm.count("help")) {
od.print(std::cout);
exit(EXIT_SUCCESS);
}
// --reflect-config handling must be next
if (vm.count("reflect-config")) {
Json::Value reflected_config;
GlobalConfig gc(GlobalConfig::default_registry());
reflected_config["global"] = reflect_config(gc.reflect());
Json::Value pass_configs = Json::arrayValue;
const auto& passes = PassRegistry::get().get_passes();
for (size_t i = 0; i < passes.size(); ++i) {
auto& pass = passes[i];
pass_configs[static_cast<int>(i)] = reflect_config(pass->reflect());
}
reflected_config["passes"] = pass_configs;
std::cout << reflected_config << std::flush;
exit(EXIT_SUCCESS);
}
if (vm.count("show-passes")) {
const auto& passes = PassRegistry::get().get_passes();
std::cout << "Registered passes: " << passes.size() << std::endl;
for (size_t i = 0; i < passes.size(); ++i) {
std::cout << i + 1 << ": " << passes[i]->name() << std::endl;
}
exit(EXIT_SUCCESS);
}
if (vm.count("dex-files")) {
args.dex_files = vm["dex-files"].as<std::vector<std::string>>();
} else {
std::cerr << "error: no input dex files" << std::endl << std::endl;
print_usage();
exit(EXIT_SUCCESS);
}
if (vm.count("warn")) {
const auto& warns = vm["warn"].as<std::vector<int>>();
for (int warn : warns) {
if (!(0 <= warn && warn <= 2)) {
std::cerr << "warning: ignoring invalid warning level option: " << warn
<< std::endl;
}
}
g_warning_level = OptWarningLevel(warns.back());
}
auto take_last = [](const auto& value) {
return value.template as<std::vector<std::string>>().back();
};
if (vm.count("config")) {
const std::string& config_file = take_last(vm["config"]);
args.entry_data["config"] =
boost::filesystem::absolute(config_file).string();
args.config = redex::parse_config(config_file);
}
if (vm.count("outdir")) {
args.out_dir = take_last(vm["outdir"]);
if (!redex::dir_is_writable(args.out_dir)) {
std::cerr << "error: outdir is not a writable directory: " << args.out_dir
<< std::endl;
exit(EXIT_FAILURE);
}
}
if (vm.count("proguard-config")) {
args.proguard_config_paths =
vm["proguard-config"].as<std::vector<std::string>>();
}
if (vm.count("jarpath")) {
const auto& jar_paths = vm["jarpath"].as<std::vector<std::string>>();
for (const auto& e : jar_paths) {
TRACE(MAIN, 2, "Command line -j option: %s", e.c_str());
args.jar_paths.emplace(e);
}
}
// We add these values to the config at the end so that they will always
// overwrite values read from the config file regardless of the order of
// arguments.
if (vm.count("apkdir")) {
args.entry_data["apk_dir"] = args.config["apk_dir"] =
take_last(vm["apkdir"]);
}
if (vm.count("printseeds")) {
args.config["printseeds"] = take_last(vm["printseeds"]);
}
if (vm.count("used-js-assets")) {
const auto& js_assets_lists =
vm["used-js-assets"].as<std::vector<std::string>>();
Json::Value array(Json::arrayValue);
for (const auto& list : js_assets_lists) {
array.append(list);
}
args.config["used-js-assets"] = array;
}
if (vm.count("arch")) {
std::string arch = take_last(vm["arch"]);
args.redex_options.arch = parse_architecture(arch);
if (args.redex_options.arch == Architecture::UNKNOWN) {
std::cerr << "warning: cannot architecture " << arch << std::endl;
}
}
if (vm.count("-S")) {
for (auto& key_value : vm["-S"].as<std::vector<std::string>>()) {
if (!add_value_to_config(args.config, key_value, false)) {
std::cerr << "warning: cannot parse -S" << key_value << std::endl;
}
}
}
if (vm.count("-J")) {
for (auto& key_value : vm["-J"].as<std::vector<std::string>>()) {
if (!add_value_to_config(args.config, key_value, true)) {
std::cerr << "warning: cannot parse -J" << key_value << std::endl;
}
}
}
args.redex_options.debug_info_kind =
parse_debug_info_kind(args.config.get("debug_info_kind", "").asString());
// Development usage only
if (vm.count("stop-pass")) {
args.stop_pass_idx = vm["stop-pass"].as<int>();
}
if (vm.count("output-ir")) {
// The out_dir is for final apk only or intermediate results only.
always_assert(args.stop_pass_idx);
args.out_dir = vm["output-ir"].as<std::string>();
}
if (vm.count("jni-summary")) {
args.redex_options.jni_summary_path = vm["jni-summary"].as<std::string>();
}
if (args.stop_pass_idx != boost::none) {
// Resize the passes list and append an additional RegAllocPass if its final
// pass is not RegAllocPass.
auto& passes_list = args.config["redex"]["passes"];
int idx = *args.stop_pass_idx;
if (idx < 0 || (size_t)idx > passes_list.size()) {
std::cerr << "Invalid stop_pass value\n";
exit(EXIT_FAILURE);
}
if (passes_list.size() > (size_t)idx) {
passes_list.resize(idx);
}
// Append the two passes when `--stop-pass` is enabled.
passes_list.append("MakePublicPass");
passes_list.append("RegAllocPass");
if (args.out_dir.empty() || !redex::dir_is_writable(args.out_dir)) {
std::cerr << "output-ir is empty or not writable" << std::endl;
exit(EXIT_FAILURE);
}
}
std::string metafiles = args.out_dir + "/meta/";
int status = [&metafiles]() -> int {
#if !IS_WINDOWS
return mkdir(metafiles.c_str(), 0755);
#else
return mkdir(metafiles.c_str());
#endif
}();
if (status != 0 && errno != EEXIST) {
// Attention: errno may get changed by syscalls or lib functions.
// Saving before printing is a conventional way of using errno.
int errsv = errno;
std::cerr << "error: cannot mkdir meta in outdir. errno = " << errsv
<< std::endl;
exit(EXIT_FAILURE);
}
TRACE(MAIN, 2, "Verify-none mode: %s",
args.redex_options.verify_none_enabled ? "Yes" : "No");
TRACE(MAIN, 2, "Art build: %s",
args.redex_options.is_art_build ? "Yes" : "No");
TRACE(MAIN, 2, "Enable InstrumentPass: %s",
args.redex_options.instrument_pass_enabled ? "Yes" : "No");
return args;
}