mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Implement gdbstub
This commit is contained in:
		
							parent
							
								
									f7b4f44adf
								
							
						
					
					
						commit
						5114d75647
					
				
					 18 changed files with 1182 additions and 9 deletions
				
			
		|  | @ -30,6 +30,8 @@ | |||
| 
 | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| 
 | ||||
| 
 | ||||
| static void PrintHelp() | ||||
| { | ||||
|  | @ -72,6 +74,7 @@ int main(int argc, char **argv) { | |||
|     Config config; | ||||
|     log_filter.ParseFilterString(Settings::values.log_filter); | ||||
| 
 | ||||
|     GDBStub::SetServerPort(static_cast<u32>(Settings::values.gdbstub_port)); | ||||
| 
 | ||||
|     EmuWindow_GLFW* emu_window = new EmuWindow_GLFW; | ||||
| 
 | ||||
|  |  | |||
|  | @ -75,6 +75,10 @@ void Config::ReadValues() { | |||
| 
 | ||||
|     // Miscellaneous
 | ||||
|     Settings::values.log_filter = glfw_config->Get("Miscellaneous", "log_filter", "*:Info"); | ||||
| 
 | ||||
|     // GDBStubebugging
 | ||||
|     Settings::values.use_gdbstub = glfw_config->GetBoolean("Debugging", "use_gdbstub", false); | ||||
|     Settings::values.gdbstub_port = glfw_config->GetInteger("Debugging", "gdbstub_port", 24689); | ||||
| } | ||||
| 
 | ||||
| void Config::Reload() { | ||||
|  |  | |||
|  | @ -66,6 +66,11 @@ region_value = | |||
| # A filter which removes logs below a certain logging level. | ||||
| # Examples: *:Debug Kernel.SVC:Trace Service.*:Critical | ||||
| log_filter = *:Info | ||||
| 
 | ||||
| [Debugging] | ||||
| # Port for listening to GDB connections. | ||||
| use_gdbstub=false | ||||
| gdbstub_port=24689 | ||||
| )"; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -62,6 +62,11 @@ void Config::ReadValues() { | |||
|     qt_config->beginGroup("Miscellaneous"); | ||||
|     Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); | ||||
|     qt_config->endGroup(); | ||||
| 
 | ||||
|     qt_config->beginGroup("Debugging"); | ||||
|     Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool(); | ||||
|     Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); | ||||
|     qt_config->endGroup(); | ||||
| } | ||||
| 
 | ||||
| void Config::SaveValues() { | ||||
|  | @ -97,6 +102,11 @@ void Config::SaveValues() { | |||
|     qt_config->beginGroup("Miscellaneous"); | ||||
|     qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); | ||||
|     qt_config->endGroup(); | ||||
| 
 | ||||
|     qt_config->beginGroup("Debugging"); | ||||
|     qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub); | ||||
|     qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); | ||||
|     qt_config->endGroup(); | ||||
| } | ||||
| 
 | ||||
| void Config::Reload() { | ||||
|  |  | |||
|  | @ -47,6 +47,12 @@ | |||
| 
 | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| #ifdef USE_GDBSTUB | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| #endif | ||||
| 
 | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| 
 | ||||
| GMainWindow::GMainWindow() : emu_thread(nullptr) | ||||
| { | ||||
|     Pica::g_debug_context = Pica::DebugContext::Construct(); | ||||
|  | @ -137,6 +143,15 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) | |||
|     microProfileDialog->setVisible(settings.value("microProfileDialogVisible").toBool()); | ||||
|     settings.endGroup(); | ||||
| 
 | ||||
| #ifdef USE_GDBSTUB | ||||
|     Gdbstub::SetServerPort(static_cast<u32>(Settings::values.gdbstub_port)); | ||||
| #endif | ||||
| 
 | ||||
|     ui.action_Use_Gdbstub->setChecked(Settings::values.use_gdbstub); | ||||
|     SetGdbstubEnabled(ui.action_Use_Gdbstub->isChecked()); | ||||
| 
 | ||||
|     GDBStub::SetServerPort(static_cast<u32>(Settings::values.gdbstub_port)); | ||||
| 
 | ||||
|     ui.action_Use_Hardware_Renderer->setChecked(Settings::values.use_hw_renderer); | ||||
|     SetHardwareRendererEnabled(ui.action_Use_Hardware_Renderer->isChecked()); | ||||
| 
 | ||||
|  | @ -167,6 +182,7 @@ GMainWindow::GMainWindow() : emu_thread(nullptr) | |||
|     connect(ui.action_Stop, SIGNAL(triggered()), this, SLOT(OnStopGame())); | ||||
|     connect(ui.action_Use_Hardware_Renderer, SIGNAL(triggered(bool)), this, SLOT(SetHardwareRendererEnabled(bool))); | ||||
|     connect(ui.action_Use_Shader_JIT, SIGNAL(triggered(bool)), this, SLOT(SetShaderJITEnabled(bool))); | ||||
|     connect(ui.action_Use_Gdbstub, SIGNAL(triggered(bool)), this, SLOT(SetGdbstubEnabled(bool))); | ||||
|     connect(ui.action_Single_Window_Mode, SIGNAL(triggered(bool)), this, SLOT(ToggleWindowMode())); | ||||
|     connect(ui.action_Hotkeys, SIGNAL(triggered()), this, SLOT(OnOpenHotkeysDialog())); | ||||
| 
 | ||||
|  | @ -414,6 +430,10 @@ void GMainWindow::SetHardwareRendererEnabled(bool enabled) { | |||
|     VideoCore::g_hw_renderer_enabled = enabled; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::SetGdbstubEnabled(bool enabled) { | ||||
|     GDBStub::ToggleServer(enabled); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::SetShaderJITEnabled(bool enabled) { | ||||
|     VideoCore::g_shader_jit_enabled = enabled; | ||||
| } | ||||
|  |  | |||
|  | @ -94,6 +94,7 @@ private slots: | |||
|     void OnConfigure(); | ||||
|     void OnDisplayTitleBars(bool); | ||||
|     void SetHardwareRendererEnabled(bool); | ||||
|     void SetGdbstubEnabled(bool); | ||||
|     void SetShaderJITEnabled(bool); | ||||
|     void ToggleWindowMode(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -74,6 +74,7 @@ | |||
|     <addaction name="separator"/> | ||||
|     <addaction name="action_Use_Hardware_Renderer"/> | ||||
|     <addaction name="action_Use_Shader_JIT"/> | ||||
|     <addaction name="action_Use_Gdbstub"/> | ||||
|     <addaction name="action_Configure"/> | ||||
|    </widget> | ||||
|    <widget class="QMenu" name="menu_View"> | ||||
|  | @ -169,6 +170,14 @@ | |||
|     <string>Use Shader JIT</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Use_Gdbstub"> | ||||
|     <property name="checkable"> | ||||
|       <bool>true</bool> | ||||
|     </property> | ||||
|     <property name="text"> | ||||
|       <string>Use Gdbstub</string> | ||||
|     </property> | ||||
|   </action> | ||||
|   <action name="action_Configure"> | ||||
|    <property name="text"> | ||||
|     <string>Configure ...</string> | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ namespace Log { | |||
|         SUB(Debug, Emulated) \ | ||||
|         SUB(Debug, GPU) \ | ||||
|         SUB(Debug, Breakpoint) \ | ||||
|         SUB(Debug, GDBStub) \ | ||||
|         CLS(Kernel) \ | ||||
|         SUB(Kernel, SVC) \ | ||||
|         CLS(Service) \ | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ enum class Class : ClassType { | |||
|     Debug_Emulated,             ///< Debug messages from the emulated programs
 | ||||
|     Debug_GPU,                  ///< GPU debugging tools
 | ||||
|     Debug_Breakpoint,           ///< Logging breakpoints and watchpoints
 | ||||
|     Debug_GDBStub,              ///< GDB Stub
 | ||||
|     Kernel,                     ///< The HLE implementation of the CTR kernel
 | ||||
|     Kernel_SVC,                 ///< Kernel system calls
 | ||||
|     Service,                    ///< HLE implementation of system services. Each major service
 | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ set(SRCS | |||
|             file_sys/archive_systemsavedata.cpp | ||||
|             file_sys/disk_archive.cpp | ||||
|             file_sys/ivfc_archive.cpp | ||||
|             gdbstub/gdbstub.cpp | ||||
|             hle/config_mem.cpp | ||||
|             hle/hle.cpp | ||||
|             hle/applets/applet.cpp | ||||
|  | @ -149,6 +150,7 @@ set(HEADERS | |||
|             file_sys/disk_archive.h | ||||
|             file_sys/file_backend.h | ||||
|             file_sys/ivfc_archive.h | ||||
|             gdbstub/gdbstub.h | ||||
|             hle/config_mem.h | ||||
|             hle/function_wrappers.h | ||||
|             hle/hle.h | ||||
|  |  | |||
|  | @ -23,6 +23,8 @@ | |||
| #include "core/arm/skyeye_common/armsupp.h" | ||||
| #include "core/arm/skyeye_common/vfp/vfp.h" | ||||
| 
 | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| 
 | ||||
| Common::Profiling::TimingCategory profile_execute("DynCom::Execute"); | ||||
| Common::Profiling::TimingCategory profile_decode("DynCom::Decode"); | ||||
| 
 | ||||
|  | @ -3548,6 +3550,7 @@ static int InterpreterTranslate(ARMul_State* cpu, int& bb_start, u32 addr) { | |||
|             CITRA_IGNORE_EXIT(-1); | ||||
|         } | ||||
|         inst_base = arm_instruction_trans[idx](inst, idx); | ||||
| 
 | ||||
| translated: | ||||
|         phys_addr += inst_size; | ||||
| 
 | ||||
|  | @ -3580,6 +3583,8 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|     Common::Profiling::ScopeTimer timer_execute(profile_execute); | ||||
|     MICROPROFILE_SCOPE(DynCom_Execute); | ||||
| 
 | ||||
|     int breakpoint_offset = -1; | ||||
| 
 | ||||
|     #undef RM | ||||
|     #undef RS | ||||
| 
 | ||||
|  | @ -3604,15 +3609,27 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|     #define INC_PC(l)   ptr += sizeof(arm_inst) + l | ||||
|     #define INC_PC_STUB ptr += sizeof(arm_inst) | ||||
| 
 | ||||
| #define GDB_BP_CHECK \ | ||||
|     cpu->Cpsr &= ~(1 << 5); \ | ||||
|     cpu->Cpsr |= cpu->TFlag << 5; \ | ||||
|     if (GDBStub::g_server_enabled) { \ | ||||
|         if (GDBStub::IsMemoryBreak() || PC == breakpoint_offset) { \ | ||||
|             GDBStub::Break(); \ | ||||
|             goto END; \ | ||||
|         } \ | ||||
|     } | ||||
| 
 | ||||
| // GCC and Clang have a C++ extension to support a lookup table of labels. Otherwise, fallback to a
 | ||||
| // clunky switch statement.
 | ||||
| #if defined __GNUC__ || defined __clang__ | ||||
| #define GOTO_NEXT_INST \ | ||||
|     GDB_BP_CHECK; \ | ||||
|     if (num_instrs >= cpu->NumInstrsToExecute) goto END; \ | ||||
|     num_instrs++; \ | ||||
|     goto *InstLabel[inst_base->idx] | ||||
| #else | ||||
| #define GOTO_NEXT_INST \ | ||||
|     GDB_BP_CHECK; \ | ||||
|     if (num_instrs >= cpu->NumInstrsToExecute) goto END; \ | ||||
|     num_instrs++; \ | ||||
|     switch(inst_base->idx) { \ | ||||
|  | @ -3878,6 +3895,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|     unsigned int addr; | ||||
|     unsigned int num_instrs = 0; | ||||
| 
 | ||||
| 
 | ||||
|     int ptr; | ||||
| 
 | ||||
|     LOAD_NZCVT; | ||||
|  | @ -3903,6 +3921,11 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|                 goto END; | ||||
|         } | ||||
| 
 | ||||
|         // Find breakpoint if one exists within the block
 | ||||
|         if (GDBStub::g_server_enabled && GDBStub::IsConnected()) { | ||||
|             breakpoint_offset = GDBStub::GetNextBreakpointFromAddress(cpu->Reg[15], GDBStub::BreakpointType::Execute); | ||||
|         } | ||||
| 
 | ||||
|         inst_base = (arm_inst *)&inst_buf[ptr]; | ||||
|         GOTO_NEXT_INST; | ||||
|     } | ||||
|  | @ -4454,7 +4477,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|             ldst_inst* inst_cream = (ldst_inst*)inst_base->component; | ||||
|             inst_cream->get_addr(cpu, inst_cream->inst, addr); | ||||
| 
 | ||||
|             cpu->Reg[BITS(inst_cream->inst, 12, 15)] = Memory::Read8(addr); | ||||
|             cpu->Reg[BITS(inst_cream->inst, 12, 15)] = cpu->ReadMemory8(addr); | ||||
| 
 | ||||
|             if (BITS(inst_cream->inst, 12, 15) == 15) { | ||||
|                 INC_PC(sizeof(ldst_inst)); | ||||
|  | @ -4472,7 +4495,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|             ldst_inst* inst_cream = (ldst_inst*)inst_base->component; | ||||
|             inst_cream->get_addr(cpu, inst_cream->inst, addr); | ||||
| 
 | ||||
|             cpu->Reg[BITS(inst_cream->inst, 12, 15)] = Memory::Read8(addr); | ||||
|             cpu->Reg[BITS(inst_cream->inst, 12, 15)] = cpu->ReadMemory8(addr); | ||||
| 
 | ||||
|             if (BITS(inst_cream->inst, 12, 15) == 15) { | ||||
|                 INC_PC(sizeof(ldst_inst)); | ||||
|  | @ -4531,7 +4554,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
| 
 | ||||
|             cpu->SetExclusiveMemoryAddress(read_addr); | ||||
| 
 | ||||
|             RD = Memory::Read8(read_addr); | ||||
|             RD = cpu->ReadMemory8(read_addr); | ||||
|             if (inst_cream->Rd == 15) { | ||||
|                 INC_PC(sizeof(generic_arm_inst)); | ||||
|                 goto DISPATCH; | ||||
|  | @ -4604,7 +4627,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|         if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) { | ||||
|             ldst_inst* inst_cream = (ldst_inst*)inst_base->component; | ||||
|             inst_cream->get_addr(cpu, inst_cream->inst, addr); | ||||
|             unsigned int value = Memory::Read8(addr); | ||||
|             unsigned int value = cpu->ReadMemory8(addr); | ||||
|             if (BIT(value, 7)) { | ||||
|                 value |= 0xffffff00; | ||||
|             } | ||||
|  | @ -6027,7 +6050,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|             ldst_inst* inst_cream = (ldst_inst*)inst_base->component; | ||||
|             inst_cream->get_addr(cpu, inst_cream->inst, addr); | ||||
|             unsigned int value = cpu->Reg[BITS(inst_cream->inst, 12, 15)] & 0xff; | ||||
|             Memory::Write8(addr, value); | ||||
|             cpu->WriteMemory8(addr, value); | ||||
|         } | ||||
|         cpu->Reg[15] += cpu->GetInstructionSize(); | ||||
|         INC_PC(sizeof(ldst_inst)); | ||||
|  | @ -6040,7 +6063,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|             ldst_inst* inst_cream = (ldst_inst*)inst_base->component; | ||||
|             inst_cream->get_addr(cpu, inst_cream->inst, addr); | ||||
|             unsigned int value = cpu->Reg[BITS(inst_cream->inst, 12, 15)] & 0xff; | ||||
|             Memory::Write8(addr, value); | ||||
|             cpu->WriteMemory8(addr, value); | ||||
|         } | ||||
|         cpu->Reg[15] += cpu->GetInstructionSize(); | ||||
|         INC_PC(sizeof(ldst_inst)); | ||||
|  | @ -6091,7 +6114,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
| 
 | ||||
|             if (cpu->IsExclusiveMemoryAccess(write_addr)) { | ||||
|                 cpu->UnsetExclusiveMemoryAddress(); | ||||
|                 Memory::Write8(write_addr, cpu->Reg[inst_cream->Rm]); | ||||
|                 cpu->WriteMemory8(write_addr, cpu->Reg[inst_cream->Rm]); | ||||
|                 RD = 0; | ||||
|             } else { | ||||
|                 // Failed to write due to mutex access
 | ||||
|  | @ -6250,8 +6273,8 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) { | |||
|         if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) { | ||||
|             swp_inst* inst_cream = (swp_inst*)inst_base->component; | ||||
|             addr = RN; | ||||
|             unsigned int value = Memory::Read8(addr); | ||||
|             Memory::Write8(addr, (RM & 0xFF)); | ||||
|             unsigned int value = cpu->ReadMemory8(addr); | ||||
|             cpu->WriteMemory8(addr, (RM & 0xFF)); | ||||
|             RD = value; | ||||
|         } | ||||
|         cpu->Reg[15] += cpu->GetInstructionSize(); | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| #include "core/memory.h" | ||||
| #include "core/arm/skyeye_common/armstate.h" | ||||
| #include "core/arm/skyeye_common/vfp/vfp.h" | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| 
 | ||||
| ARMul_State::ARMul_State(PrivilegeMode initial_mode) | ||||
| { | ||||
|  | @ -185,8 +186,25 @@ void ARMul_State::ResetMPCoreCP15Registers() | |||
|     CP15[CP15_TLB_DEBUG_CONTROL] = 0x00000000; | ||||
| } | ||||
| 
 | ||||
| static void CheckMemoryBreakpoint(u32 address, GDBStub::BreakpointType type) | ||||
| { | ||||
|     if (GDBStub::g_server_enabled && GDBStub::CheckBreakpoint(address, type)) { | ||||
|         LOG_DEBUG(Debug, "Found memory breakpoint @ %08x", address); | ||||
|         GDBStub::Break(true); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| u8 ARMul_State::ReadMemory8(u32 address) const | ||||
| { | ||||
|     CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read); | ||||
| 
 | ||||
|     return Memory::Read8(address); | ||||
| } | ||||
| 
 | ||||
| u16 ARMul_State::ReadMemory16(u32 address) const | ||||
| { | ||||
|     CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read); | ||||
| 
 | ||||
|     u16 data = Memory::Read16(address); | ||||
| 
 | ||||
|     if (InBigEndianMode()) | ||||
|  | @ -197,6 +215,8 @@ u16 ARMul_State::ReadMemory16(u32 address) const | |||
| 
 | ||||
| u32 ARMul_State::ReadMemory32(u32 address) const | ||||
| { | ||||
|     CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read); | ||||
| 
 | ||||
|     u32 data = Memory::Read32(address); | ||||
| 
 | ||||
|     if (InBigEndianMode()) | ||||
|  | @ -207,6 +227,8 @@ u32 ARMul_State::ReadMemory32(u32 address) const | |||
| 
 | ||||
| u64 ARMul_State::ReadMemory64(u32 address) const | ||||
| { | ||||
|     CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read); | ||||
| 
 | ||||
|     u64 data = Memory::Read64(address); | ||||
| 
 | ||||
|     if (InBigEndianMode()) | ||||
|  | @ -215,8 +237,17 @@ u64 ARMul_State::ReadMemory64(u32 address) const | |||
|     return data; | ||||
| } | ||||
| 
 | ||||
| void ARMul_State::WriteMemory8(u32 address, u8 data) | ||||
| { | ||||
|     CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write); | ||||
| 
 | ||||
|     Memory::Write8(address, data); | ||||
| } | ||||
| 
 | ||||
| void ARMul_State::WriteMemory16(u32 address, u16 data) | ||||
| { | ||||
|     CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write); | ||||
| 
 | ||||
|     if (InBigEndianMode()) | ||||
|         data = Common::swap16(data); | ||||
| 
 | ||||
|  | @ -225,6 +256,8 @@ void ARMul_State::WriteMemory16(u32 address, u16 data) | |||
| 
 | ||||
| void ARMul_State::WriteMemory32(u32 address, u32 data) | ||||
| { | ||||
|     CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write); | ||||
| 
 | ||||
|     if (InBigEndianMode()) | ||||
|         data = Common::swap32(data); | ||||
| 
 | ||||
|  | @ -233,6 +266,8 @@ void ARMul_State::WriteMemory32(u32 address, u32 data) | |||
| 
 | ||||
| void ARMul_State::WriteMemory64(u32 address, u64 data) | ||||
| { | ||||
|     CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write); | ||||
| 
 | ||||
|     if (InBigEndianMode()) | ||||
|         data = Common::swap64(data); | ||||
| 
 | ||||
|  |  | |||
|  | @ -153,9 +153,11 @@ public: | |||
| 
 | ||||
|     // Reads/writes data in big/little endian format based on the
 | ||||
|     // state of the E (endian) bit in the APSR.
 | ||||
|     u8 ReadMemory8(u32 address) const; | ||||
|     u16 ReadMemory16(u32 address) const; | ||||
|     u32 ReadMemory32(u32 address) const; | ||||
|     u64 ReadMemory64(u32 address) const; | ||||
|     void WriteMemory8(u32 address, u8 data); | ||||
|     void WriteMemory16(u32 address, u16 data); | ||||
|     void WriteMemory32(u32 address, u32 data); | ||||
|     void WriteMemory64(u32 address, u64 data); | ||||
|  |  | |||
|  | @ -13,6 +13,8 @@ | |||
| #include "core/hle/kernel/thread.h" | ||||
| #include "core/hw/hw.h" | ||||
| 
 | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| 
 | ||||
| namespace Core { | ||||
| 
 | ||||
| ARM_Interface*     g_app_core = nullptr;  ///< ARM11 application core
 | ||||
|  | @ -20,6 +22,21 @@ ARM_Interface*     g_sys_core = nullptr;  ///< ARM11 system (OS) core | |||
| 
 | ||||
| /// Run the core CPU loop
 | ||||
| void RunLoop(int tight_loop) { | ||||
|     if (GDBStub::g_server_enabled) { | ||||
|         GDBStub::HandlePacket(); | ||||
| 
 | ||||
|         // If the loop is halted and we want to step, use a tiny (1) number of instructions to execute.
 | ||||
|         // Otherwise get out of the loop function.
 | ||||
|         if (GDBStub::GetCpuHaltFlag()) { | ||||
|             if (GDBStub::GetCpuStepFlag()) { | ||||
|                 GDBStub::SetCpuStepFlag(false); | ||||
|                 tight_loop = 1; | ||||
|             } else { | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // If we don't have a currently active thread then don't execute instructions,
 | ||||
|     // instead advance to the next event and try to yield to the next thread
 | ||||
|     if (Kernel::GetCurrentThread() == nullptr) { | ||||
|  |  | |||
							
								
								
									
										940
									
								
								src/core/gdbstub/gdbstub.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										940
									
								
								src/core/gdbstub/gdbstub.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,940 @@ | |||
| // Copyright 2013 Dolphin Emulator Project
 | ||||
| // Licensed under GPLv2+
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| // Originally written by Sven Peter <sven@fail0verflow.com> for anergistic.
 | ||||
| 
 | ||||
| #include <csignal> | ||||
| #include <cstdarg> | ||||
| #include <cstdio> | ||||
| #include <cstring> | ||||
| #include <fcntl.h> | ||||
| #include <map> | ||||
| #include <numeric> | ||||
| 
 | ||||
| #ifdef _MSC_VER | ||||
| #include <WinSock2.h> | ||||
| #include <ws2tcpip.h> | ||||
| #include <common/x64/abi.h> | ||||
| #include <io.h> | ||||
| #include <iphlpapi.h> | ||||
| #define SHUT_RDWR 2 | ||||
| #else | ||||
| #include <sys/select.h> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/un.h> | ||||
| #include <netinet/in.h> | ||||
| #include <unistd.h> | ||||
| #endif | ||||
| 
 | ||||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include <core/arm/arm_interface.h> | ||||
| #include "core/core.h" | ||||
| #include "core/memory.h" | ||||
| #include "gdbstub.h" | ||||
| 
 | ||||
| const int GDB_BUFFER_SIZE = 10000; | ||||
| 
 | ||||
| const char GDB_STUB_START = '$'; | ||||
| const char GDB_STUB_END = '#'; | ||||
| const char GDB_STUB_ACK = '+'; | ||||
| const char GDB_STUB_NACK = '-'; | ||||
| 
 | ||||
| #ifndef SIGTRAP | ||||
| const u32 SIGTRAP = 5; | ||||
| #endif | ||||
| 
 | ||||
| #ifndef SIGTERM | ||||
| const u32 SIGTERM = 15; | ||||
| #endif | ||||
| 
 | ||||
| #ifndef MSG_WAITALL | ||||
| const u32 MSG_WAITALL = 8; | ||||
| #endif | ||||
| 
 | ||||
| const u32 R0_REGISTER = 0; | ||||
| const u32 R15_REGISTER = 15; | ||||
| const u32 CSPR_REGISTER = 25; | ||||
| 
 | ||||
| namespace GDBStub { | ||||
| 
 | ||||
| static int gdbserver_socket = -1; | ||||
| 
 | ||||
| static u8 command_buffer[GDB_BUFFER_SIZE]; | ||||
| static u32 command_length; | ||||
| 
 | ||||
| static u32 latest_signal = 0; | ||||
| static u32 send_signal = 0; | ||||
| static u32 step_break = 0; | ||||
| static bool memory_break = false; | ||||
| 
 | ||||
| // Binding to a port within the reserved ports range (0-1023) requires root permissions,
 | ||||
| // so default to a port outside of that range.
 | ||||
| static u16 gdbstub_port = 24689; | ||||
| 
 | ||||
| static bool halt_loop = true; | ||||
| static bool step_loop = false; | ||||
| std::atomic<bool> g_server_enabled(false); | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| WSADATA InitData; | ||||
| #endif | ||||
| 
 | ||||
| struct Breakpoint { | ||||
|     bool active; | ||||
|     PAddr addr; | ||||
|     u32 len; | ||||
| }; | ||||
| 
 | ||||
| static std::map<u32, Breakpoint> breakpoints_execute; | ||||
| static std::map<u32, Breakpoint> breakpoints_read; | ||||
| static std::map<u32, Breakpoint> breakpoints_write; | ||||
| 
 | ||||
| /**
 | ||||
|  * Turns hex string character into the equivalent byte. | ||||
|  * | ||||
|  * @param hex Input hex character to be turned into byte. | ||||
|  */ | ||||
| static u8 HexCharToValue(u8 hex) { | ||||
|     if (hex >= '0' && hex <= '9') { | ||||
|         return hex - '0'; | ||||
|     } else if (hex >= 'a' && hex <= 'f') { | ||||
|         return hex - 'a' + 0xA; | ||||
|     } else if (hex >= 'A' && hex <= 'F') { | ||||
|         return hex - 'A' + 0xA; | ||||
|     } | ||||
| 
 | ||||
|     LOG_ERROR(Debug_GDBStub, "Invalid nibble: %c (%02x)\n", hex, hex); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Turn nibble of byte into hex string character. | ||||
|  * | ||||
|  * @param n Nibble to be turned into hex character. | ||||
|  */ | ||||
| static u8 NibbleToHex(u8 n) { | ||||
|     n &= 0xF; | ||||
|     if (n < 0xA) { | ||||
|         return '0' + n; | ||||
|     } else { | ||||
|         return 'A' + n - 0xA; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Converts input array of u8 bytes into their equivalent hex string characters. | ||||
|  * | ||||
|  * @param dest Pointer to buffer to store output hex string characters. | ||||
|  * @param src Pointer to array of u8 bytes. | ||||
|  * @param len Length of src array. | ||||
|  */ | ||||
| static void MemToHex(u8* dest, u8* src, u32 len) { | ||||
|     while (len-- > 0) { | ||||
|         u8 tmp = *src++; | ||||
|         *dest++ = NibbleToHex(tmp >> 4); | ||||
|         *dest++ = NibbleToHex(tmp); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Converts input hex string characters into an array of equivalent of u8 bytes. | ||||
|  * | ||||
|  * @param dest Pointer to buffer to store u8 bytes. | ||||
|  * @param src Pointer to array of output hex string characters. | ||||
|  * @param len Length of src array. | ||||
|  */ | ||||
| static void HexToMem(u8* dest, u8* src, u32 len) { | ||||
|     while (len-- > 0) { | ||||
|         *dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]); | ||||
|         src += 2; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Convert a u32 into a hex string. | ||||
|  * | ||||
|  * @param dest Pointer to buffer to store output hex string characters. | ||||
|  */ | ||||
| static void IntToHex(u8* dest, u32 v) { | ||||
|     for (int i = 0; i < 8; i += 2) { | ||||
|         dest[i + 1] = NibbleToHex(v >> (4 * i)); | ||||
|         dest[i] = NibbleToHex(v >> (4 * (i + 1))); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Convert a hex string into a u32. | ||||
|  * | ||||
|  * @param src Pointer to hex string. | ||||
|  */ | ||||
| static u32 HexToInt(u8* src) { | ||||
|     u32 output = 0; | ||||
| 
 | ||||
|     for (int i = 0; i < 8; i += 2) { | ||||
|         output = (output << 4) | HexCharToValue(src[7 - i - 1]); | ||||
|         output = (output << 4) | HexCharToValue(src[7 - i]); | ||||
|     } | ||||
| 
 | ||||
|     return output; | ||||
| } | ||||
| 
 | ||||
| /// Read a byte from the gdb client.
 | ||||
| static u8 ReadByte() { | ||||
|     u8 c; | ||||
|     size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL); | ||||
|     if (received_size != 1) { | ||||
|         LOG_ERROR(Debug_GDBStub, "recv failed : %ld", received_size); | ||||
|         Deinit(); | ||||
|     } | ||||
| 
 | ||||
|     return c; | ||||
| } | ||||
| 
 | ||||
| /// Calculate the checksum of the current command buffer.
 | ||||
| static u8 CalculateChecksum(u8 *buffer, u32 length) { | ||||
|     return static_cast<u8>(std::accumulate(buffer, buffer + length, 0, std::plus<u8>())); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the list of breakpoints for a given breakpoint type. | ||||
|  * | ||||
|  * @param type Type of breakpoint list. | ||||
|  */ | ||||
| static std::map<u32, Breakpoint>& GetBreakpointList(BreakpointType type) { | ||||
|     switch (type) { | ||||
|     case BreakpointType::Execute: | ||||
|         return breakpoints_execute; | ||||
|     case BreakpointType::Read: | ||||
|         return breakpoints_read; | ||||
|     case BreakpointType::Write: | ||||
|         return breakpoints_write; | ||||
|     default: | ||||
|         return breakpoints_read; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Remove the breakpoint from the given address of the specified type. | ||||
|  * | ||||
|  * @param type Type of breakpoint. | ||||
|  * @param addr Address of breakpoint. | ||||
|  */ | ||||
| static void RemoveBreakpoint(BreakpointType type, PAddr addr) { | ||||
|     std::map<u32, Breakpoint>& p = GetBreakpointList(type); | ||||
| 
 | ||||
|     auto bp = p.find(addr); | ||||
|     if (bp != p.end()) { | ||||
|         LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: %08x bytes at %08x of type %d\n", bp->second.len, bp->second.addr, type); | ||||
|         p.erase(addr); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| PAddr GetNextBreakpointFromAddress(PAddr addr, BreakpointType type) { | ||||
|     std::map<u32, Breakpoint>& p = GetBreakpointList(type); | ||||
|     auto next_breakpoint = p.lower_bound(addr); | ||||
|     u32 breakpoint = -1; | ||||
| 
 | ||||
|     if (next_breakpoint != p.end()) | ||||
|         breakpoint = next_breakpoint->first; | ||||
| 
 | ||||
|     return breakpoint; | ||||
| } | ||||
| 
 | ||||
| bool CheckBreakpoint(PAddr addr, BreakpointType type) { | ||||
|     if (!IsConnected()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     std::map<u32, Breakpoint>& p = GetBreakpointList(type); | ||||
| 
 | ||||
|     auto bp = p.find(addr); | ||||
|     if (bp != p.end()) { | ||||
|         u32 len = bp->second.len; | ||||
| 
 | ||||
|         // IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints
 | ||||
|         // no matter if it's a 4-byte or 2-byte instruction. When you execute a
 | ||||
|         // Thumb instruction with a 4-byte breakpoint set, it will set a breakpoint on
 | ||||
|         // two instructions instead of the single instruction you placed the breakpoint
 | ||||
|         // on. So, as a way to make sure that execution breakpoints are only breaking
 | ||||
|         // on the instruction that was specified, set the length of an execution
 | ||||
|         // breakpoint to 1. This should be fine since the CPU should never begin executing
 | ||||
|         // an instruction anywhere except the beginning of the instruction.
 | ||||
|         if (type == BreakpointType::Execute) { | ||||
|             len = 1; | ||||
|         } | ||||
| 
 | ||||
|         if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) { | ||||
|             LOG_DEBUG(Debug_GDBStub, "Found breakpoint type %d @ %08x, range: %08x - %08x (%d bytes)\n", type, addr, bp->second.addr, bp->second.addr + len, len); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Send packet to gdb client. | ||||
|  * | ||||
|  * @param packet Packet to be sent to client. | ||||
|  */ | ||||
| static void SendPacket(const char packet) { | ||||
|     size_t sent_size = send(gdbserver_socket, &packet, 1, 0); | ||||
|     if (sent_size != 1) { | ||||
|         LOG_ERROR(Debug_GDBStub, "send failed"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Send reply to gdb client. | ||||
|  * | ||||
|  * @param reply Reply to be sent to client. | ||||
|  */ | ||||
| static void SendReply(const char* reply) { | ||||
|     if (!IsConnected()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     memset(command_buffer, 0, sizeof(command_buffer)); | ||||
| 
 | ||||
|     command_length = strlen(reply); | ||||
|     if (command_length + 4 > sizeof(command_buffer)) { | ||||
|         LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply"); | ||||
|     } | ||||
| 
 | ||||
|     memcpy(command_buffer + 1, reply, command_length); | ||||
| 
 | ||||
|     u8 checksum = CalculateChecksum(command_buffer, command_length + 1); | ||||
|     command_buffer[0] = GDB_STUB_START; | ||||
|     command_buffer[command_length + 1] = GDB_STUB_END; | ||||
|     command_buffer[command_length + 2] = NibbleToHex(checksum >> 4); | ||||
|     command_buffer[command_length + 3] = NibbleToHex(checksum); | ||||
| 
 | ||||
|     u8* ptr = command_buffer; | ||||
|     u32 left = command_length + 4; | ||||
|     while (left > 0) { | ||||
|         int sent_size = send(gdbserver_socket, reinterpret_cast<char*>(ptr), left, 0); | ||||
|         if (sent_size < 0) { | ||||
|             LOG_ERROR(Debug_GDBStub, "gdb: send failed"); | ||||
|             return Deinit(); | ||||
|         } | ||||
| 
 | ||||
|         left -= sent_size; | ||||
|         ptr += sent_size; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Handle query command from gdb client.
 | ||||
| static void HandleQuery() { | ||||
|     LOG_DEBUG(Debug_GDBStub, "gdb: query '%s'\n", command_buffer + 1); | ||||
| 
 | ||||
|     if (!strcmp(reinterpret_cast<const char*>(command_buffer + 1), "TStatus")) { | ||||
|         SendReply("T0"); | ||||
|     } else { | ||||
|         SendReply(""); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Handle set thread command from gdb client.
 | ||||
| static void HandleSetThread() { | ||||
|     if (memcmp(command_buffer, "Hg0", 3) == 0 || | ||||
|         memcmp(command_buffer, "Hc-1", 4) == 0 || | ||||
|         memcmp(command_buffer, "Hc0", 4) == 0 || | ||||
|         memcmp(command_buffer, "Hc1", 4) == 0) { | ||||
|         return SendReply("OK"); | ||||
|     } | ||||
| 
 | ||||
|     SendReply("E01"); | ||||
| } | ||||
| 
 | ||||
| /// Create and send signal packet.
 | ||||
| static void HandleSignal() { | ||||
|     std::string buffer = Common::StringFromFormat("T%02x%02x:%08x;%02x:%08x;", latest_signal, 15, htonl(Core::g_app_core->GetPC()), 13, htonl(Core::g_app_core->GetReg(13))); | ||||
| 
 | ||||
|     LOG_DEBUG(Debug_GDBStub, "Response: %s", buffer.c_str()); | ||||
| 
 | ||||
|     SendReply(buffer.c_str()); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Set signal and send packet to client through HandleSignal if signal flag is set using SendSignal. | ||||
|  * | ||||
|  * @param signal Signal to be sent to client. | ||||
|  */ | ||||
| int SendSignal(u32 signal) { | ||||
|     if (gdbserver_socket == -1) { | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     latest_signal = signal; | ||||
| 
 | ||||
|     if (send_signal) { | ||||
|         HandleSignal(); | ||||
|         send_signal = 0; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /// Read command from gdb client.
 | ||||
| static void ReadCommand() { | ||||
|     command_length = 0; | ||||
|     memset(command_buffer, 0, sizeof(command_buffer)); | ||||
| 
 | ||||
|     u8 c = ReadByte(); | ||||
|     if (c == '+') { | ||||
|         //ignore ack
 | ||||
|         return; | ||||
|     } else if (c == 0x03) { | ||||
|         LOG_INFO(Debug_GDBStub, "gdb: found break command\n"); | ||||
|         halt_loop = true; | ||||
|         send_signal = 1; | ||||
|         SendSignal(SIGTRAP); | ||||
|         return; | ||||
|     } else if (c != GDB_STUB_START) { | ||||
|         LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte %02x\n", c); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     while ((c = ReadByte()) != GDB_STUB_END) { | ||||
|         command_buffer[command_length++] = c; | ||||
|         if (command_length == sizeof(command_buffer)) { | ||||
|             LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow\n"); | ||||
|             SendPacket(GDB_STUB_NACK); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     u8 checksum_received = HexCharToValue(ReadByte()) << 4; | ||||
|     checksum_received |= HexCharToValue(ReadByte()); | ||||
| 
 | ||||
|     u8 checksum_calculated = CalculateChecksum(command_buffer, command_length); | ||||
| 
 | ||||
|     if (checksum_received != checksum_calculated) { | ||||
|         LOG_ERROR(Debug_GDBStub, "gdb: invalid checksum: calculated %02x and read %02x for $%s# (length: %d)\n", | ||||
|             checksum_calculated, checksum_received, command_buffer, command_length); | ||||
| 
 | ||||
|         command_length = 0; | ||||
| 
 | ||||
|         SendPacket(GDB_STUB_NACK); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     SendPacket(GDB_STUB_ACK); | ||||
| } | ||||
| 
 | ||||
| /// Check if there is data to be read from the gdb client.
 | ||||
| static bool IsDataAvailable() { | ||||
|     if (!IsConnected()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     fd_set fd_socket; | ||||
| 
 | ||||
|     FD_ZERO(&fd_socket); | ||||
|     FD_SET(gdbserver_socket, &fd_socket); | ||||
| 
 | ||||
|     struct timeval t; | ||||
|     t.tv_sec = 0; | ||||
|     t.tv_usec = 0; | ||||
| 
 | ||||
|     if (select(gdbserver_socket + 1, &fd_socket, nullptr, nullptr, &t) < 0) { | ||||
|         LOG_ERROR(Debug_GDBStub, "select failed"); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return FD_ISSET(gdbserver_socket, &fd_socket); | ||||
| } | ||||
| 
 | ||||
| /// Send requested register to gdb client.
 | ||||
| static void ReadRegister() { | ||||
|     static u8 reply[64]; | ||||
|     memset(reply, 0, sizeof(reply)); | ||||
| 
 | ||||
|     u32 id = HexCharToValue(command_buffer[1]); | ||||
|     if (command_buffer[2] != '\0') { | ||||
|         id <<= 4; | ||||
|         id |= HexCharToValue(command_buffer[2]); | ||||
|     } | ||||
| 
 | ||||
|     if (id >= R0_REGISTER && id <= R15_REGISTER) { | ||||
|         IntToHex(reply, Core::g_app_core->GetReg(id)); | ||||
|     } else if (id == CSPR_REGISTER) { | ||||
|         IntToHex(reply, Core::g_app_core->GetCPSR()); | ||||
|     } else { | ||||
|         return SendReply("E01"); | ||||
|     } | ||||
| 
 | ||||
|     SendReply(reinterpret_cast<char*>(reply)); | ||||
| } | ||||
| 
 | ||||
| /// Send all registers to the gdb client.
 | ||||
| static void ReadRegisters() { | ||||
|     static u8 buffer[GDB_BUFFER_SIZE - 4]; | ||||
|     memset(buffer, 0, sizeof(buffer)); | ||||
| 
 | ||||
|     u8* bufptr = buffer; | ||||
|     for (int i = 0; i <= CSPR_REGISTER; i++) { | ||||
|         if (i <= R15_REGISTER) { | ||||
|             IntToHex(bufptr + i * 8, Core::g_app_core->GetReg(i)); | ||||
|         } else if (i == CSPR_REGISTER) { | ||||
|             IntToHex(bufptr + i * 8, Core::g_app_core->GetCPSR()); | ||||
|         } else { | ||||
|             IntToHex(bufptr + i * 8, 0); | ||||
|             IntToHex(bufptr + (i + 1) * 8, 0); | ||||
|             i++; // These registers seem to be all 64bit instead of 32bit, so skip two instead of one
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     SendReply(reinterpret_cast<char*>(buffer)); | ||||
| } | ||||
| 
 | ||||
| /// Modify data of register specified by gdb client.
 | ||||
| static void WriteRegister() { | ||||
|     u8* buffer_ptr = command_buffer + 3; | ||||
| 
 | ||||
|     u32 id = HexCharToValue(command_buffer[1]); | ||||
|     if (command_buffer[2] != '=') { | ||||
|         ++buffer_ptr; | ||||
|         id <<= 4; | ||||
|         id |= HexCharToValue(command_buffer[2]); | ||||
|     } | ||||
| 
 | ||||
|     if (id >= R0_REGISTER && id <= R15_REGISTER) { | ||||
|         Core::g_app_core->SetReg(id, HexToInt(buffer_ptr)); | ||||
|     } else if (id == CSPR_REGISTER) { | ||||
|         Core::g_app_core->SetCPSR(HexToInt(buffer_ptr)); | ||||
|     } else { | ||||
|         return SendReply("E01"); | ||||
|     } | ||||
| 
 | ||||
|     SendReply("OK"); | ||||
| } | ||||
| 
 | ||||
| /// Modify all registers with data received from the client.
 | ||||
| static void WriteRegisters() { | ||||
|     u8* buffer_ptr = command_buffer + 1; | ||||
| 
 | ||||
|     if (command_buffer[0] != 'G') | ||||
|         return SendReply("E01"); | ||||
| 
 | ||||
|     for (int i = 0; i <= CSPR_REGISTER; i++) { | ||||
|         if (i <= R15_REGISTER) { | ||||
|             Core::g_app_core->SetReg(i, HexToInt(buffer_ptr + i * 8)); | ||||
|         } else if (i == CSPR_REGISTER) { | ||||
|             Core::g_app_core->SetCPSR(HexToInt(buffer_ptr + i * 8)); | ||||
|         } else { | ||||
|             i++; // These registers seem to be all 64bit instead of 32bit, so skip two instead of one
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     SendReply("OK"); | ||||
| } | ||||
| 
 | ||||
| /// Read location in memory specified by gdb client.
 | ||||
| static void ReadMemory() { | ||||
|     static u8 reply[GDB_BUFFER_SIZE - 4]; | ||||
| 
 | ||||
|     int i = 1; | ||||
|     PAddr addr = 0; | ||||
|     while (command_buffer[i] != ',') { | ||||
|         addr = (addr << 4) | HexCharToValue(command_buffer[i++]); | ||||
|     } | ||||
|     i++; | ||||
| 
 | ||||
|     u32 len = 0; | ||||
|     while (i < command_length) { | ||||
|         len = (len << 4) | HexCharToValue(command_buffer[i++]); | ||||
|     } | ||||
| 
 | ||||
|     if (len * 2 > sizeof(reply)) { | ||||
|         SendReply("E01"); | ||||
|     } | ||||
| 
 | ||||
|     u8* data = Memory::GetPointer(addr); | ||||
|     if (!data) { | ||||
|         return SendReply("E0"); | ||||
|     } | ||||
| 
 | ||||
|     MemToHex(reply, data, len); | ||||
|     reply[len * 2] = '\0'; | ||||
|     SendReply(reinterpret_cast<char*>(reply)); | ||||
| } | ||||
| 
 | ||||
| /// Modify location in memory with data received from the gdb client.
 | ||||
| static void WriteMemory() { | ||||
|     int i = 1; | ||||
|     PAddr addr = 0; | ||||
|     while (command_buffer[i] != ',') { | ||||
|         addr = (addr << 4) | HexCharToValue(command_buffer[i++]); | ||||
|     } | ||||
|     i++; | ||||
| 
 | ||||
|     u32 len = 0; | ||||
|     while (command_buffer[i] != ':') { | ||||
|         len = (len << 4) | HexCharToValue(command_buffer[i++]); | ||||
|     } | ||||
| 
 | ||||
|     u8* dst = Memory::GetPointer(addr); | ||||
|     if (!dst) { | ||||
|         return SendReply("E00"); | ||||
|     } | ||||
| 
 | ||||
|     HexToMem(dst, command_buffer + i + 1, len); | ||||
|     SendReply("OK"); | ||||
| } | ||||
| 
 | ||||
| void Break(bool is_memory_break) { | ||||
|     if (!halt_loop) { | ||||
|         halt_loop = true; | ||||
|         send_signal = 1; | ||||
|         SendSignal(SIGTRAP); | ||||
|     } | ||||
| 
 | ||||
|     memory_break = is_memory_break; | ||||
| } | ||||
| 
 | ||||
| /// Tell the CPU that it should perform a single step.
 | ||||
| static void Step() { | ||||
|     step_loop = true; | ||||
|     halt_loop = true; | ||||
|     send_signal = 1; | ||||
|     step_break = 1; | ||||
|     SendSignal(SIGTRAP); | ||||
| } | ||||
| 
 | ||||
| bool IsMemoryBreak() { | ||||
|     if (IsConnected()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return memory_break; | ||||
| } | ||||
| 
 | ||||
| /// Tell the CPU to continue executing.
 | ||||
| static void Continue() { | ||||
|     memory_break = false; | ||||
|     step_break = 0; | ||||
|     step_loop = false; | ||||
|     halt_loop = false; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Commit breakpoint to list of breakpoints. | ||||
|  * | ||||
|  * @param type Type of breakpoint. | ||||
|  * @param addr Address of breakpoint. | ||||
|  * @param len Length of breakpoint. | ||||
|  */ | ||||
| bool CommitBreakpoint(BreakpointType type, PAddr addr, u32 len) { | ||||
|     std::map<u32, Breakpoint>& p = GetBreakpointList(type); | ||||
| 
 | ||||
|     Breakpoint breakpoint; | ||||
|     breakpoint.active = true; | ||||
|     breakpoint.addr = addr; | ||||
|     breakpoint.len = len; | ||||
|     p.insert({ addr, breakpoint }); | ||||
| 
 | ||||
|     LOG_DEBUG(Debug_GDBStub, "gdb: added %d breakpoint: %08x bytes at %08x\n", type, breakpoint.len, breakpoint.addr); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| /// Handle add breakpoint command from gdb client.
 | ||||
| static void AddBreakpoint() { | ||||
|     BreakpointType type; | ||||
| 
 | ||||
|     u8 type_id = HexCharToValue(command_buffer[1]); | ||||
|     switch (type_id) { | ||||
|     case 0: | ||||
|     case 1: | ||||
|         type = BreakpointType::Execute; | ||||
|         break; | ||||
|     case 2: | ||||
|         type = BreakpointType::Write; | ||||
|         break; | ||||
|     case 3: | ||||
|         type = BreakpointType::Read; | ||||
|         break; | ||||
|     case 4: | ||||
|         type = BreakpointType::Access; | ||||
|         break; | ||||
|     default: | ||||
|         return SendReply("E01"); | ||||
|     } | ||||
| 
 | ||||
|     int i = 3; | ||||
|     PAddr addr = 0; | ||||
|     while (command_buffer[i] != ',') { | ||||
|         addr = addr << 4 | HexCharToValue(command_buffer[i++]); | ||||
|     } | ||||
|     i++; | ||||
| 
 | ||||
|     u32 len = 0; | ||||
|     while (i < command_length) { | ||||
|         len = len << 4 | HexCharToValue(command_buffer[i++]); | ||||
|     } | ||||
| 
 | ||||
|     if (type == BreakpointType::Access) { | ||||
|         // Access is made up of Read and Write types, so add both breakpoints
 | ||||
|         type = BreakpointType::Read; | ||||
| 
 | ||||
|         if (!CommitBreakpoint(type, addr, len)) { | ||||
|             return SendReply("E02"); | ||||
|         } | ||||
| 
 | ||||
|         type = BreakpointType::Write; | ||||
|     } | ||||
| 
 | ||||
|     if (!CommitBreakpoint(type, addr, len)) { | ||||
|         return SendReply("E02"); | ||||
|     } | ||||
| 
 | ||||
|     SendReply("OK"); | ||||
| } | ||||
| 
 | ||||
| /// Handle remove breakpoint command from gdb client.
 | ||||
| static void RemoveBreakpoint() { | ||||
|     BreakpointType type; | ||||
| 
 | ||||
|     u8 type_id = HexCharToValue(command_buffer[1]); | ||||
|     switch (type_id) { | ||||
|     case 0: | ||||
|     case 1: | ||||
|         type = BreakpointType::Execute; | ||||
|         break; | ||||
|     case 2: | ||||
|         type = BreakpointType::Write; | ||||
|         break; | ||||
|     case 3: | ||||
|         type = BreakpointType::Read; | ||||
|         break; | ||||
|     case 4: | ||||
|         type = BreakpointType::Access; | ||||
|         break; | ||||
|     default: | ||||
|         return SendReply("E01"); | ||||
|     } | ||||
| 
 | ||||
|     int i = 3; | ||||
|     PAddr addr = 0; | ||||
|     while (command_buffer[i] != ',') { | ||||
|         addr = (addr << 4) | HexCharToValue(command_buffer[i++]); | ||||
|     } | ||||
|     i++; | ||||
| 
 | ||||
|     u32 len = 0; | ||||
|     while (i < command_length) { | ||||
|         len = (len << 4) | HexCharToValue(command_buffer[i++]); | ||||
|     } | ||||
| 
 | ||||
|     if (type == BreakpointType::Access) { | ||||
|         // Access is made up of Read and Write types, so add both breakpoints
 | ||||
|         type = BreakpointType::Read; | ||||
|         RemoveBreakpoint(type, addr); | ||||
| 
 | ||||
|         type = BreakpointType::Write; | ||||
|     } | ||||
| 
 | ||||
|     RemoveBreakpoint(type, addr); | ||||
|     SendReply("OK"); | ||||
| } | ||||
| 
 | ||||
| void HandlePacket() { | ||||
|     if (!IsConnected()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (!IsDataAvailable()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ReadCommand(); | ||||
|     if (command_length == 0) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     LOG_DEBUG(Debug_GDBStub, "Packet: %s", command_buffer); | ||||
| 
 | ||||
|     switch (command_buffer[0]) { | ||||
|     case 'q': | ||||
|         HandleQuery(); | ||||
|         break; | ||||
|     case 'H': | ||||
|         HandleSetThread(); | ||||
|         break; | ||||
|     case '?': | ||||
|         HandleSignal(); | ||||
|         break; | ||||
|     case 'k': | ||||
|         Deinit(); | ||||
|         LOG_INFO(Debug_GDBStub, "killed by gdb"); | ||||
|         return; | ||||
|     case 'g': | ||||
|         ReadRegisters(); | ||||
|         break; | ||||
|     case 'G': | ||||
|         WriteRegisters(); | ||||
|         break; | ||||
|     case 'p': | ||||
|         ReadRegister(); | ||||
|         break; | ||||
|     case 'P': | ||||
|         WriteRegister(); | ||||
|         break; | ||||
|     case 'm': | ||||
|         ReadMemory(); | ||||
|         break; | ||||
|     case 'M': | ||||
|         WriteMemory(); | ||||
|         break; | ||||
|     case 's': | ||||
|         Step(); | ||||
|         return; | ||||
|     case 'C': | ||||
|     case 'c': | ||||
|         Continue(); | ||||
|         return; | ||||
|     case 'z': | ||||
|         RemoveBreakpoint(); | ||||
|         break; | ||||
|     case 'Z': | ||||
|         AddBreakpoint(); | ||||
|         break; | ||||
|     default: | ||||
|         SendReply(""); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void SetServerPort(u16 port) { | ||||
|     gdbstub_port = port; | ||||
| } | ||||
| 
 | ||||
| void ToggleServer(bool status) { | ||||
|     if (status) { | ||||
|         g_server_enabled = status; | ||||
| 
 | ||||
|         // Start server
 | ||||
|         if (!IsConnected() && Core::g_sys_core != nullptr) { | ||||
|             Init(); | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         // Stop server
 | ||||
|         if (IsConnected()) { | ||||
|             Deinit(); | ||||
|         } | ||||
| 
 | ||||
|         g_server_enabled = status; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Init(u16 port) { | ||||
|     if (!g_server_enabled) { | ||||
|         // Set the halt loop to false in case the user enabled the gdbstub mid-execution.
 | ||||
|         // This way the CPU can still execute normally.
 | ||||
|         halt_loop = false; | ||||
|         step_loop = false; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // Setup initial gdbstub status
 | ||||
|     halt_loop = true; | ||||
|     step_loop = false; | ||||
| 
 | ||||
|     breakpoints_execute.clear(); | ||||
|     breakpoints_read.clear(); | ||||
|     breakpoints_write.clear(); | ||||
| 
 | ||||
|     // Start gdb server
 | ||||
|     LOG_INFO(Debug_GDBStub, "Starting GDB server on port %d...", port); | ||||
| 
 | ||||
|     sockaddr_in saddr_server = {}; | ||||
|     saddr_server.sin_family = AF_INET; | ||||
|     saddr_server.sin_port = htons(port); | ||||
|     saddr_server.sin_addr.s_addr = INADDR_ANY; | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
|     WSAStartup(MAKEWORD(2, 2), &InitData); | ||||
| #endif | ||||
| 
 | ||||
|     int tmpsock = socket(PF_INET, SOCK_STREAM, 0); | ||||
|     if (tmpsock == -1) { | ||||
|         LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); | ||||
|     } | ||||
| 
 | ||||
|     const sockaddr* server_addr = reinterpret_cast<const sockaddr*>(&saddr_server); | ||||
|     socklen_t server_addrlen = sizeof(saddr_server); | ||||
|     if (bind(tmpsock, server_addr, server_addrlen) < 0) { | ||||
|         LOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket"); | ||||
|     } | ||||
| 
 | ||||
|     if (listen(tmpsock, 1) < 0) { | ||||
|         LOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket"); | ||||
|     } | ||||
| 
 | ||||
|     // Wait for gdb to connect
 | ||||
|     LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...\n"); | ||||
|     sockaddr_in saddr_client; | ||||
|     sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client); | ||||
|     socklen_t client_addrlen = sizeof(saddr_client); | ||||
|     gdbserver_socket = accept(tmpsock, client_addr, &client_addrlen); | ||||
|     if (gdbserver_socket < 0) { | ||||
|         // In the case that we couldn't start the server for whatever reason, just start CPU execution like normal.
 | ||||
|         halt_loop = false; | ||||
|         step_loop = false; | ||||
| 
 | ||||
|         LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client"); | ||||
|     } | ||||
|     else { | ||||
|         LOG_INFO(Debug_GDBStub, "Client connected.\n"); | ||||
|         saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr); | ||||
|     } | ||||
| 
 | ||||
|     // Clean up temporary socket if it's still alive at this point.
 | ||||
|     if (tmpsock != -1) { | ||||
|         shutdown(tmpsock, SHUT_RDWR); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Init() { | ||||
|     Init(gdbstub_port); | ||||
| } | ||||
| 
 | ||||
| void Deinit() { | ||||
|     if (!g_server_enabled) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     LOG_INFO(Debug_GDBStub, "Stopping GDB ..."); | ||||
|     if (gdbserver_socket != -1) { | ||||
|         shutdown(gdbserver_socket, SHUT_RDWR); | ||||
|         gdbserver_socket = -1; | ||||
|     } | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
|     WSACleanup(); | ||||
| #endif | ||||
| 
 | ||||
|     LOG_INFO(Debug_GDBStub, "GDB stopped."); | ||||
| } | ||||
| 
 | ||||
| bool IsConnected() { | ||||
|     return g_server_enabled && gdbserver_socket != -1; | ||||
| } | ||||
| 
 | ||||
| bool GetCpuHaltFlag() { | ||||
|     return halt_loop; | ||||
| } | ||||
| 
 | ||||
| bool GetCpuStepFlag() { | ||||
|     return step_loop; | ||||
| } | ||||
| 
 | ||||
| void SetCpuStepFlag(bool is_step) { | ||||
|     step_loop = is_step; | ||||
| } | ||||
| 
 | ||||
| }; | ||||
							
								
								
									
										89
									
								
								src/core/gdbstub/gdbstub.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/core/gdbstub/gdbstub.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,89 @@ | |||
| // Copyright 2013 Dolphin Emulator Project
 | ||||
| // Licensed under GPLv2+
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| // Originally written by Sven Peter <sven@fail0verflow.com> for anergistic.
 | ||||
| 
 | ||||
| #pragma once | ||||
| #include <atomic> | ||||
| 
 | ||||
| namespace GDBStub { | ||||
| 
 | ||||
| /// Breakpoint Method
 | ||||
| enum class BreakpointType { | ||||
|     None,     ///< None
 | ||||
|     Execute,  ///< Execution Breakpoint
 | ||||
|     Read,     ///< Read Breakpoint
 | ||||
|     Write,    ///< Write Breakpoint
 | ||||
|     Access    ///< Access (R/W) Breakpoint
 | ||||
| }; | ||||
| 
 | ||||
| /// If set to false, the server will never be started and no gdbstub-related functions will be executed.
 | ||||
| extern std::atomic<bool> g_server_enabled; | ||||
| 
 | ||||
| /**
 | ||||
|  * Set the port the gdbstub should use to listen for connections. | ||||
|  * | ||||
|  * @param port Port to listen for connection | ||||
|  */ | ||||
| void SetServerPort(u16 port); | ||||
| 
 | ||||
| /**
 | ||||
|  * Set the g_server_enabled flag and start or stop the server if possible. | ||||
|  * | ||||
|  * @param status Set the server to enabled or disabled. | ||||
|  */ | ||||
| void ToggleServer(bool status); | ||||
| 
 | ||||
| /// Start the gdbstub server.
 | ||||
| void Init(); | ||||
| 
 | ||||
| /// Stop gdbstub server.
 | ||||
| void Deinit(); | ||||
| 
 | ||||
| /// Returns true if there is an active socket connection.
 | ||||
| bool IsConnected(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Signal to the gdbstub server that it should halt CPU execution. | ||||
|  * | ||||
|  * @param is_memory_break If true, the break resulted from a memory breakpoint. | ||||
|  */ | ||||
| void Break(bool is_memory_break = false); | ||||
| 
 | ||||
| /// Determine if there was a memory breakpoint.
 | ||||
| bool IsMemoryBreak(); | ||||
| 
 | ||||
| /// Read and handle packet from gdb client.
 | ||||
| void HandlePacket(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the nearest breakpoint of the specified type at the given address. | ||||
|  * | ||||
|  * @param addr Address to search from. | ||||
|  * @param type Type of breakpoint. | ||||
|  */ | ||||
| PAddr GetNextBreakpointFromAddress(u32 addr, GDBStub::BreakpointType type); | ||||
| 
 | ||||
| /**
 | ||||
|  * Check if a breakpoint of the specified type exists at the given address. | ||||
|  * | ||||
|  * @param addr Address of breakpoint. | ||||
|  * @param type Type of breakpoint. | ||||
|  */ | ||||
| bool CheckBreakpoint(u32 addr, GDBStub::BreakpointType type); | ||||
| 
 | ||||
| // If set to true, the CPU will halt at the beginning of the next CPU loop.
 | ||||
| bool GetCpuHaltFlag(); | ||||
| 
 | ||||
| // If set to true and the CPU is halted, the CPU will step one instruction.
 | ||||
| bool GetCpuStepFlag(); | ||||
| 
 | ||||
| /**
 | ||||
|  * When set to true, the CPU will step one instruction when the CPU is halted next. | ||||
|  * | ||||
|  * @param is_step | ||||
|  */ | ||||
| void SetCpuStepFlag(bool is_step); | ||||
| 
 | ||||
| } | ||||
|  | @ -6,6 +6,7 @@ | |||
| 
 | ||||
| #include <string> | ||||
| #include <array> | ||||
| #include <common/file_util.h> | ||||
| 
 | ||||
| namespace Settings { | ||||
| 
 | ||||
|  | @ -60,6 +61,10 @@ struct Values { | |||
|     float bg_blue; | ||||
| 
 | ||||
|     std::string log_filter; | ||||
| 
 | ||||
|     // Debugging
 | ||||
|     bool use_gdbstub; | ||||
|     u16 gdbstub_port; | ||||
| } extern values; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -12,6 +12,8 @@ | |||
| 
 | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| 
 | ||||
| namespace System { | ||||
| 
 | ||||
| void Init(EmuWindow* emu_window) { | ||||
|  | @ -22,9 +24,13 @@ void Init(EmuWindow* emu_window) { | |||
|     Kernel::Init(); | ||||
|     HLE::Init(); | ||||
|     VideoCore::Init(emu_window); | ||||
| 
 | ||||
|     GDBStub::Init(); | ||||
| } | ||||
| 
 | ||||
| void Shutdown() { | ||||
|     GDBStub::Deinit(); | ||||
| 
 | ||||
|     VideoCore::Shutdown(); | ||||
|     HLE::Shutdown(); | ||||
|     Kernel::Shutdown(); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue