opt/init-classes/InitClassLoweringPass.cpp (369 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 "InitClassLoweringPass.h" #include "CFGMutation.h" #include "ConfigFiles.h" #include "ControlFlow.h" #include "InitClassPruner.h" #include "InitClassesWithSideEffects.h" #include "ScopedCFG.h" #include "Show.h" #include "Trace.h" #include "Walkers.h" using namespace init_classes; namespace { constexpr const char* METRIC_METHODS_WITH_INIT_CLASS = "methods_with_init_class"; constexpr const char* METRIC_FIELDS_ADDED = "fields_added"; constexpr const char* METRIC_INIT_CLASS_INSTRUCTIONS = "init_class_instructions"; constexpr const char* METRIC_INIT_CLASS_INSTRUCTIONS_REMOVED = "init_class_instructions_removed"; constexpr const char* METRIC_INIT_CLASS_INSTRUCTIONS_REFINED = "init_class_instructions_refined"; constexpr const char* METRIC_SGET_INSTRUCTIONS_ADDED = "sget_instructions_added"; constexpr const char* METRIC_INIT_CLASSES = "init_classes"; constexpr const char* METRIC_FIELDS_MADE_PUBLIC = "fields_made_public"; constexpr const char* METRIC_TYPES_MADE_PUBLIC = "types_made_public"; static const char* redex_field_name = "$redex_init_class"; class InitClassFields { public: DexField* get(DexType* type) const { DexField* res = nullptr; m_init_class_fields.update( type, [&](DexType* type_, InitClassField& icf, bool exist) { if (!exist) { icf.field = make_init_class_field(type_); icf.field->rstate.set_init_class(); } icf.count++; res = icf.field; }); always_assert(res); return res; } std::vector<IRInstruction*> get_replacements( DexType* type, const std::function<reg_t(DexField*)>& reg_getter) const { std::vector<IRInstruction*> insns; auto field = get(type); auto reg = reg_getter(field); auto sget_insn = (new IRInstruction(opcode::sget_opcode_for_field(field))) ->set_field(field); auto move_result_insn = (new IRInstruction( opcode::move_result_pseudo_for_sget(sget_insn->opcode()))) ->set_dest(reg); insns.push_back(sget_insn); insns.push_back(move_result_insn); return insns; } size_t get_classes_size() { return m_init_class_fields.size(); } size_t get_fields_added() { return m_fields_added; } std::vector<std::pair<DexType*, size_t>> get_ordered_init_class_reference_counts() { std::vector<std::pair<DexType*, size_t>> res; for (auto& p : m_init_class_fields) { res.emplace_back(p.first, p.second.count); } std::stable_sort(res.begin(), res.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); return res; } std::vector<DexField*> get_all() { std::vector<DexField*> res; for (auto& p : m_init_class_fields) { res.emplace_back(p.second.field); } return res; } private: const DexString* m_field_name = DexString::make_string(redex_field_name); mutable std::atomic<size_t> m_fields_added{0}; struct InitClassField { DexField* field{nullptr}; size_t count{0}; }; mutable ConcurrentMap<DexType*, InitClassField> m_init_class_fields; DexField* make_init_class_field(DexType* type) const { auto cls = type_class(type); always_assert(cls); const auto& sfields = cls->get_sfields(); if (!sfields.empty()) { // We pick the first... // 1. non-wide primitive, if any. for (auto f : sfields) { if (!type::is_wide_type(f->get_type()) && type::is_primitive(f->get_type())) { return f; } } // 2. non-wide, if any. for (auto f : sfields) { if (!type::is_wide_type(f->get_type())) { return f; } } // 3. anything. return sfields.front(); } always_assert_log(DexField::get_field(type, m_field_name, type::_int()) == nullptr, "field %s already exists!", redex_field_name); auto field = DexField::make_field(type, m_field_name, type) ->make_concrete(ACC_PUBLIC | ACC_STATIC | ACC_FINAL); field->rstate.set_root(); insert_sorted(cls->get_sfields(), field, compare_dexfields); field->set_deobfuscated_name(show_deobfuscated(field)); m_fields_added++; return field; } }; void make_public(const std::vector<DexField*>& fields, size_t* fields_made_public, size_t* types_made_public) { std::unordered_set<DexType*> visited; std::function<void(DexType*)> visit; visit = [&](DexType* type) { auto cls = type_class(type); if (cls && !cls->is_external() && !is_public(cls)) { set_public(cls); types_made_public++; visit(cls->get_super_class()); } }; for (auto& f : fields) { if (!is_public(f)) { set_public(f); fields_made_public++; } visit(f->get_class()); visit(f->get_type()); } } class LogCreator { private: DexMethodRef* m_log_e_method = DexMethod::make_method( "Landroid/util/Log;.e:(Ljava/lang/String;Ljava/lang/String;)I"); public: std::vector<IRInstruction*> get_insns(cfg::ControlFlowGraph& cfg, const DexString* tag_str, const std::string& message) const { auto tmp0 = cfg.allocate_temp(); auto tag_insn = (new IRInstruction(OPCODE_CONST_STRING))->set_string(tag_str); auto tag_result_insn = (new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT))->set_dest(tmp0); auto tmp1 = cfg.allocate_temp(); auto message_insn = (new IRInstruction(OPCODE_CONST_STRING)) ->set_string(DexString::make_string(message)); auto message_result_insn = (new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT))->set_dest(tmp1); auto invoke_insn = (new IRInstruction(OPCODE_INVOKE_STATIC)) ->set_method(m_log_e_method) ->set_srcs_size(2) ->set_src(0, tmp0) ->set_src(1, tmp1); return {tag_insn, tag_result_insn, message_insn, message_result_insn, invoke_insn}; } }; void log_in_clinits(const LogCreator& log_creator, const Scope& scope, const init_classes::InitClassesWithSideEffects& init_classes_with_side_effects) { auto tag_str = DexString::make_string("clinit-with-side-effects"); walk::parallel::classes(scope, [&](DexClass* cls) { auto type = cls->get_type(); if (init_classes_with_side_effects.refine(type) != type) { return; } auto clinit = cls->get_clinit(); if (!clinit || !clinit->get_code()) { return; } cfg::ScopedCFG cfg(clinit->get_code()); auto insns = log_creator.get_insns(*cfg, tag_str, show_deobfuscated(type)); auto block = cfg->entry_block(); auto last_load_params_it = block->get_last_param_loading_insn(); if (last_load_params_it == block->end()) { block->push_front(insns); } else { cfg->insert_after(block->to_cfg_instruction_iterator(last_load_params_it), insns); } TRACE(ICL, 6, "[InitClassLowering] added logging to %s:\n%s", SHOW(clinit), SHOW(*cfg)); }); } std::string get_init_class_message(DexMethod* method, DexType* type, const cfg::InstructionIterator& cfg_it) { std::ostringstream oss; auto pos = cfg_it.cfg().get_dbg_pos(cfg_it); if (pos) { std::unordered_set<DexPosition*> visited; for (; pos; pos = pos->parent) { if (!visited.insert(pos).second) { oss << "Cyclic"; break; } if (pos->method != nullptr) { oss << *pos->method; } else { oss << "Unknown method"; } oss << "("; if (pos->file == nullptr) { oss << "Unknown source"; } else { oss << *pos->file; } oss << ":" << pos->line << ")"; if (pos->parent != nullptr) { oss << ", parent: "; } } } else { oss << show_deobfuscated(method); } oss << " ==> " << show_deobfuscated(type); return oss.str(); } } // namespace void InitClassLoweringPass::bind_config() { bind( "drop", m_drop, m_drop, "Whether to drop the init-class instructions, instead of lowering them."); bind("log_init_classes", m_log_init_classes, m_log_init_classes, "Whether to insert log statements at all init-class instructions."); bind("log_in_clinits", m_log_in_clinits, m_log_in_clinits, "Whether to insert log statements in clinits with side-effects."); } void InitClassLoweringPass::run_pass(DexStoresVector& stores, ConfigFiles& conf, PassManager& mgr) { const auto scope = build_class_scope(stores); auto create_init_class_insns = conf.create_init_class_insns(); TRACE(ICL, 1, "[InitClassLowering] create_init_class_insns: %d", create_init_class_insns); init_classes::InitClassesWithSideEffects init_classes_with_side_effects( scope, create_init_class_insns); LogCreator log_creator; if (m_log_in_clinits) { log_in_clinits(log_creator, scope, init_classes_with_side_effects); } const DexString* tag_str = m_log_in_clinits ? DexString::make_string("init-class") : nullptr; std::atomic<size_t> sget_instructions_added{0}; std::atomic<size_t> methods_with_init_class{0}; InitClassFields init_class_fields; const auto stats = walk::parallel::methods<Stats>(scope, [&](DexMethod* method) { auto code = method->get_code(); if (!code || !method::count_opcode_of_types(code, {IOPCODE_INIT_CLASS})) { return Stats(); } cfg::ScopedCFG cfg(code); InitClassPruner pruner(init_classes_with_side_effects, method->get_class(), *cfg); pruner.apply(); auto local_stats = pruner.get_stats(); if (local_stats.init_class_instructions == 0) { return local_stats; } TRACE(ICL, 6, "[InitClassLowering] method %s with %zu init-classes:\n%s", SHOW(method), local_stats.init_class_instructions, SHOW(*cfg)); methods_with_init_class++; boost::optional<reg_t> tmp_reg; boost::optional<reg_t> wide_tmp_reg; auto get_reg = [&](DexField* field) { if (type::is_wide_type(field->get_type())) { if (!wide_tmp_reg) { wide_tmp_reg = cfg->allocate_wide_temp(); } return *wide_tmp_reg; } if (!tmp_reg) { tmp_reg = cfg->allocate_temp(); } return *tmp_reg; }; cfg::CFGMutation mutation(*cfg); size_t local_sget_instructions_added = 0; for (auto block : cfg->blocks()) { auto ii = InstructionIterable(block); for (auto it = ii.begin(); it != ii.end(); it++) { if (it->insn->opcode() != IOPCODE_INIT_CLASS) { continue; } always_assert(create_init_class_insns); auto type = it->insn->get_type(); std::vector<IRInstruction*> replacements; if (!m_drop) { replacements = init_class_fields.get_replacements(type, get_reg); local_sget_instructions_added++; } auto cfg_it = block->to_cfg_instruction_iterator(it); if (tag_str) { auto message = get_init_class_message(method, type, cfg_it); auto log_insns = log_creator.get_insns(*cfg, tag_str, message); replacements.insert(replacements.begin(), log_insns.begin(), log_insns.end()); } mutation.replace(cfg_it, replacements); } } mutation.flush(); if (local_sget_instructions_added) { sget_instructions_added += local_sget_instructions_added; } return local_stats; }); TRACE(ICL, 1, "[InitClassLowering] %zu methods have %zu sget instructions; %zu " "classes with clinits with side effects needed initialization with " "%zu added fields", (size_t)methods_with_init_class, (size_t)sget_instructions_added, init_class_fields.get_classes_size(), init_class_fields.get_fields_added()); if (traceEnabled(ICL, 5)) { for (auto& p : init_class_fields.get_ordered_init_class_reference_counts()) { auto cls = type_class(p.first); auto count = p.second; auto clinit = cls->get_clinit(); always_assert(clinit); cfg::ScopedCFG cfg(clinit->get_code()); TRACE(ICL, 5, "[InitClassLowering] clinit of %s referenced by %zu init-class " "instructions:\n%s", SHOW(cls), count, SHOW(*cfg)); } } size_t fields_made_public = 0; size_t types_made_public = 0; make_public(init_class_fields.get_all(), &fields_made_public, &types_made_public); TRACE(ICL, 5, "[InitClassLowering] made %zu existing fields and %zu classes public", fields_made_public, types_made_public); mgr.incr_metric(METRIC_METHODS_WITH_INIT_CLASS, methods_with_init_class); mgr.incr_metric(METRIC_FIELDS_ADDED, init_class_fields.get_fields_added()); mgr.incr_metric(METRIC_INIT_CLASS_INSTRUCTIONS, stats.init_class_instructions); mgr.incr_metric(METRIC_INIT_CLASS_INSTRUCTIONS_REMOVED, stats.init_class_instructions_removed); mgr.incr_metric(METRIC_INIT_CLASS_INSTRUCTIONS_REFINED, stats.init_class_instructions_refined); mgr.incr_metric(METRIC_INIT_CLASSES, init_class_fields.get_classes_size()); mgr.incr_metric(METRIC_SGET_INSTRUCTIONS_ADDED, sget_instructions_added); mgr.incr_metric(METRIC_FIELDS_MADE_PUBLIC, fields_made_public); mgr.incr_metric(METRIC_TYPES_MADE_PUBLIC, types_made_public); mgr.record_init_class_lowering(); } static InitClassLoweringPass s_pass;