Fix handling of section load addresses to match objcopy behavior, added event parsing to dependency tomls, minor cleanup

This commit is contained in:
Mr-Wiseguy 2024-08-24 03:15:56 -04:00
parent eb8aba6550
commit 49c3628a76
5 changed files with 79 additions and 94 deletions

View file

@ -181,7 +181,9 @@ int main(int argc, const char** argv) {
output_file << "// Array of this mod's loaded section addresses.\n";
output_file << "RECOMP_EXPORT int32_t section_addresses[" << num_sections << "] = {};\n\n";
for (const auto& func : mod_context.functions) {
for (size_t func_index = 0; func_index < mod_context.functions.size(); func_index++) {
auto& func = mod_context.functions[func_index];
func.name = "mod_func_" + std::to_string(func_index);
N64Recomp::recompile_function(mod_context, func, output_file, static_funcs_by_section, true);
}

View file

@ -39,7 +39,7 @@ static std::vector<std::filesystem::path> get_toml_path_array(const toml::array*
return ret;
}
static bool read_dependency_file(const std::filesystem::path& dependency_path, N64Recomp::Context& context, std::vector<N64Recomp::Dependency>& dependencies) {
static bool read_dependency_file(const std::filesystem::path& dependency_path, N64Recomp::Context& context) {
toml::table toml_data{};
try {
@ -61,7 +61,7 @@ static bool read_dependency_file(const std::filesystem::path& dependency_path, N
throw toml::parse_error("Invalid dependency entry", dependency_node.source());
}
size_t dependency_index = dependencies.size();
size_t dependency_index = context.dependencies.size();
auto read_number = [](const toml::node& node, const std::string& key, const std::string& name, int64_t min_limit, int64_t max_limit) {
toml::node_view mod_id_node = node[toml::path{key}];
@ -97,14 +97,14 @@ static bool read_dependency_file(const std::filesystem::path& dependency_path, N
}
const std::string& mod_id = mod_id_node.ref<std::string>();
dependencies.emplace_back(N64Recomp::Dependency{
context.dependencies.emplace_back(N64Recomp::Dependency{
.major_version = major_version,
.minor_version = minor_version,
.patch_version = patch_version,
.mod_id = mod_id
});
// Symbol list
// Function list (optional)
toml::node_view functions_data = dependency_node[toml::path{"functions"}];
if (functions_data.is_array()) {
const toml::array* functions_array = functions_data.as_array();
@ -116,14 +116,25 @@ static bool read_dependency_file(const std::filesystem::path& dependency_path, N
context.add_import_symbol(function_name, dependency_index);
}
}
else {
if (functions_data) {
throw toml::parse_error("Mod toml is missing data reference symbol file list", functions_data.node()->source());
}
else {
throw toml::parse_error("Invalid data reference symbol file list", functions_data.node()->source());
else if (functions_data) {
throw toml::parse_error("Invalid dependency function list", functions_data.node()->source());
}
// Event list (optional)
toml::node_view events_data = dependency_node[toml::path{"events"}];
if (events_data.is_array()) {
const toml::array* events_array = events_data.as_array();
for (const auto& event_node : *events_array) {
if (!event_node.is_string()) {
throw toml::parse_error("Invalid dependency event", event_node.source());
}
const std::string& event_name = event_node.ref<std::string>();
context.add_dependency_event(event_name, dependency_index);
}
}
else if (events_data) {
throw toml::parse_error("Invalid dependency event list", events_data.node()->source());
}
}
}
@ -221,7 +232,7 @@ static inline uint32_t round_up_16(uint32_t value) {
return (value + 15) & (~15);
}
N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, std::vector<N64Recomp::Dependency>&& dependencies, bool& good) {
N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, bool& good) {
N64Recomp::Context ret{};
good = false;
@ -248,13 +259,16 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st
// TODO avoid a copy here.
ret.rom = input_context.rom;
ret.dependencies = std::move(dependencies);
// Copy the dependency data from the input context.
ret.dependencies = input_context.dependencies;
ret.import_symbols = input_context.import_symbols;
ret.dependency_events = input_context.dependency_events;
ret.dependency_events_by_name = input_context.dependency_events_by_name;
uint32_t rom_to_ram = (uint32_t)-1;
size_t output_section_index = (size_t)-1;
ret.sections.resize(1);
size_t num_imports = ret.import_symbols.size();
// Mapping of input section to output section for fixing up relocations.
std::unordered_map<uint16_t, uint16_t> input_section_to_output_section{};
@ -387,10 +401,7 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st
event_name, cur_func.name);
return {};
}
ret.callbacks.emplace_back(N64Recomp::Callback {
output_func_index,
event_index
});
ret.add_callback(event_index, output_func_index);
}
ret.section_functions[output_section_index].push_back(output_func_index);
@ -553,9 +564,6 @@ N64Recomp::Context build_mod_context(const N64Recomp::Context& input_context, st
}
}
// Copy the import reference symbols from the input context as-is to this context.
ret.import_symbols = input_context.import_symbols;
// Copy the reference sections from the input context as-is for resolving reference symbol relocations.
ret.copy_reference_sections_from(input_context);
@ -603,11 +611,9 @@ int main(int argc, const char** argv) {
}
}
// Read the imported symbols, placing them at the end of the reference symbol list.
std::vector<N64Recomp::Dependency> dependencies{};
// Read the dependency files.
for (const std::filesystem::path& dependency_path : config.dependency_paths) {
if (!read_dependency_file(dependency_path, context, dependencies)) {
if (!read_dependency_file(dependency_path, context)) {
fmt::print(stderr, "Failed to read dependency file: {}\n", dependency_path.string());
return EXIT_FAILURE;
}
@ -638,7 +644,7 @@ int main(int argc, const char** argv) {
}
bool mod_context_good;
N64Recomp::Context mod_context = build_mod_context(context, std::move(dependencies), mod_context_good);
N64Recomp::Context mod_context = build_mod_context(context, mod_context_good);
std::vector<uint8_t> symbols_bin = N64Recomp::symbols_to_bin_v1(mod_context);
if (symbols_bin.empty()) {
fmt::print(stderr, "Failed to create symbol file\n");

View file

@ -389,7 +389,7 @@ namespace N64Recomp {
return true;
}
bool add_dependency_event(size_t dependency_index, const std::string& event_name, size_t& dependency_event_index_out) {
bool add_dependency_event(const std::string& event_name, size_t dependency_index) {
size_t dependency_event_index = dependency_events.size();
dependency_events.emplace_back(DependencyEvent{
.dependency_index = dependency_index,
@ -397,7 +397,6 @@ namespace N64Recomp {
});
// TODO Check if dependency_events_by_name already contains the name and show a conflict error if so.
dependency_events_by_name[event_name] = dependency_event_index;
dependency_event_index_out = dependency_event_index;
return true;
}
@ -410,30 +409,7 @@ namespace N64Recomp {
return true;
}
bool add_callback(size_t dependency_index, const std::string& event_name, size_t function_index) {
auto find_it = dependency_events_by_name.find(event_name);
size_t dependency_event_index;
if (find_it == dependency_events_by_name.end()) {
// Event doesn't already exist, so add it.
if (!add_dependency_event(dependency_index, event_name, dependency_event_index)) {
return false;
}
}
else {
dependency_event_index = find_it->second;
// Make sure the event that we found was for this dependency.
if (dependency_events[dependency_event_index].dependency_index != dependency_index) {
return false;
}
}
callbacks.emplace_back(Callback{
.function_index = function_index,
.dependency_event_index = dependency_event_index
});
return true;
}
bool add_callback_by_dependency_event(size_t dependency_event_index, size_t function_index) {
bool add_callback(size_t dependency_event_index, size_t function_index) {
callbacks.emplace_back(Callback{
.function_index = function_index,
.dependency_event_index = dependency_event_index

View file

@ -233,6 +233,34 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
std::unordered_map<std::string, ELFIO::section*> reloc_sections_by_name;
std::unordered_map<std::string, ELFIO::section*> bss_sections_by_name;
// First pass over the sections to find the load addresses and track the minimum load address value. This mimics the objcopy raw binary output behavior.
uint32_t min_load_address = (uint32_t)-1;
for (const auto& section : elf_file.sections) {
auto& section_out = context.sections[section->get_index()];
ELFIO::Elf_Word type = section->get_type();
ELFIO::Elf_Xword flags = section->get_flags();
// Check if this section will end up in the ROM. It must not be a nobits (NOLOAD) type, must have the alloc flag set and must have a nonzero size.
if (type != ELFIO::SHT_NOBITS && (section->get_flags() & ELFIO::SHF_ALLOC) && section->get_size() != 0) {
std::optional<size_t> segment_index = get_segment(segments, section_out.size, section->get_offset());
if (!segment_index.has_value()) {
fmt::print(stderr, "Could not find segment that section {} belongs to!\n", section->get_name());
return nullptr;
}
const SegmentEntry& segment = segments[segment_index.value()];
// Calculate the load address of the section based on that of the segment.
// This will get modified afterwards in the next pass to offset by the minimum load address.
section_out.rom_addr = segment.physical_address + (section->get_offset() - segment.data_offset);
// Track the minimum load address.
min_load_address = std::min(min_load_address, section_out.rom_addr);
}
else {
// Otherwise mark this section as having an invalid rom address
section_out.rom_addr = (uint32_t)-1;
}
}
// Iterate over every section to record rom addresses and find the symbol table
for (const auto& section : elf_file.sections) {
auto& section_out = context.sections[section->get_index()];
@ -260,6 +288,7 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
return nullptr;
}
// FIXME This should be using SH_INFO to create a reloc section to target section mapping instead of using the name.
std::string reloc_target_section = section_name.substr(strlen(".rel"));
// If this reloc section is for a section that has been marked as relocatable, record it in the reloc section lookup.
@ -280,45 +309,17 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
}
}
// If this section isn't bss (SHT_NOBITS) and ends up in the rom (SHF_ALLOC),
// find this section's rom address and copy it into the rom
if (type != ELFIO::SHT_NOBITS && section->get_flags() & ELFIO::SHF_ALLOC && section->get_size() != 0) {
//// Find the segment this section is in to determine the physical (rom) address of the section
//auto segment_it = std::upper_bound(segments.begin(), segments.end(), section->get_offset(),
// [](ELFIO::Elf64_Off section_offset, const SegmentEntry& segment) {
// return section_offset < segment.data_offset;
// }
//);
//if (segment_it == segments.begin()) {
// fmt::print(stderr, "Could not find segment that section {} belongs to!\n", section_name.c_str());
// return nullptr;
//}
//// Upper bound returns the iterator after the element we're looking for, so rewind by one
//// This is safe because we checked if segment_it was segments.begin() already, which is the minimum value it could be
//const SegmentEntry& segment = *(segment_it - 1);
//// Check to be sure that the section is actually in this segment
//if (section->get_offset() >= segment.data_offset + segment.memory_size) {
// fmt::print(stderr, "Section {} out of range of segment at offset 0x{:08X}\n", section_name.c_str(), segment.data_offset);
// return nullptr;
//}
std::optional<size_t> segment_index = get_segment(segments, section_out.size, section->get_offset());
if (!segment_index.has_value()) {
fmt::print(stderr, "Could not find segment that section {} belongs to!\n", section_name.c_str());
return nullptr;
}
const SegmentEntry& segment = segments[segment_index.value()];
// Calculate the rom address based on this section's offset into the segment and the segment's rom address
section_out.rom_addr = segment.physical_address + (section->get_offset() - segment.data_offset);
// Resize the output rom if needed to fit this section
// If this section was marked as being in the ROM in the previous pass, copy it into the ROM now.
if (section_out.rom_addr != (uint32_t)-1) {
// Adjust the section's final ROM address to account for the minimum load address.
section_out.rom_addr -= min_load_address;
// Resize the output rom if needed to fit this section.
size_t required_rom_size = section_out.rom_addr + section_out.size;
if (required_rom_size > context.rom.size()) {
context.rom.resize(required_rom_size);
}
// Copy this section's data into the rom
// Copy this section's data into the rom.
std::copy(section->get_data(), section->get_data() + section->get_size(), &context.rom[section_out.rom_addr]);
} else {
// Otherwise mark this section as having an invalid rom address
section_out.rom_addr = (uint32_t)-1;
}
// Check if this section is marked as executable, which means it has code in it
if (section->get_flags() & ELFIO::SHF_EXECINSTR) {
@ -348,7 +349,11 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP
context.bss_section_to_section[section_out.bss_section_index] = section_index;
}
if (context.has_reference_symbols() || section_out.relocatable) {
// Check if this section is in the ROM and relocatable.
const ELFIO::section* elf_section = elf_file.sections[section_index];
bool in_rom = (elf_section->get_type() != ELFIO::SHT_NOBITS) && (elf_section->get_flags() & ELFIO::SHF_ALLOC);
bool is_relocatable = section_out.relocatable || context.has_reference_symbols();
if (in_rom && is_relocatable) {
// Check if a reloc section was found that corresponds with this section
auto reloc_find = reloc_sections_by_name.find(section_out.name);
if (reloc_find != reloc_sections_by_name.end()) {

View file

@ -199,7 +199,6 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
cur_func.vram = cur_section.ram_addr + funcs[func_index].section_offset;
cur_func.rom = cur_section.rom_addr + funcs[func_index].section_offset;
cur_func.words.resize(funcs[func_index].size / sizeof(uint32_t)); // Filled in later
cur_func.name = "mod_func_" + std::to_string(start_func_index + func_index);
cur_func.section_index = section_index;
}
@ -322,8 +321,7 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
std::string_view dependency_event_name{ string_data + name_start, string_data + name_start + name_size };
size_t dummy_dependency_event_index;
mod_context.add_dependency_event(dependency_index, std::string{dependency_event_name}, dummy_dependency_event_index);
mod_context.add_dependency_event(std::string{dependency_event_name}, dependency_index);
}
const ReplacementV1* replacements = reinterpret_data<ReplacementV1>(data, offset, num_replacements);
@ -365,8 +363,6 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
// Add the function to the exported function list.
mod_context.exported_funcs[export_index] = func_index;
// Populate the exported function's name from the string data.
mod_context.functions[func_index].name = std::string_view(string_data + name_start, string_data + name_start + name_size);
}
const CallbackV1* callbacks = reinterpret_data<CallbackV1>(data, offset, num_callbacks);
@ -390,7 +386,7 @@ bool parse_v1(std::span<const char> data, const std::unordered_map<uint32_t, uin
callback_index, function_index, mod_context.functions.size());
}
if (!mod_context.add_callback_by_dependency_event(dependency_event_index, function_index)) {
if (!mod_context.add_callback(dependency_event_index, function_index)) {
printf("Failed to add callback %zu\n", callback_index);
}
}