Compare commits

...

13 commits

Author SHA1 Message Date
dcvz
c39a9b6c7e
Enable ARM64 Linux CI checks (#157)
Some checks failed
validate / blaze/ubuntu-22.04 (arm64, Debug) (push) Has been cancelled
validate / blaze/ubuntu-22.04 (arm64, Release) (push) Has been cancelled
validate / macos-13 (x64, Debug) (push) Has been cancelled
validate / macos-13 (x64, Release) (push) Has been cancelled
validate / macos-14 (arm64, Debug) (push) Has been cancelled
validate / macos-14 (arm64, Release) (push) Has been cancelled
validate / ubuntu-latest (x64, Debug) (push) Has been cancelled
validate / ubuntu-latest (x64, Release) (push) Has been cancelled
validate / windows-latest (x64, Debug) (push) Has been cancelled
validate / windows-latest (x64, Release) (push) Has been cancelled
2025-10-07 21:00:01 -04:00
Ethan Lafrenais
6888c1f1df
Update fmt to 11.1.0 to fix Clang 20 compile error (#154)
Some checks failed
validate / macos-13 (x64, Debug) (push) Has been cancelled
validate / macos-13 (x64, Release) (push) Has been cancelled
validate / macos-14 (arm64, Debug) (push) Has been cancelled
validate / macos-14 (arm64, Release) (push) Has been cancelled
validate / ubuntu-latest (x64, Debug) (push) Has been cancelled
validate / ubuntu-latest (x64, Release) (push) Has been cancelled
validate / windows-latest (x64, Debug) (push) Has been cancelled
validate / windows-latest (x64, Release) (push) Has been cancelled
2025-09-06 20:27:17 -04:00
Wiseguy
afc2ff93a5
Implement mdebug parsing for static symbols in IDO elfs (#155)
Co-authored-by: Tharo <17233964+Thar0@users.noreply.github.com>
2025-09-06 18:44:18 -04:00
Wiseguy
a49c51b37f
Temporarily disable arm runner due to URL change (#156) 2025-09-06 18:40:04 -04:00
Mr-Wiseguy
6db56f597d Fix bug with patch recompilation when a non-relocatable reference section has the same index as the patch elf's event section
Some checks failed
validate / blaze/ubuntu-22.04 (arm64, Debug) (push) Has been cancelled
validate / blaze/ubuntu-22.04 (arm64, Release) (push) Has been cancelled
validate / macos-13 (x64, Debug) (push) Has been cancelled
validate / macos-13 (x64, Release) (push) Has been cancelled
validate / macos-14 (arm64, Debug) (push) Has been cancelled
validate / macos-14 (arm64, Release) (push) Has been cancelled
validate / ubuntu-latest (x64, Debug) (push) Has been cancelled
validate / ubuntu-latest (x64, Release) (push) Has been cancelled
validate / windows-latest (x64, Debug) (push) Has been cancelled
validate / windows-latest (x64, Release) (push) Has been cancelled
2025-08-28 22:53:48 -04:00
Mr-Wiseguy
a13e5cff96 Add missing osSetTime and osSetCount functions to recompiler symbol lists
Some checks failed
validate / macos-13 (x64, Release) (push) Has been cancelled
validate / macos-14 (arm64, Debug) (push) Has been cancelled
validate / macos-14 (arm64, Release) (push) Has been cancelled
validate / ubuntu-latest (x64, Debug) (push) Has been cancelled
validate / ubuntu-latest (x64, Release) (push) Has been cancelled
validate / blaze/ubuntu-22.04 (arm64, Debug) (push) Has been cancelled
validate / blaze/ubuntu-22.04 (arm64, Release) (push) Has been cancelled
validate / macos-13 (x64, Debug) (push) Has been cancelled
validate / windows-latest (x64, Debug) (push) Has been cancelled
validate / windows-latest (x64, Release) (push) Has been cancelled
2025-08-11 01:55:39 -04:00
Mr-Wiseguy
facc807049 Fix out of bounds exception in recompilation with special sections in function call relocs 2025-08-11 01:54:36 -04:00
Wiseguy
c1a6dc93bf
Implement fixed address and globally loaded sections in mods, respect section index in mod self-section relocs (#150)
Some checks failed
validate / blaze/ubuntu-22.04 (arm64, Debug) (push) Has been cancelled
validate / blaze/ubuntu-22.04 (arm64, Release) (push) Has been cancelled
validate / macos-13 (x64, Debug) (push) Has been cancelled
validate / macos-13 (x64, Release) (push) Has been cancelled
validate / macos-14 (arm64, Debug) (push) Has been cancelled
validate / macos-14 (arm64, Release) (push) Has been cancelled
validate / ubuntu-latest (x64, Debug) (push) Has been cancelled
validate / ubuntu-latest (x64, Release) (push) Has been cancelled
validate / windows-latest (x64, Debug) (push) Has been cancelled
validate / windows-latest (x64, Release) (push) Has been cancelled
2025-07-19 03:39:41 -04:00
Tharo
6860826da3
Implement remaining float comparisons in the op table (ordered/unordered and signaling/non-signaling still unimplemented) (#144)
Some checks failed
validate / blaze/ubuntu-22.04 (arm64, Debug) (push) Has been cancelled
validate / blaze/ubuntu-22.04 (arm64, Release) (push) Has been cancelled
validate / macos-13 (x64, Debug) (push) Has been cancelled
validate / macos-13 (x64, Release) (push) Has been cancelled
validate / macos-14 (arm64, Debug) (push) Has been cancelled
validate / macos-14 (arm64, Release) (push) Has been cancelled
validate / ubuntu-latest (x64, Debug) (push) Has been cancelled
validate / ubuntu-latest (x64, Release) (push) Has been cancelled
validate / windows-latest (x64, Debug) (push) Has been cancelled
validate / windows-latest (x64, Release) (push) Has been cancelled
2025-07-17 22:50:15 -04:00
Wiseguy
7b8b3c1920
Implement remaining odd float operations in live recompiler (#149) 2025-07-17 22:44:49 -04:00
Rainchus
6e7a5bdb2f
Add dmtc1 and dmfc1 functionality to the recompiler (#134)
Some checks failed
validate / blaze/ubuntu-22.04 (arm64, Debug) (push) Has been cancelled
validate / blaze/ubuntu-22.04 (arm64, Release) (push) Has been cancelled
validate / macos-13 (x64, Debug) (push) Has been cancelled
validate / macos-13 (x64, Release) (push) Has been cancelled
validate / macos-14 (arm64, Debug) (push) Has been cancelled
validate / macos-14 (arm64, Release) (push) Has been cancelled
validate / ubuntu-latest (x64, Debug) (push) Has been cancelled
validate / ubuntu-latest (x64, Release) (push) Has been cancelled
validate / windows-latest (x64, Debug) (push) Has been cancelled
validate / windows-latest (x64, Release) (push) Has been cancelled
* make osPiReadIo no longer ignored

* remove added dmtc1/dmfc1 functionality

* add dmtc1 and dmfc1 to recompiler
2025-07-07 01:55:15 -04:00
MelonSpeedruns
e76668356b
Fixed paths with spaces not being able to Compress-Archive properly. (#141)
* Fixed paths with spaces not being able to compress properly.

Needs testing on Linux and Mac!

* Fixed path for additional files
2025-07-07 01:53:02 -04:00
Wiseguy
3531bc0317
Optional dependencies for mod tool and add dependency name vector in recompiler context (#147) 2025-07-07 01:52:18 -04:00
15 changed files with 1386 additions and 66 deletions

View file

@ -98,6 +98,7 @@ add_library(N64RecompElf)
target_sources(N64RecompElf PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/elf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/mdebug.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/symbol_lists.cpp
)

View file

@ -238,15 +238,27 @@ constexpr int get_fpr_double_context_offset(int fpr_index) {
return offsetof(recomp_context, f0.d) + sizeof(recomp_context::f0) * fpr_index;
}
constexpr int get_fpr_u32l_context_offset(int fpr_index) {
constexpr bool is_fpr_u32l(N64Recomp::Operand operand) {
return
operand == N64Recomp::Operand::FdU32L ||
operand == N64Recomp::Operand::FsU32L ||
operand == N64Recomp::Operand::FtU32L;
return false;
}
constexpr void get_fpr_u32l_context_offset(int fpr_index, sljit_compiler* compiler, int odd_float_address_register, sljit_sw& out, sljit_sw& outw) {
if (fpr_index & 1) {
// TODO implement odd floats.
assert(false);
return -1;
// return fmt::format("ctx->f_odd[({} - 1) * 2]", fpr_index);
assert(compiler != nullptr);
// Load ctx->f_odd into the address register.
sljit_emit_op1(compiler, SLJIT_MOV_P, odd_float_address_register, 0, SLJIT_MEM1(Registers::ctx), offsetof(recomp_context, f_odd));
// sljit_emit_op0(compiler, SLJIT_BREAKPOINT);
out = SLJIT_MEM1(odd_float_address_register);
// Set a memory offset of ((fpr_index - 1) * 2) * sizeof(*f_odd).
outw = ((fpr_index - 1) * 2) * sizeof(*recomp_context::f_odd);
}
else {
return offsetof(recomp_context, f0.u32l) + sizeof(recomp_context::f0) * fpr_index;
out = SLJIT_MEM1(Registers::ctx);
outw = offsetof(recomp_context, f0.u32l) + sizeof(recomp_context::f0) * fpr_index;
}
}
@ -265,7 +277,10 @@ void get_gpr_values(int gpr, sljit_sw& out, sljit_sw& outw) {
}
}
bool get_operand_values(N64Recomp::Operand operand, const N64Recomp::InstructionContext& context, sljit_sw& out, sljit_sw& outw) {
bool get_operand_values(N64Recomp::Operand operand, const N64Recomp::InstructionContext& context, sljit_sw& out, sljit_sw& outw,
sljit_compiler* compiler, int odd_float_address_register
)
{
using namespace N64Recomp;
switch (operand) {
@ -303,16 +318,13 @@ bool get_operand_values(N64Recomp::Operand operand, const N64Recomp::Instruction
outw = get_fpr_double_context_offset(context.ft);
break;
case Operand::FdU32L:
out = SLJIT_MEM1(Registers::ctx);
outw = get_fpr_u32l_context_offset(context.fd);
get_fpr_u32l_context_offset(context.fd, compiler, odd_float_address_register, out, outw);
break;
case Operand::FsU32L:
out = SLJIT_MEM1(Registers::ctx);
outw = get_fpr_u32l_context_offset(context.fs);
get_fpr_u32l_context_offset(context.fs, compiler, odd_float_address_register, out, outw);
break;
case Operand::FtU32L:
out = SLJIT_MEM1(Registers::ctx);
outw = get_fpr_u32l_context_offset(context.ft);
get_fpr_u32l_context_offset(context.ft, compiler, odd_float_address_register, out, outw);
break;
case Operand::FdU32H:
assert(false);
@ -389,16 +401,30 @@ void N64Recomp::LiveGenerator::process_binary_op(const BinaryOp& op, const Instr
if (outputs_to_zero(op.output, ctx)) {
return;
}
// Float u32l input operands are not allowed in a binary operation.
if (is_fpr_u32l(op.operands.operands[0]) || is_fpr_u32l(op.operands.operands[1])) {
assert(false);
errored = true;
return;
}
// A float u32l output operand is only allowed for lwc1, which has an op type of LW.
if (is_fpr_u32l(op.output) && op.type != BinaryOpType::LW) {
assert(false);
errored = true;
return;
}
sljit_sw dst;
sljit_sw dstw;
sljit_sw src1;
sljit_sw src1w;
sljit_sw src2;
sljit_sw src2w;
bool output_good = get_operand_values(op.output, ctx, dst, dstw);
bool input0_good = get_operand_values(op.operands.operands[0], ctx, src1, src1w);
bool input1_good = get_operand_values(op.operands.operands[1], ctx, src2, src2w);
bool output_good = get_operand_values(op.output, ctx, dst, dstw, compiler, Registers::arithmetic_temp2);
bool input0_good = get_operand_values(op.operands.operands[0], ctx, src1, src1w, nullptr, 0);
bool input1_good = get_operand_values(op.operands.operands[1], ctx, src2, src2w, nullptr, 0);
if (!output_good || !input0_good || !input1_good) {
assert(false);
@ -748,6 +774,10 @@ void N64Recomp::LiveGenerator::process_binary_op(const BinaryOp& op, const Instr
case BinaryOpType::LessEqDouble:
do_float_compare_op(SLJIT_F_LESS_EQUAL, SLJIT_SET_F_LESS_EQUAL, true);
break;
case BinaryOpType::False:
// Load 0 into condition destination
sljit_emit_op1(compiler, SLJIT_MOV, dst, dstw, SLJIT_IMM, 0);
break;
// Loads
case BinaryOpType::LD:
@ -866,12 +896,19 @@ void N64Recomp::LiveGenerator::process_unary_op(const UnaryOp& op, const Instruc
return;
}
// A unary op may have a float u32l as the source or destination, but not both.
if (is_fpr_u32l(op.input) && is_fpr_u32l(op.output)) {
assert(false);
errored = true;
return;
}
sljit_sw dst;
sljit_sw dstw;
sljit_sw src;
sljit_sw srcw;
bool output_good = get_operand_values(op.output, ctx, dst, dstw);
bool input_good = get_operand_values(op.input, ctx, src, srcw);
bool output_good = get_operand_values(op.output, ctx, dst, dstw, compiler, Registers::arithmetic_temp3);
bool input_good = get_operand_values(op.input, ctx, src, srcw, compiler, Registers::arithmetic_temp3);
if (!output_good || !input_good) {
assert(false);
@ -1089,7 +1126,13 @@ void N64Recomp::LiveGenerator::process_unary_op(const UnaryOp& op, const Instruc
emit_l_from_d_func(do_floor_l_d);
break;
case UnaryOpType::None:
jit_op = SLJIT_MOV;
// Only write 32 bits to the output is a fpr u32l operand.
if (is_fpr_u32l(op.output)) {
jit_op = SLJIT_MOV32;
}
else {
jit_op = SLJIT_MOV;
}
break;
case UnaryOpType::ToS32:
case UnaryOpType::ToInt32:
@ -1128,7 +1171,7 @@ void N64Recomp::LiveGenerator::process_store_op(const StoreOp& op, const Instruc
sljit_sw srcw;
sljit_sw imm = (sljit_sw)(int16_t)ctx.imm16;
get_operand_values(op.value_input, ctx, src, srcw);
get_operand_values(op.value_input, ctx, src, srcw, compiler, Registers::arithmetic_temp2);
// Only LO16 relocs are valid on stores.
if (ctx.reloc_type != RelocType::R_MIPS_NONE && ctx.reloc_type != RelocType::R_MIPS_LO16) {
@ -1456,6 +1499,13 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
return;
}
// Branch conditions do not allow float u32l operands.
if (is_fpr_u32l(op.operands.operands[0]) || is_fpr_u32l(op.operands.operands[1])) {
assert(false);
errored = true;
return;
}
sljit_s32 condition_type;
bool cmp_signed = op.operands.operand_operations[0] == UnaryOpType::ToS64;
// Comparisons need to be inverted to account for the fact that the generator is expected to generate a code block that only runs if
@ -1509,8 +1559,8 @@ void N64Recomp::LiveGenerator::emit_branch_condition(const ConditionalBranchOp&
sljit_sw src2;
sljit_sw src2w;
get_operand_values(op.operands.operands[0], ctx, src1, src1w);
get_operand_values(op.operands.operands[1], ctx, src2, src2w);
get_operand_values(op.operands.operands[0], ctx, src1, src1w, nullptr, 0);
get_operand_values(op.operands.operands[1], ctx, src2, src2w, nullptr, 0);
// Relocations aren't valid on conditional branches.
if(ctx.reloc_type != RelocType::R_MIPS_NONE) {

View file

@ -35,6 +35,8 @@ struct ModManifest {
std::vector<toml::table> config_options;
std::vector<std::string> dependencies;
std::vector<std::string> full_dependency_strings;
std::vector<std::string> optional_dependencies;
std::vector<std::string> full_optional_dependency_strings;
};
struct ModInputs {
@ -323,6 +325,31 @@ ModManifest parse_mod_config_manifest(const std::filesystem::path& basedir, cons
});
}
// Optional dependency list (optional)
const toml::array& optional_dependency_array = read_toml_array(manifest_table, "optional_dependencies", false);
if (!optional_dependency_array.empty()) {
// Reserve room for all the dependencies.
ret.dependencies.reserve(optional_dependency_array.size());
optional_dependency_array.for_each([&ret](const auto& el) {
if constexpr (toml::is_string<decltype(el)>) {
size_t dependency_id_length;
bool dependency_version_has_label;
if (!validate_dependency_string(el.template ref<std::string>(), dependency_id_length, dependency_version_has_label)) {
throw toml::parse_error("Invalid optional dependency entry", el.source());
}
if (dependency_version_has_label) {
throw toml::parse_error("Dependency versions may not have labels", el.source());
}
std::string dependency_id = el.template ref<std::string>().substr(0, dependency_id_length);
ret.optional_dependencies.emplace_back(dependency_id);
ret.full_optional_dependency_strings.emplace_back(el.template ref<std::string>());
}
else {
throw toml::parse_error("Invalid type for optional dependency entry", el.source());
}
});
}
// Config schema (optional)
const toml::array& config_options_array = read_toml_array(manifest_table, "config_options", false);
if (!config_options_array.empty()) {
@ -507,6 +534,10 @@ void write_manifest(const std::filesystem::path& path, const ModManifest& manife
output_data.emplace("dependencies", string_vector_to_toml(manifest.full_dependency_strings));
}
if (!manifest.full_optional_dependency_strings.empty()) {
output_data.emplace("optional_dependencies", string_vector_to_toml(manifest.full_optional_dependency_strings));
}
if (!manifest.config_options.empty()) {
toml::array options_array{};
for (const auto& option : manifest.config_options) {
@ -974,11 +1005,11 @@ bool create_mod_zip(const std::filesystem::path& output_dir, const ModConfig& co
#ifdef _WIN32
std::filesystem::path temp_zip_path = output_path;
temp_zip_path.replace_extension(".zip");
std::string command_string = fmt::format("powershell -command Compress-Archive -Force -CompressionLevel Optimal -DestinationPath \"{}\" -Path \"{}\",\"{}\",\"{}\"",
std::string command_string = fmt::format("powershell -command Compress-Archive -Force -CompressionLevel Optimal -DestinationPath '{}' -Path '{}','{}','{}'",
temp_zip_path.string(), (output_dir / symbol_filename).string(), (output_dir / binary_filename).string(), (output_dir / manifest_filename).string());
for (const auto& cur_file : config.inputs.additional_files) {
command_string += fmt::format(",\"{}\"", cur_file.string());
command_string += fmt::format(",'{}'", cur_file.string());
}
STARTUPINFOA si{};
@ -1138,8 +1169,9 @@ int main(int argc, const char** argv) {
}
}
// Copy the dependencies from the config into the context.
// Copy the dependencies and optional dependencies from the config into the context.
context.add_dependencies(config.manifest.dependencies);
context.add_dependencies(config.manifest.optional_dependencies);
N64Recomp::ElfParsingConfig elf_config {
.bss_section_suffix = {},
@ -1149,7 +1181,8 @@ int main(int argc, const char** argv) {
.entrypoint_address = 0,
.use_absolute_symbols = false,
.unpaired_lo16_warnings = false,
.all_sections_relocatable = true
.all_sections_relocatable = true,
.use_mdebug = false,
};
bool dummy_found_entrypoint;
N64Recomp::DataSymbolMap dummy_syms_map;

View file

@ -104,6 +104,8 @@ namespace N64Recomp {
bool executable = false;
bool relocatable = false; // TODO is this needed? relocs being non-empty should be an equivalent check.
bool has_mips32_relocs = false;
bool fixed_address = false; // Only used in mods, indicates that the section shouldn't be relocated or placed into mod memory.
bool globally_loaded = false; // Only used in mods, indicates that the section's functions should be globally loaded. Does not actually load the section's contents into ram.
std::optional<uint32_t> got_ram_addr = std::nullopt;
};
@ -127,11 +129,19 @@ namespace N64Recomp {
std::unordered_map<std::string, size_t> manually_sized_funcs;
// The section names that were specified as relocatable
std::unordered_set<std::string> relocatable_sections;
// Symbols to ignore.
std::unordered_set<std::string> ignored_syms;
// Manual mappings of mdebug file records to elf sections.
std::unordered_map<std::string, std::string> mdebug_text_map;
std::unordered_map<std::string, std::string> mdebug_data_map;
std::unordered_map<std::string, std::string> mdebug_rodata_map;
std::unordered_map<std::string, std::string> mdebug_bss_map;
bool has_entrypoint;
int32_t entrypoint_address;
bool use_absolute_symbols;
bool unpaired_lo16_warnings;
bool all_sections_relocatable;
bool use_mdebug;
};
struct DataSymbol {
@ -233,6 +243,8 @@ namespace N64Recomp {
//// Mod dependencies and their symbols
//// Imported values
// Dependency names.
std::vector<std::string> dependencies;
// Mapping of dependency name to dependency index.
std::unordered_map<std::string, size_t> dependencies_by_name;
// List of symbols imported from dependencies.
@ -276,6 +288,7 @@ namespace N64Recomp {
size_t dependency_index = dependencies_by_name.size();
dependencies.emplace_back(id);
dependencies_by_name.emplace(id, dependency_index);
dependency_events_by_name.resize(dependencies_by_name.size());
dependency_imports_by_name.resize(dependencies_by_name.size());
@ -295,6 +308,7 @@ namespace N64Recomp {
for (const std::string& dep : new_dependencies) {
size_t dependency_index = dependencies_by_name.size();
dependencies.emplace_back(dep);
dependencies_by_name.emplace(dep, dependency_index);
}

@ -1 +1 @@
Subproject commit 8e728044f673774160f43b44a07c6b185352310f
Subproject commit 0e8aad961d66904cfda8d7cc894f6f6eee2d9f30

View file

@ -245,6 +245,46 @@ std::vector<N64Recomp::FunctionTextHook> get_function_hooks(const toml::table* p
return ret;
}
void get_mdebug_mappings(const toml::array* mdebug_mappings_array,
std::unordered_map<std::string, std::string>& mdebug_text_map,
std::unordered_map<std::string, std::string>& mdebug_data_map,
std::unordered_map<std::string, std::string>& mdebug_rodata_map,
std::unordered_map<std::string, std::string>& mdebug_bss_map
) {
mdebug_mappings_array->for_each([&mdebug_text_map, &mdebug_data_map, &mdebug_rodata_map, &mdebug_bss_map](auto&& el) {
if constexpr (toml::is_table<decltype(el)>) {
std::optional<std::string> filename = el["filename"].template value<std::string>();
std::optional<std::string> input_section = el["input_section"].template value<std::string>();
std::optional<std::string> output_section = el["output_section"].template value<std::string>();
if (filename.has_value() && input_section.has_value() && output_section.has_value()) {
const std::string& input_section_val = input_section.value();
if (input_section_val == ".text") {
mdebug_text_map.emplace(filename.value(), output_section.value());
}
else if (input_section_val == ".data") {
mdebug_data_map.emplace(filename.value(), output_section.value());
}
else if (input_section_val == ".rodata") {
mdebug_rodata_map.emplace(filename.value(), output_section.value());
}
else if (input_section_val == ".bss") {
mdebug_bss_map.emplace(filename.value(), output_section.value());
}
else {
throw toml::parse_error("Invalid input section in mdebug file mapping entry", el.source());
}
}
else {
throw toml::parse_error("Mdebug file mappings entry is missing required value(s)", el.source());
}
}
else {
throw toml::parse_error("Invalid mdebug file mappings entry", el.source());
}
});
}
N64Recomp::Config::Config(const char* path) {
// Start this config out as bad so that it has to finish parsing without errors to be good.
entrypoint = 0;
@ -368,6 +408,22 @@ N64Recomp::Config::Config(const char* path) {
unpaired_lo16_warnings = true;
}
// Control whether the recompiler should look for and parse mdebug (optional, defaults to false)
std::optional<bool> use_mdebug_opt = input_data["use_mdebug"].value<bool>();
if (use_mdebug_opt.has_value()) {
use_mdebug = use_mdebug_opt.value();
}
else {
use_mdebug = false;
}
// Symbols to ignore when parsing mdebug (option, defaults to empty)
toml::node_view mdebug_mappings_data = input_data["mdebug_file_mappings"];
if (mdebug_mappings_data.is_array()) {
get_mdebug_mappings(mdebug_mappings_data.as_array(),
mdebug_text_map, mdebug_data_map, mdebug_rodata_map, mdebug_bss_map);
}
std::optional<std::string> recomp_include_opt = input_data["recomp_include"].value<std::string>();
if (recomp_include_opt.has_value()) {
recomp_include = recomp_include_opt.value();

View file

@ -4,6 +4,7 @@
#include <cstdint>
#include <filesystem>
#include <vector>
#include <unordered_map>
namespace N64Recomp {
struct InstructionPatch {
@ -42,6 +43,7 @@ namespace N64Recomp {
bool single_file_output;
bool use_absolute_symbols;
bool unpaired_lo16_warnings;
bool use_mdebug;
bool trace_mode;
bool allow_exports;
bool strict_patch_mode;
@ -62,6 +64,11 @@ namespace N64Recomp {
std::vector<ManualFunction> manual_functions;
std::string bss_section_suffix;
std::string recomp_include;
// Manual mappings of mdebug file records to elf sections.
std::unordered_map<std::string, std::string> mdebug_text_map;
std::unordered_map<std::string, std::string> mdebug_data_map;
std::unordered_map<std::string, std::string> mdebug_rodata_map;
std::unordered_map<std::string, std::string> mdebug_bss_map;
Config(const char* path);
bool good() { return !bad; }

View file

@ -6,6 +6,8 @@
#include "recompiler/context.h"
#include "elfio/elfio.hpp"
#include "mdebug.h"
bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELFIO::section* symtab_section, const N64Recomp::ElfParsingConfig& elf_config, bool dumping_context, std::unordered_map<uint16_t, std::vector<N64Recomp::DataSymbol>>& data_syms) {
bool found_entrypoint_func = false;
ELFIO::symbol_section_accessor symbols{ elf_file, symtab_section };
@ -178,10 +180,13 @@ bool read_symbols(N64Recomp::Context& context, const ELFIO::elfio& elf_file, ELF
fmt::print("Symbol \"{}\" not in a valid section ({})\n", name, section_index);
}
// Move this symbol into the corresponding non-bss section if it's in a bss section.
// Move this symbol into the corresponding non-bss section if it's in a bss section and the paired section is relocatable.
auto find_bss_it = bss_section_to_target_section.find(target_section_index);
if (find_bss_it != bss_section_to_target_section.end()) {
target_section_index = find_bss_it->second;
uint16_t new_target_section_index = find_bss_it->second;
if (new_target_section_index < context.sections.size() && context.sections[new_target_section_index].relocatable) {
target_section_index = find_bss_it->second;
}
}
data_syms[target_section_index].emplace_back(
@ -215,7 +220,7 @@ std::optional<size_t> get_segment(const std::vector<SegmentEntry>& segments, ELF
return std::nullopt;
}
ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfParsingConfig& elf_config, const ELFIO::elfio& elf_file) {
ELFIO::section* read_sections(N64Recomp::Context& context, ELFIO::section*& mdebug_section_out, const N64Recomp::ElfParsingConfig& elf_config, const ELFIO::elfio& elf_file) {
ELFIO::section* symtab_section = nullptr;
std::vector<SegmentEntry> segments{};
segments.resize(elf_file.segments.size());
@ -283,6 +288,11 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
symtab_section = section.get();
}
// Check if this section is an mdebug section and record it if so. Note we expect just one mdebug section
if (type == 0x70000005/* SHT_MIPS_DEBUG */) {
mdebug_section_out = section.get();
}
if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(section_name)) {
section_out.relocatable = true;
}
@ -310,10 +320,7 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
if (type == ELFIO::SHT_NOBITS && section_name.ends_with(elf_config.bss_section_suffix)) {
std::string bss_target_section = section_name.substr(0, section_name.size() - elf_config.bss_section_suffix.size());
// If this bss section is for a section that has been marked as relocatable, record it in the reloc section lookup
if (elf_config.all_sections_relocatable || elf_config.relocatable_sections.contains(bss_target_section)) {
bss_sections_by_name[bss_target_section] = section.get();
}
bss_sections_by_name[bss_target_section] = section.get();
}
// If this section was marked as being in the ROM in the previous pass, copy it into the ROM now.
@ -630,14 +637,15 @@ bool N64Recomp::Context::from_elf_file(const std::filesystem::path& elf_file_pat
}
if (elf_file.get_encoding() != ELFIO::ELFDATA2MSB) {
fmt::print("Incorrect endianness\n");
fmt::print("Incorrect elf endianness\n");
return false;
}
setup_context_for_elf(out, elf_file);
// Read all of the sections in the elf and look for the symbol table section
ELFIO::section* symtab_section = read_sections(out, elf_config, elf_file);
ELFIO::section* mdebug_section = nullptr;
ELFIO::section* symtab_section = read_sections(out, mdebug_section, elf_config, elf_file);
// If no symbol table was found then exit
if (symtab_section == nullptr) {
@ -647,5 +655,17 @@ bool N64Recomp::Context::from_elf_file(const std::filesystem::path& elf_file_pat
// Read all of the symbols in the elf and look for the entrypoint function
found_entrypoint_out = read_symbols(out, elf_file, symtab_section, elf_config, for_dumping_context, data_syms_out);
// Process an mdebug section for static symbols. The presence of an mdebug section in the input is optional.
if (elf_config.use_mdebug) {
if (mdebug_section == nullptr) {
fmt::print("\"use_mdebug\" set to true in config, but no mdebug section is present in the elf!\n");
return false;
}
if (!N64Recomp::MDebug::parse_mdebug(elf_config, mdebug_section->get_data(), static_cast<uint32_t>(mdebug_section->get_offset()), out, data_syms_out)) {
fmt::print("Failed to parse mdebug section\n");
return false;
}
}
return true;
}

View file

@ -310,6 +310,9 @@ int main(int argc, char** argv) {
std::unordered_set<std::string> relocatable_sections{};
relocatable_sections.insert(relocatable_sections_ordered.begin(), relocatable_sections_ordered.end());
std::unordered_set<std::string> ignored_syms_set{};
ignored_syms_set.insert(config.ignored_funcs.begin(), config.ignored_funcs.end());
N64Recomp::Context context{};
if (!config.elf_path.empty() && !config.symbols_file_path.empty()) {
@ -347,11 +350,17 @@ int main(int argc, char** argv) {
N64Recomp::ElfParsingConfig elf_config {
.bss_section_suffix = config.bss_section_suffix,
.relocatable_sections = std::move(relocatable_sections),
.ignored_syms = std::move(ignored_syms_set),
.mdebug_text_map = config.mdebug_text_map,
.mdebug_data_map = config.mdebug_data_map,
.mdebug_rodata_map = config.mdebug_rodata_map,
.mdebug_bss_map = config.mdebug_bss_map,
.has_entrypoint = config.has_entrypoint,
.entrypoint_address = config.entrypoint,
.use_absolute_symbols = config.use_absolute_symbols,
.unpaired_lo16_warnings = config.unpaired_lo16_warnings,
.all_sections_relocatable = false,
.use_mdebug = config.use_mdebug,
};
for (const auto& func_size : config.manual_func_sizes) {
@ -359,7 +368,9 @@ int main(int argc, char** argv) {
}
bool found_entrypoint_func;
N64Recomp::Context::from_elf_file(config.elf_path, context, elf_config, dumping_context, data_syms, found_entrypoint_func);
if (!N64Recomp::Context::from_elf_file(config.elf_path, context, elf_config, dumping_context, data_syms, found_entrypoint_func)) {
exit_failure("Failed to parse elf\n");
}
// Add any manual functions
add_manual_functions(context, config.manual_functions);
@ -646,6 +657,11 @@ int main(int argc, char** argv) {
continue;
}
// Ignore R_MIPS_NONE relocs, which get produced during symbol parsing for non-relocatable reference sections.
if (reloc.type == N64Recomp::RelocType::R_MIPS_NONE) {
continue;
}
// Check if the reloc points to the event section.
if (reloc.target_section == event_section_index) {
// It does, so find the function it's pointing at.

670
src/mdebug.cpp Normal file
View file

@ -0,0 +1,670 @@
#include <algorithm>
#include <unordered_map>
#include "fmt/format.h"
#include "mdebug.h"
struct MDebugSymbol {
std::string name;
uint32_t address;
uint32_t size;
bool is_func;
bool is_static;
bool is_bss;
bool is_rodata;
bool ignored;
};
struct MDebugFile {
std::string filename;
std::vector<MDebugSymbol> symbols;
};
class MDebugInfo {
public:
MDebugInfo(const N64Recomp::ElfParsingConfig& config, const char* mdebug_section, uint32_t mdebug_offset) {
using namespace N64Recomp;
good_ = false;
// Read, byteswap and relocate the symbolic header. Relocation here means convert file-relative offsets to section-relative offsets.
MDebug::HDRR hdrr;
std::memcpy(&hdrr, mdebug_section, sizeof(MDebug::HDRR));
hdrr.swap();
hdrr.relocate(mdebug_offset);
// Check the magic value and version number are what we expect.
if (hdrr.magic != MDebug::MAGIC || hdrr.vstamp != 0) {
fmt::print(stderr, "Warning: Found an mdebug section with bad magic value or version (magic={} version={}). Skipping.\n", hdrr.magic, hdrr.vstamp);
return;
}
// Read the various records that are relevant for collecting static symbols and where they are declared.
std::vector<MDebug::FDR> fdrs = hdrr.read_fdrs(mdebug_section);
std::vector<MDebug::AUX> all_auxs = hdrr.read_auxs(mdebug_section);
std::vector<MDebug::PDR> all_pdrs = hdrr.read_pdrs(mdebug_section);
std::vector<MDebug::SYMR> all_symrs = hdrr.read_symrs(mdebug_section);
// For each file descriptor
for (size_t fdr_index = 0; fdr_index < fdrs.size(); fdr_index++) {
MDebug::FDR& fdr = fdrs[fdr_index];
MDebugSymbol pending_sym{};
bool pending_sym_ready = false;
auto flush_pending_sym = [&]() {
if (pending_sym_ready) {
// Handle ignored symbols.
pending_sym.ignored = config.ignored_syms.contains(pending_sym.name);
// Add the symbol.
add_symbol(fdr_index, std::move(pending_sym));
}
pending_sym_ready = false;
};
const char* fdr_name = fdr.get_name(mdebug_section + hdrr.cbSsOffset);
add_file(fdr_name);
// For every symbol record in the file descriptor record.
for (auto symr : fdr.get_symrs(all_symrs)) {
MDebug::ST type = symr.get_st();
MDebug::SC storage_class = symr.get_sc();
switch (type) {
case MDebug::ST_PROC:
case MDebug::ST_STATICPROC:
flush_pending_sym();
if (symr.value != 0) {
pending_sym.name = fdr.get_string(mdebug_section + hdrr.cbSsOffset, symr.iss);
pending_sym.address = symr.value;
pending_sym.size = 0;
pending_sym.is_func = true;
pending_sym.is_static = (type == MDebug::ST_STATICPROC);
pending_sym.is_bss = false;
pending_sym.is_rodata = false;
pending_sym_ready = true;
}
break;
case MDebug::ST_END:
if (pending_sym.is_func) {
pending_sym.size = symr.value;
}
flush_pending_sym();
break;
case MDebug::ST_GLOBAL:
case MDebug::ST_STATIC:
flush_pending_sym();
if (symr.value != 0) {
pending_sym.name = fdr.get_string(mdebug_section + hdrr.cbSsOffset, symr.iss);
pending_sym.address = symr.value;
pending_sym.size = 0;
pending_sym.is_func = false;
pending_sym.is_static = (type == MDebug::ST_STATIC);
pending_sym.is_bss = (storage_class == MDebug::SC_BSS);
pending_sym.is_rodata = (storage_class == MDebug::SC_RDATA);
pending_sym_ready = true;
}
break;
default:
flush_pending_sym();
break;
}
}
flush_pending_sym();
}
good_ = true;
}
bool is_identifier_char(char c) {
if (c >= 'a' && c <= 'z') {
return true;
}
if (c >= 'A' && c <= 'Z') {
return true;
}
if (c == '_') {
return true;
}
if (c >= '0' && c <= '9') {
return true;
}
return false;
}
std::string sanitize_section_name(std::string section_name) {
// Skip periods at the start of the section name.
size_t start_pos = 0;
while (section_name[start_pos] == '.' && start_pos < section_name.size()) {
start_pos++;
}
std::string ret = section_name.substr(start_pos);
for (size_t char_index = 0; char_index < ret.size(); char_index++) {
if (!is_identifier_char(ret[char_index])) {
ret[char_index] = '_';
}
}
return ret;
}
bool populate_context(const N64Recomp::ElfParsingConfig& elf_config, N64Recomp::Context& context, N64Recomp::DataSymbolMap& data_syms) {
size_t num_files = files_.size();
std::vector<uint16_t> file_text_sections{};
std::vector<uint16_t> file_data_sections{};
std::vector<uint16_t> file_rodata_sections{};
std::vector<uint16_t> file_bss_sections{};
file_text_sections.resize(num_files, (uint16_t)-1);
file_data_sections.resize(num_files, (uint16_t)-1);
file_rodata_sections.resize(num_files, (uint16_t)-1);
file_bss_sections.resize(num_files, (uint16_t)-1);
std::unordered_map<std::string, std::vector<size_t>> mdebug_symbol_names{}; // Maps symbol name to list of files that have a symbol of that name.
// Build a lookup of section name to elf section index.
std::unordered_map<std::string, uint16_t> elf_sections_by_name{};
for (uint16_t section_index = 0; section_index < context.sections.size(); section_index++) {
const N64Recomp::Section& section = context.sections[section_index];
elf_sections_by_name.emplace(section.name, section_index);
}
// First pass to collect symbol names and map mdebug files to elf sections.
for (size_t file_index = 0; file_index < num_files; file_index++) {
const MDebugFile& file = files_[file_index];
if (file.symbols.empty()) {
continue;
}
bool has_funcs = false;
bool has_data = false;
bool has_rodata = false;
bool has_bss = false;
// Find the section that this file's .text was placed into by looking up global functions.
int file_text_section = -1;
uint32_t min_data_address = 0x0;
uint32_t max_data_address = 0x0;
uint32_t min_rodata_address = 0x0;
uint32_t max_rodata_address = 0x0;
uint32_t min_bss_address = 0x0;
uint32_t max_bss_address = 0x0;
for (const auto& sym : file.symbols) {
if (sym.ignored) {
continue;
}
mdebug_symbol_names[sym.name].emplace_back(file_index);
if (sym.is_func) {
has_funcs = true;
if (!sym.is_static) {
auto find_it = context.functions_by_name.find(sym.name);
if (find_it != context.functions_by_name.end()) {
const auto& found_sym = context.functions[find_it->second];
file_text_section = found_sym.section_index;
}
}
}
else {
if (sym.address != 0) {
// .bss
if (sym.is_bss) {
has_bss = true;
if (min_bss_address == 0) {
min_bss_address = sym.address;
}
else {
min_bss_address = std::min(min_bss_address, sym.address);
}
max_bss_address = std::max(max_bss_address, sym.address + sym.size);
}
// .rodata
else if (sym.is_rodata) {
has_rodata = true;
if (min_rodata_address == 0) {
min_rodata_address = sym.address;
}
else {
min_rodata_address = std::min(min_rodata_address, sym.address);
}
max_rodata_address = std::max(max_rodata_address, sym.address + sym.size);
}
// .data
else {
has_data = true;
if (min_data_address == 0) {
min_data_address = sym.address;
}
else {
min_data_address = std::min(min_data_address, sym.address);
}
max_data_address = std::max(max_data_address, sym.address + sym.size);
}
}
}
}
if (!has_funcs) {
continue;
}
// Manual file text section mapping.
{
auto find_text_mapping_it = elf_config.mdebug_text_map.find(file.filename);
if (find_text_mapping_it != elf_config.mdebug_text_map.end()) {
auto find_text_section_it = elf_sections_by_name.find(find_text_mapping_it->second);
if (find_text_section_it == elf_sections_by_name.end()) {
printf(".text section for mdebug source file \"%s\" is mapped to section \"%s\", which doesn't exist in the elf\n", file.filename.c_str(), find_text_mapping_it->second.c_str());
return false;
}
file_text_section = find_text_section_it->second;
}
}
if (file_text_section == -1) {
printf("Couldn't determine elf section of mdebug info for file %s\n", file.filename.c_str());
return false;
}
file_text_sections[file_index] = file_text_section;
const N64Recomp::Section& text_section = context.sections[file_text_section];
if (has_data) {
uint16_t file_data_section;
// Manual file data section mapping.
auto find_data_mapping_it = elf_config.mdebug_data_map.find(file.filename);
if (find_data_mapping_it != elf_config.mdebug_data_map.end()) {
auto find_data_section_it = elf_sections_by_name.find(find_data_mapping_it->second);
if (find_data_section_it == elf_sections_by_name.end()) {
printf(".data section for mdebug source file \"%s\" is mapped to section \"%s\", which doesn't exist in the elf\n", file.filename.c_str(), find_data_mapping_it->second.c_str());
return false;
}
file_data_section = find_data_section_it->second;
}
// Automatic mapping, attempt to use the same section that .text was placed in.
else {
if (min_data_address < text_section.ram_addr || max_data_address > text_section.ram_addr + text_section.size) {
printf("File %s has static data in mdebug which did not overlap with section %s\n", file.filename.c_str(), text_section.name.c_str());
return false;
}
file_data_section = file_text_section;
}
file_data_sections[file_index] = file_data_section;
}
if (has_rodata) {
uint16_t file_rodata_section;;
// Manual file rodata section mapping.
auto find_rodata_mapping_it = elf_config.mdebug_data_map.find(file.filename);
if (find_rodata_mapping_it != elf_config.mdebug_data_map.end()) {
auto find_rodata_section_it = elf_sections_by_name.find(find_rodata_mapping_it->second);
if (find_rodata_section_it == elf_sections_by_name.end()) {
printf(".rodata section for mdebug source file \"%s\" is mapped to section \"%s\", which doesn't exist in the elf\n", file.filename.c_str(), find_rodata_mapping_it->second.c_str());
return false;
}
file_rodata_section = find_rodata_section_it->second;
}
// Automatic mapping, attempt to use the same section that .text was placed in.
else {
if (min_rodata_address < text_section.ram_addr || max_rodata_address > text_section.ram_addr + text_section.size) {
printf("File %s has static rodata in mdebug which did not overlap with section %s\n", file.filename.c_str(), text_section.name.c_str());
return false;
}
file_rodata_section = file_text_section;
}
file_rodata_sections[file_index] = file_rodata_section;
}
if (has_bss) {
uint16_t file_bss_section;
// Manual file bss section mapping.
auto find_bss_mapping_it = elf_config.mdebug_data_map.find(file.filename);
if (find_bss_mapping_it != elf_config.mdebug_data_map.end()) {
auto find_bss_section_it = elf_sections_by_name.find(find_bss_mapping_it->second);
if (find_bss_section_it == elf_sections_by_name.end()) {
printf(".bss section for mdebug source file \"%s\" is mapped to section \"%s\", which doesn't exist in the elf\n", file.filename.c_str(), find_bss_mapping_it->second.c_str());
return false;
}
file_bss_section = find_bss_section_it->second;
}
// Automatic mapping, attempt to use the corresponding bss section for the section .text was placed in.
else {
if (text_section.bss_section_index == (uint16_t)-1) {
printf("File %s has static bss in mdebug but no paired bss section. Use the \"bss_section_suffix\" option to pair bss sections.\n", file.filename.c_str());
return false;
}
const N64Recomp::Section& bss_section = context.sections[text_section.bss_section_index];
if (min_bss_address < bss_section.ram_addr || max_bss_address > bss_section.ram_addr + bss_section.size) {
printf("File %s has static bss in mdebug which did not overlap with bss section %s\n", file.filename.c_str(), bss_section.name.c_str());
return false;
}
file_bss_section = text_section.bss_section_index;
}
file_bss_sections[file_index] = file_bss_section;
}
}
// Maps symbol name to list of sections that will receive a symbol with that name.
std::unordered_map<std::string, std::vector<uint16_t>> symbol_name_to_sections;
// Elf section of each mdebug symbol, indexed by file index and then symbol index.
std::vector<std::vector<uint16_t>> file_symbol_sections;
file_symbol_sections.resize(num_files);
// Second pass to assign symbols to elf sections. This allows the third pass to rename symbols if necessary.
// This has to be done after the first pass as we don't know section indices while processing symbols at that point.
for (size_t file_index = 0; file_index < num_files; file_index++) {
const MDebugFile& file = files_[file_index];
file_symbol_sections[file_index].resize(file.symbols.size());
for (size_t sym_index = 0; sym_index < file.symbols.size(); sym_index++) {
const MDebugSymbol& sym = file.symbols[sym_index];
if (sym.ignored) {
continue;
}
if (sym.is_static) {
uint16_t sym_section;
// Static .text
if (sym.is_func) {
sym_section = file_text_sections[file_index];
}
// Static .bss
else if (sym.is_bss) {
sym_section = file_text_sections[file_index];
}
// Static .data/.rodata
else {
sym_section = file_text_sections[file_index];
}
symbol_name_to_sections[sym.name].emplace_back(sym_section);
file_symbol_sections[file_index][sym_index] = sym_section;
}
}
}
// Mapping of datasym name to section.
std::unordered_map<std::string, uint16_t> datasyms_by_name{};
for (const auto &[section_index, section_datasyms] : data_syms) {
for (const auto& datasym : section_datasyms) {
datasyms_by_name.emplace(datasym.name, section_index);
}
}
// Third pass to populate the context and data symbol map, renaming symbols as needed to avoid conflicts.
for (size_t file_index = 0; file_index < num_files; file_index++) {
const MDebugFile& file = files_[file_index];
for (size_t sym_index = 0; sym_index < file.symbols.size(); sym_index++) {
const MDebugSymbol& sym = file.symbols[sym_index];
if (sym.ignored) {
continue;
}
uint16_t sym_section = file_symbol_sections[file_index][sym_index];
// Skip symbols with no section. This should only apply to static data/bss symbols in a file that had no functions.
if (sym_section == (uint16_t)-1) {
continue;
}
if (sym.is_static) {
bool already_exists = false;
bool already_exists_in_section = false;
// Check if the symbol name exists in the base symbol list already.
auto find_in_context_it = context.functions_by_name.find(sym.name);
if (find_in_context_it != context.functions_by_name.end()) {
already_exists = true;
uint16_t found_section_index = context.functions[find_in_context_it->second].section_index;
if (sym_section == found_section_index) {
already_exists_in_section = true;
}
}
// Check if the symbol name exists in the data syms already.
auto find_in_datasyms_it = datasyms_by_name.find(sym.name);
if (find_in_datasyms_it != datasyms_by_name.end()) {
already_exists = true;
uint16_t found_section_index = find_in_datasyms_it->second;
if (sym_section == found_section_index) {
already_exists_in_section = true;
}
}
// Check if the symbol name exists in the mdebug symbols.
auto find_in_mdebug_it = symbol_name_to_sections.find(sym.name);
if (find_in_mdebug_it != symbol_name_to_sections.end()) {
const std::vector<uint16_t>& section_list = find_in_mdebug_it->second;
size_t count_in_section = std::count(section_list.begin(), section_list.end(), sym_section);
// The count will always be at least one because of this symbol itself, so check that it's greater than one for duplicates.
if (count_in_section > 1) {
already_exists_in_section = true;
}
// If the symbol name shows up in multiple sections, rename it.
else if (section_list.size() > 1) {
already_exists = true;
}
}
std::string sym_output_name = sym.name;
if (already_exists_in_section) {
sym_output_name += fmt::format("_{}_{:08X}", sanitize_section_name(context.sections[sym_section].name), sym.address);
printf("Renamed static symbol \"%s\" to \"%s\"\n", sym.name.c_str(), sym_output_name.c_str());
}
else if (already_exists) {
sym_output_name += "_" + sanitize_section_name(context.sections[sym_section].name);
printf("Renamed static symbol \"%s\" to \"%s\"\n", sym.name.c_str(), sym_output_name.c_str());
}
// Emit the symbol.
if (sym.is_func) {
uint32_t section_vram = context.sections[sym_section].ram_addr;
uint32_t section_offset = sym.address - section_vram;
uint32_t rom_address = static_cast<uint32_t>(section_offset + context.sections[sym_section].rom_addr);
const uint32_t* words = reinterpret_cast<const uint32_t*>(context.rom.data() + rom_address);
uint32_t num_instructions = sym.size / sizeof(uint32_t);
std::vector<uint32_t> insn_words(num_instructions);
insn_words.assign(words, words + num_instructions);
context.functions_by_vram[sym.address].push_back(context.functions.size());
context.section_functions[sym_section].push_back(context.functions.size());
context.functions.emplace_back(N64Recomp::Function{
sym.address,
rom_address,
std::move(insn_words),
std::move(sym_output_name),
sym_section,
// TODO read these from elf config.
false, // ignored
false, // reimplemented
false, // stubbed
});
}
else {
data_syms[sym_section].emplace_back(N64Recomp::DataSymbol {
sym.address,
std::move(sym_output_name)
});
}
}
}
}
return true;
}
void print() {
printf("Mdebug Info\n");
for (const auto& file : files_) {
printf(" File %s\n", file.filename.c_str());
for (const auto& symbol : file.symbols) {
printf(" %s @ 0x%08X (size 0x%08X)\n", symbol.name.c_str(), symbol.address, symbol.size);
}
}
}
bool good() {
return good_;
}
private:
void add_file(std::string&& filename) {
files_.emplace_back(MDebugFile{
.filename = std::move(filename),
.symbols = {}
});
}
void add_symbol(size_t file_index, MDebugSymbol&& sym) {
symbols_by_name_.emplace(sym.name, std::make_pair(file_index, files_[file_index].symbols.size()));
files_[file_index].symbols.emplace_back(std::move(sym));
}
std::vector<MDebugFile> files_;
// Map of symbol name to file index, symbol index. Multimap because multiple symbols may have the same name due to statics.
std::unordered_multimap<std::string, std::pair<size_t, size_t>> symbols_by_name_;
bool good_ = false;
};
#if 0
bool get_func(const char *mdata, const MDebug::HDRR& hdrr, const MDebug::FDR& fdr, const MDebug::PDR& pdr, const std::vector<MDebug::AUX>& all_auxs, std::span<const MDebug::SYMR> all_symrs, N64Recomp::Function& func_out) {
func_out = {};
std::pair<uint32_t, uint32_t> sym_bounds = pdr.sym_bounds(all_symrs, all_auxs);
std::span<const MDebug::SYMR> fdr_symrs = fdr.get_symrs(all_symrs);
for (uint32_t i = sym_bounds.first; i < sym_bounds.second; i++) {
const MDebug::SYMR& symr = fdr_symrs[i];
MDebug::ST type = symr.get_st();
if (type == MDebug::ST_PROC || type == MDebug::ST_STATICPROC) {
const char* name = fdr.get_string(mdata + hdrr.cbSsOffset, symr.iss);
printf(" %s\n", name);
}
else if (type == MDebug::ST_END) {
printf(" %08lX\n", symr.value);
}
}
fflush(stdout);
return true;
}
void read_mdebug(N64Recomp::Context& context, ELFIO::section* mdebug_section, std::unordered_map<uint16_t, std::vector<N64Recomp::DataSymbol>>& data_syms) {
if (mdebug_section == nullptr) {
return;
}
ELFIO::Elf64_Off base_offset = mdebug_section->get_offset();
const char *mdata = mdebug_section->get_data();
// Read, byteswap and relocate the symbolic header. Relocation here means convert file-relative offsets to section-relative offsets.
MDebug::HDRR hdrr;
std::memcpy(&hdrr, mdata, sizeof(MDebug::HDRR));
hdrr.swap();
hdrr.relocate(base_offset);
// Check the magic value and version number are what we expect.
if (hdrr.magic != MDebug::MAGIC || hdrr.vstamp != 0) {
fmt::print(stderr, "Warning: Found an mdebug section with bad magic value or version (magic={} version={}). Skipping.\n", hdrr.magic, hdrr.vstamp);
return;
}
// Read the various records that are relevant for collecting static symbols and where they are declared.
std::vector<MDebug::FDR> fdrs = hdrr.read_fdrs(mdata);
std::vector<MDebug::AUX> all_auxs = hdrr.read_auxs(mdata);
std::vector<MDebug::PDR> all_pdrs = hdrr.read_pdrs(mdata);
std::vector<MDebug::SYMR> all_symrs = hdrr.read_symrs(mdata);
// For each file descriptor
for (MDebug::FDR& fdr : fdrs) {
const char* fdr_name = fdr.get_name(mdata + hdrr.cbSsOffset);
printf("%s @ 0x%08X:\n", fdr_name, fdr.adr);
printf(" Procedures:\n");
bool at_function = false;
// For every symbol record in the file descriptor record.
for (auto symr : fdr.get_symrs(all_symrs)) {
MDebug::ST type = symr.get_st();
if (type == MDebug::ST_PROC || type == MDebug::ST_STATICPROC) {
at_function = true;
const char* name = fdr.get_string(mdata + hdrr.cbSsOffset, symr.iss);
printf(" %s @ 0x%08X\n", name, symr.value);
auto find_it = context.functions_by_name.find(name);
if (find_it != context.functions_by_name.end()) {
printf(" in map: 0x%08lX\n", context.functions[find_it->second].vram);
}
}
else if (type == MDebug::ST_END) {
printf(" size: 0x%08lX\n", symr.value);
}
}
printf(" Globals:\n");
for (auto symr : fdr.get_symrs(all_symrs)) {
MDebug::ST type = symr.get_st();
if (type == MDebug::ST_GLOBAL) {
const char* name = fdr.get_string(mdata + hdrr.cbSsOffset, symr.iss);
printf(" %s @ 0x%08X\n", name, symr.value);
}
}
// // Consider only procedures and symbols defined in this file
// std::span<MDebug::AUX> auxs = fdr.get_auxs(all_auxs);
// std::span<MDebug::PDR> pdrs = fdr.get_pdrs(all_pdrs);
// std::span<MDebug::SYMR> symrs = fdr.get_symrs(all_symrs);
// std::vector<std::pair<uint32_t,uint32_t>> bounds(pdrs.size());
// // For each procedure record, determine which symbols belong to it
// for (MDebug::PDR& pdr : pdrs) {
// auto res = pdr.sym_bounds(symrs, auxs);
// bounds.push_back(res);
// }
// // For each symbol defined in this file
// for (uint32_t isym = 0; isym < symrs.size(); isym++) {
// MDebug::SYMR& symr = symrs[isym];
// // // Skip non-statics
// // if (symr.get_st() != MDebug::ST_STATIC && symr.get_st() != MDebug::ST_STATICPROC) {
// // continue;
// // }
// // // Find the name of the PDR, if any, that contains this symbol. We computed the (ordered)
// // // symbol bounds above, bsearch for the PDR.
// // auto it = std::lower_bound(bounds.begin(), bounds.end(), isym,
// // [](const auto& bound, uint32_t x) {
// // return bound.second <= x;
// // }
// // );
// // const char* pdr_name = nullptr;
// // if (it != bounds.end() && it->first <= isym && isym < it->second) {
// // // The procedure name is the name of the first symbol
// // pdr_name = fdr.get_string(mdata + hdrr.cbSsOffset, symrs[it->first].iss);
// // }
// // Get the name of the static symbol
// const char* sym_name = fdr.get_string(mdata + hdrr.cbSsOffset, symr.iss);
// // // Present info (TODO: plug into everything else)
// // std::printf("0x%08X %s (in %s, in %s(0x%08X))\n", symr.value, sym_name, pdr_name, fdr_name, fdr.adr);
// // Present info (TODO: plug into everything else)
// std::printf("0x%08X (0x%08X) %s (in %s(0x%08X))\n", symr.value, symr.bits, sym_name, fdr_name, fdr.adr);
// }
}
}
#endif
bool N64Recomp::MDebug::parse_mdebug(const N64Recomp::ElfParsingConfig& elf_config, const char* mdebug_section, uint32_t mdebug_offset, N64Recomp::Context& context, N64Recomp::DataSymbolMap& data_syms) {
MDebugInfo mdebug_info{ elf_config, mdebug_section, mdebug_offset };
if (!mdebug_info.populate_context(elf_config, context, data_syms)) {
return false;
}
return true;
}

411
src/mdebug.h Normal file
View file

@ -0,0 +1,411 @@
#ifndef __RECOMP_MDEBUG_H__
#define __RECOMP_MDEBUG_H__
#include <cassert>
#include <cstdint>
#include <span>
#include <vector>
#include <utility>
#include "recompiler/context.h"
namespace N64Recomp
{
namespace MDebug {
#ifdef _MSC_VER
inline uint32_t bswap32(uint32_t val) {
return _byteswap_ulong(val);
}
inline uint16_t bswap16(uint16_t val) {
return _byteswap_ushort(val);
}
#else
constexpr uint32_t bswap32(uint32_t val) {
return __builtin_bswap32(val);
}
constexpr uint16_t bswap16(uint16_t val) {
return __builtin_bswap16(val);
}
#endif
/* MIPS Symbol Table Debugging Format */
enum SC {
SC_NIL = 0,
SC_TEXT = 1, /* .text symbol */
SC_DATA = 2, /* .data symbol */
SC_BSS = 3, /* .bss symbol */
SC_REGISTER = 4, /* value of symbol is register number */
SC_ABS = 5, /* value of symbol is absolute */
SC_UNDEFINED = 6, /* value of symbol is undefined */
SC_CDBLOCAL = 7, /* variable value is in se->va.?? */
SC_BITS = 8, /* variable is a bit field */
SC_CDBSYSTEM = 9, /* variable value is in cdb address space */
SC_REGIMAGE = 10, /* register value is saved on stack */
SC_INFO = 11, /* symbol contains debugger information */
SC_USERSTRUCT = 12, /* address in struct user for current process */
SC_SDATA = 13, /* load time only small data */
SC_SBSS = 14, /* load time only small common */
SC_RDATA = 15, /* load time only read-only data */
SC_VAR = 16, /* var parameter (FORTRAN, Pascal) */
SC_COMMON = 17, /* common variable */
SC_SCOMMON = 18, /* small common */
SC_VARREGISTER = 19, /* var parameter in a register */
SC_VARIANT = 20, /* variant record */
SC_SUNDEFINED = 21, /* small undefined (external) data */
SC_INIT = 22, /* .init section symbol */
SC_BASEDVAR = 23, /* FORTRAN or PL/1 ptr based var */
SC_XDATA = 24, /* exception handling data */
SC_PDATA = 25, /* procedure section */
SC_FINI = 26, /* .fini section */
SC_RCONST = 27, /* .rconst section */
SC_MAX = 32
};
enum ST {
ST_NIL = 0,
ST_GLOBAL = 1, /* external symbol */
ST_STATIC = 2, /* static symbol */
ST_PARAM = 3, /* procedure argument */
ST_LOCAL = 4, /* local variable */
ST_LABEL = 5, /* label */
ST_PROC = 6, /* procedure */
ST_BLOCK = 7, /* beginning of block */
ST_END = 8, /* end of something */
ST_MEMBER = 9, /* member of struct/union/enum/.. */
ST_TYPEDEF = 10, /* type definition */
ST_FILE = 11, /* filename */
ST_REGRELOC = 12, /* register relocation */
ST_FORWARD = 13, /* forwarding address */
ST_STATICPROC = 14, /* load time only static procedures */
/* (CONSTANT and STAPARAM are in different orders between different sources...) */
ST_CONSTANT = 15, /* constant */
ST_STAPARAM = 16, /* FORTRAN static parameters */
ST_STRUCT = 26, /* structure */
ST_UNION = 27, /* union */
ST_ENUM = 28, /* enum */
ST_INDIRECT = 34
};
struct SYMR {
int32_t iss; /* index into String Space of name */
int32_t value; /* symbol value; can be an address, size or frame offset depending on symbol type */
uint32_t bits; /* Bitfield: */
#if 0
ST st : 6; /* symbol type */
SC sc : 5; /* storage class - text, data, etc */
int32_t _reserved : 1; /* reserved bit */
int32_t index : 20; /* index into sym/aux table */
#endif
inline ST get_st() const {
return (ST)(bits >> 26 & 0b111111);
}
inline SC get_sc() const {
return (SC)(bits >> 21 & 0b11111);
}
inline int32_t get_index() const {
return bits & 0b11111111111111111111;
}
void swap() {
iss = bswap32(iss);
value = bswap32(value);
bits = bswap32(bits);
}
};
union AUX {
uint32_t any_;
uint32_t ti; /* type information record */
uint32_t rndx; /* relative index into symbol table */
uint32_t dnLow; /* low dimension of array */
uint32_t dnHigh; /* high dimension of array */
uint32_t isym; /* symbol table index (end of proc) */
uint32_t iss; /* index into string space (not used) */
uint32_t width; /* width for non-default sized struct fields */
uint32_t count; /* count of ranges for variant arm */
void swap() {
any_ = bswap32(any_);
}
};
struct PDR {
uint32_t addr; /* memory address of start of procedure */
uint32_t isym; /* start of local symbol entries */
uint32_t iline; /* start of line number entries */
uint32_t regmask; /* save register mask */
uint32_t regoffset; /* save register offset */
uint32_t iopt; /* start of optimization symbol entries */
uint32_t fregmask; /* save floating point register mask */
uint32_t fregoffset; /* save floating point register offset */
uint32_t frameoffset; /* frame size */
uint16_t framereg; /* frame pointer register */
uint16_t pcreg; /* offset or reg of return pc */
int32_t lnLow; /* lowest line in the procedure */
int32_t lnHigh; /* highest line in the procedure */
int32_t cbLineOffset; /* byte offset for this procedure from the fd base */
void swap() {
addr = bswap32(addr);
isym = bswap32(isym);
iline = bswap32(iline);
regmask = bswap32(regmask);
regoffset = bswap32(regoffset);
iopt = bswap32(iopt);
fregmask = bswap32(fregmask);
fregoffset = bswap32(fregoffset);
frameoffset = bswap32(frameoffset);
framereg = bswap16(framereg);
pcreg = bswap16(pcreg);
lnLow = bswap32(lnLow);
lnHigh = bswap32(lnHigh);
cbLineOffset = bswap32(cbLineOffset);
}
inline std::pair<uint32_t,uint32_t> sym_bounds(std::span<const SYMR> symrs, std::span<const AUX> auxs) const {
const SYMR& first = symrs[isym];
const ST first_st = first.get_st();
// The first symbol is the symbol of the procedure itself. The procedure name is the name of this symbol.
assert(first_st == ST_PROC || first_st == ST_STATICPROC);
const AUX& aux = auxs[first.get_index()];
const SYMR& last = symrs[aux.isym - 1];
const ST last_st = last.get_st();
// The last symbol is the END marker, pointed to by the first AUX of the stPROC symbol.
assert(last_st == ST_END);
// Return the symbol bounds
return std::make_pair(isym, aux.isym);
}
};
enum LANG {
LANG_C = 0,
LANG_PASCAL = 1,
LANG_FORTRAN = 2,
LANG_ASM = 3,
LANG_MACHINE = 4,
LANG_NIL = 5,
LANG_ADA = 6,
LANG_PL1 = 7,
LANG_COBOL = 8,
LANG_STDC = 9,
LANG_CPLUSPLUSV2 = 10,
LANG_MAX = 11
};
struct FDR {
uint32_t adr; /* memory address of beginning of file */
int32_t rss; /* file name (of source, if known) */
int32_t issBase; /* file's string space */
int32_t cbSs; /* number of bytes in the ss */
int32_t isymBase; /* beginning of symbols */
int32_t csym; /* count file's of symbols */
int32_t ilineBase; /* file's line symbols */
int32_t cline; /* count of file's line symbols */
int32_t ioptBase; /* file's optimization entries */
int32_t copt; /* count of file's optimization entries */
uint16_t ipdFirst; /* start of procedures for this file */
uint16_t cpd; /* count of procedures for this file */
int32_t iauxBase; /* file's auxiliary entries */
int32_t caux; /* count of file's auxiliary entries */
int32_t rfdBase; /* index into the file indirect table */
int32_t crfd; /* count file indirect entries */
uint32_t bits; /* Bitfield: */
#if 0
LANG lang : 5; /* language for this file */
uint32_t fMerge : 1; /* whether this file can be merged */
uint32_t fReadin : 1; /* true if it was read in (not just created) */
uint32_t fBigEndian : 1; /* true if AUXU's are big endian */
uint32_t glevel : 2; /* level this file was compiled with */
uint32_t _reserved : 20; /* reserved bits */
#endif
int32_t cbLineOffset; /* byte offset from header for this file ln's */
int32_t cbLine; /* size of lines for this file */
inline LANG get_lang() const {
return (LANG)(bits >> 27 & 0b11111);
}
inline uint32_t get_fMerge() const {
return bits >> 26 & 1;
}
inline uint32_t get_fReadin() const {
return bits >> 25 & 1;
}
inline uint32_t get_fBigEndian() const {
return bits >> 24 & 1;
}
inline uint32_t get_glevel() const {
return bits >> 22 & 0b11;
}
void swap() {
adr = bswap32(adr);
rss = bswap32(rss);
issBase = bswap32(issBase);
cbSs = bswap32(cbSs);
isymBase = bswap32(isymBase);
csym = bswap32(csym);
ilineBase = bswap32(ilineBase);
cline = bswap32(cline);
ioptBase = bswap32(ioptBase);
copt = bswap32(copt);
ipdFirst = bswap16(ipdFirst);
cpd = bswap16(cpd);
iauxBase = bswap32(iauxBase);
caux = bswap32(caux);
rfdBase = bswap32(rfdBase);
crfd = bswap32(crfd);
bits = bswap32(bits);
cbLineOffset = bswap32(cbLineOffset);
cbLine = bswap32(cbLine);
}
inline std::span<const AUX> get_auxs(const std::vector<AUX>& all_auxs) const {
return std::span(all_auxs).subspan(iauxBase, caux);
}
inline std::span<const PDR> get_pdrs(const std::vector<PDR>& all_pdrs) const {
return std::span(all_pdrs).subspan(ipdFirst, cpd);
}
inline std::span<const SYMR> get_symrs(std::span<const SYMR> all_symrs) const {
return std::span(all_symrs).subspan(isymBase, csym);
}
inline const char* get_string(const char* data, size_t index) const {
return data + issBase + index;
}
inline const char* get_name(const char* data) const {
return get_string(data, rss);
}
};
static const uint16_t MAGIC = 0x7009;
/**
* mdebug sections always start with a Symbolic Header (HDRR) containing
* file-relative (not section-relative) offsets for where to find the rest
* of the data.
*/
struct HDRR {
uint16_t magic; /* 0x7009 */
uint16_t vstamp; /* version stamp */
int32_t ilineMax; /* number of line number entries */
int32_t cbLine; /* number of bytes for line number entries */
int32_t cbLineOffset; /* offset to start of line number entries */
int32_t idnMax; /* max index into dense number table */
int32_t cbDnOffset; /* offset to start dense number table */
int32_t ipdMax; /* number of procedures */
int32_t cbPdOffset; /* offset to procedure descriptor table */
int32_t isymMax; /* number of local symbols */
int32_t cbSymOffset; /* offset to start of local symbols */
int32_t ioptMax; /* max index into optimization symbol entries */
int32_t cbOptOffset; /* offset to optimization symbol entries */
int32_t iauxMax; /* number of auxillary symbol entries */
int32_t cbAuxOffset; /* offset to start of auxillary symbol entries */
int32_t issMax; /* max index into local strings */
int32_t cbSsOffset; /* offset to start of local strings */
int32_t issExtMax; /* max index into external strings */
int32_t cbSsExtOffset; /* offset to start of external strings */
int32_t ifdMax; /* number of file descriptor entries */
int32_t cbFdOffset; /* offset to file descriptor table */
int32_t crfd; /* number of relative file descriptor entries */
int32_t cbRfdOffset; /* offset to relative file descriptor table */
int32_t iextMax; /* max index into external symbols */
int32_t cbExtOffset; /* offset to start of external symbol entries */
void swap() {
magic = bswap16(magic);
vstamp = bswap16(vstamp);
ilineMax = bswap32(ilineMax);
cbLine = bswap32(cbLine);
cbLineOffset = bswap32(cbLineOffset);
idnMax = bswap32(idnMax);
cbDnOffset = bswap32(cbDnOffset);
ipdMax = bswap32(ipdMax);
cbPdOffset = bswap32(cbPdOffset);
isymMax = bswap32(isymMax);
cbSymOffset = bswap32(cbSymOffset);
ioptMax = bswap32(ioptMax);
cbOptOffset = bswap32(cbOptOffset);
iauxMax = bswap32(iauxMax);
cbAuxOffset = bswap32(cbAuxOffset);
issMax = bswap32(issMax);
cbSsOffset = bswap32(cbSsOffset);
issExtMax = bswap32(issExtMax);
cbSsExtOffset = bswap32(cbSsExtOffset);
ifdMax = bswap32(ifdMax);
cbFdOffset = bswap32(cbFdOffset);
crfd = bswap32(crfd);
cbRfdOffset = bswap32(cbRfdOffset);
iextMax = bswap32(iextMax);
cbExtOffset = bswap32(cbExtOffset);
}
void relocate(uint32_t offset) {
cbLineOffset -= offset;
cbDnOffset -= offset;
cbPdOffset -= offset;
cbSymOffset -= offset;
cbOptOffset -= offset;
cbAuxOffset -= offset;
cbSsOffset -= offset;
cbSsExtOffset -= offset;
cbFdOffset -= offset;
cbRfdOffset -= offset;
cbExtOffset -= offset;
}
inline std::vector<FDR> read_fdrs(const char* data) {
std::vector<FDR> fdrs(ifdMax);
const FDR* p = reinterpret_cast<const FDR*>(data + cbFdOffset);
fdrs.assign(p, p + ifdMax);
for (FDR& fdr : fdrs)
fdr.swap();
return fdrs;
}
inline std::vector<AUX> read_auxs(const char* data) {
std::vector<AUX> auxs(iauxMax);
const AUX* p = reinterpret_cast<const AUX*>(data + cbAuxOffset);
auxs.assign(p, p + iauxMax);
for (AUX& aux : auxs)
aux.swap();
return auxs;
}
inline std::vector<PDR> read_pdrs(const char* data) {
std::vector<PDR> pdrs(ipdMax);
const PDR* p = reinterpret_cast<const PDR*>(data + cbPdOffset);
pdrs.assign(p, p + ipdMax);
for (PDR& pdr : pdrs)
pdr.swap();
return pdrs;
}
inline std::vector<SYMR> read_symrs(const char* data) {
std::vector<SYMR> symrs(isymMax);
const SYMR* p = reinterpret_cast<const SYMR*>(data + cbSymOffset);
symrs.assign(p, p + isymMax);
for (SYMR& symr : symrs)
symr.swap();
return symrs;
}
};
bool parse_mdebug(const N64Recomp::ElfParsingConfig& elf_config, const char* mdebug_section, uint32_t mdebug_offset, N64Recomp::Context& context, N64Recomp::DataSymbolMap& data_syms);
}
}
#endif

View file

@ -20,6 +20,11 @@ struct FileSubHeaderV1 {
uint32_t string_data_size;
};
enum class SectionFlags : uint32_t {
FixedAddress = 1 << 0,
GloballyLoaded = 1 << 1,
};
struct SectionHeaderV1 {
uint32_t flags;
uint32_t file_offset;
@ -172,10 +177,22 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
cur_section.bss_size = section_header->bss_size;
cur_section.name = "mod_section_" + std::to_string(section_index);
cur_section.relocatable = true;
cur_section.fixed_address = (section_header->flags & static_cast<uint32_t>(SectionFlags::FixedAddress)) != 0;
cur_section.globally_loaded = (section_header->flags & static_cast<uint32_t>(SectionFlags::GloballyLoaded)) != 0;
if (cur_section.fixed_address && !cur_section.globally_loaded) {
printf("Fixed address sections that aren't globally loaded aren't currently supported\n");
return false;
}
if (cur_section.globally_loaded && !cur_section.fixed_address) {
printf("A globally loaded section must have a fixed address\n");
return false;
}
uint32_t num_funcs = section_header->num_funcs;
uint32_t num_relocs = section_header->num_relocs;
const FuncV1* funcs = reinterpret_data<FuncV1>(data, offset, num_funcs);
if (funcs == nullptr) {
printf("Failed to read funcs (count: %d)\n", num_funcs);

View file

@ -9,6 +9,8 @@ namespace N64Recomp {
{ InstrId::cpu_mflo, { UnaryOpType::None, Operand::Rd, Operand::Lo } },
{ InstrId::cpu_mtc1, { UnaryOpType::None, Operand::FsU32L, Operand::Rt } },
{ InstrId::cpu_mfc1, { UnaryOpType::ToInt32, Operand::Rt, Operand::FsU32L } },
{ InstrId::cpu_dmtc1, { UnaryOpType::None, Operand::FsU64, Operand::Rt } },
{ InstrId::cpu_dmfc1, { UnaryOpType::None, Operand::Rt, Operand::FsU64 } },
// Float operations
{ InstrId::cpu_mov_s, { UnaryOpType::None, Operand::Fd, Operand::Fs, true } },
{ InstrId::cpu_mov_d, { UnaryOpType::None, Operand::FdDouble, Operand::FsDouble, true } },
@ -98,34 +100,46 @@ namespace N64Recomp {
{ InstrId::cpu_mul_d, { BinaryOpType::MulDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } },
{ InstrId::cpu_div_s, { BinaryOpType::DivFloat, Operand::Fd, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true, true } },
{ InstrId::cpu_div_d, { BinaryOpType::DivDouble, Operand::FdDouble, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true, true } },
// Float comparisons TODO remaining operations and investigate ordered/unordered and default values
{ InstrId::cpu_c_lt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_nge_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_olt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ult_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_lt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_nge_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_olt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ult_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_le_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngt_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ole_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ule_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_le_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngt_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ole_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ule_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_eq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ueq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngl_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_seq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
// Float comparisons TODO investigate ordered/unordered and default values
// Single Ordered
{ InstrId::cpu_c_f_s, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_eq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_olt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ole_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_sf_s, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_seq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_lt_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_le_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
// Single Unordered
{ InstrId::cpu_c_un_s, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ueq_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ult_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ule_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngle_s, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngl_s, { BinaryOpType::EqualFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_nge_s, { BinaryOpType::LessFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
{ InstrId::cpu_c_ngt_s, { BinaryOpType::LessEqFloat, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Fs, Operand::Ft }}, true } },
// Double Ordered
{ InstrId::cpu_c_f_d, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_eq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ueq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngl_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_olt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ole_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
/* TODO rename to c_sf_d when fixed in rabbitizer */
{ InstrId::cpu_c_df_d, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
/* TODO rename to c_seq_d when fixed in rabbitizer */
{ InstrId::cpu_c_deq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_lt_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_le_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
// Double Unordered
{ InstrId::cpu_c_un_d, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ueq_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ult_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ule_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngle_d, { BinaryOpType::False, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngl_d, { BinaryOpType::EqualDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_nge_d, { BinaryOpType::LessDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
{ InstrId::cpu_c_ngt_d, { BinaryOpType::LessEqDouble, Operand::Cop1cs, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::FsDouble, Operand::FtDouble }}, true } },
// Loads
{ InstrId::cpu_ld, { BinaryOpType::LD, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },
{ InstrId::cpu_lw, { BinaryOpType::LW, Operand::Rt, {{ UnaryOpType::None, UnaryOpType::None }, { Operand::Base, Operand::ImmS16 }}} },

View file

@ -152,6 +152,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
}
N64Recomp::RelocType reloc_type = N64Recomp::RelocType::R_MIPS_NONE;
bool has_reloc = false;
uint32_t reloc_section = 0;
uint32_t reloc_target_section_offset = 0;
size_t reloc_reference_symbol = (size_t)-1;
@ -162,6 +163,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
// Check if this instruction has a reloc.
if (section.relocs.size() > 0 && section.relocs[reloc_index].address == instr_vram) {
has_reloc = true;
// Get the reloc data for this instruction
const auto& reloc = section.relocs[reloc_index];
reloc_section = reloc.target_section;
@ -257,7 +259,7 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
return true;
};
auto print_func_call_by_address = [&generator, reloc_target_section_offset, reloc_section, reloc_reference_symbol, reloc_type, &context, &func, &static_funcs_out, &needs_link_branch, &print_indent, &process_delay_slot, &print_link_branch]
auto print_func_call_by_address = [&generator, reloc_target_section_offset, has_reloc, reloc_section, reloc_reference_symbol, reloc_type, &context, &func, &static_funcs_out, &needs_link_branch, &print_indent, &process_delay_slot, &print_link_branch]
(uint32_t target_func_vram, bool tail_call = false, bool indent = false)
{
bool call_by_lookup = false;
@ -294,7 +296,12 @@ bool process_instruction(GeneratorType& generator, const N64Recomp::Context& con
}
}
else {
JalResolutionResult jal_result = resolve_jal(context, func.section_index, target_func_vram, matched_func_index);
uint32_t target_section = func.section_index;
// If this instruction has a reloc and the target section is a normal section, use the section of the reloc when searching for a matching target function.
if (has_reloc && reloc_section < 65500) {
target_section = reloc_section;
}
JalResolutionResult jal_result = resolve_jal(context, target_section, target_func_vram, matched_func_index);
switch (jal_result) {
case JalResolutionResult::NoMatch:

View file

@ -94,6 +94,7 @@ const std::unordered_set<std::string> N64Recomp::reimplemented_funcs {
"osSetEventMesg",
// Timer functions
"osGetTime",
"osSetTime",
"osSetTimer",
"osStopTimer",
// Voice functions
@ -114,6 +115,7 @@ const std::unordered_set<std::string> N64Recomp::reimplemented_funcs {
"osVirtualToPhysical",
// Coprocessor 0/1 functions
"osGetCount",
"osSetCount",
"__osSetFpcCsr",
// Cache funcs
"osInvalDCache",
@ -329,6 +331,7 @@ const std::unordered_set<std::string> N64Recomp::ignored_funcs {
"osSetTimer",
"osStopTimer",
"osGetTime",
"osSetTime",
"__osInsertTimer",
"__osTimerInterrupt",
"__osTimerServicesInit",
@ -391,6 +394,7 @@ const std::unordered_set<std::string> N64Recomp::ignored_funcs {
// Coprocessor 0/1 functions
"__osSetCount",
"osGetCount",
"osSetCount",
"__osSetSR",
"__osGetSR",
"__osSetCause",