diff options
| author | Lioncash <mai.iam2048@gmail.com> | 2023-04-24 09:05:49 -0400 |
|---|---|---|
| committer | Lioncash <mai.iam2048@gmail.com> | 2023-04-24 09:10:43 -0400 |
| commit | d991cbaf3c098044323e0330cb66dd76138f8d43 (patch) | |
| tree | 43c0ab04aba889cb9c3bf8402fa8bc80526c86b3 /Source/Core/Common/Debug/CodeTrace.cpp | |
| parent | ffbbd72741aa74d8bc602635ac9fdf9f9d0bb1bd (diff) | |
Common: Move CodeTrace.cpp/.h into Core
This interface relies on Core details and shouldn't be in Common to
begin with, since it's not a general utility.
Diffstat (limited to 'Source/Core/Common/Debug/CodeTrace.cpp')
| -rw-r--r-- | Source/Core/Common/Debug/CodeTrace.cpp | 396 |
1 files changed, 0 insertions, 396 deletions
diff --git a/Source/Core/Common/Debug/CodeTrace.cpp b/Source/Core/Common/Debug/CodeTrace.cpp deleted file mode 100644 index 88ff656a92..0000000000 --- a/Source/Core/Common/Debug/CodeTrace.cpp +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright 2022 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Common/Debug/CodeTrace.h" - -#include <algorithm> -#include <chrono> -#include <regex> - -#include "Common/Event.h" -#include "Core/Core.h" -#include "Core/Debugger/PPCDebugInterface.h" -#include "Core/HW/CPU.h" -#include "Core/PowerPC/PowerPC.h" -#include "Core/System.h" - -namespace -{ -bool IsInstructionLoadStore(std::string_view ins) -{ - return (ins.starts_with('l') && !ins.starts_with("li")) || ins.starts_with("st") || - ins.starts_with("psq_l") || ins.starts_with("psq_s"); -} - -u32 GetMemoryTargetSize(std::string_view instr) -{ - // Word-size operations are taken as the default, check the others. - auto op = instr.substr(0, 4); - - constexpr char BYTE_TAG = 'b'; - constexpr char HALF_TAG = 'h'; - constexpr char DOUBLE_WORD_TAG = 'd'; - constexpr char PAIRED_TAG = 'p'; - - // Actual range is 0 to size - 1; - if (op.find(BYTE_TAG) != std::string::npos) - { - return 1; - } - else if (op.find(HALF_TAG) != std::string::npos) - { - return 2; - } - else if (op.find(DOUBLE_WORD_TAG) != std::string::npos || - op.find(PAIRED_TAG) != std::string::npos) - { - return 8; - } - - return 4; -} - -bool CompareMemoryTargetToTracked(const std::string& instr, const u32 mem_target, - const std::set<u32>& mem_tracked) -{ - // This function is hit often and should be optimized. - auto it_lower = std::lower_bound(mem_tracked.begin(), mem_tracked.end(), mem_target); - - if (it_lower == mem_tracked.end()) - return false; - else if (*it_lower == mem_target) - return true; - - // If the base value doesn't hit, still need to check if longer values overlap. - return *it_lower < mem_target + GetMemoryTargetSize(instr); -} -} // namespace - -void CodeTrace::SetRegTracked(const std::string& reg) -{ - m_reg_autotrack.push_back(reg); -} - -InstructionAttributes CodeTrace::GetInstructionAttributes(const TraceOutput& instruction) const -{ - auto& system = Core::System::GetInstance(); - - // Slower process of breaking down saved instruction. Only used when stepping through code if a - // decision has to be made, otherwise used afterwards on a log file. - InstructionAttributes tmp_attributes; - tmp_attributes.instruction = instruction.instruction; - tmp_attributes.address = system.GetPPCState().pc; - std::string instr = instruction.instruction; - std::smatch match; - - // Convert sp, rtoc, and ps to r1, r2, and F#. ps is handled like a float operation. - static const std::regex replace_sp("(\\W)sp"); - instr = std::regex_replace(instr, replace_sp, "$1r1"); - static const std::regex replace_rtoc("rtoc"); - instr = std::regex_replace(instr, replace_rtoc, "r2"); - static const std::regex replace_ps("(\\W)p(\\d+)"); - instr = std::regex_replace(instr, replace_ps, "$1f$2"); - - // Pull all register numbers out and store them. Limited to Reg0 if ps operation, as ps get - // too complicated to track easily. - // ex: add r4, r5, r6 -> r4 = Reg0, r5 = Reg1, r6 = Reg2. Reg0 is always the target register. - static const std::regex regis( - "\\W([rfp]\\d+)[^r^f]*(?:([rf]\\d+))?[^r^f\\d]*(?:([rf]\\d+))?[^r^f\\d]*(?:([rf]\\d+))?", - std::regex::optimize); - - if (std::regex_search(instr, match, regis)) - { - tmp_attributes.reg0 = match.str(1); - if (match[2].matched) - tmp_attributes.reg1 = match.str(2); - if (match[3].matched) - tmp_attributes.reg2 = match.str(3); - if (match[4].matched) - tmp_attributes.reg3 = match.str(4); - - if (instruction.memory_target) - { - tmp_attributes.memory_target = instruction.memory_target; - tmp_attributes.memory_target_size = GetMemoryTargetSize(instr); - - if (instr.starts_with("st") || instr.starts_with("psq_s")) - tmp_attributes.is_store = true; - else - tmp_attributes.is_load = true; - } - } - - return tmp_attributes; -} - -TraceOutput CodeTrace::SaveCurrentInstruction(const Core::CPUThreadGuard& guard) const -{ - auto& system = guard.GetSystem(); - auto& power_pc = system.GetPowerPC(); - auto& ppc_state = power_pc.GetPPCState(); - auto& debug_interface = power_pc.GetDebugInterface(); - - // Quickly save instruction and memory target for fast logging. - TraceOutput output; - const std::string instr = debug_interface.Disassemble(&guard, ppc_state.pc); - output.instruction = instr; - output.address = ppc_state.pc; - - if (IsInstructionLoadStore(output.instruction)) - output.memory_target = debug_interface.GetMemoryAddressFromInstruction(instr); - - return output; -} - -AutoStepResults CodeTrace::AutoStepping(const Core::CPUThreadGuard& guard, bool continue_previous, - AutoStop stop_on) -{ - AutoStepResults results; - - if (m_recording) - return results; - - TraceOutput pc_instr = SaveCurrentInstruction(guard); - const InstructionAttributes instr = GetInstructionAttributes(pc_instr); - - // Not an instruction we should start autostepping from (ie branches). - if (instr.reg0.empty() && !continue_previous) - return results; - - m_recording = true; - - // Once autostep stops, it can be told to continue running without resetting the tracked - // registers and memory. - if (!continue_previous) - { - m_reg_autotrack.clear(); - m_mem_autotrack.clear(); - m_reg_autotrack.push_back(instr.reg0); - - // It wouldn't necessarily be wrong to also record the memory of a load operation, as the - // value exists there too. May or may not be desirable depending on task. Leaving it out. - if (instr.is_store) - { - const u32 size = GetMemoryTargetSize(instr.instruction); - for (u32 i = 0; i < size; i++) - m_mem_autotrack.insert(instr.memory_target.value() + i); - } - } - - // Count is important for feedback on how much work was done. - - HitType hit = HitType::SKIP; - HitType stop_condition = HitType::SAVELOAD; - - // Could use bit flags, but I organized it to have decreasing levels of verbosity, so the - // less-than comparison ignores what is needed for the current usage. - if (stop_on == AutoStop::Always) - stop_condition = HitType::SAVELOAD; - else if (stop_on == AutoStop::Used) - stop_condition = HitType::PASSIVE; - else if (stop_on == AutoStop::Changed) - stop_condition = HitType::ACTIVE; - - auto& power_pc = guard.GetSystem().GetPowerPC(); - power_pc.GetBreakPoints().ClearAllTemporary(); - using clock = std::chrono::steady_clock; - clock::time_point timeout = clock::now() + std::chrono::seconds(4); - - PowerPC::CoreMode old_mode = power_pc.GetMode(); - power_pc.SetMode(PowerPC::CoreMode::Interpreter); - - do - { - power_pc.SingleStep(); - - pc_instr = SaveCurrentInstruction(guard); - hit = TraceLogic(pc_instr); - results.count += 1; - } while (clock::now() < timeout && hit < stop_condition && - !(m_reg_autotrack.empty() && m_mem_autotrack.empty())); - - // Report the timeout to the caller. - if (clock::now() >= timeout) - results.timed_out = true; - - power_pc.SetMode(old_mode); - m_recording = false; - - results.reg_tracked = m_reg_autotrack; - results.mem_tracked = m_mem_autotrack; - - // Doesn't currently need to report the hit type to the caller. Denoting when the reg and mem - // trackers are both empty is important, as it means our target was overwritten and can no longer - // be tracked. Different actions can be taken on a timeout vs empty trackers, so they are reported - // individually. - return results; -} - -HitType CodeTrace::TraceLogic(const TraceOutput& current_instr, bool first_hit) -{ - // Tracks the original value that is in the targeted register or memory through loads, stores, - // register moves, and value changes. Also finds when it is used. ps operations are not fully - // supported. -ux memory instructions may need special cases. - // Should not be called if reg and mem tracked are empty. - - // Using a std::set because it can easily insert the memory range being accessed without - // causing duplicates, and quickly erases all members of the memory range without caring if the - // element actually exists. - - bool mem_hit = false; - if (current_instr.memory_target && !m_mem_autotrack.empty()) - { - mem_hit = CompareMemoryTargetToTracked(current_instr.instruction, *current_instr.memory_target, - m_mem_autotrack); - } - - // Optimization for tracking a memory target when no registers are being tracked. - if (m_reg_autotrack.empty() && !mem_hit) - return HitType::SKIP; - - // Break instruction down into parts to be analyzed. - const InstructionAttributes instr = GetInstructionAttributes(current_instr); - - // Not an instruction we care about (branches). - if (instr.reg0.empty()) - return HitType::SKIP; - - // The reg_itr will be used later for erasing. - auto reg_itr = std::find(m_reg_autotrack.begin(), m_reg_autotrack.end(), instr.reg0); - const bool match_reg123 = - (!instr.reg1.empty() && std::find(m_reg_autotrack.begin(), m_reg_autotrack.end(), - instr.reg1) != m_reg_autotrack.end()) || - (!instr.reg2.empty() && std::find(m_reg_autotrack.begin(), m_reg_autotrack.end(), - instr.reg2) != m_reg_autotrack.end()) || - (!instr.reg3.empty() && std::find(m_reg_autotrack.begin(), m_reg_autotrack.end(), - instr.reg3) != m_reg_autotrack.end()); - const bool match_reg0 = reg_itr != m_reg_autotrack.end(); - - if (!match_reg0 && !match_reg123 && !mem_hit) - return HitType::SKIP; - - // Checks if the intstruction is a type that needs special handling. - const auto CompareInstruction = [](std::string_view instruction, const auto& type_compare) { - return std::any_of(type_compare.begin(), type_compare.end(), - [&instruction](std::string_view s) { return instruction.starts_with(s); }); - }; - - // Exclusions from updating tracking logic. mt operations are too complex and specialized. - // Combiner used later. - static const std::array<std::string_view, 3> exclude{"dc", "ic", "mt"}; - static const std::array<std::string_view, 2> compare{"c", "fc"}; - - // rlwimi, at least, can preserve parts of the target register. Not sure if rldimi can too or if - // there are any others like this. - static const std::array<std::string_view, 1> combiner{"rlwimi"}; - - static const std::array<std::string_view, 2> mover{"mr", "fmr"}; - - // Link register for when r0 gets overwritten - if (instr.instruction.starts_with("mflr") && match_reg0) - { - m_reg_autotrack.erase(reg_itr); - return HitType::OVERWRITE; - } - if (instr.instruction.starts_with("mtlr") && match_reg0) - { - // LR is not something tracked - return HitType::MOVED; - } - - if (CompareInstruction(instr.instruction, exclude)) - return HitType::SKIP; - else if (CompareInstruction(instr.instruction, compare)) - return HitType::PASSIVE; - else if (match_reg123 && !match_reg0 && (instr.is_store || instr.is_load)) - return HitType::POINTER; - - // Update tracking logic. At this point a memory or register hit happened. - // Save/Load - if (instr.memory_target) - { - if (mem_hit) - { - // If hit a tracked memory. Load -> Add register to tracked. Store -> Remove tracked memory - // if overwritten. - - if (instr.is_load && !match_reg0) - { - m_reg_autotrack.push_back(instr.reg0); - return HitType::SAVELOAD; - } - else if (instr.is_store && !match_reg0) - { - // On First Hit it wouldn't necessarily be wrong to track the register, which contains the - // same value. A matter of preference. - if (first_hit) - return HitType::SAVELOAD; - - for (u32 i = 0; i < instr.memory_target_size; i++) - m_mem_autotrack.erase(*instr.memory_target + i); - - return HitType::OVERWRITE; - } - else - { - // If reg0 and store/load are both already tracked, do nothing. - return HitType::SAVELOAD; - } - } - else if (instr.is_store && match_reg0) - { - // If store to untracked memory, then track memory. - for (u32 i = 0; i < instr.memory_target_size; i++) - m_mem_autotrack.insert(*instr.memory_target + i); - - return HitType::SAVELOAD; - } - else if (instr.is_load && match_reg0) - { - // Not wrong to track load memory_target here. Preference. - if (first_hit) - return HitType::SAVELOAD; - - // If untracked load is overwriting tracked register, then remove register - m_reg_autotrack.erase(reg_itr); - return HitType::OVERWRITE; - } - } - else if (!match_reg0 && !match_reg123) - { - // Skip if no matches. Happens most often. - return HitType::SKIP; - } - else - { - // If tracked register data is being stored in a new register, save new register. - if (match_reg123 && !match_reg0) - { - m_reg_autotrack.push_back(instr.reg0); - - // This should include any instruction that can reach this point and is not ACTIVE. Can only - // think of mr at this time. - if (CompareInstruction(instr.instruction, mover)) - return HitType::MOVED; - - return HitType::ACTIVE; - } - // If tracked register is overwritten, stop tracking. - else if (match_reg0 && !match_reg123) - { - if (CompareInstruction(instr.instruction, combiner) || first_hit) - return HitType::UPDATED; - - m_reg_autotrack.erase(reg_itr); - return HitType::OVERWRITE; - } - else if (match_reg0 && match_reg123) - { - // Or moved - return HitType::UPDATED; - } - } - - // Should not reach this - return HitType::SKIP; -} |
