diff --git a/src/alloy/core.h b/src/alloy/core.h index 421718e7a..d61b3dd2c 100644 --- a/src/alloy/core.h +++ b/src/alloy/core.h @@ -14,6 +14,7 @@ #include #include +#include #include #include diff --git a/src/alloy/delegate.h b/src/alloy/delegate.h new file mode 100644 index 000000000..e6ad2fcd1 --- /dev/null +++ b/src/alloy/delegate.h @@ -0,0 +1,76 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2013 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef ALLOY_DELEGATE_H_ +#define ALLOY_DELEGATE_H_ + +#include + +#include +#include + + +namespace alloy { + + +// TODO(benvanik): go lockfree, and don't hold the lock while emitting. + +template +class Delegate { +public: + Delegate() { + lock_ = AllocMutex(); + } + ~Delegate() { + FreeMutex(lock_); + } + + typedef std::function listener_t; + + void AddListener(listener_t const& listener) { + LockMutex(lock_); + listeners_.push_back(listener); + UnlockMutex(lock_); + } + + void RemoveListener(listener_t const& listener) { + LockMutex(lock_); + for (auto it = listeners_.begin(); it != listeners_.end(); ++it) { + if (it == listener) { + listeners_.erase(it); + break; + } + } + UnlockMutex(lock_); + } + + void RemoveAllListeners() { + LockMutex(lock_); + listeners_.clear(); + UnlockMutex(lock_); + } + + void operator()(T& e) const { + LockMutex(lock_); + for (auto &listener : listeners_) { + listener(e); + } + UnlockMutex(lock_); + } + +private: + alloy::Mutex* lock_; + std::vector listeners_; +}; + + +} // namespace alloy + + +#endif // ALLOY_DELEGATE_H_ diff --git a/src/alloy/frontend/ppc/ppc_context.h b/src/alloy/frontend/ppc/ppc_context.h index a16901ea0..a3562421c 100644 --- a/src/alloy/frontend/ppc/ppc_context.h +++ b/src/alloy/frontend/ppc/ppc_context.h @@ -65,7 +65,7 @@ typedef union { #pragma pack(push, 4) typedef struct XECACHEALIGN64 PPCContext_s { // Most frequently used registers first. - uint64_t r[32]; // General purpose registers + uint64_t r[32]; // General purpose registers uint64_t lr; // Link register uint64_t ctr; // Count register @@ -190,6 +190,7 @@ typedef struct XECACHEALIGN64 PPCContext_s { uint8_t* membase; runtime::Runtime* runtime; runtime::ThreadState* thread_state; + uint32_t suspend_flag; void SetRegFromString(const char* name, const char* value); bool CompareRegWithString(const char* name, const char* value, diff --git a/src/alloy/mutex.h b/src/alloy/mutex.h index 2a5922099..3b6ed6391 100644 --- a/src/alloy/mutex.h +++ b/src/alloy/mutex.h @@ -10,7 +10,7 @@ #ifndef ALLOY_MUTEX_H_ #define ALLOY_MUTEX_H_ -#include +#include namespace alloy { diff --git a/src/alloy/runtime/debugger.cc b/src/alloy/runtime/debugger.cc index 2211daaab..cd2979dd4 100644 --- a/src/alloy/runtime/debugger.cc +++ b/src/alloy/runtime/debugger.cc @@ -25,11 +25,55 @@ Breakpoint::~Breakpoint() { Debugger::Debugger(Runtime* runtime) : runtime_(runtime) { + threads_lock_ = AllocMutex(); breakpoints_lock_ = AllocMutex(); } Debugger::~Debugger() { FreeMutex(breakpoints_lock_); + FreeMutex(threads_lock_); +} + +int Debugger::SuspendAllThreads(uint32_t timeout_ms) { + int result = 0; + LockMutex(threads_lock_); + for (auto it = threads_.begin(); it != threads_.end(); ++it) { + ThreadState* thread_state = it->second; + if (thread_state->Suspend(timeout_ms)) { + result = 1; + } + } + UnlockMutex(threads_lock_); + return result; +} + +int Debugger::ResumeThread(uint32_t thread_id) { + LockMutex(threads_lock_); + auto it = threads_.find(thread_id); + if (it == threads_.end()) { + UnlockMutex(threads_lock_); + return 1; + } + + // Found thread. Note that it could be deleted as soon as we unlock. + ThreadState* thread_state = it->second; + int result = thread_state->Resume(); + + UnlockMutex(threads_lock_); + return result; +} + +int Debugger::ResumeAllThreads() { + int result = 0; + LockMutex(threads_lock_); + for (auto it = threads_.begin(); it != threads_.end(); ++it) { + ThreadState* thread_state = it->second; + if (thread_state->Resume()) { + result = 1; + } + } + UnlockMutex(threads_lock_); + return result; } int Debugger::AddBreakpoint(Breakpoint* breakpoint) { @@ -106,6 +150,21 @@ void Debugger::FindBreakpoints( UnlockMutex(breakpoints_lock_); } +void Debugger::OnThreadCreated(ThreadState* thread_state) { + LockMutex(threads_lock_); + threads_[thread_state->thread_id()] = thread_state; + UnlockMutex(threads_lock_); +} + +void Debugger::OnThreadDestroyed(ThreadState* thread_state) { + LockMutex(threads_lock_); + auto it = threads_.find(thread_state->thread_id()); + if (it != threads_.end()) { + threads_.erase(it); + } + UnlockMutex(threads_lock_); +} + void Debugger::OnFunctionDefined(FunctionInfo* symbol_info, Function* function) { // Man, I'd love not to take this lock. @@ -134,5 +193,12 @@ void Debugger::OnFunctionDefined(FunctionInfo* symbol_info, void Debugger::OnBreakpointHit( ThreadState* thread_state, Breakpoint* breakpoint) { - // + // Suspend all threads immediately. + SuspendAllThreads(); + + // Notify listeners. + BreakpointHitEvent e(this, thread_state, breakpoint); + breakpoint_hit(e); + + // Note that we stay suspended. } \ No newline at end of file diff --git a/src/alloy/runtime/debugger.h b/src/alloy/runtime/debugger.h index be2ef3687..20386b466 100644 --- a/src/alloy/runtime/debugger.h +++ b/src/alloy/runtime/debugger.h @@ -18,6 +18,7 @@ namespace alloy { namespace runtime { +class Debugger; class Function; class FunctionInfo; class Runtime; @@ -37,9 +38,40 @@ public: Type type() const { return type_; } uint64_t address() const { return address_; } + const char* id() const { return id_.c_str(); } + void set_id(const char* id) { id_ = id; } + private: Type type_; uint64_t address_; + + std::string id_; +}; + + +class DebugEvent { +public: + DebugEvent(Debugger* debugger) : + debugger_(debugger) {} + virtual ~DebugEvent() {} + Debugger* debugger() const { return debugger_; } +protected: + Debugger* debugger_; +}; + + +class BreakpointHitEvent : public DebugEvent { +public: + BreakpointHitEvent( + Debugger* debugger, ThreadState* thread_state, Breakpoint* breakpoint) : + thread_state_(thread_state), breakpoint_(breakpoint), + DebugEvent(debugger) {} + virtual ~BreakpointHitEvent() {} + ThreadState* thread_state() const { return thread_state_; } + Breakpoint* breakpoint() const { return breakpoint_; } +protected: + ThreadState* thread_state_; + Breakpoint* breakpoint_; }; @@ -50,20 +82,34 @@ public: Runtime* runtime() const { return runtime_; } + int SuspendAllThreads(uint32_t timeout_ms = UINT_MAX); + int ResumeThread(uint32_t thread_id); + int ResumeAllThreads(); + int AddBreakpoint(Breakpoint* breakpoint); int RemoveBreakpoint(Breakpoint* breakpoint); void FindBreakpoints( uint64_t address, std::vector& out_breakpoints); + void OnThreadCreated(ThreadState* thread_state); + void OnThreadDestroyed(ThreadState* thread_state); void OnFunctionDefined(FunctionInfo* symbol_info, Function* function); + void OnBreakpointHit(ThreadState* thread_state, Breakpoint* breakpoint); +public: + Delegate breakpoint_hit; + private: Runtime* runtime_; + Mutex* threads_lock_; + typedef std::unordered_map ThreadMap; + ThreadMap threads_; + Mutex* breakpoints_lock_; - typedef std::multimap BreakpointsMultimap; - BreakpointsMultimap breakpoints_; + typedef std::multimap BreakpointMultimap; + BreakpointMultimap breakpoints_; }; diff --git a/src/alloy/runtime/thread_state.h b/src/alloy/runtime/thread_state.h index 683633255..1d211979c 100644 --- a/src/alloy/runtime/thread_state.h +++ b/src/alloy/runtime/thread_state.h @@ -32,6 +32,9 @@ public: void* backend_data() const { return backend_data_; } void* raw_context() const { return raw_context_; } + virtual int Suspend(uint32_t timeout_ms = UINT_MAX) = 0; + virtual int Resume() = 0; + static void Bind(ThreadState* thread_state); static ThreadState* Get(); static uint32_t GetThreadID(); diff --git a/src/alloy/sources.gypi b/src/alloy/sources.gypi index 144246165..808af15b8 100644 --- a/src/alloy/sources.gypi +++ b/src/alloy/sources.gypi @@ -7,6 +7,7 @@ 'arena.cc', 'arena.h', 'core.h', + 'delegate.h', 'memory.cc', 'memory.h', 'mutex.h', diff --git a/src/xenia/core/mutex.h b/src/xenia/core/mutex.h index 41b0c4c67..74856d878 100644 --- a/src/xenia/core/mutex.h +++ b/src/xenia/core/mutex.h @@ -16,7 +16,7 @@ typedef struct xe_mutex xe_mutex_t; -xe_mutex_t* xe_mutex_alloc(uint32_t spin_count); +xe_mutex_t* xe_mutex_alloc(uint32_t spin_count = 10000); void xe_mutex_free(xe_mutex_t* mutex); int xe_mutex_lock(xe_mutex_t* mutex); diff --git a/src/xenia/cpu/processor.cc b/src/xenia/cpu/processor.cc index 8da21c26a..83da74b04 100644 --- a/src/xenia/cpu/processor.cc +++ b/src/xenia/cpu/processor.cc @@ -59,18 +59,23 @@ Processor::Processor(Emulator* emulator) : DebugTarget(emulator->debug_server()) { InitializeIfNeeded(); + debug_client_states_lock_ = xe_mutex_alloc(); + emulator_->debug_server()->AddTarget("cpu", this); } Processor::~Processor() { emulator_->debug_server()->RemoveTarget("cpu"); + xe_mutex_lock(debug_client_states_lock_); for (auto it = debug_client_states_.begin(); it != debug_client_states_.end(); ++it) { DebugClientState* client_state = it->second; delete client_state; } debug_client_states_.clear(); + xe_mutex_unlock(debug_client_states_lock_); + xe_mutex_free(debug_client_states_lock_); if (interrupt_thread_block_) { memory_->HeapFree(interrupt_thread_block_, 2048); @@ -99,6 +104,31 @@ int Processor::Setup() { return result; } + // Setup debugger events. + auto debugger = runtime_->debugger(); + auto debug_server = emulator_->debug_server(); + debugger->breakpoint_hit.AddListener( + [debug_server](BreakpointHitEvent& e) { + const char* breakpoint_id = e.breakpoint()->id(); + if (!breakpoint_id) { + // This is not a breakpoint we know about. Ignore. + return; + } + + json_t* event_json = json_object(); + json_t* type_json = json_string("breakpoint"); + json_object_set_new(event_json, "type", type_json); + + json_t* thread_id_json = json_integer(e.thread_state()->thread_id()); + json_object_set_new(event_json, "threadId", thread_id_json); + json_t* breakpoint_id_json = json_string(breakpoint_id); + json_object_set_new(event_json, "breakpointId", breakpoint_id_json); + + debug_server->BroadcastEvent(event_json); + + json_decref(event_json); + }); + interrupt_thread_lock_ = xe_mutex_alloc(10000); interrupt_thread_state_ = new XenonThreadState( runtime_, 0, 16 * 1024, 0); @@ -177,19 +207,26 @@ uint64_t Processor::ExecuteInterrupt( void Processor::OnDebugClientConnected(uint32_t client_id) { DebugClientState* client_state = new DebugClientState(runtime_); + xe_mutex_lock(debug_client_states_lock_); debug_client_states_[client_id] = client_state; + xe_mutex_unlock(debug_client_states_lock_); } void Processor::OnDebugClientDisconnected(uint32_t client_id) { DebugClientState* client_state = debug_client_states_[client_id]; + xe_mutex_lock(debug_client_states_lock_); debug_client_states_.erase(client_id); + xe_mutex_unlock(debug_client_states_lock_); delete client_state; } json_t* Processor::OnDebugRequest( uint32_t client_id, const char* command, json_t* request, bool& succeeded) { + xe_mutex_lock(debug_client_states_lock_); DebugClientState* client_state = debug_client_states_[client_id]; + xe_mutex_unlock(debug_client_states_lock_); + XEASSERTNOTNULL(client_state); succeeded = true; if (xestrcmpa(command, "get_module_list") == 0) { @@ -343,6 +380,7 @@ json_t* Processor::OnDebugRequest( Breakpoint* breakpoint = new Breakpoint( type, address); + breakpoint->set_id(breakpoint_id); if (client_state->AddBreakpoint(breakpoint_id, breakpoint)) { succeeded = false; return json_string("Error adding breakpoint"); diff --git a/src/xenia/cpu/processor.h b/src/xenia/cpu/processor.h index 866d0683b..3232187c9 100644 --- a/src/xenia/cpu/processor.h +++ b/src/xenia/cpu/processor.h @@ -91,6 +91,7 @@ private: typedef std::unordered_map BreakpointMap; BreakpointMap breakpoints_; }; + xe_mutex_t* debug_client_states_lock_; typedef std::unordered_map DebugClientStateMap; DebugClientStateMap debug_client_states_; }; diff --git a/src/xenia/cpu/xenon_thread_state.cc b/src/xenia/cpu/xenon_thread_state.cc index 1510f66b9..84f221942 100644 --- a/src/xenia/cpu/xenon_thread_state.cc +++ b/src/xenia/cpu/xenon_thread_state.cc @@ -46,12 +46,24 @@ XenonThreadState::XenonThreadState( alloy::tracing::WriteEvent(EventType::ThreadInit({ })); + + runtime_->debugger()->OnThreadCreated(this); } XenonThreadState::~XenonThreadState() { + runtime_->debugger()->OnThreadDestroyed(this); + alloy::tracing::WriteEvent(EventType::ThreadDeinit({ })); xe_free_aligned(context_); memory_->HeapFree(stack_address_, stack_size_); } + +int XenonThreadState::Suspend(uint32_t timeout_ms) { + return 0; +} + +int XenonThreadState::Resume() { + return 0; +} diff --git a/src/xenia/cpu/xenon_thread_state.h b/src/xenia/cpu/xenon_thread_state.h index de4c312e9..bdecbd8df 100644 --- a/src/xenia/cpu/xenon_thread_state.h +++ b/src/xenia/cpu/xenon_thread_state.h @@ -31,6 +31,9 @@ public: PPCContext* context() const { return context_; } + virtual int Suspend(uint32_t timeout_ms = UINT_MAX); + virtual int Resume(); + private: size_t stack_size_; uint64_t thread_state_address; diff --git a/src/xenia/debug/debug_client.h b/src/xenia/debug/debug_client.h index 62a741566..ad263e129 100644 --- a/src/xenia/debug/debug_client.h +++ b/src/xenia/debug/debug_client.h @@ -16,6 +16,8 @@ XEDECLARECLASS2(xe, debug, DebugServer); +struct json_t; + namespace xe { namespace debug { @@ -31,6 +33,8 @@ public: virtual int Setup() = 0; virtual void Close() = 0; + virtual void SendEvent(json_t* event_json) = 0; + protected: void MakeReady(); diff --git a/src/xenia/debug/debug_server.cc b/src/xenia/debug/debug_server.cc index a97d67c36..bb81db681 100644 --- a/src/xenia/debug/debug_server.cc +++ b/src/xenia/debug/debug_server.cc @@ -126,6 +126,15 @@ DebugTarget* DebugServer::GetTarget(const char* name) { return target; } +void DebugServer::BroadcastEvent(json_t* event_json) { + // TODO(benvanik): avoid lock somehow? + xe_mutex_lock(lock_); + for (auto client : clients_) { + client->SendEvent(event_json); + } + xe_mutex_unlock(lock_); +} + int DebugServer::WaitForClient() { while (!has_clients()) { WaitForSingleObject(client_event_, INFINITE); diff --git a/src/xenia/debug/debug_server.h b/src/xenia/debug/debug_server.h index 030568e18..3c140de0c 100644 --- a/src/xenia/debug/debug_server.h +++ b/src/xenia/debug/debug_server.h @@ -21,6 +21,8 @@ XEDECLARECLASS2(xe, debug, DebugClient); XEDECLARECLASS2(xe, debug, DebugTarget); XEDECLARECLASS2(xe, debug, Protocol); +struct json_t; + namespace xe { namespace debug { @@ -43,6 +45,8 @@ public: void RemoveTarget(const char* name); DebugTarget* GetTarget(const char* name); + void BroadcastEvent(json_t* event_json); + int WaitForClient(); private: diff --git a/src/xenia/debug/protocols/gdb/gdb_client.h b/src/xenia/debug/protocols/gdb/gdb_client.h index 9070dfc23..53570cb75 100644 --- a/src/xenia/debug/protocols/gdb/gdb_client.h +++ b/src/xenia/debug/protocols/gdb/gdb_client.h @@ -37,6 +37,8 @@ public: virtual int Setup(); virtual void Close(); + virtual void SendEvent(json_t* event_json) {} + private: static void StartCallback(void* param); diff --git a/src/xenia/debug/protocols/ws/ws_client.cc b/src/xenia/debug/protocols/ws/ws_client.cc index 50fc100a9..211667032 100644 --- a/src/xenia/debug/protocols/ws/ws_client.cc +++ b/src/xenia/debug/protocols/ws/ws_client.cc @@ -371,7 +371,12 @@ void WSClient::EventThread() { delete this; } -void WSClient::Write(const char* value) { +void WSClient::SendEvent(json_t* event_json) { + char* str = json_dumps(event_json, 0); + Write(str); +} + +void WSClient::Write(char* value) { const uint8_t* buffers[] = { (uint8_t*)value, }; @@ -475,7 +480,8 @@ void WSClient::OnMessage(const uint8_t* data, size_t length) { json_object_set_new(response, "result", result_json); // Encode response to string and send back. - const char* response_string = json_dumps(response, JSON_INDENT(2)); + // String freed by Write. + char* response_string = json_dumps(response, JSON_INDENT(2)); Write(response_string); json_decref(request); diff --git a/src/xenia/debug/protocols/ws/ws_client.h b/src/xenia/debug/protocols/ws/ws_client.h index 128c94831..32d265032 100644 --- a/src/xenia/debug/protocols/ws/ws_client.h +++ b/src/xenia/debug/protocols/ws/ws_client.h @@ -38,7 +38,9 @@ public: virtual int Setup(); virtual void Close(); - void Write(const char* value); + virtual void SendEvent(json_t* event_json); + + void Write(char* value); void Write(const uint8_t** buffers, size_t* lengths, size_t count, bool binary = true);