Files
Xenia-Canary/src/xenia/cpu/processor.h
chss95cs@gmail.com 90c771526d "Fix" debug console, we were checking the cvar before any cvars were loaded, and the condition it checks in AttachConsole is somehow always false
Remove dead #if 0'd code in math.h

On amd64, page_size == 4096 constant, on amd64 w/ win32, allocation_granularity == 65536. These values for x86 windows havent changed over the last 20 years so this is probably safe
and gives a modest code size reduction

Enable XE_USE_KUSER_SHARED. This sources host time from KUSER_SHARED instead of from QueryPerformanceCounter, which is far faster, but only has a granularity of 100 nanoseconds.

In some games seemingly random crashes were happening that were hard to trace because
the faulting thread was actually not the one that was misbehaving, another threads stack was underflowing into the faulting thread.

Added a bunch of code to synchronize the guest stack and host stack so that if a guest longjmps the host's stack will be adjusted.
Changes were also made to allow the guest to call into a piece of an existing x64 function.

This synchronization might have a slight performance impact on lower end cpus, to disable it set enable_host_guest_stack_synchronization to false.
It is possible it may have introduced regressions, but i dont know of any yet

So far, i know the synchronization change fixes the "hub crash" in super sonic and allows the game "london 2012" to go ingame.

Removed emit_useless_fpscr_updates, not emitting these updates breaks the raiden game

MapGuestAddressToMachineCode now returns nullptr if no address was found, instead of the start of the function

add Processor::LookupModule

Add Backend::DeinitializeBackendContext
Use WriteRegisterRangeFromRing_WithKnownBound<0, 0xFFFF> in WriteRegisterRangeFromRing for inlining (previously regressed on performance of ExecutePacketType0)

add notes about flags that trap in XamInputGetCapabilities

0 == 3 in XamInputGetCapabilities

Name arg 2 of XamInputSetState

PrefetchW in critical section kernel funcs if available & doing cmpxchg

Add terminated field to X_KTHREAD, set it on termination

Expanded the logic of NtResumeThread/NtSuspendThread to include checking the type of the handle (in release, LookupObject doesnt seem to do anything with the type)
and returning X_STATUS_OBJECT_TYPE_MISMATCH if invalid. Do termination check in NtSuspendThread.

Add basic host exception messagebox, need to flesh it out more (maybe use the new stack tracking stuff if on guest thrd?)

Add rdrand patching hack, mostly affects users with nvidia cards who have many threads on zen

Use page_size_shift in more places

Once again disable precompilation! Raiden is mostly weird ppc asm which probably breaks the precompilation. The code is still useful for running the compiler over the whole of an xex in debug to test for issues
"Fix" debug console, we were checking the cvar before any cvars were loaded, and the condition it checks in AttachConsole is somehow always false

Remove dead #if 0'd code in math.h

On amd64, page_size == 4096 constant, on amd64 w/ win32, allocation_granularity == 65536. These values for x86 windows havent changed over the last 20 years so this is probably safe
and gives a modest code size reduction

Enable XE_USE_KUSER_SHARED. This sources host time from KUSER_SHARED instead of from QueryPerformanceCounter, which is far faster, but only has a granularity of 100 nanoseconds.

In some games seemingly random crashes were happening that were hard to trace because
the faulting thread was actually not the one that was misbehaving, another threads stack was underflowing into the faulting thread.

Added a bunch of code to synchronize the guest stack and host stack so that if a guest longjmps the host's stack will be adjusted.
Changes were also made to allow the guest to call into a piece of an existing x64 function.

This synchronization might have a slight performance impact on lower end cpus, to disable it set enable_host_guest_stack_synchronization to false.
It is possible it may have introduced regressions, but i dont know of any yet

So far, i know the synchronization change fixes the "hub crash" in super sonic and allows the game "london 2012" to go ingame.

Removed emit_useless_fpscr_updates, not emitting these updates breaks the raiden game

MapGuestAddressToMachineCode now returns nullptr if no address was found, instead of the start of the function

add Processor::LookupModule

Add Backend::DeinitializeBackendContext
Use WriteRegisterRangeFromRing_WithKnownBound<0, 0xFFFF> in WriteRegisterRangeFromRing for inlining (previously regressed on performance of ExecutePacketType0)

add notes about flags that trap in XamInputGetCapabilities

0 == 3 in XamInputGetCapabilities

Name arg 2 of XamInputSetState

PrefetchW in critical section kernel funcs if available & doing cmpxchg

Add terminated field to X_KTHREAD, set it on termination

Expanded the logic of NtResumeThread/NtSuspendThread to include checking the type of the handle (in release, LookupObject doesnt seem to do anything with the type)
and returning X_STATUS_OBJECT_TYPE_MISMATCH if invalid. Do termination check in NtSuspendThread.

Add basic host exception messagebox, need to flesh it out more (maybe use the new stack tracking stuff if on guest thrd?)

Add rdrand patching hack, mostly affects users with nvidia cards who have many threads on zen

Use page_size_shift in more places

Once again disable precompilation! Raiden is mostly weird ppc asm which probably breaks the precompilation. The code is still useful for running the compiler over the whole of an xex in debug to test for issues
2022-11-27 09:39:33 -08:00

281 lines
10 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_PROCESSOR_H_
#define XENIA_CPU_PROCESSOR_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "xenia/base/cvar.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/base/mutex.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/debug_listener.h"
#include "xenia/cpu/entry_table.h"
#include "xenia/cpu/export_resolver.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/module.h"
#include "xenia/cpu/ppc/ppc_frontend.h"
#include "xenia/cpu/thread_debug_info.h"
#include "xenia/cpu/thread_state.h"
#include "xenia/memory.h"
DECLARE_bool(debug);
namespace xe {
namespace cpu {
constexpr fourcc_t kProcessorSaveSignature = make_fourcc("PROC");
class Breakpoint;
class StackWalker;
class XexModule;
enum class Irql : uint32_t {
PASSIVE = 0,
APC = 1,
DISPATCH = 2,
DPC = 3,
};
// Describes the current state of the emulator as known to the debugger.
// This determines which state the debugger is in and what operations are
// allowed.
enum class ExecutionState {
// Target is running; the debugger is not waiting for any events.
kRunning,
// Target is running in stepping mode with the debugger waiting for feedback.
kStepping,
// Target is paused for debugging.
kPaused,
// Target has been stopped and cannot be restarted (crash, etc).
kEnded,
};
class Processor {
public:
Processor(Memory* memory, ExportResolver* export_resolver);
~Processor();
Memory* memory() const { return memory_; }
StackWalker* stack_walker() const { return stack_walker_.get(); }
ppc::PPCFrontend* frontend() const { return frontend_.get(); }
backend::Backend* backend() const { return backend_.get(); }
ExportResolver* export_resolver() const { return export_resolver_; }
bool Setup(std::unique_ptr<backend::Backend> backend);
// Runs any pre-launch logic once the module and thread have been setup.
void PreLaunch();
// The current execution state of the emulator.
ExecutionState execution_state() const { return execution_state_; }
// True if a debug listener is attached and the debugger is active.
bool is_debugger_attached() const { return !!debug_listener_; }
// Gets the active debug listener, if any.
DebugListener* debug_listener() const { return debug_listener_; }
// Sets the active debug listener, if any.
// This can be used to detach the listener.
void set_debug_listener(DebugListener* debug_listener);
// Sets a handler that will be called from a random thread when a debugger
// listener is required (such as on a breakpoint hit/etc).
// Will only be called if the debug listener has not already been specified
// with set_debug_listener.
void set_debug_listener_request_handler(
std::function<DebugListener*(Processor*)> handler) {
debug_listener_handler_ = std::move(handler);
}
void set_debug_info_flags(uint32_t debug_info_flags) {
debug_info_flags_ = debug_info_flags;
}
bool AddModule(std::unique_ptr<Module> module);
void RemoveModule(const std::string_view name);
Module* GetModule(const std::string_view name);
std::vector<Module*> GetModules();
Module* builtin_module() const { return builtin_module_; }
Function* DefineBuiltin(const std::string_view name,
BuiltinFunction::Handler handler, void* arg0,
void* arg1);
Function* QueryFunction(uint32_t address);
std::vector<Function*> FindFunctionsWithAddress(uint32_t address);
void RemoveFunctionByAddress(uint32_t address);
Function* LookupFunction(uint32_t address);
Module* LookupModule(uint32_t address);
Function* LookupFunction(Module* module, uint32_t address);
Function* ResolveFunction(uint32_t address);
bool Execute(ThreadState* thread_state, uint32_t address);
bool ExecuteRaw(ThreadState* thread_state, uint32_t address);
uint64_t Execute(ThreadState* thread_state, uint32_t address, uint64_t args[],
size_t arg_count);
uint64_t ExecuteInterrupt(ThreadState* thread_state, uint32_t address,
uint64_t args[], size_t arg_count);
Irql RaiseIrql(Irql new_value);
void LowerIrql(Irql old_value);
bool Save(ByteStream* stream);
bool Restore(ByteStream* stream);
// Returns a list of debugger info for all threads that have ever existed.
// This is the preferred way to sample thread state vs. attempting to ask
// the kernel.
std::vector<ThreadDebugInfo*> QueryThreadDebugInfos();
// Returns the debugger info for the given thread.
ThreadDebugInfo* QueryThreadDebugInfo(uint32_t thread_id);
// Adds a breakpoint to the debugger and activates it (if enabled).
// The given breakpoint will not be owned by the debugger and must remain
// allocated so long as it is added.
void AddBreakpoint(Breakpoint* breakpoint);
// Removes a breakpoint from the debugger and deactivates it.
void RemoveBreakpoint(Breakpoint* breakpoint);
// Finds a breakpoint that may be registered at the given address.
Breakpoint* FindBreakpoint(uint32_t address);
// Returns all currently registered breakpoints.
std::vector<Breakpoint*> breakpoints() const;
// Shows the debug listener, focusing it if it already exists.
void ShowDebugger();
// Pauses target execution by suspending all threads.
// The debug listener will be requested if it has not been attached.
void Pause();
// Continues target execution from wherever it is.
// This will cancel any active step operations and resume all threads.
void Continue();
// Steps the given thread a single x64 host instruction.
// If the step is over a branch the branch will be followed.
void StepHostInstruction(uint32_t thread_id);
// Steps the given thread a single PPC guest instruction.
// If the step is over a branch the branch will be followed.
void StepGuestInstruction(uint32_t thread_id);
// Steps the given thread until the guest address is hit.
// Returns false if the step could not be completed (invalid target address).
bool StepToGuestAddress(uint32_t thread_id, uint32_t pc);
// Steps the given thread to the target of the branch at the specified guest
// address. The address must specify a branch instruction.
// Returns the new PC guest address.
uint32_t StepIntoGuestBranchTarget(uint32_t thread_id, uint32_t pc);
// Steps the thread to a point where it's safe to terminate or read its
// context. Returns the PC after we've finished stepping.
// Pass true for ignore_host if you've stopped the thread yourself
// in host code you want to ignore.
// Returns the new PC guest address.
uint32_t StepToGuestSafePoint(uint32_t thread_id, bool ignore_host = false);
public:
// TODO(benvanik): hide.
void OnThreadCreated(uint32_t handle, ThreadState* thread_state,
Thread* thread);
void OnThreadExit(uint32_t thread_id);
void OnThreadDestroyed(uint32_t thread_id);
void OnThreadEnteringWait(uint32_t thread_id);
void OnThreadLeavingWait(uint32_t thread_id);
bool OnUnhandledException(Exception* ex);
bool OnThreadBreakpointHit(Exception* ex);
uint8_t* AllocateFunctionTraceData(size_t size);
private:
// Synchronously demands a debug listener.
void DemandDebugListener();
// Suspends all known threads (except the caller).
bool SuspendAllThreads();
// Resumes the given thread.
bool ResumeThread(uint32_t thread_id);
// Resumes all known threads (except the caller).
bool ResumeAllThreads();
// Updates all cached thread execution info (state, call stacks, etc).
// The given override thread handle and context will be used in place of
// sampled values for that thread.
void UpdateThreadExecutionStates(
uint32_t override_handle = 0,
HostThreadContext* override_context = nullptr);
// Suspends all breakpoints, uninstalling them as required.
// No breakpoints will be triggered until they are resumed.
void SuspendAllBreakpoints();
// Resumes all breakpoints, re-installing them if required.
void ResumeAllBreakpoints();
void OnFunctionDefined(Function* function);
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
void OnStepCompleted(ThreadDebugInfo* thread_info);
void OnBreakpointHit(ThreadDebugInfo* thread_info, Breakpoint* breakpoint);
// Calculates the next guest instruction based on the current thread state and
// current PC. This will look for branches and other control flow
// instructions.
uint32_t CalculateNextGuestInstruction(ThreadDebugInfo* thread_info,
uint32_t current_pc);
bool DemandFunction(Function* function);
Memory* memory_ = nullptr;
std::unique_ptr<StackWalker> stack_walker_;
std::function<DebugListener*(Processor*)> debug_listener_handler_;
DebugListener* debug_listener_ = nullptr;
// Which debug features are enabled in generated code.
uint32_t debug_info_flags_ = 0;
// If specified, the file trace data gets written to when running.
std::filesystem::path functions_trace_path_;
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
std::unique_ptr<ppc::PPCFrontend> frontend_;
std::unique_ptr<backend::Backend> backend_;
ExportResolver* export_resolver_ = nullptr;
EntryTable entry_table_;
xe::global_critical_region global_critical_region_;
ExecutionState execution_state_ = ExecutionState::kPaused;
std::vector<std::unique_ptr<Module>> modules_;
Module* builtin_module_ = nullptr;
uint32_t next_builtin_address_ = 0xFFFF0000u;
// Maps thread ID to state. Updated on thread create, and threads are never
// removed. Must be guarded with the global lock.
std::map<uint32_t, std::unique_ptr<ThreadDebugInfo>> thread_debug_infos_;
// TODO(benvanik): cleanup/change structures.
std::vector<Breakpoint*> breakpoints_;
Irql irql_;
};
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_PROCESSOR_H_