Breakpoint hits reaching all the way to UI.
Nasty json only hackery right now, but fixable to support other protocols.
This commit is contained in:
parent
a0256fac45
commit
475ddc1fcf
|
@ -14,6 +14,7 @@
|
|||
#include <xenia/common.h>
|
||||
|
||||
#include <alloy/arena.h>
|
||||
#include <alloy/delegate.h>
|
||||
#include <alloy/mutex.h>
|
||||
#include <alloy/string_buffer.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 <functional>
|
||||
|
||||
#include <alloy/core.h>
|
||||
#include <alloy/mutex.h>
|
||||
|
||||
|
||||
namespace alloy {
|
||||
|
||||
|
||||
// TODO(benvanik): go lockfree, and don't hold the lock while emitting.
|
||||
|
||||
template <typename T>
|
||||
class Delegate {
|
||||
public:
|
||||
Delegate() {
|
||||
lock_ = AllocMutex();
|
||||
}
|
||||
~Delegate() {
|
||||
FreeMutex(lock_);
|
||||
}
|
||||
|
||||
typedef std::function<void(T&)> 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<listener_t> listeners_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace alloy
|
||||
|
||||
|
||||
#endif // ALLOY_DELEGATE_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,
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#ifndef ALLOY_MUTEX_H_
|
||||
#define ALLOY_MUTEX_H_
|
||||
|
||||
#include <alloy/core.h>
|
||||
#include <xenia/common.h>
|
||||
|
||||
|
||||
namespace alloy {
|
||||
|
|
|
@ -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.
|
||||
}
|
|
@ -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<Breakpoint*>& 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<BreakpointHitEvent> breakpoint_hit;
|
||||
|
||||
private:
|
||||
Runtime* runtime_;
|
||||
|
||||
Mutex* threads_lock_;
|
||||
typedef std::unordered_map<uint32_t, ThreadState*> ThreadMap;
|
||||
ThreadMap threads_;
|
||||
|
||||
Mutex* breakpoints_lock_;
|
||||
typedef std::multimap<uint64_t, Breakpoint*> BreakpointsMultimap;
|
||||
BreakpointsMultimap breakpoints_;
|
||||
typedef std::multimap<uint64_t, Breakpoint*> BreakpointMultimap;
|
||||
BreakpointMultimap breakpoints_;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
'arena.cc',
|
||||
'arena.h',
|
||||
'core.h',
|
||||
'delegate.h',
|
||||
'memory.cc',
|
||||
'memory.h',
|
||||
'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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -91,6 +91,7 @@ private:
|
|||
typedef std::unordered_map<std::string, alloy::runtime::Breakpoint*> BreakpointMap;
|
||||
BreakpointMap breakpoints_;
|
||||
};
|
||||
xe_mutex_t* debug_client_states_lock_;
|
||||
typedef std::unordered_map<uint32_t, DebugClientState*> DebugClientStateMap;
|
||||
DebugClientStateMap debug_client_states_;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -37,6 +37,8 @@ public:
|
|||
virtual int Setup();
|
||||
virtual void Close();
|
||||
|
||||
virtual void SendEvent(json_t* event_json) {}
|
||||
|
||||
private:
|
||||
static void StartCallback(void* param);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue