tools/common/ToolsCommon.cpp (254 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 "ToolsCommon.h"
#include <boost/filesystem.hpp>
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#include <boost/iostreams/filtering_stream.hpp> // uses deprecated auto_ptr
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#include <iostream>
#include <json/json.h>
#include "CommentFilter.h"
#include "DexLoader.h"
#include "DexOutput.h"
#include "DexPosition.h"
#include "DexUtil.h"
#include "IRMetaIO.h"
#include "InstructionLowering.h"
#include "JarLoader.h"
#include "Macros.h"
#include "Show.h"
#include "Timer.h"
#include "Walkers.h"
#if IS_WINDOWS
#include <io.h>
#endif
namespace {
/**
* Entry file contains the list of dex files, config file and original command
* line arguments.
*/
const std::string ENTRY_FILE = "/entry.json";
void load_entry_file(const std::string& input_ir_dir, Json::Value* entry_data) {
std::ifstream istrm(input_ir_dir + ENTRY_FILE);
istrm >> *entry_data;
}
void write_entry_file(const std::string& output_ir_dir,
const Json::Value& entry_data) {
std::ofstream ostrm(output_ir_dir + ENTRY_FILE);
ostrm << entry_data;
}
/**
* Init the IR meta to default values.
*/
void init_ir_meta(DexStoresVector& stores) {
Timer t("Init default meta");
Scope classes = build_class_scope(stores);
walk::parallel::classes(classes, [](DexClass* cls) {
cls->set_deobfuscated_name(show(cls));
for (DexField* field : cls->get_sfields()) {
field->set_deobfuscated_name(show(field));
}
for (DexField* field : cls->get_ifields()) {
field->set_deobfuscated_name(show(field));
}
for (DexMethod* method : cls->get_dmethods()) {
method->set_deobfuscated_name(show(method));
}
for (DexMethod* method : cls->get_vmethods()) {
method->set_deobfuscated_name(show(method));
}
});
}
/**
* Write meta data to file.
* Development usage only
*/
void write_ir_meta(const std::string& output_ir_dir, DexStoresVector& stores) {
Timer t("Dumping IR meta");
Scope classes = build_class_scope(stores);
ir_meta_io::dump(classes, output_ir_dir);
}
/**
* Write intermediate dex to files.
* Development usage only
*/
void write_intermediate_dex(const RedexOptions& redex_options,
ConfigFiles& conf,
const std::string& output_ir_dir,
DexStoresVector& stores,
Json::Value& dex_files) {
Timer write_int_dex_timer("Write intermediate dex");
{
Timer t("Instruction lowering");
instruction_lowering::run(stores);
}
std::unique_ptr<PositionMapper> pos_mapper(PositionMapper::make(""));
for (size_t store_number = 0; store_number < stores.size(); ++store_number) {
auto& store = stores[store_number];
Timer t("Writing intermediate dexes");
dex_files.append(Json::nullValue);
Json::Value& store_files = dex_files[dex_files.size() - 1];
store_files["name"] = store.get_name();
store_files["list"] = Json::arrayValue;
for (size_t i = 0; i < store.get_dexen().size(); i++) {
if (store.get_dexen()[i].empty()) {
continue;
}
std::string filename =
redex::get_dex_output_name(output_ir_dir, store, i);
auto gtypes = std::make_shared<GatheredTypes>(&store.get_dexen()[i]);
write_classes_to_dex(redex_options,
filename,
&store.get_dexen()[i],
std::move(gtypes),
/* locator_index= */ nullptr,
store_number,
&store.get_name(),
i,
conf,
pos_mapper.get(),
/* method_to_id= */ nullptr,
/* code_debug_lines= */ nullptr,
/* iodi_metadata= */ nullptr,
stores[0].get_dex_magic());
auto basename = boost::filesystem::path(filename).filename().string();
store_files["list"].append(basename);
}
}
}
/**
* Load intermediate dex
*/
void load_intermediate_dex(const std::string& input_ir_dir,
const Json::Value& dex_files,
DexStoresVector& stores) {
Timer t("Load intermediate dex");
dex_stats_t dex_stats;
for (const Json::Value& store_files : dex_files) {
std::string store_name = store_files["name"].asString();
DexStore store(store_name);
stores.emplace_back(std::move(store));
for (const Json::Value& file_name : store_files["list"]) {
auto location = boost::filesystem::path(input_ir_dir);
location /= file_name.asString();
// `string().c_str()` to get guaranteed `const char*`.
DexClasses classes = load_classes_from_dex(
DexLocation::make_location(store_name, location.string()),
&dex_stats);
stores.back().add_classes(std::move(classes));
}
}
}
/**
* Load IR meta data
*/
bool load_ir_meta(const std::string& input_ir_dir) {
Timer t("Loading IR meta");
return ir_meta_io::load(input_ir_dir);
}
static void assert_dex_magic_consistency(const std::string& source,
const std::string& target) {
always_assert_log(source.compare(target) == 0,
"APK contains dex file of different versions: %s vs %s\n",
source.c_str(), target.c_str());
}
bool is_zip(const std::string& filename) {
char buffer[2];
std::ifstream infile(filename);
always_assert(infile);
infile.read(buffer, 2);
// the first two bytes of a ZIP file are usually "PK"
return buffer[0] == 'P' && buffer[1] == 'K';
}
} // namespace
namespace redex {
bool dir_is_writable(const std::string& dir) {
if (!boost::filesystem::is_directory(dir)) {
return false;
}
#if IS_WINDOWS
return _access(dir.c_str(), 2) == 0;
#else
return access(dir.c_str(), W_OK) == 0;
#endif
}
Json::Value parse_config(const std::string& config_file) {
std::ifstream config_stream(config_file);
if (!config_stream) {
std::cerr << "error: cannot find config file: " << config_file << std::endl;
exit(EXIT_FAILURE);
}
boost::iostreams::filtering_istream inbuf;
inbuf.push(CommentFilter());
inbuf.push(config_stream);
Json::Value ret;
inbuf >> ret; // parse JSON
return ret;
}
/**
* Dumping dex, IR meta data and entry file
*/
void write_all_intermediate(ConfigFiles& conf,
const std::string& output_ir_dir,
const RedexOptions& redex_options,
DexStoresVector& stores,
Json::Value& entry_data) {
Timer t("Dumping all");
redex_options.serialize(entry_data);
entry_data["dex_list"] = Json::arrayValue;
write_ir_meta(output_ir_dir, stores);
write_intermediate_dex(redex_options, conf, output_ir_dir, stores,
entry_data["dex_list"]);
write_entry_file(output_ir_dir, entry_data);
}
/**
* Loading entry file, dex files and IR meta data
*/
void load_all_intermediate(const std::string& input_ir_dir,
DexStoresVector& stores,
Json::Value* entry_data) {
Timer t("Loading all");
load_entry_file(input_ir_dir, entry_data);
load_intermediate_dex(input_ir_dir, (*entry_data)["dex_list"], stores);
// load external classes
Scope external_classes;
if (!(*entry_data).get("jars", Json::nullValue).empty()) {
for (const Json::Value& item : (*entry_data)["jars"]) {
const std::string jar_path = item.asString();
always_assert(load_jar_file(DexLocation::make_location("", jar_path),
&external_classes));
}
}
init_ir_meta(stores);
if (!load_ir_meta(input_ir_dir)) {
std::string error =
"Use default IR meta instead. The process result may be greatly "
"different from the result of running whole optimization passes with "
"redex-all\n";
std::cerr << error;
TRACE_NO_LINE(MAIN, 1, "%s", error.c_str());
}
}
/**
* Helper to load classes from a list of input dex files into a DexStoresVector.
* Processes dex (.dex) files as well as DexMetadata files (.json)
*/
void load_classes_from_dexes_and_metadata(
const std::vector<std::string>& dex_files,
DexStoresVector& stores,
dex_stats_t& input_totals,
std::vector<dex_stats_t>& input_dexes_stats) {
always_assert_log(!stores.empty(),
"Cannot load classes into empty DexStoresVector");
for (const auto& filename : dex_files) {
if (filename.size() >= 5 &&
filename.compare(filename.size() - 4, 4, ".dex") == 0) {
auto location = DexLocation::make_location("dex", filename);
assert_dex_magic_consistency(stores[0].get_dex_magic(),
load_dex_magic_from_dex(location));
dex_stats_t dex_stats;
DexClasses classes = load_classes_from_dex(location, &dex_stats);
input_totals += dex_stats;
input_dexes_stats.push_back(dex_stats);
stores[0].add_classes(std::move(classes));
} else if (is_zip(filename)) {
std::cerr << "error: Input files are expected to be DEX (with filename "
"ending in "
".dex), or a JSON metadata file. However, \""
<< filename
<< "\" is a ZIP. If this is an APK, please extract "
"the DEX files from it and pass those as the inputs.";
exit(EXIT_FAILURE);
} else {
DexMetadata store_metadata;
store_metadata.parse(filename);
DexStore store(store_metadata);
for (const auto& file_path : store_metadata.get_files()) {
auto location = DexLocation::make_location(store.get_name(), file_path);
assert_dex_magic_consistency(stores[0].get_dex_magic(),
load_dex_magic_from_dex(location));
dex_stats_t dex_stats;
DexClasses classes = load_classes_from_dex(location, &dex_stats);
input_totals += dex_stats;
input_dexes_stats.push_back(dex_stats);
store.add_classes(std::move(classes));
}
stores.emplace_back(std::move(store));
}
}
}
/**
* Helper to get the output name of a specific dex file when a series of dex
* files are being output by redex programs.
* Index corresponds to the position in the order dex files are passed into
* the redex programs: classes.dex -> 0, classes2.dex -> 1, classes3.dex -> 2...
*/
std::string get_dex_output_name(const std::string& output_dir,
const DexStore& store,
int index) {
return output_dir + "/" + dex_name(store, index);
}
} // namespace redex