mirror of
https://github.com/Mr-Wiseguy/N64Recomp.git
synced 2024-12-28 02:16:09 +00:00
Implement hook insertion (#73)
* Implement function hook insertion * Fix recompiled code indentation * Add _matherr to renamed_funcs * Replace after_vram by before_vram * Emit dummy value if relocatable_sections_ordered is empty
This commit is contained in:
parent
5c687ee962
commit
6eb7d5bd3e
|
@ -40,6 +40,12 @@ namespace RecompPort {
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct FunctionHook {
|
||||||
|
std::string func_name;
|
||||||
|
int32_t before_vram;
|
||||||
|
std::string text;
|
||||||
|
};
|
||||||
|
|
||||||
struct FunctionSize {
|
struct FunctionSize {
|
||||||
std::string func_name;
|
std::string func_name;
|
||||||
uint32_t size_bytes;
|
uint32_t size_bytes;
|
||||||
|
@ -71,6 +77,7 @@ namespace RecompPort {
|
||||||
std::vector<std::string> ignored_funcs;
|
std::vector<std::string> ignored_funcs;
|
||||||
DeclaredFunctionMap declared_funcs;
|
DeclaredFunctionMap declared_funcs;
|
||||||
std::vector<InstructionPatch> instruction_patches;
|
std::vector<InstructionPatch> instruction_patches;
|
||||||
|
std::vector<FunctionHook> function_hooks;
|
||||||
std::vector<FunctionSize> manual_func_sizes;
|
std::vector<FunctionSize> manual_func_sizes;
|
||||||
std::vector<ManualFunction> manual_functions;
|
std::vector<ManualFunction> manual_functions;
|
||||||
std::string bss_section_suffix;
|
std::string bss_section_suffix;
|
||||||
|
@ -110,6 +117,7 @@ namespace RecompPort {
|
||||||
bool ignored;
|
bool ignored;
|
||||||
bool reimplemented;
|
bool reimplemented;
|
||||||
bool stubbed;
|
bool stubbed;
|
||||||
|
std::unordered_map<int32_t, std::string> function_hooks;
|
||||||
|
|
||||||
Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> words, std::string name, ELFIO::Elf_Half section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false)
|
Function(uint32_t vram, uint32_t rom, std::vector<uint32_t> words, std::string name, ELFIO::Elf_Half section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false)
|
||||||
: vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {}
|
: vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {}
|
||||||
|
|
|
@ -224,6 +224,50 @@ std::vector<RecompPort::InstructionPatch> get_instruction_patches(const toml::ta
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<RecompPort::FunctionHook> get_function_hooks(const toml::table* patches_data) {
|
||||||
|
std::vector<RecompPort::FunctionHook> ret;
|
||||||
|
|
||||||
|
// Check if the function hook array exists.
|
||||||
|
const toml::node_view func_hook_data = (*patches_data)["hook"];
|
||||||
|
|
||||||
|
if (func_hook_data.is_array()) {
|
||||||
|
const toml::array* func_hook_array = func_hook_data.as_array();
|
||||||
|
ret.reserve(func_hook_array->size());
|
||||||
|
|
||||||
|
// Copy all the hooks into the output vector.
|
||||||
|
func_hook_array->for_each([&ret](auto&& el) {
|
||||||
|
if constexpr (toml::is_table<decltype(el)>) {
|
||||||
|
const toml::table& cur_hook = *el.as_table();
|
||||||
|
|
||||||
|
// Get the vram and make sure it's 4-byte aligned.
|
||||||
|
std::optional<uint32_t> before_vram = cur_hook["before_vram"].value<uint32_t>();
|
||||||
|
std::optional<std::string> func_name = cur_hook["func"].value<std::string>();
|
||||||
|
std::optional<std::string> text = cur_hook["text"].value<std::string>();
|
||||||
|
|
||||||
|
if (!func_name.has_value() || !text.has_value()) {
|
||||||
|
throw toml::parse_error("Function hook is missing required value(s)", el.source());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (before_vram.has_value() && before_vram.value() & 0b11) {
|
||||||
|
// Not properly aligned, so throw an error (and make it look like a normal toml one).
|
||||||
|
throw toml::parse_error("before_vram is not word-aligned", el.source());
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.push_back(RecompPort::FunctionHook{
|
||||||
|
.func_name = func_name.value(),
|
||||||
|
.before_vram = before_vram.has_value() ? (int32_t)before_vram.value() : 0,
|
||||||
|
.text = text.value(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw toml::parse_error("Invalid function hook entry", el.source());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) {
|
std::filesystem::path concat_if_not_empty(const std::filesystem::path& parent, const std::filesystem::path& child) {
|
||||||
if (!child.empty()) {
|
if (!child.empty()) {
|
||||||
return parent / child;
|
return parent / child;
|
||||||
|
@ -348,6 +392,9 @@ RecompPort::Config::Config(const char* path) {
|
||||||
|
|
||||||
// Manual function sizes (optional)
|
// Manual function sizes (optional)
|
||||||
manual_func_sizes = get_func_sizes(table);
|
manual_func_sizes = get_func_sizes(table);
|
||||||
|
|
||||||
|
// Fonction hooks (optional)
|
||||||
|
function_hooks = get_function_hooks(table);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const toml::parse_error& err) {
|
catch (const toml::parse_error& err) {
|
||||||
|
|
40
src/main.cpp
40
src/main.cpp
|
@ -628,6 +628,7 @@ std::unordered_set<std::string> renamed_funcs{
|
||||||
"div64_64",
|
"div64_64",
|
||||||
"div64_32",
|
"div64_32",
|
||||||
"__moddi3",
|
"__moddi3",
|
||||||
|
"_matherr",
|
||||||
};
|
};
|
||||||
|
|
||||||
bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, uint32_t entrypoint, bool has_entrypoint, bool use_absolute_symbols) {
|
bool read_symbols(RecompPort::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, uint32_t entrypoint, bool has_entrypoint, bool use_absolute_symbols) {
|
||||||
|
@ -1499,6 +1500,41 @@ int main(int argc, char** argv) {
|
||||||
func.words[instruction_index] = byteswap(patch.value);
|
func.words[instruction_index] = byteswap(patch.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply any function hooks.
|
||||||
|
for (const RecompPort::FunctionHook& patch : config.function_hooks) {
|
||||||
|
// Check if the specified function exists.
|
||||||
|
auto func_find = context.functions_by_name.find(patch.func_name);
|
||||||
|
if (func_find == context.functions_by_name.end()) {
|
||||||
|
// Function doesn't exist, present an error to the user instead of silently failing to stub it out.
|
||||||
|
// This helps prevent typos in the config file or functions renamed between versions from causing issues.
|
||||||
|
exit_failure(fmt::format("Function {} has a function hook but does not exist!", patch.func_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
RecompPort::Function& func = context.functions[func_find->second];
|
||||||
|
int32_t func_vram = func.vram;
|
||||||
|
|
||||||
|
// Check that the function actually contains this vram address.
|
||||||
|
if (patch.before_vram < func_vram || patch.before_vram >= func_vram + func.words.size() * sizeof(func.words[0])) {
|
||||||
|
exit_failure(fmt::format("Function {} has a function hook for vram 0x{:08X} but doesn't contain that vram address!", patch.func_name, (uint32_t)patch.before_vram));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No after_vram means this will be placed at the start of the function
|
||||||
|
size_t instruction_index = -1;
|
||||||
|
|
||||||
|
// Calculate the instruction index.
|
||||||
|
if (patch.before_vram != 0) {
|
||||||
|
instruction_index = (static_cast<size_t>(patch.before_vram) - func_vram) / sizeof(uint32_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a function hook already exits for that instruction index.
|
||||||
|
auto hook_find = func.function_hooks.find(instruction_index);
|
||||||
|
if (hook_find != func.function_hooks.end()) {
|
||||||
|
exit_failure(fmt::format("Function {} already has a function hook for vram 0x{:08X}!", patch.func_name, (uint32_t)patch.before_vram));
|
||||||
|
}
|
||||||
|
|
||||||
|
func.function_hooks[instruction_index] = patch.text;
|
||||||
|
}
|
||||||
|
|
||||||
std::ofstream single_output_file;
|
std::ofstream single_output_file;
|
||||||
|
|
||||||
if (config.single_file_output) {
|
if (config.single_file_output) {
|
||||||
|
@ -1700,6 +1736,9 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
|
||||||
fmt::print(overlay_file, "static int overlay_sections_by_index[] = {{\n");
|
fmt::print(overlay_file, "static int overlay_sections_by_index[] = {{\n");
|
||||||
|
if (relocatable_sections_ordered.empty()) {
|
||||||
|
fmt::print(overlay_file, " -1,\n");
|
||||||
|
} else {
|
||||||
for (const std::string& section : relocatable_sections_ordered) {
|
for (const std::string& section : relocatable_sections_ordered) {
|
||||||
// Check if this is an empty overlay
|
// Check if this is an empty overlay
|
||||||
if (section == "*") {
|
if (section == "*") {
|
||||||
|
@ -1714,6 +1753,7 @@ int main(int argc, char** argv) {
|
||||||
fmt::print(overlay_file, " {},\n", relocatable_section_indices[section]);
|
fmt::print(overlay_file, " {},\n", relocatable_section_indices[section]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fmt::print(overlay_file, "}};\n");
|
fmt::print(overlay_file, "}};\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,23 +24,33 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
const auto& instr = instructions[instr_index];
|
const auto& instr = instructions[instr_index];
|
||||||
needs_link_branch = false;
|
needs_link_branch = false;
|
||||||
is_branch_likely = false;
|
is_branch_likely = false;
|
||||||
|
uint32_t instr_vram = instr.getVram();
|
||||||
|
|
||||||
|
auto print_indent = [&]() {
|
||||||
|
fmt::print(output_file, " ");
|
||||||
|
};
|
||||||
|
|
||||||
|
auto hook_find = func.function_hooks.find(instr_index);
|
||||||
|
if (hook_find != func.function_hooks.end()) {
|
||||||
|
fmt::print(output_file, " {}\n", hook_find->second);
|
||||||
|
if (indent) {
|
||||||
|
print_indent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Output a comment with the original instruction
|
// Output a comment with the original instruction
|
||||||
if (instr.isBranch() || instr.getUniqueId() == InstrId::cpu_j) {
|
if (instr.isBranch() || instr.getUniqueId() == InstrId::cpu_j) {
|
||||||
fmt::print(output_file, " // {}\n", instr.disassemble(0, fmt::format("L_{:08X}", (uint32_t)instr.getBranchVramGeneric())));
|
fmt::print(output_file, " // 0x{:08X}: {}\n", instr_vram, instr.disassemble(0, fmt::format("L_{:08X}", (uint32_t)instr.getBranchVramGeneric())));
|
||||||
} else if (instr.getUniqueId() == InstrId::cpu_jal) {
|
} else if (instr.getUniqueId() == InstrId::cpu_jal) {
|
||||||
fmt::print(output_file, " // {}\n", instr.disassemble(0, fmt::format("0x{:08X}", (uint32_t)instr.getBranchVramGeneric())));
|
fmt::print(output_file, " // 0x{:08X}: {}\n", instr_vram, instr.disassemble(0, fmt::format("0x{:08X}", (uint32_t)instr.getBranchVramGeneric())));
|
||||||
} else {
|
} else {
|
||||||
fmt::print(output_file, " // {}\n", instr.disassemble(0));
|
fmt::print(output_file, " // 0x{:08X}: {}\n", instr_vram, instr.disassemble(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t instr_vram = instr.getVram();
|
|
||||||
|
|
||||||
if (skipped_insns.contains(instr_vram)) {
|
if (skipped_insns.contains(instr_vram)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool at_reloc = false;
|
bool at_reloc = false;
|
||||||
bool reloc_handled = false;
|
bool reloc_handled = false;
|
||||||
RecompPort::RelocType reloc_type = RecompPort::RelocType::R_MIPS_NONE;
|
RecompPort::RelocType reloc_type = RecompPort::RelocType::R_MIPS_NONE;
|
||||||
|
@ -71,10 +81,6 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto print_indent = [&]() {
|
|
||||||
fmt::print(output_file, " ");
|
|
||||||
};
|
|
||||||
|
|
||||||
auto print_line = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
|
auto print_line = [&]<typename... Ts>(fmt::format_string<Ts...> fmt_str, Ts ...args) {
|
||||||
print_indent();
|
print_indent();
|
||||||
fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...));
|
fmt::vprint(output_file, fmt_str, fmt::make_format_args(args...));
|
||||||
|
@ -106,7 +112,7 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto print_func_call = [&](uint32_t target_func_vram, bool link_branch = true) {
|
auto print_func_call = [&](uint32_t target_func_vram, bool link_branch = true, bool indent = false) {
|
||||||
const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram);
|
const auto matching_funcs_find = context.functions_by_vram.find(target_func_vram);
|
||||||
std::string jal_target_name;
|
std::string jal_target_name;
|
||||||
uint32_t section_vram_start = section.ram_addr;
|
uint32_t section_vram_start = section.ram_addr;
|
||||||
|
@ -173,7 +179,11 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
needs_link_branch = link_branch;
|
needs_link_branch = link_branch;
|
||||||
|
if (indent) {
|
||||||
print_unconditional_branch(" {}(rdram, ctx)", jal_target_name);
|
print_unconditional_branch(" {}(rdram, ctx)", jal_target_name);
|
||||||
|
} else {
|
||||||
|
print_unconditional_branch("{}(rdram, ctx)", jal_target_name);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -183,9 +193,9 @@ bool process_instruction(const RecompPort::Context& context, const RecompPort::C
|
||||||
if (context.functions_by_vram.find(branch_target) != context.functions_by_vram.end()) {
|
if (context.functions_by_vram.find(branch_target) != context.functions_by_vram.end()) {
|
||||||
fmt::print(output_file, "{{\n ");
|
fmt::print(output_file, "{{\n ");
|
||||||
fmt::print("Tail call in {} to 0x{:08X}\n", func.name, branch_target);
|
fmt::print("Tail call in {} to 0x{:08X}\n", func.name, branch_target);
|
||||||
print_func_call(branch_target, false);
|
print_func_call(branch_target, false, true);
|
||||||
print_line(" return");
|
print_line(" return");
|
||||||
fmt::print(output_file, ";\n }}\n");
|
fmt::print(output_file, " }}\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1112,6 +1122,11 @@ bool RecompPort::recompile_function(const RecompPort::Context& context, const Re
|
||||||
std::set<uint32_t> branch_labels;
|
std::set<uint32_t> branch_labels;
|
||||||
instructions.reserve(func.words.size());
|
instructions.reserve(func.words.size());
|
||||||
|
|
||||||
|
auto hook_find = func.function_hooks.find(-1);
|
||||||
|
if (hook_find != func.function_hooks.end()) {
|
||||||
|
fmt::print(output_file, " {}\n", hook_find->second);
|
||||||
|
}
|
||||||
|
|
||||||
// First pass, disassemble each instruction and collect branch labels
|
// First pass, disassemble each instruction and collect branch labels
|
||||||
uint32_t vram = func.vram;
|
uint32_t vram = func.vram;
|
||||||
for (uint32_t word : func.words) {
|
for (uint32_t word : func.words) {
|
||||||
|
|
Loading…
Reference in a new issue