arm: Improve TLB implementation and fault handling in NCE

This commit enhances the Translation Lookaside Buffer (TLB) implementation
in the ARM Native Code Execution (NCE) component to increase stability,
particularly on Android devices. The changes prioritize robustness and
error recovery over performance optimizations.

Key improvements:
- Replace set-associative TLB with a simpler linear search implementation
- Implement a basic LRU replacement policy for TLB entries
- Add validation checks for memory addresses before TLB insertion
- Ensure proper page alignment for guest and host addresses
- Enhance alignment fault handling with instruction skipping as fallback
- Add comprehensive debug logging for memory access errors
- Improve error recovery in guest memory access scenarios

These changes should significantly reduce crashes during emulation on
Android devices by gracefully handling memory access edge cases that
previously resulted in hard crashes.

Co-Authored-By: Camille LaVey <camillelavey@citron-emu.org>
Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron 2025-02-28 17:11:07 +10:00
parent 9b293c3a98
commit cfe437aacf

View file

@ -144,15 +144,22 @@ bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info,
auto* fpctx = GetFloatingPointState(host_ctx);
auto& memory = guest_ctx->system->ApplicationMemory();
// Match and execute an instruction.
// Log the alignment fault for debugging
LOG_DEBUG(Core_ARM, "Alignment fault at PC={:X}", host_ctx.pc);
// Try to handle the instruction
auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx);
if (next_pc) {
host_ctx.pc = *next_pc;
return true;
}
// We couldn't handle the access.
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
// If we couldn't handle it, try to skip the instruction as a fallback
LOG_DEBUG(Core_ARM, "Could not handle alignment fault, skipping instruction");
host_ctx.pc += 4; // Skip to next instruction
// Return true to continue execution
return true;
}
bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
@ -163,24 +170,27 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
// Get the ArmNce instance from the guest context
ArmNce* nce = guest_ctx->parent;
// Check TLB first with proper synchronization
// Check TLB first
if (TlbEntry* entry = nce->FindTlbEntry(fault_addr)) {
if (!entry->writable && info->si_code == SEGV_ACCERR) {
LOG_DEBUG(Core_ARM, "Write to read-only memory at {:X}", fault_addr);
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
}
return true;
}
// TLB miss handling
// TLB miss handling with better error checking
if (memory.InvalidateNCE(fault_addr, Memory::CITRON_PAGESIZE)) {
// Get the host address directly since GetHostAddressInfo isn't available
const u64 host_addr = reinterpret_cast<u64>(memory.GetPointer(fault_addr));
const bool writable = true; // Default to writable for now
if (host_addr) {
nce->AddTlbEntry(fault_addr, host_addr, Memory::CITRON_PAGESIZE, writable);
nce->AddTlbEntry(fault_addr, host_addr, Memory::CITRON_PAGESIZE, true);
return true;
} else {
LOG_DEBUG(Core_ARM, "Failed to get host address for guest address {:X}", fault_addr);
}
} else {
LOG_DEBUG(Core_ARM, "Memory invalidation failed for address {:X}", fault_addr);
}
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
@ -396,15 +406,17 @@ void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) {
TlbEntry* ArmNce::FindTlbEntry(u64 guest_addr) {
std::lock_guard lock(m_tlb_mutex);
const size_t set_index = GetTlbSetIndex(guest_addr);
const size_t set_start = set_index * TLB_WAYS;
for (size_t i = 0; i < TLB_WAYS; i++) {
TlbEntry& entry = m_tlb[set_start + i];
// Simple linear search - more reliable than complex indexing
for (size_t i = 0; i < TLB_SIZE; i++) {
TlbEntry& entry = m_tlb[i];
if (entry.valid &&
guest_addr >= entry.guest_addr &&
guest_addr < (entry.guest_addr + entry.size)) {
UpdateTlbEntryStats(entry);
// Simple access tracking - just increment counter
if (entry.access_count < 1000) { // Prevent overflow
entry.access_count++;
}
return &entry;
}
}
@ -412,65 +424,55 @@ TlbEntry* ArmNce::FindTlbEntry(u64 guest_addr) {
}
void ArmNce::AddTlbEntry(u64 guest_addr, u64 host_addr, u32 size, bool writable) {
// Validate addresses before proceeding
if (!host_addr) {
LOG_ERROR(Core_ARM, "Invalid host address for guest address {:X}", guest_addr);
return;
}
std::lock_guard lock(m_tlb_mutex);
const size_t set_index = GetTlbSetIndex(guest_addr);
const size_t set_start = set_index * TLB_WAYS;
// First try to find an invalid entry
size_t replace_idx = TLB_SIZE;
for (size_t i = 0; i < TLB_SIZE; i++) {
if (!m_tlb[i].valid) {
replace_idx = i;
break;
}
}
// Find replacement entry using enhanced replacement policy
const size_t replace_idx = FindReplacementEntry(set_start);
// If no invalid entries, use simple LRU
if (replace_idx == TLB_SIZE) {
u32 lowest_count = UINT32_MAX;
for (size_t i = 0; i < TLB_SIZE; i++) {
if (m_tlb[i].access_count < lowest_count) {
lowest_count = m_tlb[i].access_count;
replace_idx = i;
}
}
}
// Safety check
if (replace_idx >= TLB_SIZE) {
replace_idx = 0; // Fallback to first entry if something went wrong
}
// Page align the addresses for consistency
const u64 page_mask = size - 1;
const u64 aligned_guest = guest_addr & ~page_mask;
const u64 aligned_host = host_addr & ~page_mask;
m_tlb[replace_idx] = {
.guest_addr = guest_addr & ~(size - 1),
.host_addr = host_addr & ~(size - 1),
.guest_addr = aligned_guest,
.host_addr = aligned_host,
.size = size,
.valid = true,
.writable = writable,
.last_access_time = ++m_tlb_access_counter,
.last_access_time = 0, // Not used in simplified implementation
.access_count = 1
};
}
size_t ArmNce::GetTlbSetIndex(u64 guest_addr) const {
// Improved set index calculation to reduce conflicts
return ((guest_addr >> 12) ^ (guest_addr >> 18)) % TLB_SETS;
}
size_t ArmNce::FindReplacementEntry(size_t set_start) {
u64 oldest_access = std::numeric_limits<u64>::max();
size_t replace_idx = set_start;
// Find invalid entry first
for (size_t i = 0; i < TLB_WAYS; i++) {
const size_t idx = set_start + i;
if (!m_tlb[idx].valid) {
return idx;
}
}
// Otherwise use LRU with access frequency consideration
for (size_t i = 0; i < TLB_WAYS; i++) {
const size_t idx = set_start + i;
const TlbEntry& entry = m_tlb[idx];
// Factor in both access time and frequency
u64 weight = entry.last_access_time + (entry.access_count << 8);
if (weight < oldest_access) {
oldest_access = weight;
replace_idx = idx;
}
}
return replace_idx;
}
void ArmNce::UpdateTlbEntryStats(TlbEntry& entry) {
entry.last_access_time = ++m_tlb_access_counter;
if (entry.access_count < std::numeric_limits<u32>::max()) {
entry.access_count++;
}
}
void ArmNce::InvalidateTlb() {
std::lock_guard lock(m_tlb_mutex);
for (auto& entry : m_tlb) {