Re: [hatari-devel] TAT's debugger

[ Thread Index | Date Index | More lists.tuxfamily.org/hatari-devel Archives ]


Hi,

On 26.8.2021 23.26, Nicolas Pomarède wrote:
Le 26/08/2021 à 20:41, Troed Sångberg a écrit :
I did - it was discussed on #atariscne IRC a few times during development. I haven't had time to test it yet but considering the demoscene origin I expect I will love it :)

The obvious question then becomes whether to mainline the new debugger interface.

just had a look at the screenshot, and there're definitely some good ideas. Maybe we need to let Tat settle things first and then see how it could be added to main hatari. the gfx debugger is nice (although I have a much-better-never-released version myself as a stand alone 68000 program :) )

From what I understand, the debugger UI uses a separate dir + some patches to support sending commands over TCP to hatari, so it seems quite posible to do, depending on Tat's will.

I think I got the Hatari modifications extracted
from Steven's Github repo, they're in the attached
patch.

Would this part of those changes be acceptable:
https://github.com/tattlemuss/hatari/commit/b0e77a84f13cbb9b010f63b12b3652ff0ef62732
?

Tool itself would add files listed at [1].


	- Eero

[1]
tools/hrdb/hrdb.qrc
tools/hrdb/ui/disasmwidget.h
tools/hrdb/ui/mainwindow.h
tools/hrdb/ui/memoryviewwidget.h
tools/hrdb/ui/disasmwidget.cpp
tools/hrdb/ui/prefsdialog.h
tools/hrdb/ui/breakpointswidget.cpp
tools/hrdb/ui/exceptiondialog.cpp
tools/hrdb/ui/addbreakpointdialog.h
tools/hrdb/ui/rundialog.h
tools/hrdb/ui/graphicsinspector.h
tools/hrdb/ui/memoryviewwidget.cpp
tools/hrdb/ui/breakpointswidget.h
tools/hrdb/ui/consolewindow.cpp
tools/hrdb/ui/mainwindow.cpp
tools/hrdb/ui/graphicsinspector.cpp
tools/hrdb/ui/prefsdialog.cpp
tools/hrdb/ui/addbreakpointdialog.cpp
tools/hrdb/ui/rundialog.cpp
tools/hrdb/ui/exceptiondialog.h
tools/hrdb/ui/quicklayout.h
tools/hrdb/ui/consolewindow.h
tools/hrdb/.gitignore
tools/hrdb/docs/KEYS.md
tools/hrdb/main.cpp
tools/hrdb/transport/remotecommand.h
tools/hrdb/transport/dispatcher.cpp
tools/hrdb/transport/dispatcher.h
tools/hrdb/hopper/buffer.h
tools/hrdb/hopper/instruction.h
tools/hrdb/hopper/decode.h
tools/hrdb/hopper/decode.cpp
tools/hrdb/models/breakpoint.cpp
tools/hrdb/models/stringparsers.cpp
tools/hrdb/models/stringsplitter.cpp
tools/hrdb/models/targetmodel.cpp
tools/hrdb/models/symboltablemodel.cpp
tools/hrdb/models/memory.cpp
tools/hrdb/models/symboltablemodel.h
tools/hrdb/models/registers.h
tools/hrdb/models/disassembler.cpp
tools/hrdb/models/symboltable.cpp
tools/hrdb/models/stringparsers.h
tools/hrdb/models/disassembler.h
tools/hrdb/models/targetmodel.h
tools/hrdb/models/memory.h
tools/hrdb/models/exceptionmask.cpp
tools/hrdb/models/breakpoint.h
tools/hrdb/models/exceptionmask.h
tools/hrdb/models/session.cpp
tools/hrdb/models/registers.cpp
tools/hrdb/models/stringsplitter.h
tools/hrdb/models/session.h
tools/hrdb/models/symboltable.h
tools/hrdb/hardware/hardware_st.h
tools/hrdb/hrdb.pro
tools/hrdb/images/breakpoint10.png
tools/hrdb/images/pcbreakpoint10.png
tools/hrdb/images/pc10.png


PS. Looking at Steven's other pages, I wonder
why he didn't use Hatari's NatFeats example code
(which has been available since 2014) here:
http://clarets.org/steve/projects/20200831_atari_natfeats.html

Should NatFeats examples be "marketed" more? (Where?)
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -199,6 +199,7 @@ check_include_files(sys/times.h HAVE_SYS_TIMES_H)
 check_include_files(utime.h HAVE_UTIME_H)
 check_include_files(sys/utime.h HAVE_SYS_UTIME_H)
 check_include_files("sys/socket.h;sys/un.h" HAVE_UNIX_DOMAIN_SOCKETS)
+check_include_files("winsock.h" HAVE_WINSOCK_SOCKETS)
 
 # #############################
 # Check for optional functions:
--- a/cmake/config-cmake.h
+++ b/cmake/config-cmake.h
@@ -66,6 +66,9 @@
 /* Define to 1 if you have unix domain sockets */
 #cmakedefine HAVE_UNIX_DOMAIN_SOCKETS 1
 
+/* Define to 1 if you have Winsock sockets */
+#cmakedefine HAVE_WINSOCK_SOCKETS 1
+
 /* Define to 1 if you have the 'gettimeofday' function. */
 #cmakedefine HAVE_GETTIMEOFDAY 1
 
--- a/src/control.c
+++ b/src/control.c
@@ -529,6 +529,7 @@ const char *Control_SetFifo(const char *path)
 		return "opening non-blocking read-only FIFO failed";
 	}
 	ControlFifo = fifo;
+
 	return NULL;
 }
 
--- a/src/cpu/disasm.c
+++ b/src/cpu/disasm.c
@@ -2162,6 +2162,15 @@ uae_u32 m68k_disasm_2(TCHAR *buf, int bufsize, uaecptr pc, uae_u16 *bufpc, int b
 				if (dp->duse) {
 					pc = ShowEA(NULL, pc, opcode, dp->dreg, dp->dmode, dp->size, instrname, &deaddr2, &actualea_dst, safemode);
 				}
+				if (lookup->mnemo == i_RTS || lookup->mnemo == i_RTD || lookup->mnemo == i_RTR || lookup->mnemo == i_RTE) {
+					uaecptr a = regs.regs[15];
+					TCHAR eas[100];
+					if (lookup->mnemo == i_RTE || lookup->mnemo == i_RTR) {
+						a += 2;
+					}
+					_stprintf(eas, _T(" == $%08x"), get_ilong_debug(a));
+					_tcscat(instrname, eas);
+				}
 			}
 		}
 
--- a/src/ikbd.c
+++ b/src/ikbd.c
@@ -1773,7 +1773,7 @@ static int IKBD_CheckPressedKey(void)
 void IKBD_InterruptHandler_AutoSend(void)
 {
 	/* Handle user events and other messages, (like quit message) */
-	Main_EventHandler();
+	Main_EventHandler(false);
 
 	/* Remove this interrupt from list and re-order.
 	 * (needs to be done after UI event handling so
--- a/src/includes/main.h
+++ b/src/includes/main.h
@@ -67,7 +67,7 @@ extern Uint32 Main_SetRunVBLs(Uint32 vbls);
 extern const char* Main_SetVBLSlowdown(int factor);
 extern void Main_WaitOnVbl(void);
 extern void Main_WarpMouse(int x, int y, bool restore);
-extern void Main_EventHandler(void);
+extern void Main_EventHandler(bool remoteDebugging);
 extern void Main_SetTitle(const char *title);
 
 #endif /* ifndef HATARI_MAIN_H */
--- a/src/ioMemTabFalcon.c
+++ b/src/ioMemTabFalcon.c
@@ -30,6 +30,7 @@ const char IoMemTabFalc_fileid[] = "Hatari ioMemTabFalcon.c";
 #include "configuration.h"
 #include "statusbar.h"
 #include "stMemory.h"
+#include "debugui.h"
 #if ENABLE_DSP_EMU
 #include "falcon/dsp.h"
 #endif
@@ -413,6 +414,9 @@ const INTERCEPT_ACCESS_FUNC IoMemTable_Falcon[] =
 
 	{ 0xffc020, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte },
 	{ 0xffc021, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte },
+
+	{ 0xffc123, SIZE_BYTE, IoMem_BusErrorOddReadAccess, DebugUI_Trigger },
+
 	{ 0xffd020, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte },
 	{ 0xffd074, SIZE_WORD, IoMemTabFalc_Compatible_ReadWord, IoMemTabFalc_Compatible_WriteWord },
 	{ 0xffd420, SIZE_BYTE, IoMemTabFalc_Compatible_ReadByte, IoMemTabFalc_Compatible_WriteByte },
--- a/src/ioMemTabST.c
+++ b/src/ioMemTabST.c
@@ -34,7 +34,7 @@ const char IoMemTabST_fileid[] = "Hatari ioMemTabST.c";
 #include "video.h"
 #include "blitter.h"
 #include "stMemory.h"
-
+#include "debugui.h"
 
 /*-----------------------------------------------------------------------*/
 /*
@@ -114,6 +114,8 @@ const INTERCEPT_ACCESS_FUNC IoMemTable_ST[] =
 	{ 0xff8a3c, SIZE_BYTE, Blitter_Control_ReadByte, Blitter_Control_WriteByte },
 	{ 0xff8a3d, SIZE_BYTE, Blitter_Skew_ReadByte, Blitter_Skew_WriteByte },
 
+	{ 0xffc123, SIZE_BYTE, IoMem_BusErrorOddReadAccess, DebugUI_Trigger },
+
 	{ 0xfffa01, SIZE_BYTE, MFP_GPIP_ReadByte, MFP_GPIP_WriteByte },
 	{ 0xfffa03, SIZE_BYTE, MFP_ActiveEdge_ReadByte, MFP_ActiveEdge_WriteByte },
 	{ 0xfffa05, SIZE_BYTE, MFP_DataDirection_ReadByte, MFP_DataDirection_WriteByte },
--- a/src/ioMemTabSTE.c
+++ b/src/ioMemTabSTE.c
@@ -27,6 +27,7 @@ const char IoMemTabSTE_fileid[] = "Hatari ioMemTabSTE.c";
 #include "blitter.h"
 #include "statusbar.h"
 #include "stMemory.h"
+#include "debugui.h"
 
 
 /**
@@ -213,6 +214,8 @@ const INTERCEPT_ACCESS_FUNC IoMemTable_STE[] =
 	{ 0xff9220, SIZE_WORD, Joy_SteLightpenX_ReadWord, IoMem_WriteWithoutInterception },     /* Lightpen X position */
 	{ 0xff9222, SIZE_WORD, Joy_SteLightpenY_ReadWord, IoMem_WriteWithoutInterception },     /* Lightpen Y position */
 
+	{ 0xffc123, SIZE_BYTE, IoMem_BusErrorOddReadAccess, DebugUI_Trigger },
+
 	{ 0xfffa01, SIZE_BYTE, MFP_GPIP_ReadByte, MFP_GPIP_WriteByte },
 	{ 0xfffa03, SIZE_BYTE, MFP_ActiveEdge_ReadByte, MFP_ActiveEdge_WriteByte },
 	{ 0xfffa05, SIZE_BYTE, MFP_DataDirection_ReadByte, MFP_DataDirection_WriteByte },
--- a/src/ioMemTabTT.c
+++ b/src/ioMemTabTT.c
@@ -36,6 +36,7 @@ const char IoMemTabTT_fileid[] = "Hatari ioMemTabTT.c";
 #include "screen.h"
 #include "video.h"
 #include "stMemory.h"
+#include "debugui.h"
 
 
 /**
@@ -185,6 +186,8 @@ const INTERCEPT_ACCESS_FUNC IoMemTable_TT[] =
 	{ 0xff9000, SIZE_WORD, IoMem_VoidRead, IoMem_VoidWrite },                /* No bus error here */
 	{ 0xff9200, SIZE_WORD, IoMemTabTT_ReadDIPSwitches, IoMem_VoidWrite },    /* DIP switches */
 
+	{ 0xffc123, SIZE_BYTE, IoMem_BusErrorOddReadAccess, DebugUI_Trigger },
+
 	{ 0xfffa01, SIZE_BYTE, MFP_GPIP_ReadByte, MFP_GPIP_WriteByte },
 	{ 0xfffa03, SIZE_BYTE, MFP_ActiveEdge_ReadByte, MFP_ActiveEdge_WriteByte },
 	{ 0xfffa05, SIZE_BYTE, MFP_DataDirection_ReadByte, MFP_DataDirection_WriteByte },
--- a/src/main.c
+++ b/src/main.c
@@ -57,6 +57,7 @@ const char Main_fileid[] = "Hatari main.c";
 #include "video.h"
 #include "avi_record.h"
 #include "debugui.h"
+#include "remotedebug.h"
 #include "clocks_timings.h"
 
 #include "hatari-glue.h"
@@ -506,7 +507,7 @@ static void Main_HandleMouseMotion(SDL_Event *pEvent)
  * Here we process the SDL events (keyboard, mouse, ...) and map it to
  * Atari IKBD events.
  */
-void Main_EventHandler(void)
+void Main_EventHandler(bool remoteDebugging)
 {
 	bool bContinueProcessing;
 	SDL_Event event;
@@ -519,6 +520,7 @@ void Main_EventHandler(void)
 
 		/* check remote process control */
 		remotepause = Control_CheckUpdates();
+		remotepause |= RemoteDebug_Update();
 
 		if ( bEmulationActive || remotepause )
 		{
@@ -534,6 +536,12 @@ void Main_EventHandler(void)
 		}
 		if (!events)
 		{
+			/* RDB change. If we are running the remote debugger break loop,
+			all we want to do is service the events then exit. So exit now that
+			we have exhausted the available events */
+			if (remoteDebugging)
+				break;
+
 			/* no events -> if emulation is active or
 			 * user is quitting -> return from function.
 			 */
@@ -774,6 +782,7 @@ static void Main_Init(void)
 	
 	/* done as last, needs CPU & DSP running... */
 	DebugUI_Init();
+    RemoteDebug_Init();
 }
 
 
@@ -783,6 +792,7 @@ static void Main_Init(void)
  */
 static void Main_UnInit(void)
 {
+	RemoteDebug_UnInit();
 	Screen_ReturnFromFullScreen();
 	Floppy_UnInit();
 	HDC_UnInit();
--- a/src/video.c
+++ b/src/video.c
@@ -456,7 +456,7 @@ const char Video_fileid[] = "Hatari video.c";
 #include "ikbd.h"
 #include "floppy_ipf.h"
 #include "statusbar.h"
-
+#include "remotedebug.h"
 
 /* The border's mask allows to keep track of all the border tricks		*/
 /* applied to one video line. The masks for all lines are stored in the array	*/
@@ -4550,6 +4550,13 @@ void Video_InterruptHandler_VBL ( void )
 	/* Process shortcut keys */
 	ShortCut_ActKey();
 
+	/* Check if remote debug requested a break.
+	 * Ideally it would be good to move this check somewhere else. Living here means
+	 * that single-stepping after break immediately jumps into the VBL routine
+	 * which can be very confusing, but it needs to be somewhere near here in
+	 * the emulation loop. But for the moment it mimics the keyboard shortcut. */
+	RemoteDebug_CheckRemoteBreak();
+
 	/* Update the IKBD's internal clock */
 	IKBD_UpdateClockOnVBL ();
 
--- a/src/debug/CMakeLists.txt
+++ b/src/debug/CMakeLists.txt
@@ -11,4 +11,4 @@ add_library(Debug
 	    log.c debugui.c breakcond.c debugcpu.c debugInfo.c
 	    ${DSPDBG_C} evaluate.c history.c symbols.c vars.c
 	    profile.c profilecpu.c profiledsp.c
-	    natfeats.c console.c 68kDisass.c)
+	    natfeats.c console.c 68kDisass.c remotedebug.c)
--- a/src/debug/breakcond.c
+++ b/src/debug/breakcond.c
@@ -1681,3 +1681,30 @@ bool BreakAddr_Command(char *args, bool bForDsp)
 	}
 	return true;
 }
+
+bool BreakCond_GetCpuBreakpointInfo(int index, bc_breakpoint_query_t *result)
+{
+	result->expression = "";
+	result->ccount = 0;
+	result->hits = 0;
+	if ((index <= 0) || (index > CpuBreakPoints.count))
+		return false;
+
+	const bc_breakpoint_t* orig = &CpuBreakPoints.breakpoint[index - 1];
+	result->expression = orig->expression;
+	result->ccount = orig->ccount;
+	result->hits = orig->hits;
+	result->once = orig->options.once;
+	result->quiet = orig->options.quiet;
+	result->trace = orig->options.trace;
+	return true;
+}
+
+bool BreakCond_RemoveCpuBreakpoint(int position)
+{
+	bool ret;
+	/* This function subtracts one for the position, since
+	to the user breakpoint IDs start at 1 */
+	ret = BreakCond_Remove(&CpuBreakPoints, position);
+	return ret;
+}
--- a/src/debug/breakcond.h
+++ b/src/debug/breakcond.h
@@ -25,4 +25,20 @@ extern bool BreakAddr_Command(char *expression, bool bforDsp);
 /* extra functions exported for the test code */
 extern int BreakCond_MatchCpuExpression(int position, const char *expression);
 
+/* Remote Debugging Functions */
+typedef struct {
+	const char *expression;
+	int ccount;	/* condition count */
+	int hits;	/* how many times breakpoint hit */
+	bool once;	/* one-time only */
+	bool quiet;	/* do not report, only count */
+	bool trace;	/* write to log */
+} bc_breakpoint_query_t;
+
+/* Remote debugging: query data for CPU breakpoint N.
+	Breakpoints are indexed from 1!
+	Returns true if data found */
+extern bool BreakCond_GetCpuBreakpointInfo(int position, bc_breakpoint_query_t *result);
+extern bool BreakCond_RemoveCpuBreakpoint(int position);
+
 #endif
--- a/src/debug/debugcpu.c
+++ b/src/debug/debugcpu.c
@@ -1065,3 +1065,8 @@ void DebugCpu_InitSession(void)
 	disasm_addr = M68000_GetPC();
 	Profile_CpuStop();
 }
+
+void DebugCpu_SetSteps(int steps)
+{
+	nCpuSteps = steps;
+}
--- a/src/debug/debugcpu.h
+++ b/src/debug/debugcpu.h
@@ -22,4 +22,7 @@ extern int DebugCpu_MemDump(int nArgc, char *psArgs[]);
 extern int DebugCpu_Register(int nArgc, char *psArgs[]);
 extern int DebugCpu_GetRegisterAddress(const char *reg, Uint32 **addr);
 
+/* For RemoteDebug */
+extern void DebugCpu_SetSteps(int steps);
+
 #endif /* HATARI_DEBUGCPU_H */
--- a/src/debug/debugui.c
+++ b/src/debug/debugui.c
@@ -63,6 +63,8 @@ static int parseFiles;
 /* to which directory to change after (potentially recursed) scripts parsing finishes */
 static char *finalDir;
 
+/* Function to read incoming remote debugger commands (set when a socket is attached) */
+static DebugUI_ProcessRemoteCommands remoteDebugcmdCallback = NULL;
 
 /**
  * Save/Restore snapshot of debugging session variables
@@ -1176,39 +1178,52 @@ void DebugUI(debug_reason_t reason)
 	DebugCpu_InitSession();
 	DebugDsp_InitSession();
 	Symbols_LoadCurrentProgram();
-	DebugInfo_ShowSessionInfo();
-
-	/* override paused message so that user knows to look into console
-	 * on how to continue in case he invoked the debugger by accident.
-	 */
-	Statusbar_AddMessage("Console Debugger", 100);
-	Statusbar_Update(sdlscrn, true);
 
 	/* disable normal GUI alerts while on console */
 	alertLevel = Log_SetAlertLevel(LOG_FATAL);
 
-	cmdret = DEBUGGER_CMDDONE;
-	do
+	if (remoteDebugcmdCallback)
 	{
-		/* Read command from the keyboard and give previous
-		 * command for freeing / adding to history
+		/* Replacement loop for the console debugger,
+		 * for when single-stepping or breakpointing has occurred.
 		 */
-		psCmd = DebugUI_GetCommand(psCmd);
-		if (!psCmd)
-			break;
 
-		/* returns new expression expanded string */
-		if (!(expCmd = DebugUI_EvaluateExpressions(psCmd)))
-			continue;
-
-		/* Parse and execute the command string */
-		cmdret = DebugUI_ParseCommand(expCmd);
-		free(expCmd);
+		/* Pass control to remote debugging */
+		(void) remoteDebugcmdCallback();
 	}
-	while (cmdret != DEBUGGER_END);
+	else
+	{
+		DebugInfo_ShowSessionInfo();
 
-	/* free exit command */
-	DebugUI_FreeCommand(psCmd);
+		/* override paused message so that user knows to look into console
+		* on how to continue in case he invoked the debugger by accident.
+		*/
+		Statusbar_AddMessage("Console Debugger", 100);
+		Statusbar_Update(sdlscrn, true);
+
+		cmdret = DEBUGGER_CMDDONE;
+		do
+		{
+			/* Read command from the keyboard and give previous
+			* command for freeing / adding to history
+			*/
+			psCmd = DebugUI_GetCommand(psCmd);
+			if (!psCmd)
+				break;
+
+			/* returns new expression expanded string */
+			if (!(expCmd = DebugUI_EvaluateExpressions(psCmd)))
+				continue;
+
+			/* Parse and execute the command string */
+			cmdret = DebugUI_ParseCommand(expCmd);
+			free(expCmd);
+		}
+		while (cmdret != DEBUGGER_END);
+
+		/* free exit command */
+		DebugUI_FreeCommand(psCmd);
+	}
 
 	Log_SetAlertLevel(alertLevel);
 
@@ -1374,3 +1389,19 @@ void DebugUI_Exceptions(int nr, long pc)
 	fprintf(stderr,"%s exception at 0x%lx!\n", ex[nr].name, pc);
 	DebugUI(REASON_CPU_EXCEPTION);
 }
+
+void DebugUI_Trigger()
+{
+	DebugUI(REASON_USER);
+}
+
+/* Register the callback to process remote command input */
+void DebugUI_RegisterRemoteDebug(DebugUI_ProcessRemoteCommands cmdCallback)
+{
+	remoteDebugcmdCallback = cmdCallback;
+}
+
+int DebugUI_ParseConsoleCommand(const char* command)
+{
+	return DebugUI_ParseCommand(command);
+}
--- a/src/debug/debugui.h
+++ b/src/debug/debugui.h
@@ -29,11 +29,21 @@ typedef enum {
 	REASON_USER        // e.g. keyboard shortcut
 } debug_reason_t;
 
+/* Callback type to register if remote debugging is enabled */
+typedef bool (*DebugUI_ProcessRemoteCommands)(void);
+
 extern void DebugUI_Init(void);
 extern void DebugUI(debug_reason_t reason);
 extern void DebugUI_Exceptions(int nr, long pc);
 extern bool DebugUI_ParseLine(const char *input);
 extern bool DebugUI_AddParseFile(const char *input);
 extern void DebugUI_MemorySnapShot_Capture(const char *path, bool bSave);
+extern void DebugUI_Trigger(void);
+
+// Register the callback to process remote command input
+extern void DebugUI_RegisterRemoteDebug(DebugUI_ProcessRemoteCommands cmdCallback);
+
+/* Process command from remote debug. Returns DEBUGGER_* code */
+extern int DebugUI_ParseConsoleCommand(const char* command);
 
 #endif /* HATARI_DEBUGUI_H */
--- a/src/debug/symbols.c
+++ b/src/debug/symbols.c
@@ -932,3 +932,22 @@ int Symbols_Command(int nArgc, char *psArgs[])
 	}
 	return DEBUGGER_CMDDONE;
 }
+
+int Symbols_CpuSymbolCount(void)
+{
+	if (!CpuSymbolsList)
+		return 0;
+	return CpuSymbolsList->namecount;
+}
+
+bool Symbols_GetCpuSymbol(int index, rdb_symbol_t* result)
+{
+	if (index >= Symbols_CpuSymbolCount())
+		return false;
+
+	const symbol_t* entry = CpuSymbolsList->names + index;
+	result->name = entry->name;
+	result->address = entry->address;
+	result->type = symbol_char(entry->type);
+	return true;
+}
--- a/src/debug/symbols.h
+++ b/src/debug/symbols.h
@@ -49,4 +49,13 @@ extern void Symbols_LoadCurrentProgram(void);
 extern char *Symbols_MatchCommand(const char *text, int state);
 extern int Symbols_Command(int nArgc, char *psArgs[]);
 
+/* Remote debug code */
+typedef struct {
+	char *name;
+	Uint32 address;
+	char type;
+} rdb_symbol_t;
+extern int Symbols_CpuSymbolCount(void);
+extern bool Symbols_GetCpuSymbol(int index, rdb_symbol_t* result);
+
 #endif
--- a/src/debug/vars.c
+++ b/src/debug/vars.c
@@ -307,3 +307,14 @@ int Vars_List(int nArgc, char *psArgv[])
 	fputs("Some of the variables are valid only in specific situations.\n", stderr);
 	return DEBUGGER_CMDDONE;
 }
+
+bool Vars_QueryVariable(Uint32 position, const var_addr_t **result)
+{
+	*result = NULL;
+	if (position >= ARRAY_SIZE(hatari_vars))
+		return false;
+
+	const var_addr_t *hvar = hatari_vars + position;
+	*result = hvar;
+	return true;
+}
--- a/src/debug/vars.h
+++ b/src/debug/vars.h
@@ -56,4 +56,8 @@ extern int Vars_List(int nArgc, char *psArgv[]);
 extern Uint32 Vars_GetAesOpcode(void);
 extern Uint32 Vars_GetVdiOpcode(void);
 
+/* Remote debugging: query data for variable [0..n].
+	Returns true if variable found */
+extern bool Vars_QueryVariable(Uint32 position, const var_addr_t **result);
+
 #endif
--- /dev/null
+++ b/src/debug/remotedebug.c
@@ -0,0 +1,1217 @@
+/*
+ * Hatari - remotedebug.c
+ * 
+ * This file is distributed under the GNU General Public License, version 2
+ * or at your option any later version. Read the file gpl.txt for details.
+ * 
+ * Remote debugging support via a network port.
+ * 
+ */
+
+#include "remotedebug.h"
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#if HAVE_UNIX_DOMAIN_SOCKETS
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/fcntl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+#if HAVE_WINSOCK_SOCKETS
+#include <winsock.h>
+#endif
+
+#include "main.h"		/* For ARRAY_SIZE, event handler */
+#include "m68000.h"		/* Must be after main.h for "unlikely" */
+#include "debugui.h"	/* For DebugUI_RegisterRemoteDebug */
+#include "debug_priv.h"	/* For debugOutput control */
+#include "debugcpu.h"	/* For stepping */
+#include "evaluate.h"
+#include "stMemory.h"
+#include "breakcond.h"
+#include "symbols.h"
+#include "log.h"
+#include "vars.h"
+#include "memory.h"
+#include "configuration.h"
+
+// For status bar updates
+#include "screen.h"
+#include "statusbar.h"
+
+// TCP port for remote debugger access
+#define RDB_PORT                   (56001)
+
+// Max character count in a command sent to Hatari
+#define RDB_CMD_MAX_SIZE           (300)
+
+// How many bytes we collect to send chunks for the "mem" command
+#define RDB_MEM_BLOCK_SIZE         (2048)
+
+// How many bytes in the internal network send buffer
+#define RDB_SEND_BUFFER_SIZE       (512)
+
+// Network timeout when in break loop, to allow event handler update.
+// Currently 0.5sec
+#define RDB_SELECT_TIMEOUT_USEC   (500000)
+
+/* Remote debugging break command was sent from debugger */
+static bool bRemoteBreakRequest = false;
+
+/* Processing is stopped and the remote debug loop is active */
+static bool bRemoteBreakIsActive = false;
+
+// -----------------------------------------------------------------------------
+// Structure managing connection state
+typedef struct RemoteDebugState
+{
+	int SocketFD;						/* handle for the port/socket. -1 if not available */
+	int AcceptedFD;						/* handle for the accepted connection from client, or
+											-1 if not connected */
+
+	/* Input (receive/command) buffer data */
+	char cmd_buf[RDB_CMD_MAX_SIZE+1];	/* accumulated command string */
+	int cmd_pos;						/* offset in cmd_buf for new data */
+
+	FILE* original_stdout;				/* original file pointers for redirecting output */
+	FILE* original_stderr;
+	FILE* original_debugOutput;
+	FILE* debugOutput;					/* our file handle to output */
+
+	/* Output (send) buffer data */
+	char sendBuffer[RDB_SEND_BUFFER_SIZE];	/* buffer for replies */
+	int sendBufferPos;					/* next byte to write into buffer */
+} RemoteDebugState;
+
+// -----------------------------------------------------------------------------
+// Force send of data in sendBuffer
+static void flush_data(RemoteDebugState* state)
+{
+	// Flush existing data
+	send(state->AcceptedFD, state->sendBuffer, state->sendBufferPos, 0);
+	state->sendBufferPos = 0;
+}
+
+// -----------------------------------------------------------------------------
+// Add data to sendBuffer, flush if necessary
+static void add_data(RemoteDebugState* state, const char* data, size_t size)
+{
+	// Flush data if it won't fit
+	if (state->sendBufferPos + size > RDB_SEND_BUFFER_SIZE)
+		flush_data(state);
+
+	memcpy(state->sendBuffer + state->sendBufferPos, data, size);
+	state->sendBufferPos += size;
+}
+
+// -----------------------------------------------------------------------------
+// Transmission functions (wrapped for platform portability)
+// -----------------------------------------------------------------------------
+static void send_str(RemoteDebugState* state, const char* pStr)
+{
+	add_data(state, pStr, strlen(pStr));
+}
+
+// -----------------------------------------------------------------------------
+static void send_hex(RemoteDebugState* state, uint32_t val)
+{
+	char str[9];
+	int size = sprintf(str, "%X", val);
+	add_data(state, str, size);
+}
+
+// -----------------------------------------------------------------------------
+static void send_char(RemoteDebugState* state, char val)
+{
+	add_data(state, &val, 1);
+}
+
+// -----------------------------------------------------------------------------
+static void send_bool(RemoteDebugState* state, bool val)
+{
+	send_char(state, val ? '1' : '0');
+}
+
+// -----------------------------------------------------------------------------
+static void send_key_value(RemoteDebugState* state, const char* pStr, uint32_t val)
+{
+	send_str(state, " ");
+	send_str(state, pStr);
+	send_str(state, ":");
+	send_hex(state, val);
+}
+
+// -----------------------------------------------------------------------------
+static void send_term(RemoteDebugState* state)
+{
+	send_char(state, 0);
+}
+
+//-----------------------------------------------------------------------------
+static bool read_hex_char(char c, uint8_t* result)
+{
+	if (c >= '0' && c <= '9')
+	{
+		*result = (uint8_t)(c - '0');
+		return true;
+	}
+	if (c >= 'a' && c <= 'f')
+	{
+		*result = (uint8_t)(10 + c - 'a');
+		return true;
+	}
+	if (c >= 'A' && c <= 'F')
+	{
+		*result = (uint8_t)(10 + c - 'A');
+		return true;
+	}
+	*result = 0;
+	return false;
+}
+
+// -----------------------------------------------------------------------------
+// Send the out-of-band status to flag start/stop
+static int RemoteDebug_NotifyState(RemoteDebugState* state)
+{
+	char tmp[100];
+	sprintf(tmp, "!status %x %x", bRemoteBreakIsActive ? 0 : 1, M68000_GetPC());
+	send_str(state, tmp);
+	send_term(state);
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+static int RemoteDebug_NotifyConfig(RemoteDebugState* state)
+{
+	const CNF_SYSTEM* system = &ConfigureParams.System;
+	char tmp[100];
+	sprintf(tmp, "!config %x %x", 
+		system->nMachineType, system->nCpuLevel);
+	
+	send_str(state, tmp);
+	send_term(state);
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+/* Restore any debugOutput settings to the original saved state. Close
+   any file we opened. */
+static void RemoteDebug_CloseDebugOutput(RemoteDebugState* state)
+{
+	/* Restore old stdio, if set */
+#ifndef __WINDOWS__
+	if (state->original_stderr != NULL)
+		stderr = state->original_stderr;
+	if (state->original_stdout != NULL)
+		stdout = state->original_stdout;
+	if (state->original_debugOutput != NULL)
+		stdout = state->original_debugOutput;
+	if (state->debugOutput)
+		fclose(state->debugOutput);
+#endif
+	state->original_stderr = NULL;
+	state->original_stdout = NULL;
+	state->original_debugOutput = NULL;
+	state->debugOutput = NULL;
+}
+
+// -----------------------------------------------------------------------------
+//    DEBUGGER COMMANDS
+// -----------------------------------------------------------------------------
+/* Return short status info in a useful format, mainly whether it's running */
+static int RemoteDebug_Status(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	char tmp[100];
+	sprintf(tmp, "OK %x %x", bRemoteBreakIsActive ? 0 : 1, M68000_GetPC());
+	send_str(state, tmp);
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+/* Put in a break request which is serviced elsewhere in the main loop */
+static int RemoteDebug_Break(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	// Only set a break request if we are running
+	if (!bRemoteBreakIsActive)
+	{
+		bRemoteBreakRequest = true;
+		send_str(state, "OK");
+	}
+	else
+	{
+		return 1;
+	}
+	
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+/* Step next instruction. This is currently a passthrough to the normal debugui code. */
+static int RemoteDebug_Step(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	DebugCpu_SetSteps(1);
+	send_str(state, "OK");
+
+	// Restart
+	bRemoteBreakIsActive = false;
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+static int RemoteDebug_Run(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	send_str(state, "OK");
+	bRemoteBreakIsActive = false;
+	return 0;
+}
+
+/**
+ * Dump register contents. 
+ * This also includes Hatari variables, which we treat as a subset of regs.
+ * 
+ * Input: "regs\n"
+ * 
+ * Output: "regs <reg:value>*N\n"
+ */
+static int RemoteDebug_Regs(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	int regIdx;
+	Uint32 varIndex;
+	varIndex = 0;
+	const var_addr_t* var;
+
+	static const int regIds[] = {
+		REG_D0, REG_D1, REG_D2, REG_D3, REG_D4, REG_D5, REG_D6, REG_D7,
+		REG_A0, REG_A1, REG_A2, REG_A3, REG_A4, REG_A5, REG_A6, REG_A7 };
+	static const char *regNames[] = {
+		"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+		"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7" };
+
+	send_str(state, "OK ");
+
+	// Normal regs
+	for (regIdx = 0; regIdx < ARRAY_SIZE(regIds); ++regIdx)
+		send_key_value(state, regNames[regIdx], Regs[regIds[regIdx]]);
+		
+	// Special regs
+	send_key_value(state, "PC", M68000_GetPC());
+	send_key_value(state, "USP", regs.usp);
+	send_key_value(state, "ISP", regs.isp);
+	send_key_value(state, "SR", M68000_GetSR());
+	send_key_value(state, "EX", regs.exception);
+
+	// Variables
+	while (Vars_QueryVariable(varIndex, &var))
+	{
+		Uint32 value;
+		value = Vars_GetValue(var);
+		send_key_value(state, var->name, value);
+		++varIndex;
+	}
+
+	return 0;
+}
+
+/**
+ * Dump the requested area of ST memory.
+ *
+ * Input: "mem <start addr> <size in bytes>\n"
+ *
+ * Output: "mem <address-expr> <size-expr> <memory as base16 string>\n"
+ */
+
+static int RemoteDebug_Mem(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	int arg;
+	Uint8 value;
+	Uint32 memdump_addr = 0;
+	Uint32 memdump_count = 0;
+	int offset = 0;
+	const char* err_str = NULL;
+
+	/* For remote debug, only "address" "count" is supported */
+	arg = 1;
+	if (nArgc >= arg + 2)
+	{
+		err_str = Eval_Expression(psArgs[arg], &memdump_addr, &offset, false);
+		if (err_str)
+			return 1;
+
+		++arg;
+		err_str = Eval_Expression(psArgs[arg], &memdump_count, &offset, false);
+		if (err_str)
+			return 1;
+		++arg;
+	}
+	else
+	{
+		// Not enough args
+		return 1;
+	}
+
+	send_str(state, "OK ");
+	send_hex(state, memdump_addr);
+	send_str(state, " ");
+	send_hex(state, memdump_count);
+	send_str(state, " ");
+
+	// Need to flush here before we switch to our existing buffer system
+	flush_data(state);
+
+	// Send data in blocks of "buffer_size" memory bytes
+	// (We don't need a terminator when sending)
+	const uint32_t buffer_size = RDB_MEM_BLOCK_SIZE*2;
+	char* buffer = malloc(buffer_size);
+	const char* hex = "0123456789ABCDEF";
+	uint32_t pos = 0;
+	while (pos < memdump_count)
+	{
+		uint32_t block_size = memdump_count - pos;
+		if (block_size > RDB_MEM_BLOCK_SIZE)
+			block_size = RDB_MEM_BLOCK_SIZE;
+
+		for (uint32_t i = 0; i < block_size; ++i)
+		{
+			value = STMemory_ReadByte(memdump_addr);
+			buffer[i * 2]     = hex[(value >> 4) & 15];
+			buffer[i * 2 + 1] = hex[ value & 15];
+			++pos;
+			++memdump_addr;
+		}
+		send(state->AcceptedFD, buffer, block_size * 2, 0);
+	}
+
+	free(buffer);
+	return 0;
+}
+
+/**
+ * Write the requested area of ST memory.
+ *
+ * Input: "memset <start addr> <hex-data>\n"
+ *
+ * Output: "OK"/"NG"
+ */
+
+static int RemoteDebug_Memset(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	int arg;
+	Uint32 memdump_addr = 0;
+	Uint32 memdump_end = 0;
+	Uint32 memdump_count = 0;
+	uint8_t valHi;
+	uint8_t valLo;
+	int offset = 0;
+	const char* err_str = NULL;
+
+	/* For remote debug, only "address" "count" is supported */
+	arg = 1;
+	if (nArgc >= arg + 3)
+	{
+		// Address
+		err_str = Eval_Expression(psArgs[arg], &memdump_addr, &offset, false);
+		if (err_str)
+			return 1;
+
+		++arg;
+		// Size
+		err_str = Eval_Expression(psArgs[arg], &memdump_count, &offset, false);
+		if (err_str)
+			return 1;
+		++arg;
+	}
+	else
+	{
+		// Not enough args
+		return 1;
+	}
+
+	memdump_end = memdump_addr + memdump_count;
+	uint32_t pos = 0;
+	while (memdump_addr < memdump_end)
+	{
+		if (!read_hex_char(psArgs[arg][pos], &valHi))
+			return 1;
+		++pos;
+		if (!read_hex_char(psArgs[arg][pos], &valLo))
+			return 1;
+		++pos;
+
+		//put_byte(memdump_addr, (valHi << 4) | valLo);
+		STMemory_WriteByte(memdump_addr, (valHi << 4) | valLo);
+		++memdump_addr;
+	}
+	send_str(state, "OK ");
+	// Report changed range so tools can decide to update
+	send_hex(state, memdump_end - memdump_count);
+	send_str(state, " ");
+	send_hex(state, memdump_count);
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+/* Set a breakpoint at an address. */
+static int RemoteDebug_bp(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	int arg = 1;
+	if (nArgc >= arg + 1)
+	{
+		// Pass to standard simple function
+		if (BreakCond_Command(psArgs[arg], false))
+		{
+			send_str(state, "OK");
+			return 0;
+		}
+	}
+	return 1;
+}
+
+// -----------------------------------------------------------------------------
+/* List all breakpoints */
+static int RemoteDebug_bplist(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	int i, count;
+
+	count = BreakCond_CpuBreakPointCount();
+	send_str(state, "OK ");
+	send_hex(state, count);
+	send_str(state, " ");
+
+	/* NOTE breakpoint query indices start at 1 */
+	for (i = 1; i <= count; ++i)
+	{
+		bc_breakpoint_query_t query;
+		BreakCond_GetCpuBreakpointInfo(i, &query);
+
+		send_str(state, query.expression);
+		/* Note this has the ` character to flag the expression end,
+		since the expression can contain spaces */
+		send_str(state, "`");
+		send_hex(state, query.ccount); send_str(state, " ");
+		send_hex(state, query.hits); send_str(state, " ");
+		send_bool(state, query.once); send_str(state, " ");
+		send_bool(state, query.quiet); send_str(state, " ");
+		send_bool(state, query.trace); send_str(state, " ");
+	}
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+/* Remove breakpoint number N.
+   NOTE breakpoint IDs start at 1!
+*/
+static int RemoteDebug_bpdel(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	int arg = 1;
+	Uint32 bp_position;
+	if (nArgc >= arg + 1)
+	{
+		if (Eval_Number(psArgs[arg], &bp_position))
+		{
+			if (BreakCond_RemoveCpuBreakpoint(bp_position))
+			{
+				send_str(state, "OK");
+				return 0;
+			}
+		}
+	}
+	return 1;
+}
+
+// -----------------------------------------------------------------------------
+/* "symlist" -- List all CPU symbols */
+static int RemoteDebug_symlist(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	int i, count;
+	count = Symbols_CpuSymbolCount();
+	send_str(state, "OK ");
+	send_hex(state, count);
+	send_str(state, " ");
+	
+	for (i = 0; i < count; ++i)
+	{
+		rdb_symbol_t query;
+		if (!Symbols_GetCpuSymbol(i, &query))
+			break;
+		send_str(state, query.name);
+		send_str(state, "`");
+		send_hex(state, query.address);
+		send_str(state, " ");
+		send_char(state, query.type);
+		send_str(state, " ");
+	}
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+/* "exmask" -- Read or set exception mask */
+/* returns "OK <mask val>"" */
+static int RemoteDebug_exmask(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	int arg = 1;
+	Uint32 mask;
+	int offset;
+	const char* err_str;
+
+	if (nArgc == 2)
+	{
+		// Assumed to set the mask
+		err_str = Eval_Expression(psArgs[arg], &mask, &offset, false);
+		if (err_str)
+			return 1;
+		ExceptionDebugMask = mask;
+	}
+
+	// Always respond with the mask value, so that setting comes back
+	// to the remote debugger
+	send_str(state, "OK ");
+	send_hex(state, ExceptionDebugMask);
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+/* "console <text>" pass command to debugui console input */
+/* returns "OK" */
+static int RemoteDebug_console(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	if (nArgc == 2)
+	{
+		int cmdRet = DebugUI_ParseConsoleCommand(psArgs[1]);
+
+		/* handle a command that restarts execution */
+		if (cmdRet == DEBUGGER_END)
+			bRemoteBreakIsActive = false;
+
+		fflush(debugOutput);
+		fflush(stderr);
+		// Insert an out-of-band notification, in case of restart
+		RemoteDebug_NotifyState(state);
+	}
+	send_str(state, "OK");
+	return 0;
+}
+
+// -----------------------------------------------------------------------------
+/* "setstd <filename> redirects stdout/stderr to given file */
+/* returns "OK"/"NG" */
+static int RemoteDebug_setstd(int nArgc, char *psArgs[], RemoteDebugState* state)
+{
+	if (nArgc == 2)
+	{
+		// Create the output file
+		const char* filename = psArgs[1];
+		FILE* outpipe = fopen(filename, "w");
+		if (outpipe)
+		{
+			// Switch back to "normal settings"
+			RemoteDebug_CloseDebugOutput(state);
+
+			// Record the original states
+			state->original_stderr = stderr;
+			state->original_stdout = stdout;
+			state->original_debugOutput = debugOutput;
+
+			// Switch over redirect
+#ifdef __WINDOWS__
+			freopen(filename, "w", stdout);
+			freopen(filename, "w", stderr);
+#else
+			stderr = outpipe;
+			stdout = outpipe;
+#endif
+			debugOutput = outpipe;
+			state->debugOutput = outpipe;
+			send_str(state, "OK");
+			return 0;
+		}
+	}
+	return 1;
+}
+// -----------------------------------------------------------------------------
+/* DebugUI command structure */
+typedef struct
+{
+	int (*pFunction)(int argc, char *argv[], RemoteDebugState* state);
+	const char *sName;
+	bool split_args;
+} rdbcommand_t;
+
+/* Array of all remote debug command descriptors */
+static const rdbcommand_t remoteDebugCommandList[] = {
+	{ RemoteDebug_Status,   "status"	, true		},
+	{ RemoteDebug_Break,    "break"		, true		},
+	{ RemoteDebug_Step,     "step"		, true		},
+	{ RemoteDebug_Run, 		"run"		, true		},
+	{ RemoteDebug_Regs,     "regs"		, true		},
+	{ RemoteDebug_Mem,      "mem"		, true		},
+	{ RemoteDebug_Memset,   "memset"	, true		},
+	{ RemoteDebug_bp, 		"bp"		, false		},
+	{ RemoteDebug_bplist,	"bplist"	, true		},
+	{ RemoteDebug_bpdel,	"bpdel"		, true		},
+	{ RemoteDebug_symlist,	"symlist"	, true		},
+	{ RemoteDebug_exmask,	"exmask"	, true		},
+	{ RemoteDebug_console,	"console"	, false		},
+	{ RemoteDebug_setstd,	"setstd"	, true		},
+
+	/* Terminator */
+	{ NULL, NULL }
+};
+
+/**
+ * Parse remote debug command and execute it.
+ * Command should return 0 if successful.
+ * Returns -1 if command not parsed
+ */
+static int RemoteDebug_Parse(const char *input_orig, RemoteDebugState* state)
+{
+	char *psArgs[64], *input, *input2;
+	const char *delim;
+	int nArgc = -1;
+	int retval;
+
+	input = strdup(input_orig);
+	input2 = strdup(input_orig);
+	// NO CHECK use the safer form of strtok
+	psArgs[0] = strtok(input, " \t");
+
+	/* Search the command ... */
+	const rdbcommand_t* pCommand = remoteDebugCommandList;
+	while (pCommand->pFunction)
+	{
+		if (!strcmp(psArgs[0], pCommand->sName))
+			break;
+		++pCommand;
+	}
+	if (!pCommand->pFunction)
+	{
+		free(input);
+		return -1;
+	}
+
+	delim = " \t";
+
+	/* Separate arguments and put the pointers into psArgs */
+	if (pCommand->split_args)
+	{
+		for (nArgc = 1; nArgc < ARRAY_SIZE(psArgs); nArgc++)
+		{
+			psArgs[nArgc] = strtok(NULL, delim);
+			if (psArgs[nArgc] == NULL)
+				break;
+		}
+	}
+	else
+	{
+		/* Single arg to pass through to some internal calls,
+		   for example breakpoint parsing
+		*/
+		psArgs[1] = input + strlen(psArgs[0]) + 1;
+		nArgc = 2;
+	}
+	
+	if (nArgc >= ARRAY_SIZE(psArgs))
+	{
+		retval = -1;
+	}
+	else
+	{
+		/* ... and execute the function */
+		retval = pCommand->pFunction(nArgc, psArgs, state);
+	}
+	free(input);
+	free(input2);
+	return retval;
+}
+
+
+// -----------------------------------------------------------------------------
+#if HAVE_WINSOCK_SOCKETS
+static void SetNonBlocking(SOCKET socket, u_long nonblock)
+{
+	// Set socket to blocking
+	u_long mode = nonblock;  // 0 to enable blocking socket
+	ioctlsocket(socket, FIONBIO, &mode);
+}
+#define GET_SOCKET_ERROR		WSAGetLastError()
+#define RDB_CLOSE				closesocket
+
+#endif
+#if HAVE_UNIX_DOMAIN_SOCKETS
+static void SetNonBlocking(int socket, u_long nonblock)
+{
+	// Set socket to blocking
+	int	on = fcntl(socket, F_GETFL);
+	if (nonblock)
+		on = (on | O_NONBLOCK);
+	else
+		on &= ~O_NONBLOCK;
+	fcntl(socket, F_SETFL, on);
+}
+
+// Set the socket to allow reusing the port. Avoids problems when
+// exiting and restarting with hrdb still live.
+static void SetReuseAddr(int fd)
+{
+	int val = 1;
+	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+}
+
+#define GET_SOCKET_ERROR		errno
+#define RDB_CLOSE				close
+#endif
+
+static RemoteDebugState g_rdbState;
+
+static void RemoteDebugState_Init(RemoteDebugState* state)
+{
+	state->SocketFD = -1;
+	state->AcceptedFD = -1;
+	memset(state->cmd_buf, 0, sizeof(state->cmd_buf));
+	state->cmd_pos = 0;
+	state->original_stdout = NULL;
+	state->original_stderr = NULL;
+	state->original_debugOutput = NULL;
+	state->debugOutput = NULL;
+	state->sendBufferPos = 0;
+}
+
+static int RemoteDebugState_TryAccept(RemoteDebugState* state, bool blocking)
+{
+	fd_set set;
+	struct timeval timeout;
+
+	if (blocking)
+	{
+		// Connection active
+		// Check socket with timeout
+		FD_ZERO(&set);
+		FD_SET(state->SocketFD, &set);
+
+		// On Linux, need to reset the timeout on each loop
+		// see "select(2)"
+		timeout.tv_sec = 0;
+		timeout.tv_usec = RDB_SELECT_TIMEOUT_USEC;
+		int rv = select(state->SocketFD + 1, &set, NULL, NULL, &timeout);
+		if (rv <= 0)
+			return state->AcceptedFD;
+	}
+
+	state->AcceptedFD = accept(state->SocketFD, NULL, NULL);
+	if (state->AcceptedFD != -1)
+	{
+		printf("Remote Debug connection accepted\n");
+		// reset send buffer
+		state->sendBufferPos = 0;
+		// Send connected handshake, so client can
+		// drop any subsequent commands
+		send_str(state, "!connected");
+		send_term(state);
+		flush_data(state);
+
+		// New connection, so do an initial report.
+		RemoteDebug_NotifyConfig(state);
+		RemoteDebug_NotifyState(state);
+		flush_data(state);
+	}
+	return state->AcceptedFD;
+}
+
+/* Process any command data that has been read into the pending
+	command buffer, and execute them.
+*/
+static void RemoteDebug_ProcessBuffer(RemoteDebugState* state)
+{
+	int cmd_ret;
+	int num_commands = 0;
+	while (1)
+	{
+		// Scan for a complete command
+		char* endptr = memchr(state->cmd_buf, 0, state->cmd_pos);
+		if (!endptr)
+			break;
+
+		int length = endptr - state->cmd_buf;
+
+		const char* pCmd = state->cmd_buf;
+
+		// Process this command
+		cmd_ret = RemoteDebug_Parse(pCmd, state);
+
+		if (cmd_ret != 0)
+		{
+			// return an error if something failed
+			send_str(state, "NG");
+		}
+		send_term(state);
+
+		// Copy extra bytes to the start
+		// -1 here is for the terminator
+		int extra_length = state->cmd_pos - length - 1;
+		memcpy(state->cmd_buf, endptr + 1, extra_length);
+		state->cmd_pos = extra_length;
+		++num_commands;
+	}
+
+	if (num_commands)
+		flush_data(state);
+}
+
+/*	Handle activity from the accepted connection.
+	Disconnect if socket lost or other errors.
+*/
+static void RemoteDebugState_UpdateAccepted(RemoteDebugState* state)
+{
+	int remaining;
+	fd_set set;
+	struct timeval timeout;
+#if HAVE_WINSOCK_SOCKETS
+	int winerr;
+#endif
+
+	// Connection active
+	// Check socket with timeout
+	FD_ZERO(&set);
+	FD_SET(state->AcceptedFD, &set);
+
+	// On Linux, need to reset the timeout on each loop
+	// see "select(2)"
+	timeout.tv_sec = 0;
+	timeout.tv_usec = RDB_SELECT_TIMEOUT_USEC;
+
+	int rv = select(state->AcceptedFD + 1, &set, NULL, NULL, &timeout);
+	if (rv < 0)
+	{
+		// select error, Lost connection?
+		return;
+	}
+	else if (rv == 0)
+	{
+		// timeout, socket does not have anything to read.
+		// Run event handler while we know nothing changes
+		Main_EventHandler(true);
+		return;
+	}
+
+	// Read input and accumulate a command (blocking)
+	remaining = RDB_CMD_MAX_SIZE - state->cmd_pos;
+	int bytes = recv(state->AcceptedFD, 
+		&state->cmd_buf[state->cmd_pos],
+		remaining,
+		0);
+	if (bytes > 0)
+	{
+		// New data. Is there a command in there (null-terminated string)
+		state->cmd_pos += bytes;
+		RemoteDebug_ProcessBuffer(state);
+	}
+	else if (bytes == 0)
+	{
+		// This represents an orderly EOF, even in Winsock
+		printf("Remote Debug connection closed\n");
+		RDB_CLOSE(state->AcceptedFD);
+		state->AcceptedFD = -1;
+
+		// Bail out of the loop here so we don't just spin
+		return;
+	}
+	else
+	{
+		// On Windows -1 simply means a general error and might be OK.
+		// So we check for known errors that should cause us to exit.
+#if HAVE_WINSOCK_SOCKETS
+		winerr = WSAGetLastError();
+		if (winerr == WSAECONNRESET)
+		{
+			printf("Remote Debug connection reset\n");
+			RDB_CLOSE(state->AcceptedFD);
+			state->AcceptedFD = -1;
+		}
+		printf("Unknown cmd %d\n", WSAGetLastError());
+#endif
+	}
+}
+
+/* Update with a suitable message, when we are in the break loop */
+static void SetStatusbarMessage(const RemoteDebugState* state)
+{
+	if (state->AcceptedFD != -1)
+		Statusbar_AddMessage("hrdb connected -- debugging", 100);
+	else
+		Statusbar_AddMessage("break -- waiting for hrdb", 100);
+	Statusbar_Update(sdlscrn, true);
+}
+
+/*
+	Handle the loop once Hatari enters break mode, and update
+	network connections and commands/responses. Also calls
+	main system event handler (at intervals) to keep the UI
+	responsive.
+
+	Exits when
+	- commands cause a restart
+	- main socket lost
+	- quit request from UI
+*/
+static bool RemoteDebug_BreakLoop(void)
+{
+	RemoteDebugState* state;
+	state = &g_rdbState;
+
+	// This is set to true to prevent re-entrancy in RemoteDebug_Update()
+	bRemoteBreakIsActive = true;
+
+	if (state->AcceptedFD != -1)
+	{
+		// Notify after state change happens
+		RemoteDebug_NotifyConfig(state);
+		RemoteDebug_NotifyState(state);
+		flush_data(state);
+	}
+
+	SetStatusbarMessage(state);
+
+	// Set the socket to blocking on the connection now, so we
+	// sleep until data is available.
+	SetNonBlocking(state->AcceptedFD, 0);
+
+	while (bRemoteBreakIsActive)
+	{
+		// Handle main exit states
+		if (state->SocketFD == -1)
+			break;
+
+		if (bQuitProgram)
+			break;
+
+		// If we don't have a connection yet, we need to get one
+		if (state->AcceptedFD == -1)
+		{
+			// Try to reconnect (with select())
+			RemoteDebugState_TryAccept(state, true);
+			if (state->AcceptedFD != -1)
+			{
+				// Set the socket to blocking on the connection now, so we
+				// sleep until data is available.
+				SetNonBlocking(state->AcceptedFD, 0);
+				SetStatusbarMessage(state);
+			}
+			else
+			{
+				// No connection so update events
+				Main_EventHandler(true);
+			}
+		}
+		else
+		{
+			// Already connected, check for messages or disconnection
+			RemoteDebugState_UpdateAccepted(state);
+			if (state->AcceptedFD == -1)
+			{
+				// disconnected
+				SetStatusbarMessage(state);
+			}
+		}
+	}
+	bRemoteBreakIsActive = false;
+	// Clear any break request that might have been set
+	bRemoteBreakRequest = false;
+
+	// Switch back to non-blocking for the update loop
+	if (state->AcceptedFD != -1)
+	{
+		RemoteDebug_NotifyConfig(state);
+		RemoteDebug_NotifyState(state);
+		flush_data(state);
+
+		SetNonBlocking(state->AcceptedFD, 1);
+	}
+
+	// TODO: this return code no longer used
+	return true;
+}
+
+/*
+	Create a socket for the port and start to listen over TCP
+*/
+static int RemoteDebugState_InitServer(RemoteDebugState* state)
+{
+	state->AcceptedFD = -1;
+
+	// Create listening socket on port
+	struct sockaddr_in sa;
+
+	state->SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if (state->SocketFD == -1) {
+		fprintf(stderr, "Failed to open socket\n");
+		return 1;
+	}
+#if HAVE_UNIX_DOMAIN_SOCKETS
+	SetReuseAddr(state->SocketFD);
+#endif
+
+	// Socket is non-blokcing to start with
+	SetNonBlocking(state->SocketFD, 1);
+
+	memset(&sa, 0, sizeof sa);
+	sa.sin_family = AF_INET;
+	sa.sin_port = htons(RDB_PORT);
+	sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+	if (bind(state->SocketFD,(struct sockaddr *)&sa, sizeof sa) == -1) {
+		fprintf(stderr, "Failed to bind socket (%d)\n", GET_SOCKET_ERROR);
+		RDB_CLOSE(state->SocketFD);
+		state->SocketFD =-1;
+		return 1;
+	}
+  
+	if (listen(state->SocketFD, 1) == -1) {
+		fprintf(stderr, "Failed to listen() on socket\n");
+		RDB_CLOSE(state->SocketFD);
+		state->SocketFD =-1;
+		return 1;
+	}
+
+	// Socket is now in a listening state and could accept 
+	printf("Remote Debug Listening on port %d\n", RDB_PORT);
+	return 0;
+}
+
+// This is the per-frame update, to check for new connections
+// while Hatari is running at full speed
+static void RemoteDebugState_Update(RemoteDebugState* state)
+{
+	if (state->SocketFD == -1)
+	{
+		// TODO: we should try to re-init periodically
+		return;
+	}
+
+	int remaining;
+	if (state->AcceptedFD != -1)
+	{
+		// Connection is active
+		// Read input and accumulate a command
+		remaining = RDB_CMD_MAX_SIZE - state->cmd_pos;
+		
+#if HAVE_UNIX_DOMAIN_SOCKETS
+		int bytes = recv(state->AcceptedFD, 
+			&state->cmd_buf[state->cmd_pos],
+			remaining,
+			MSG_DONTWAIT);
+#endif
+#if HAVE_WINSOCK_SOCKETS
+		int bytes = recv(state->AcceptedFD, 
+			&state->cmd_buf[state->cmd_pos],
+			remaining,
+			0);
+#endif
+
+		if (bytes > 0)
+		{
+			// New data. Is there a command in there (null-terminated string)
+			state->cmd_pos += bytes;
+			RemoteDebug_ProcessBuffer(state);
+		}
+		else if (bytes == 0)
+		{
+			// This represents an orderly EOF
+			printf("Remote Debug connection closed\n");
+			RDB_CLOSE(state->AcceptedFD);
+			state->AcceptedFD = -1;
+			return;
+		}
+	}
+	else
+	{
+		if (RemoteDebugState_TryAccept(state, false) != -1)
+		{
+			// Set this connection to non-blocking since we are
+			// in "running" state and it will poll every VBL
+			SetNonBlocking(state->AcceptedFD, 1);
+			return;
+		}
+	}
+}
+
+void RemoteDebug_Init(void)
+{
+	printf("Starting remote debug\n");
+	RemoteDebugState_Init(&g_rdbState);
+	
+#if HAVE_WINSOCK_SOCKETS
+	WORD wVersionRequested;
+	WSADATA wsaData;
+	int err;
+
+	wVersionRequested = MAKEWORD(1, 0);
+	err = WSAStartup(wVersionRequested, &wsaData);
+	if (err != 0)
+	{
+		printf("WSAStartup failed with error: %d\n", err);
+		// No socket can be made, and initial state will
+		// still reflect this (SocketFD == -1)
+		return;
+	}
+#endif
+
+	if (RemoteDebugState_InitServer(&g_rdbState) == 0)
+	{
+		// Socket created, so use our break loop
+		DebugUI_RegisterRemoteDebug(RemoteDebug_BreakLoop);
+	}
+}
+
+void RemoteDebug_UnInit()
+{
+	printf("Stopping remote debug\n");
+	DebugUI_RegisterRemoteDebug(NULL);
+
+	RemoteDebug_CloseDebugOutput(&g_rdbState);
+	if (g_rdbState.AcceptedFD != -1)
+	{
+		RDB_CLOSE(g_rdbState.AcceptedFD);
+	}
+
+	if (g_rdbState.SocketFD != -1)
+	{
+		RDB_CLOSE(g_rdbState.SocketFD);
+	}
+
+	g_rdbState.AcceptedFD = -1;
+	g_rdbState.SocketFD = -1;
+	g_rdbState.cmd_pos = 0;
+}
+
+bool RemoteDebug_Update(void)
+{
+	// This function is called from the main event handler, which
+	// is also called while break is active. So protect against
+	// re-entrancy.
+	if (!bRemoteBreakIsActive)
+	{
+		RemoteDebugState_Update(&g_rdbState);
+	}
+	return bRemoteBreakIsActive;
+}
+
+/**
+ * Debugger invocation if requested by remote debugger.
+ * 
+ * NOTE: we could just run our own loop here, rather than going through DebugUI?
+ */
+void RemoteDebug_CheckRemoteBreak(void)
+{
+	if (bRemoteBreakRequest)
+	{
+		bRemoteBreakRequest = false;
+		// Stop and wait for inputs from the control socket
+		DebugUI(REASON_USER);
+	}
+}
--- /dev/null
+++ b/src/debug/remotedebug.h
@@ -0,0 +1,18 @@
+/*
+  Hatari - remotedebug.h
+
+  This file is distributed under the GNU General Public License, version 2
+  or at your option any later version. Read the file gpl.txt for details.
+*/
+
+#ifndef HATARI_REMOTE_H
+#define HATARI_REMOTE_H
+
+#include <stdbool.h>
+
+extern void RemoteDebug_Init(void);
+extern void RemoteDebug_UnInit(void);
+extern bool RemoteDebug_Update(void);
+// Read the flag to see if remote break was requested
+extern void RemoteDebug_CheckRemoteBreak(void);
+#endif /* HATARI_REMOTE_H */
--- a/python-ui/hatari.py
+++ b/python-ui/hatari.py
@@ -27,6 +27,8 @@ def _path_quote(path):
     "quote spaces in paths as expected by Hatari socket API"
     return path.replace(" ", "\\ ")
 
+def debugout(str):
+    pass #print(str)
 
 # Running Hatari instance
 class Hatari:
@@ -94,6 +96,7 @@ class Hatari:
 
     def _send_message(self, msg):
         if self.control:
+            debugout(">>>" + msg)
             self.control.send(bytes(msg, "ASCII"))
             return True
         else:
@@ -126,15 +129,49 @@ class Hatari:
 
     def debug_command(self, cmd):
         "debug_command(command), runs given Hatari debugger command"
-        return self._send_message("hatari-debug %s\n" % cmd)
+        return self._send_message("hatari-rdb cmd %s\n" % cmd)
 
-    def pause(self):
+    def collect_response(self, fileobj, endtag):
+        # Poll for output until a particular code is seen.
+        # Used for break response
+        select.select([fileobj], [], [])
+        output = []
+        timeout = 1.0
+        waited_time = 0.0
+        while waited_time < timeout:
+            lines = fileobj.readlines()
+            if len(lines) > 0:
+                #print(lines)
+                output += lines
+                for f in lines:
+                    if f.startswith(endtag):
+                        return True, output
+            time.sleep(0.02)
+            waited_time += 0.02 # approximate but enough
+        print("Timeout? %s" % endtag)
+        return False, output
+
+    def pause(self, fileobj):
         "pause(), pauses Hatari emulation"
-        return self._send_message("hatari-stop\n")
+        ret = self._send_message("hatari-rdb break\n")
+        if not ret:
+            return False
 
     def unpause(self):
         "unpause(), continues Hatari emulation"
-        return self._send_message("hatari-cont\n")
+        self._send_message("hatari-rdb unbreak\n")
+
+    def get_status(self):
+        "status(), request CPU status"
+        return self._send_message("hatari-rdb status\n")
+
+    def send_rdb_cmd(self, cmd):
+        "send a hatari-rdb command"
+        return self._send_message("hatari-rdb %s\n" % cmd)
+
+    def echo(self, arg):
+        "echo(arg) bounce back message for sync"
+        return self._send_message("hatari-rdb echo %s\n" % arg)
 
     def _open_output_file(self, hataricommand, option, path):
         if os.path.exists(path):
@@ -171,12 +208,12 @@ class Hatari:
         "get_lines(file) -> list of lines readable from given Hatari output file"
         # wait until data is available, then wait for some more
         # and only then the data can be read, otherwise its old
-        print("Request&wait data from Hatari...")
+        debugout("Request&wait data from Hatari...")
         select.select([fileobj], [], [])
         time.sleep(0.1)
-        print("...read the data lines")
+        debugout("...read the data lines")
         lines = fileobj.readlines()
-        print("".join(lines))
+        #debugout("".join(lines))
         return lines
 
     def enable_embed_info(self):
diff --git a/python-ui/hatariui.py b/python-ui/hatariui.py
index 1f9c33e0..cb08aa64 100755
--- a/python-ui/hatariui.py
+++ b/python-ui/hatariui.py
@@ -27,7 +27,7 @@ from gi.repository import Gtk
 from gi.repository import Gdk
 from gi.repository import GLib
 
-from debugui import HatariDebugUI
+from debugui import HatariDebugUI, TargetState
 from hatari import Hatari, HatariConfigMapping
 from uihelpers import UInfo, UIHelp, create_button, create_toolbutton, \
      create_toggle, HatariTextInsert, get_open_filename, get_save_filename
@@ -78,6 +78,8 @@ class UICallbacks:
         self.mainwin = None
         self.hatariwin = None
         self.debugui = None
+        self.disasm = None
+        self.debug_disasm = None
         self.panels = {}
 
         # dialogs are created when needed
@@ -310,8 +312,10 @@ class UICallbacks:
     # ------- debug callback -----------
     def debugger(self, widget):
         if not self.debugui:
-            self.debugui = HatariDebugUI(self.hatari)
-        self.debugui.show()
+            # NO CHECK
+            self.target_state = TargetState(self.hatari)
+            self.debugui = HatariDebugUI(self.hatari, self.target_state)
+        self.debugui.show()     # NO CHECK hide
 
     # ------- trace callback -----------
     def trace(self, widget):


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/