tools/bytecode_debugger/InjectDebug.cpp (159 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 "InjectDebug.h"
#include "DexClass.h"
#include "DexLoader.h"
#include "DexOutput.h"
#include "DexPosition.h"
#include "DexUtil.h"
#include "IRCode.h"
#include "InstructionLowering.h"
#include "RedexContext.h"
#include "ToolsCommon.h"
#include "TypeInference.h"
InjectDebug::InjectDebug(const std::string& outdir,
const std::vector<std::string>& dex_files)
: m_conf(Json::Value(), outdir), m_dex_files(dex_files) {
if (!g_redex) {
g_redex = new RedexContext();
}
}
InjectDebug::~InjectDebug() {
delete g_redex;
g_redex = nullptr;
}
void InjectDebug::run() {
load_dex();
inject_all();
write_dex();
}
void InjectDebug::load_dex() {
DexStore root_store("classes");
root_store.set_dex_magic(load_dex_magic_from_dex(
DexLocation::make_location("dex", m_dex_files[0])));
m_stores.emplace_back(std::move(root_store));
dex_stats_t input_totals;
std::vector<dex_stats_t> input_dexes_stats;
redex::load_classes_from_dexes_and_metadata(m_dex_files, m_stores,
input_totals, input_dexes_stats);
}
void InjectDebug::inject_register(
IRCode* ir_code,
const IRList::iterator& ir_it,
const type_inference::TypeEnvironment& type_env,
reg_t reg) {
// Get general type of register (either java object or primitive)
DexType* reg_type;
if (type_env.get_type(reg).element() == IRType::REFERENCE) {
reg_type = type::java_lang_Object();
} else {
reg_type = type::_int();
}
auto reg_string = DexString::make_string("v" + std::to_string(reg));
ir_code->insert_before(
ir_it,
std::make_unique<DexDebugOpcodeStartLocal>(reg, reg_string, reg_type));
}
void InjectDebug::inject_method(DexMethod* dex_method, int* line_start) {
IRCode* ir_code = dex_method->get_code();
if (ir_code == nullptr) return;
DexDebugItem* dbg = ir_code->get_debug_item();
if (dbg != nullptr) dbg->get_entries().clear();
ir_code->build_cfg(false);
type_inference::TypeInference type_inf(ir_code->cfg());
type_inf.run(dex_method);
std::unordered_map<const IRInstruction*, type_inference::TypeEnvironment>&
type_envs = type_inf.get_type_environments();
boost::sub_range<IRList> param_instructions =
ir_code->get_param_instructions();
auto ir_it = param_instructions.begin();
// Emit local variables for every param
for (; ir_it != param_instructions.end(); ++ir_it) {
if (ir_it->type == MethodItemType::MFLOW_OPCODE) {
type_inf.analyze_instruction(ir_it->insn, &type_envs.at(ir_it->insn));
if (ir_it->insn->has_dest()) {
inject_register(ir_code, ir_it, type_envs.at(ir_it->insn),
ir_it->insn->dest());
}
}
}
for (; ir_it != ir_code->end(); ++ir_it) {
switch (ir_it->type) {
case MethodItemType::MFLOW_OPCODE: {
ir_code->insert_before(
ir_it, std::make_unique<DexPosition>(dex_method->get_name(),
dex_method->get_name(),
*line_start));
++(*line_start);
// Make debugger stop at every instruction, and provide local variables to
// debug each instruction's source and destination registers
type_inf.analyze_instruction(ir_it->insn, &type_envs.at(ir_it->insn));
for (reg_t src_reg : ir_it->insn->srcs_vec()) {
inject_register(ir_code, ir_it, type_envs.at(ir_it->insn), src_reg);
}
if (ir_it->insn->has_dest()) {
inject_register(ir_code, ir_it, type_envs.at(ir_it->insn),
ir_it->insn->dest());
}
if (ir_it->insn->has_move_result_pseudo()) {
// Must check dest() of next instruction for the result of current instr
IRList::iterator next_it = std::next(ir_it);
if (next_it->insn->has_dest()) {
type_inf.analyze_instruction(next_it->insn,
&type_envs.at(next_it->insn));
inject_register(ir_code, ir_it, type_envs.at(next_it->insn),
next_it->insn->dest());
}
++ir_it;
}
break;
}
// Remove any previous instances of debug entries
case MethodItemType::MFLOW_DEBUG:
case MethodItemType::MFLOW_POSITION: {
ir_it->type = MethodItemType::MFLOW_FALLTHROUGH;
break;
}
default:
break;
};
}
}
void InjectDebug::inject_all() {
// Use IR instructions to generate dex debug information
for (DexStore& store : m_stores) {
for (DexClasses& classes : store.get_dexen()) {
for (DexClass* dex_class : classes) {
// Line numbers within a single class should be unique so that JDB
// can find a unique location with class name and line number
int line_start = 0;
for (DexMethod* dex_method : dex_class->get_dmethods()) {
inject_method(dex_method, &line_start);
}
for (DexMethod* dex_method : dex_class->get_vmethods()) {
inject_method(dex_method, &line_start);
}
}
}
}
}
void InjectDebug::write_dex() {
std::unique_ptr<PositionMapper> pos_mapper(PositionMapper::make(""));
instruction_lowering::run(m_stores, true);
for (size_t store_num = 0; store_num < m_stores.size(); ++store_num) {
DexStore& store = m_stores[store_num];
for (size_t i = 0; i < store.get_dexen().size(); ++i) {
std::string filename =
redex::get_dex_output_name(m_conf.get_outdir(), store, i);
auto gtypes = std::make_shared<GatheredTypes>(&store.get_dexen()[i]);
DexOutput dout{
filename.c_str(), // filename
&store.get_dexen()[i], // classes
std::move(gtypes),
nullptr, // locator_index
false, // normal_primary_dex
store_num,
nullptr, // store name
i, // dex_number,
DebugInfoKind::BytecodeDebugger,
nullptr, // iodi_metadata
m_conf, // redex options config
pos_mapper.get(), // position_mapper
nullptr, // method_to_id
nullptr, // code_debug_lines
};
dout.prepare(SortMode::DEFAULT, {SortMode::DEFAULT}, m_conf,
m_stores[0].get_dex_magic());
dout.write();
}
}
}