/** ****************************************************************************** * 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. * ****************************************************************************** */ #include #include #include #include #include #include #include using namespace alloy; using namespace alloy::backend; using namespace alloy::frontend::ppc; using namespace alloy::runtime; using namespace xe; using namespace xe::cpu; namespace { void InitializeIfNeeded(); void CleanupOnShutdown(); void InitializeIfNeeded() { static bool has_initialized = false; if (has_initialized) { return; } has_initialized = true; //ppc::RegisterDisasmCategoryAltivec(); //ppc::RegisterDisasmCategoryALU(); //ppc::RegisterDisasmCategoryControl(); //ppc::RegisterDisasmCategoryFPU(); //ppc::RegisterDisasmCategoryMemory(); atexit(CleanupOnShutdown); } void CleanupOnShutdown() { } } Processor::Processor(Emulator* emulator) : emulator_(emulator), export_resolver_(emulator->export_resolver()), runtime_(0), memory_(emulator->memory()), interrupt_thread_lock_(NULL), interrupt_thread_state_(NULL), interrupt_thread_block_(0), DebugTarget(emulator->debug_server()) { InitializeIfNeeded(); emulator_->debug_server()->AddTarget("cpu", this); } Processor::~Processor() { emulator_->debug_server()->RemoveTarget("cpu"); 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(); if (interrupt_thread_block_) { memory_->HeapFree(interrupt_thread_block_, 2048); delete interrupt_thread_state_; xe_mutex_free(interrupt_thread_lock_); } delete runtime_; } int Processor::Setup() { XEASSERTNULL(runtime_); runtime_ = new XenonRuntime(memory_, export_resolver_); if (!runtime_) { return 1; } Backend* backend = 0; // Backend* backend = new alloy::backend::ivm::IVMBackend( // runtime); // Backend* backend = new alloy::backend::x64::X64Backend( // runtime); int result = runtime_->Initialize(backend); if (result) { return result; } interrupt_thread_lock_ = xe_mutex_alloc(10000); interrupt_thread_state_ = new XenonThreadState( runtime_, 0, 16 * 1024, 0); interrupt_thread_block_ = memory_->HeapAlloc( 0, 2048, MEMORY_FLAG_ZERO); interrupt_thread_state_->context()->r[13] = interrupt_thread_block_; return 0; } void Processor::AddRegisterAccessCallbacks( xe::cpu::RegisterAccessCallbacks callbacks) { runtime_->AddRegisterAccessCallbacks(callbacks); } int Processor::Execute(XenonThreadState* thread_state, uint64_t address) { // Attempt to get the function. Function* fn; if (runtime_->ResolveFunction(address, &fn)) { // Symbol not found in any module. XELOGCPU("Execute(%.8X): failed to find function", address); return 1; } PPCContext* context = thread_state->context(); // This could be set to anything to give us a unique identifier to track // re-entrancy/etc. uint32_t lr = 0xBEBEBEBE; // Setup registers. context->lr = lr; // Execute the function. fn->Call(thread_state, lr); return 0; } uint64_t Processor::Execute( XenonThreadState* thread_state, uint64_t address, uint64_t arg0) { PPCContext* context = thread_state->context(); context->r[3] = arg0; if (Execute(thread_state, address)) { return 0xDEADBABE; } return context->r[3]; } uint64_t Processor::Execute( XenonThreadState* thread_state, uint64_t address, uint64_t arg0, uint64_t arg1) { PPCContext* context = thread_state->context(); context->r[3] = arg0; context->r[4] = arg1; if (Execute(thread_state, address)) { return 0xDEADBABE; } return context->r[3]; } uint64_t Processor::ExecuteInterrupt( uint32_t cpu, uint64_t address, uint64_t arg0, uint64_t arg1) { // Acquire lock on interrupt thread (we can only dispatch one at a time). xe_mutex_lock(interrupt_thread_lock_); // Set 0x10C(r13) to the current CPU ID. uint8_t* p = memory_->membase(); XESETUINT8BE(p + interrupt_thread_block_ + 0x10C, cpu); // Execute interrupt. uint64_t result = Execute(interrupt_thread_state_, address, arg0, arg1); xe_mutex_unlock(interrupt_thread_lock_); return result; } void Processor::OnDebugClientConnected(uint32_t client_id) { DebugClientState* client_state = new DebugClientState(runtime_); debug_client_states_[client_id] = client_state; } void Processor::OnDebugClientDisconnected(uint32_t client_id) { DebugClientState* client_state = debug_client_states_[client_id]; debug_client_states_.erase(client_id); delete client_state; } json_t* Processor::OnDebugRequest( uint32_t client_id, const char* command, json_t* request, bool& succeeded) { DebugClientState* client_state = debug_client_states_[client_id]; succeeded = true; if (xestrcmpa(command, "get_module_list") == 0) { json_t* list = json_array(); Runtime::ModuleList modules = runtime_->GetModules(); for (Runtime::ModuleList::iterator it = modules.begin(); it != modules.end(); ++it) { XexModule* module = (XexModule*)(*it); json_t* module_json = json_object(); json_t* module_name_json = json_string(module->name()); json_object_set_new(module_json, "name", module_name_json); json_array_append_new(list, module_json); } return list; /*} else if (xestrcmpa(command, "get_module") == 0) { return json_null();*/ } else if (xestrcmpa(command, "get_function_list") == 0) { json_t* module_name_json = json_object_get(request, "module"); if (!module_name_json || !json_is_string(module_name_json)) { succeeded = false; return json_string("Module name not specified"); } const char* module_name = json_string_value(module_name_json); XexModule* module = (XexModule*)runtime_->GetModule(module_name); if (!module) { succeeded = false; return json_string("Module not found"); } json_t* list = json_array(); module->ForEachFunction([&](FunctionInfo* info) { json_t* fn_json = json_object(); const char* name = info->name(); char name_buffer[32]; if (!name) { xesnprintfa(name_buffer, XECOUNT(name_buffer), "sub_%.8X", info->address()); name = name_buffer; } json_t* name_json = json_string(name); json_object_set_new(fn_json, "name", name_json); json_t* address_json = json_integer(info->address()); json_object_set_new(fn_json, "address", address_json); json_t* link_status_json = json_integer(info->status()); json_object_set_new(fn_json, "linkStatus", link_status_json); json_array_append_new(list, fn_json); }); return list; } else if (xestrcmpa(command, "get_function") == 0) { json_t* address_json = json_object_get(request, "address"); if (!address_json || !json_is_number(address_json)) { succeeded = false; return json_string("Function address not specified"); } uint64_t address = (uint64_t)json_number_value(address_json); FunctionInfo* info; if (runtime_->LookupFunctionInfo(address, &info)) { succeeded = false; return json_string("Function not found"); } // Demand a new function with all debug info retained. // If we ever wanted absolute x64 addresses/etc we could // use the x64 from the function in the symbol table. Function* fn; if (runtime_->frontend()->DefineFunction(info, true, &fn)) { succeeded = false; return json_string("Unable to resolve function"); } DebugInfo* debug_info = fn->debug_info(); if (!debug_info) { succeeded = false; return json_string("No debug info present for function"); } json_t* fn_json = json_object(); const char* name = info->name(); char name_buffer[32]; if (!name) { xesnprintfa(name_buffer, XECOUNT(name_buffer), "sub_%.8X", info->address()); name = name_buffer; } json_t* name_json = json_string(name); json_object_set_new(fn_json, "name", name_json); json_t* start_address_json = json_integer(info->address()); json_object_set_new(fn_json, "startAddress", start_address_json); json_t* end_address_json = json_integer(info->end_address()); json_object_set_new(fn_json, "endAddress", end_address_json); json_t* link_status_json = json_integer(info->status()); json_object_set_new(fn_json, "linkStatus", link_status_json); json_t* disasm_json = json_object(); json_t* disasm_str_json; disasm_str_json = json_loads(debug_info->source_json(), 0, NULL); json_object_set_new(disasm_json, "source", disasm_str_json); disasm_str_json = json_string(debug_info->raw_hir_disasm()); json_object_set_new(disasm_json, "rawHir", disasm_str_json); disasm_str_json = json_string(debug_info->hir_disasm()); json_object_set_new(disasm_json, "hir", disasm_str_json); disasm_str_json = json_string(debug_info->raw_lir_disasm()); json_object_set_new(disasm_json, "rawLir", disasm_str_json); disasm_str_json = json_string(debug_info->lir_disasm()); json_object_set_new(disasm_json, "lir", disasm_str_json); disasm_str_json = json_string(debug_info->machine_code_disasm()); json_object_set_new(disasm_json, "machineCode", disasm_str_json); json_object_set_new(fn_json, "disasm", disasm_json); delete fn; return fn_json; } else if (xestrcmpa(command, "add_breakpoints") == 0) { // breakpoints: [{}] json_t* breakpoints_json = json_object_get(request, "breakpoints"); if (!breakpoints_json || !json_is_array(breakpoints_json)) { succeeded = false; return json_string("Breakpoints not specified"); } if (!json_array_size(breakpoints_json)) { // No-op; return json_null(); } for (size_t n = 0; n < json_array_size(breakpoints_json); n++) { json_t* breakpoint_json = json_array_get(breakpoints_json, n); if (!breakpoint_json || !json_is_object(breakpoint_json)) { succeeded = false; return json_string("Invalid breakpoint type"); } json_t* breakpoint_id_json = json_object_get(breakpoint_json, "id"); json_t* type_json = json_object_get(breakpoint_json, "type"); json_t* address_json = json_object_get(breakpoint_json, "address"); if (!breakpoint_id_json || !json_is_string(breakpoint_id_json) || !type_json || !json_is_string(type_json) || !address_json || !json_is_number(address_json)) { succeeded = false; return json_string("Invalid breakpoint members"); } const char* breakpoint_id = json_string_value(breakpoint_id_json); const char* type_str = json_string_value(type_json); uint64_t address = (uint64_t)json_number_value(address_json); Breakpoint::Type type; if (xestrcmpa(type_str, "temp") == 0) { type = Breakpoint::TEMP_TYPE; } else if (xestrcmpa(type_str, "code") == 0) { type = Breakpoint::CODE_TYPE; } else { succeeded = false; return json_string("Unknown breakpoint type"); } Breakpoint* breakpoint = new Breakpoint( type, address); if (client_state->AddBreakpoint(breakpoint_id, breakpoint)) { succeeded = false; return json_string("Error adding breakpoint"); } } return json_null(); } else if (xestrcmpa(command, "remove_breakpoints") == 0) { // breakpointIds: ['id'] json_t* breakpoint_ids_json = json_object_get(request, "breakpointIds"); if (!breakpoint_ids_json || !json_is_array(breakpoint_ids_json)) { succeeded = false; return json_string("Breakpoint IDs not specified"); } if (!json_array_size(breakpoint_ids_json)) { // No-op; return json_null(); } for (size_t n = 0; n < json_array_size(breakpoint_ids_json); n++) { json_t* breakpoint_id_json = json_array_get(breakpoint_ids_json, n); if (!breakpoint_id_json || !json_is_string(breakpoint_id_json)) { succeeded = false; return json_string("Invalid breakpoint ID type"); } const char* breakpoint_id = json_string_value(breakpoint_id_json); if (client_state->RemoveBreakpoint(breakpoint_id)) { succeeded = false; return json_string("Unable to remove breakpoints"); } } return json_null(); } else if (xestrcmpa(command, "remove_all_breakpoints") == 0) { if (client_state->RemoveAllBreakpoints()) { succeeded = false; return json_string("Unable to remove breakpoints"); } return json_null(); } else { succeeded = false; return json_string("Unknown command"); } } Processor::DebugClientState::DebugClientState(XenonRuntime* runtime) : runtime_(runtime) { breakpoints_lock_ = xe_mutex_alloc(10000); } Processor::DebugClientState::~DebugClientState() { RemoveAllBreakpoints(); xe_mutex_free(breakpoints_lock_); } int Processor::DebugClientState::AddBreakpoint( const char* breakpoint_id, Breakpoint* breakpoint) { xe_mutex_lock(breakpoints_lock_); breakpoints_[breakpoint_id] = breakpoint; int result = runtime_->debugger()->AddBreakpoint(breakpoint); xe_mutex_unlock(breakpoints_lock_); return result; } int Processor::DebugClientState::RemoveBreakpoint(const char* breakpoint_id) { xe_mutex_lock(breakpoints_lock_); Breakpoint* breakpoint = breakpoints_[breakpoint_id]; if (breakpoint) { breakpoints_.erase(breakpoint_id); runtime_->debugger()->RemoveBreakpoint(breakpoint); delete breakpoint; } xe_mutex_unlock(breakpoints_lock_); return 0; } int Processor::DebugClientState::RemoveAllBreakpoints() { xe_mutex_lock(breakpoints_lock_); for (auto it = breakpoints_.begin(); it != breakpoints_.end(); ++it) { Breakpoint* breakpoint = it->second; runtime_->debugger()->RemoveBreakpoint(breakpoint); delete breakpoint; } breakpoints_.clear(); xe_mutex_unlock(breakpoints_lock_); return 0; }