2
0
Brucey 5 жил өмнө
parent
commit
c1f9ee69c3

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+*.bak
+.bmx
+*.a
+*.i
+*.i2

+ 76 - 0
appstub.mod/appstub.bmx

@@ -0,0 +1,76 @@
+
+Strict
+
+NoDebug
+
+Module Dbg.Appstub
+
+ModuleInfo "Version: 1.20"
+ModuleInfo "Authors: Mark Sibly, Simon Armstrong"
+ModuleInfo "License: zlib/libpng"
+ModuleInfo "Copyright: Blitz Research Ltd"
+ModuleInfo "Modserver: BRL"
+
+ModuleInfo "History: 1.20 Release"
+ModuleInfo "History: Fixed 'invalid typetag' issue"
+ModuleInfo "History: 1.19 Release"
+ModuleInfo "History: Removed some debug output"
+ModuleInfo "History: 1.18 Release"
+ModuleInfo "History: Changed debugger.stdio.bmx so it handles deep stacks better"
+ModuleInfo "History: 1.17 Release"
+ModuleInfo "History: Added Brucey's SIGPIPE fix for GTK"
+ModuleInfo "History: 1.16 Release"
+ModuleInfo "History: Added experimental dll support"
+ModuleInfo "History: 1.15 Release"
+ModuleInfo "History: Fixed Const string reporting not being escaped"
+ModuleInfo "History: 1.14 Release"
+ModuleInfo "History: Debug output lines now prepended with ~~>"
+ModuleInfo "History: 1.13 Release"
+ModuleInfo "History: Removed unused debugger sources"
+ModuleInfo "History: 1.12 Release"
+ModuleInfo "History: Now manually builds app menu for MacOS Tiger"
+ModuleInfo "History: 1.11 Release"
+ModuleInfo "History: Fixed MacOS debug string overflow"
+ModuleInfo "History: 1.10 Release"
+ModuleInfo "History: Added kludge for MacOS Menu Quit generating errors"
+ModuleInfo "History: 1.09 Release"
+ModuleInfo "History: Fixed MacOS AutoreleasePool problem"
+ModuleInfo "History: 1.08 Release"
+ModuleInfo "History: Fixed Tiger build warnings in debugger.macos.m"
+ModuleInfo "History: 1.07 Release"
+ModuleInfo "History: Improved Win32 debugger switching"
+ModuleInfo "History: Fixed macos debugger dying with extern types"
+ModuleInfo "History: 1.06 Release"
+ModuleInfo "History: Tweaked MacOS debugger"
+ModuleInfo "History: 1.05 Release"
+ModuleInfo "History: Fixed C Compiler warnings"
+ModuleInfo "History: Fixed multidim arrays hanging debugger"
+ModuleInfo "History: 1.04 Release"
+ModuleInfo "History: Fixed buffer overflow in debug strings"
+ModuleInfo "History: Modified float and double debug output to match compiler precision"
+
+?Debug
+Import "debugger_mt.stdio.bmx"	'Let's give Otus's new MT friendly debugger a whirl!
+?
+
+?osx
+Import "appstub.macos.m"
+Import "-framework Cocoa"
+Import "-framework Carbon"
+?ios
+Import "appstub.ios.c"
+?Win32
+Import "appstub.win32.c"
+?Linux
+Import "appstub.linux.c"
+?emscripten
+Import "appstub.linux.c"
+?nx
+Import "appstub.nx.c"
+?
+
+Extern
+Function _bb_main()
+End Extern
+
+_bb_main

+ 18 - 0
appstub.mod/appstub.ios.c

@@ -0,0 +1,18 @@
+
+#include <brl.mod/blitz.mod/blitz.h>
+
+#include <signal.h>
+
+int __bb_brl_appstub_appstub();
+
+int SDL_main( int argc,char *argv[] ){
+
+	signal( SIGPIPE,SIG_IGN );
+	
+	bbStartup( argc, argv,0,0 );
+
+	__bb_brl_appstub_appstub();
+	
+	exit( 0 );
+}
+

+ 42 - 0
appstub.mod/appstub.linux.c

@@ -0,0 +1,42 @@
+
+#include <brl.mod/blitz.mod/blitz.h>
+
+#include <signal.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+int __bb_brl_appstub_appstub();
+
+#ifndef __ANDROID__
+int main( int argc,char *argv[] ){
+#else
+int SDL_main( int argc,char *argv[] ){
+#endif
+
+	signal( SIGPIPE,SIG_IGN );
+	
+	bbStartup( argc,argv,0,0 );
+	
+	__bb_brl_appstub_appstub();
+
+	return 0;
+}
+
+#ifndef __EMSCRIPTEN__
+size_t bmx_process_vm_readv(size_t dataSize, void * pointer, void * buffer) {
+
+	struct iovec local;
+	struct iovec remote;
+	pid_t pid = getpid();
+	
+	local.iov_base = buffer;
+	local.iov_len = dataSize;
+	
+	remote.iov_base = pointer;
+	remote.iov_len = dataSize;
+	
+	size_t result = process_vm_readv(pid, &local, 1, &remote, 1, 0);
+	
+	return result;
+}
+#endif

+ 139 - 0
appstub.mod/appstub.macos.m

@@ -0,0 +1,139 @@
+
+#include <brl.mod/blitz.mod/blitz.h>
+
+#import <AppKit/AppKit.h>
+
+void __bb_brl_appstub_appstub();
+
+static int app_argc;
+static char **app_argv;
+
+static NSMutableArray *_appArgs;
+
+static void createAppMenu( NSString *appName ){
+
+	NSMenu *appMenu;
+	NSMenuItem *item;
+	NSString *title;
+	
+	[NSApp setMainMenu:[NSMenu new]];
+	
+	appMenu=[NSMenu new];
+	
+	title=[@"Hide " stringByAppendingString:appName];
+	[appMenu addItemWithTitle:@"Hide" action:@selector(hide:) keyEquivalent:@"h"];
+
+	item=(NSMenuItem*)[appMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+	[item setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
+	
+	[appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
+	
+	[appMenu addItem:[NSMenuItem separatorItem]];
+
+	title=[@"Quit " stringByAppendingString:appName];
+	[appMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+	
+	item=[NSMenuItem new];
+	[item setSubmenu:appMenu];
+	[[NSApp mainMenu] addItem:item];
+	
+	[NSApp performSelector:NSSelectorFromString(@"setAppleMenu:") withObject:appMenu];
+}
+
+static void run(){
+
+	signal( SIGPIPE,SIG_IGN );
+	
+	bbStartup( app_argc,app_argv,0,0 );
+
+	__bb_brl_appstub_appstub();
+	
+	exit( 0 );
+}
+
+void bbFlushAutoreleasePool(){
+	// nothing to do here.
+}
+
+@interface BlitzMaxAppDelegate : NSObject{
+}
+@end
+
+@implementation BlitzMaxAppDelegate
+-(void)applicationWillTerminate:(NSNotification*)notification{
+	exit(0);
+}
+
+-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender{
+	return NSTerminateCancel;
+}
+
+-(BOOL)application:(NSApplication*)app openFile:(NSString*)path{
+	[_appArgs addObject:path];
+	return YES;
+}
+
+-(void)applicationDidFinishLaunching:(NSNotification*)notification{
+	int i;
+	app_argc=[_appArgs count];
+	app_argv=(char**)malloc( (app_argc+1)*sizeof(char*) );
+	for( i=0;i<app_argc;++i ){
+		NSString *t=[_appArgs objectAtIndex:i];
+		char *p=(char*)malloc( [t length]+1 );
+		strcpy( p,[t cString] );
+		app_argv[i]=p;
+	}
+	app_argv[i]=0;
+	[_appArgs release];
+
+	[NSApp activateIgnoringOtherApps:YES];
+	
+	run();
+}
+@end
+
+int main( int argc,char *argv[] ){
+	int i;
+	CFURLRef url;
+	char *app_file,*p;
+	
+	@autoreleasepool {
+
+		[NSApplication sharedApplication];
+		
+		app_argc=argc;
+		app_argv=argv;
+		
+		url=CFBundleCopyExecutableURL( CFBundleGetMainBundle() );
+	
+		app_file=malloc( 4096 );
+		CFURLGetFileSystemRepresentation( url,true,(UInt8*)app_file,4096 );
+		
+		if( strstr( app_file,".app/Contents/MacOS/" ) ){
+			//GUI app!
+			//
+			p=strrchr( app_file,'/' );
+			if( p ){
+				++p;
+			}else{
+				 p=app_file;
+			}
+			createAppMenu( [NSString stringWithCString:p] );
+			free( app_file );
+		
+			[NSApp setDelegate:[[BlitzMaxAppDelegate alloc] init]];
+			
+			_appArgs=[[NSMutableArray arrayWithCapacity:10] retain];
+			[_appArgs addObject:[NSString stringWithCString:argv[0]] ];
+				
+			[NSApp run];
+		}else{
+			//Console app!
+			//
+			free( app_file );
+	
+			run();
+		}
+	
+	}
+}

+ 24 - 0
appstub.mod/appstub.nx.c

@@ -0,0 +1,24 @@
+#include <brl.mod/blitz.mod/blitz.h>
+
+#include <signal.h>
+#include <unistd.h>
+#include <switch.h>
+
+int __bb_brl_appstub_appstub();
+
+int main( int argc,char *argv[] ){
+	
+	socketInitializeDefault();
+	nxlinkStdio();
+
+	signal( SIGPIPE,SIG_IGN );
+	
+	bbStartup( argc,argv,0,0 );
+	
+	__bb_brl_appstub_appstub();
+
+	socketExit();
+
+	return 0;
+}
+

+ 75 - 0
appstub.mod/appstub.win32.c

@@ -0,0 +1,75 @@
+
+#include <brl.mod/blitz.mod/blitz.h>
+
+#include <windows.h>
+
+//Enable exception debugging
+#define BB_DEBUG_EXCEPTIONS
+
+#ifdef BB_DEBUG_EXCEPTIONS
+
+static LONG WINAPI unhandledExceptionFilter( EXCEPTION_POINTERS *xinfo ){
+	const char *p="EXCEPTION_UNKNOWN";
+	switch( xinfo->ExceptionRecord->ExceptionCode ){
+	case EXCEPTION_ACCESS_VIOLATION:p="EXCEPTION_ACCESS_VIOLATION";break;
+	case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:p="EXCEPTION_ARRAY_BOUNDS_EXCEEDED";break;
+	case EXCEPTION_BREAKPOINT:p="EXCEPTION_BREAKPOINT";break;
+	case EXCEPTION_DATATYPE_MISALIGNMENT:p="EXCEPTION_DATATYPE_MISALIGNMENT";break;
+	case EXCEPTION_FLT_DENORMAL_OPERAND:p="EXCEPTION_FLT_DENORMAL_OPERAND";break;
+	case EXCEPTION_FLT_DIVIDE_BY_ZERO:p="EXCEPTION_FLT_DIVIDE_BY_ZERO";break;
+	case EXCEPTION_FLT_INEXACT_RESULT:p="EXCEPTION_FLT_INEXACT_RESULT";break;
+	case EXCEPTION_FLT_INVALID_OPERATION:p="EXCEPTION_FLT_INVALID_OPERATION";break;
+	case EXCEPTION_FLT_OVERFLOW:p="EXCEPTION_FLT_OVERFLOW";break;
+	case EXCEPTION_FLT_STACK_CHECK:p="EXCEPTION_FLT_STACK_CHECK";break;
+	case EXCEPTION_FLT_UNDERFLOW:p="EXCEPTION_FLT_UNDERFLOW";break;
+	case EXCEPTION_ILLEGAL_INSTRUCTION:p="EXCEPTION_ILLEGAL_INSTRUCTION";break;
+	case EXCEPTION_IN_PAGE_ERROR:p="EXCEPTION_IN_PAGE_ERROR";break;
+	case EXCEPTION_INT_DIVIDE_BY_ZERO:p="EXCEPTION_INT_DIVIDE_BY_ZERO";break;
+	case EXCEPTION_INT_OVERFLOW:p="EXCEPTION_INT_OVERFLOW";break;
+	case EXCEPTION_INVALID_DISPOSITION:p="EXCEPTION_INVALID_DISPOSITION";break;
+	case EXCEPTION_NONCONTINUABLE_EXCEPTION:p="EXCEPTION_NONCONTINUABLE_EXCEPTION";break;
+	case EXCEPTION_PRIV_INSTRUCTION:p="EXCEPTION_PRIV_INSTRUCTION";break;
+	case EXCEPTION_SINGLE_STEP:p="EXCEPTION_SINGLE_STEP";break;
+	case EXCEPTION_STACK_OVERFLOW:p="EXCEPTION_STACK_OVERFLOW";break;
+	}
+	MessageBoxA( GetActiveWindow(),p,"Windows exception",MB_OK );
+	bbOnDebugStop();
+	exit( 0 );
+}
+
+#endif
+
+void bbLibStartup(wchar_t * buf);
+
+void __bb_dbg_appstub_appstub();
+
+int main( int argc,char *argv[] ){
+
+#ifdef BB_DEBUG_EXCEPTIONS
+
+	SetUnhandledExceptionFilter( unhandledExceptionFilter );
+
+#endif
+
+	bbStartup( argc,argv,0,0 );
+
+	__bb_dbg_appstub_appstub();
+
+	return 0;
+}
+
+wchar_t bbLibFile[MAX_PATH];
+
+BOOL WINAPI DllMain( HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved ){
+
+	if( fdwReason!=DLL_PROCESS_ATTACH ) return 1;
+
+	GetModuleFileNameW( hinstDLL,bbLibFile,MAX_PATH );
+
+	return 1;
+}
+
+void bbLibInit() {
+	bbLibStartup(bbLibFile);
+	__bb_dbg_appstub_appstub();
+}

+ 711 - 0
appstub.mod/debugger.stdio.bmx

@@ -0,0 +1,711 @@
+
+Strict
+
+NoDebug
+
+Private
+
+?Win32
+Extern "Win32"
+Const SW_SHOW=5
+Const SW_RESTORE=9
+Function IsIconic( hwnd )
+Function GetForegroundWindow()
+Function SetForegroundWindow( hwnd )
+Function ShowWindow( hwnd,cmdShow )
+Function GetCurrentThreadId()
+End Extern
+?
+
+?MacOS
+Extern
+Function CGDisplayIsCaptured( displayId )
+End Extern
+?
+
+Extern
+Global bbStringClass:Byte
+Global bbArrayClass:Byte
+Global bbNullObject:Byte
+Global bbEmptyArray:Byte
+Global bbEmptyString:Byte
+Global brl_blitz_NullFunctionError:Byte Ptr
+Function bbIsMainThread()="bbIsMainThread"
+Function bbGCValidate:Int( mem:Int ) = "bbGCValidate"
+End Extern
+
+Function ToHex$( val )
+	Local buf:Short[8]
+	For Local k=7 To 0 Step -1
+		Local n=(val&15)+Asc("0")
+		If n>Asc("9") n=n+(Asc("A")-Asc("9")-1)
+		buf[k]=n
+		val:Shr 4
+	Next
+	Return String.FromShorts( buf,8 ).ToLower()
+End Function
+
+Function IsAlpha( ch )
+	Return (ch>=Asc("a") And ch<=Asc("z")) Or (ch>=Asc("A") And ch<=Asc("Z"))
+End Function
+
+Function IsNumeric( ch )
+	Return ch>=Asc("0") And ch<=Asc("9")
+End Function
+
+Function IsAlphaNumeric( ch )
+	Return IsAlpha(ch) Or IsNumeric(ch)
+End Function
+
+Function IsUnderscore( ch )
+	Return ch=Asc("_")
+End Function
+
+Function Ident$( tag$ Var )
+	If Not tag Return ""
+	If Not IsAlpha( tag[0] ) And Not IsUnderscore( tag[0] ) Return ""
+	Local i=1
+	While i<tag.length And (IsAlphaNumeric(tag[i]) Or IsUnderscore(tag[i]))
+		i:+1
+	Wend
+	Local id$=tag[..i]
+	tag=tag[i..]
+	Return id
+End Function
+
+Function TypeName$( tag$ Var )
+
+	Local t$=tag[..1]
+	tag=tag[1..]
+
+	Select t
+	Case "b"
+		Return "Byte"
+	Case "s"
+		Return "Short"
+	Case "i"
+		Return "Int"
+	Case "l"
+		Return "Long"
+	Case "f"
+		Return "Float"
+	Case "d"
+		Return "Double"
+	Case "$"
+		Return "String"
+	Case "z"
+		Return "CString"
+	Case "w"
+		Return "WString"
+	Case ":","?"
+		Local id$=Ident( tag )
+		While tag And tag[0]=Asc(".")
+			tag=tag[1..]
+			id=Ident( tag )
+		Wend
+		If Not id DebugError "Invalid object typetag"
+		Return id
+	Case "*"
+		Return TypeName( tag )+" Ptr"
+	Case "["
+		While tag[..1]=","
+			tag=tag[1..]
+			t:+","
+		Wend
+		If tag[..1]<>"]" DebugError "Invalid array typetag"
+		tag=tag[1..]
+		Return TypeName( tag )+t+"]"
+	Case "("
+		If tag[..1]<>")"
+			t:+TypeName( tag )
+			While tag[..1]=","
+				tag=tag[1..]
+				t:+","+TypeName( tag )
+			Wend
+			If tag[..1]<>")" DebugError "Invalid function typetag"
+		EndIf
+		tag=tag[1..]
+		Return TypeName( tag )+t+")"
+	End Select
+
+	DebugError "Invalid debug typetag:"+t
+
+End Function
+
+'int offsets into 12 byte DebugStm struct
+Const DEBUGSTM_FILE=0
+Const DEBUGSTM_LINE=1
+Const DEBUGSTM_CHAR=2
+
+'int offsets into 16 byte DebugDecl struct
+Const DEBUGDECL_KIND=0
+Const DEBUGDECL_NAME=1
+Const DEBUGDECL_TYPE=2
+Const DEBUGDECL_ADDR=3
+
+'DEBUGDECL_KIND values
+Const DEBUGDECLKIND_END=0
+Const DEBUGDECLKIND_CONST=1
+Const DEBUGDECLKIND_LOCAL=2
+Const DEBUGDECLKIND_FIELD=3
+Const DEBUGDECLKIND_GLOBAL=4
+Const DEBUGDECLKIND_VARPARAM=5
+Const DEBUGDECLKIND_TYPEMETHOD=6
+Const DEBUGDECLKIND_TYPEFUNCTION=7
+
+'int offsets into 12+n_decls*4 byte DebugScope struct
+Const DEBUGSCOPE_KIND=0
+Const DEBUGSCOPE_NAME=1
+Const DEBUGSCOPE_DECLS=2
+
+'DEBUGSCOPE_KIND values
+Const DEBUGSCOPEKIND_FUNCTION=1
+Const DEBUGSCOPEKIND_TYPE=2
+Const DEBUGSCOPEKIND_LOCAL=3
+
+Function DebugError( t$ )
+	WriteStderr "Debugger Error:"+t+"~n"
+	End
+End Function
+
+Function DebugStmFile$( stm:Int Ptr )
+	Return String.FromCString( Byte Ptr stm[DEBUGSTM_FILE] )
+End Function
+
+Function DebugStmLine( stm:Int Ptr )
+	Return stm[DEBUGSTM_LINE]
+End Function
+
+Function DebugStmChar( stm:Int Ptr )
+	Return stm[DEBUGSTM_CHAR]
+End Function
+
+Function DebugDeclKind$( decl:Int Ptr )
+	Select decl[DEBUGDECL_KIND]
+	Case DEBUGDECLKIND_CONST Return "Const"
+	Case DEBUGDECLKIND_LOCAL Return "Local"
+	Case DEBUGDECLKIND_FIELD Return "Field"
+	Case DEBUGDECLKIND_GLOBAL Return "Global"
+	Case DEBUGDECLKIND_VARPARAM Return "Local"
+	End Select
+	DebugError "Invalid decl kind"
+End Function
+
+Function DebugDeclName$( decl:Int Ptr )
+	Return String.FromCString( Byte Ptr decl[DEBUGDECL_NAME] )
+End Function
+
+Function DebugDeclType$( decl:Int Ptr )
+	Local t$=String.FromCString( Byte Ptr decl[DEBUGDECL_TYPE] )
+	Local ty$=TypeName( t )
+	Return ty
+End Function
+
+Function DebugDeclSize( decl:Int Ptr )
+
+	Local tag=(Byte Ptr Ptr(decl+DEBUGDECL_TYPE))[0][0]
+
+	Select tag
+	Case Asc("b") Return 1
+	Case Asc("s") Return 2
+	Case Asc("l") Return 8
+	Case Asc("d") Return 8
+	End Select
+	
+	Return 4
+
+End Function
+
+Function DebugEscapeString$( s$ )
+	s=s.Replace( "~~","~~~~")
+	s=s.Replace( "~0","~~0" )
+	s=s.Replace( "~t","~~t" )
+	s=s.Replace( "~n","~~n" )
+	s=s.Replace( "~r","~~r" )
+	s=s.Replace( "~q","~~q" )
+	Return "~q"+s+"~q"
+End Function
+
+Function DebugDeclValue$( decl:Int Ptr,inst:Byte Ptr )
+	If decl[DEBUGDECL_KIND]=DEBUGDECLKIND_CONST
+		Local p:Byte Ptr=Byte Ptr decl[DEBUGDECL_ADDR]
+		Return DebugEscapeString(String.FromShorts( Short Ptr(p+12),(Int Ptr (p+8))[0] ))
+	EndIf
+
+	Local p:Byte Ptr
+	Select decl[DEBUGDECL_KIND]
+	Case DEBUGDECLKIND_GLOBAL
+		p=Byte Ptr decl[DEBUGDECL_ADDR]
+	Case DEBUGDECLKIND_LOCAL,DEBUGDECLKIND_FIELD
+		p=Byte Ptr (inst+decl[DEBUGDECL_ADDR])
+	Case DEBUGDECLKIND_VARPARAM
+		p=Byte Ptr (inst+decl[DEBUGDECL_ADDR])
+		p=Byte Ptr ( (Int Ptr p)[0] )
+	Default
+		DebugError "Invalid decl kind"
+	End Select
+	
+	Local tag=(Byte Ptr Ptr(decl+DEBUGDECL_TYPE))[0][0]
+	
+	Select tag
+	Case Asc("b")
+		Return String.FromInt( (Byte Ptr p)[0] )
+	Case Asc("s")
+		Return String.FromInt( (Short Ptr p)[0] )
+	Case Asc("i")
+		Return String.FromInt( (Int Ptr p)[0] )
+	Case Asc("l")
+		Return String.FromLong( (Long Ptr p)[0] )
+	Case Asc("f")
+		Return String.FromFloat( (Float Ptr p)[0] )
+	Case Asc("d")
+		Return String.FromDouble( (Double Ptr p)[0] )
+	Case Asc("$")
+		p=(Byte Ptr Ptr p)[0]
+		Local sz=Int Ptr(p+8)[0]
+		Local s$=String.FromShorts( Short Ptr(p+12),sz )
+		Return DebugEscapeString( s )
+	Case Asc("z")
+		p=(Byte Ptr Ptr p)[0]
+		If Not p Return "Null"
+		Local s$=String.FromCString( p )
+		Return DebugEscapeString( s )
+	Case Asc("w")
+		p=(Byte Ptr Ptr p)[0]
+		If Not p Return "Null"
+		Local s$=String.FromWString( Short Ptr p )
+		Return DebugEscapeString( s )
+	Case Asc("*"),Asc("?")
+		Return "$"+ToHex( (Int Ptr p)[0] )
+	Case Asc("(")
+		p=(Byte Ptr Ptr p)[0]
+		If p=brl_blitz_NullFunctionError Return "Null"
+	Case Asc(":")
+		p=(Byte Ptr Ptr p)[0]
+		If p=Varptr bbNullObject Return "Null"
+		If p=Varptr bbEmptyArray Return "Null[]"
+		If p=Varptr bbEmptyString Return "Null$"
+	Case Asc("[")
+		p=(Byte Ptr Ptr p)[0]
+		If Not p Return "Null"
+		If Not (Int Ptr (p+20))[0] Return "Null"
+	Default
+		DebugError "Invalid decl typetag:"+Chr(tag)
+	End Select
+	
+	Return "$"+ToHex( Int p )
+
+End Function
+
+Function DebugScopeKind$( scope:Int Ptr )
+	Select scope[DEBUGSCOPE_KIND]
+	Case DEBUGSCOPEKIND_FUNCTION Return "Function"
+	Case DEBUGSCOPEKIND_TYPE Return "Type"
+	Case DEBUGSCOPEKIND_LOCAL Return "Local"
+	End Select
+	DebugError "Invalid scope kind"
+End Function
+
+Function DebugScopeName$( scope:Int Ptr )
+	Return String.FromCString( Byte Ptr scope[DEBUGSCOPE_NAME] )
+End Function
+
+Function DebugScopeDecls:Int Ptr[]( scope:Int Ptr )
+	Local n,p:Int Ptr=scope+DEBUGSCOPE_DECLS
+	While p[n]<>DEBUGDECLKIND_END
+		n:+1
+	Wend
+	Local decls:Int Ptr[n]
+	For Local i=0 Until n
+		decls[i]=p+i*4
+	Next
+	Return decls
+End Function
+
+Function DebugObjectScope:Int Ptr( inst:Byte Ptr )
+	Local clas:Int Ptr Ptr=(Int Ptr Ptr Ptr inst)[0]
+	Return clas[2]
+End Function
+
+Extern
+Global bbOnDebugStop()
+Global bbOnDebugLog( message$ )
+Global bbOnDebugEnterStm( stm:Int Ptr )
+Global bbOnDebugEnterScope( scope:Int Ptr,inst:Byte Ptr )
+Global bbOnDebugLeaveScope()
+Global bbOnDebugPushExState()
+Global bbOnDebugPopExState()
+Global bbOnDebugUnhandledEx( ex:Object )
+End Extern
+
+bbOnDebugStop=OnDebugStop
+bbOnDebugLog=OnDebugLog
+bbOnDebugEnterStm=OnDebugEnterStm
+bbOnDebugEnterScope=OnDebugEnterScope
+bbOnDebugLeaveScope=OnDebugLeaveScope
+bbOnDebugPushExState=OnDebugPushExState
+bbOnDebugPopExState=OnDebugPopExState
+bbOnDebugUnhandledEx=OnDebugUnhandledEx
+
+?Win32
+Global _ideHwnd=GetForegroundWindow();
+Global _appHwnd
+?
+
+'********** Debugger code here **********
+
+Const MODE_RUN=0
+Const MODE_STEP=1
+Const MODE_STEPIN=2
+Const MODE_STEPOUT=3
+
+Type TScope
+	Field scope:Int Ptr,inst:Byte Ptr,stm:Int Ptr
+End Type
+
+Type TExState
+	Field scopeStackTop
+End Type
+
+Global mode,debugLevel,funcLevel
+Global currentScope:TScope=New TScope
+Global scopeStack:TScope[],scopeStackTop
+Global exStateStack:TExState[],exStateStackTop
+
+Function ReadDebug$()
+	Return ReadStdin()
+End Function
+
+Function WriteDebug( t$ )
+	WriteStderr "~~>"+t
+End Function
+
+Function DumpScope( scope:Int Ptr,inst:Byte Ptr )
+
+	Local decl:Int Ptr=scope+DEBUGSCOPE_DECLS
+	
+	Local kind$=DebugScopeKind( scope ),name$=DebugScopeName( scope )
+	
+	If Not name name="<local>"
+	
+	WriteDebug kind+" "+name+"~n"
+	
+	While decl[DEBUGDECL_KIND]<>DEBUGDECLKIND_END
+	
+		Select decl[DEBUGDECL_KIND]
+		Case DEBUGDECLKIND_TYPEMETHOD,DEBUGDECLKIND_TYPEFUNCTION
+			decl:+4
+			Continue
+		End Select
+
+		Local kind$=DebugDeclKind( decl )
+		Local name$=DebugDeclname( decl )
+		Local tipe$=DebugDeclType( decl )
+		Local value$=DebugDeclValue( decl,inst )
+		
+		WriteDebug kind+" "+name+":"+tipe+"="+value+"~n"
+
+		decl:+4	
+	Wend
+End Function
+
+Function DumpClassScope( clas:Int Ptr,inst:Byte Ptr )
+
+	Local supa:Int Ptr=Int Ptr clas[0]
+	
+	If Not supa Return
+	
+	DumpClassScope supa,inst
+	
+	DumpScope Int Ptr clas[2],inst
+
+End Function
+
+Function DumpObject( inst:Byte Ptr,index )
+
+	Local clas:Int Ptr=(Int Ptr Ptr inst)[0]
+	
+	If clas=Int Ptr Varptr bbStringClass
+
+		WriteDebug DebugEscapeString(String.FromShorts( Short Ptr(inst+12),(Int Ptr (inst+8))[0] ))+"~n"
+
+		Return
+
+	Else If clas=Int Ptr Varptr bbArrayClass
+	
+		Local length=(Int Ptr (inst+20))[0]
+		
+		If Not length Return
+		
+		Local decl:Int[3]
+		decl[0]=DEBUGDECLKIND_LOCAL
+		decl[2]=(Int Ptr (inst+8))[0]
+		
+		Local sz=DebugDeclSize( decl )
+		
+		Local p:Byte Ptr=Byte Ptr(20+(Int Ptr (inst+12))[0]*4)
+
+		For Local i=1 To 10
+
+			If index>=length Exit
+			
+			decl[3]=Int(p+index*sz)
+		
+			Local value$=DebugDeclValue( decl,inst )
+			
+			WriteDebug "["+index+"]="+value+"~n"
+			
+			index:+1
+			
+		Next
+		
+		If index<length
+
+			WriteDebug "...=$"+ToHex(Int inst)+":"+index+"~n"
+	
+		EndIf
+		
+	Else
+			
+		If Not clas[0]
+			WriteDebug "Object~n"
+			Return
+		EndIf
+	
+		DumpClassScope clas,inst
+	
+	EndIf
+	
+End Function
+
+Function DumpScopeStack()
+	For Local i=Max(scopeStackTop-100,0) Until scopeStackTop
+		Local t:TScope=scopeStack[i]
+		Local stm:Int Ptr=t.stm
+		If Not stm Continue
+		WriteDebug "@"+DebugStmFile(stm)+"<"+DebugStmLine(stm)+","+DebugStmChar(stm)+">~n"
+		DumpScope t.scope,t.inst
+	Next
+End Function
+
+Function UpdateDebug( msg$ )
+	Global indebug
+	If indebug Return
+	indebug=True
+	
+?Win32
+	_appHwnd=GetForegroundWindow();
+	'SetForegroundWindow( _ideHwnd );
+?
+?MacOs
+	'fullscreen debug too hard in MacOS!
+	If CGDisplayIsCaptured( 0 )
+		WriteStdout msg
+		End
+	EndIf
+?
+	WriteDebug msg
+	Repeat
+		WriteDebug "~n"
+		Local line$=ReadDebug()
+
+		Select line[..1].ToLower()
+		Case "r"
+			mode=MODE_RUN
+			Exit
+		Case "s"
+			mode=MODE_STEP
+			debugLevel=funcLevel
+			Exit
+		Case "e"
+			mode=MODE_STEPIN
+			Exit
+		Case "l"
+			mode=MODE_STEPOUT
+			debugLevel=scopeStackTop-1
+			Exit
+		Case "t"
+			WriteDebug "StackTrace{~n"
+			DumpScopeStack
+			WriteDebug "}~n"
+		Case "d"
+			Local t$=line[1..].Trim()
+			Local index
+			Local i=t.Find(":")
+			If i<>-1
+				index=Int( t[i+1..] )
+				t=t[..i]
+			EndIf
+			If t[..1]="$" t=t[1..].Trim()
+			If t[..2].ToLower()="0x" t=t[2..].Trim()
+			Local pointer = Int( "$"+t )
+			If Not (pointer And bbGCValidate(pointer)) Then Continue
+			Local inst:Int Ptr=Int Ptr pointer
+			
+			Local cmd$="ObjectDump@"+ToHex( Int inst )
+			If i<>-1 cmd:+":"+index
+			WriteDebug cmd$+"{~n"
+
+			DumpObject inst,index
+			WriteDebug "}~n"
+		Case "h"
+			WriteDebug "T - Stack trace~n"
+			WriteDebug "R - Run from here~n"
+			WriteDebug "S - Step through source code~n"
+			WriteDebug "E - Step into function call~n"
+			WriteDebug "L - Leave function or local block~n"
+			WriteDebug "Q - Quit~n"
+			WriteDebug "H - This text~n"
+			WriteDebug "Dxxxxxxxx - Dump object at hex address xxxxxxxx~n"
+		Case "q"
+			End
+		End Select
+	Forever
+
+?Win32
+	If _appHwnd And _appHwnd<>_ideHwnd 
+		If IsIconic(_apphwnd)
+			ShowWindow _appHwnd,SW_RESTORE
+		Else
+			ShowWindow _appHwnd,SW_SHOW
+		EndIf		
+		_apphwnd=0
+	EndIf
+?
+	indebug=False
+End Function
+
+Function OnDebugStop()
+?Threaded
+	If Not bbIsMainThread() Return
+?
+	UpdateDebug "DebugStop:~n"
+End Function
+
+Function OnDebugLog( message$ )
+?Threaded
+	If Not bbIsMainThread() Return
+?
+	WriteStdout "DebugLog:"+message+"~n"
+End Function
+
+Function OnDebugEnterStm( stm:Int Ptr )
+?Threaded
+	If Not bbIsMainThread() Return
+?
+	currentScope.stm=stm
+	
+	Select mode
+	Case MODE_RUN
+		Return
+	Case MODE_STEP
+		If funcLevel>debugLevel 
+			Return
+		EndIf
+	Case MODE_STEPOUT
+		If scopeStackTop>debugLevel
+			Return
+		EndIf
+	End Select
+	
+	UpdateDebug "Debug:~n"
+End Function
+
+Function OnDebugEnterScope( scope:Int Ptr,inst:Byte Ptr )
+?Threaded
+	If Not bbIsMainThread() Return
+?
+	GCSuspend
+
+	If scopeStackTop=scopeStack.length 
+		scopeStack=scopeStack[..scopeStackTop * 2 + 32]
+		For Local i=scopeStackTop Until scopeStack.length
+			scopeStack[i]=New TScope
+		Next
+	EndIf
+	
+	currentScope=scopeStack[scopeStackTop]
+
+	currentScope.scope=scope
+	currentScope.inst=inst
+
+	scopeStackTop:+1
+
+	If currentScope.scope[DEBUGSCOPE_KIND]=DEBUGSCOPEKIND_FUNCTION funcLevel:+1
+
+	GCResume	
+End Function
+
+Function OnDebugLeaveScope()
+?Threaded
+	If Not bbIsMainThread() Return
+?
+	GCSuspend
+
+	If Not scopeStackTop DebugError "scope stack underflow"
+
+	If currentScope.scope[DEBUGSCOPE_KIND]=DEBUGSCOPEKIND_FUNCTION funcLevel:-1
+	
+	scopeStackTop:-1
+
+	If scopeStackTop
+		currentScope=scopeStack[scopeStackTop-1]
+	Else
+		currentScope=New TScope
+	EndIf
+
+	GCResume	
+End Function
+
+Function OnDebugPushExState()
+?Threaded
+	If Not bbIsMainThread() Return
+?
+	GCSuspend
+
+	If exStateStackTop=exStateStack.length 
+		exStateStack=exStateStack[..exStateStackTop * 2 + 32]
+		For Local i=exStateStackTop Until exStateStack.length
+			exStateStack[i]=New TExState
+		Next
+	EndIf
+	
+	exStateStack[exStateStackTop].scopeStackTop=scopeStackTop
+	
+	exStateStackTop:+1
+
+	GCResume	
+End Function
+
+Function OnDebugPopExState()
+?Threaded
+	If Not bbIsMainThread() Return
+?
+	GCSuspend
+
+	If Not exStateStackTop DebugError "exception stack underflow"
+
+	exStateStackTop:-1
+
+	scopeStackTop=exStateStack[exStateStackTop].scopeStackTop
+	
+	If scopeStackTop
+		currentScope=scopeStack[scopeStackTop-1]
+	Else
+		currentScope=New TScope
+	EndIf
+
+	GCResume	
+End Function
+
+Function OnDebugUnhandledEx( ex:Object )
+?Threaded
+	If Not bbIsMainThread() Return
+?
+	GCSuspend
+	
+	UpdateDebug "Unhandled Exception:"+ex.ToString()+"~n"
+
+	GCResume	
+End Function
+

+ 180 - 0
appstub.mod/debugger.stdio.glue.c

@@ -0,0 +1,180 @@
+
+#include "brl.mod/blitz.mod/blitz.h"
+
+// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+BBString * bmx_debugger_DebugScopeName(struct BBDebugScope * scope) {
+	return bbStringFromCString(scope->name);
+}
+
+unsigned int bmx_debugger_DebugScopeKind(struct BBDebugScope * scope) {
+	return scope->kind;
+}
+
+struct BBDebugDecl * bmx_debugger_DebugScopeDecl(struct BBDebugScope * scope) {
+	return &scope->decls[0];
+}
+
+// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+BBString * bmx_debugger_DebugDeclName(struct BBDebugDecl * decl) {
+	return bbStringFromCString(decl->name);
+}
+
+BBString * bmx_debugger_DebugDeclType(struct BBDebugDecl * decl) {
+	return bbStringFromCString(decl->type_tag);
+}
+
+unsigned int bmx_debugger_DebugDeclKind(struct BBDebugDecl * decl) {
+	return decl->kind;
+}
+
+struct BBDebugDecl * bmx_debugger_DebugDeclNext( struct BBDebugDecl * decl ) {
+	return ((char *)decl) + sizeof(struct BBDebugDecl);
+}
+
+void * bmx_debugger_DebugDecl_VarAddress( struct BBDebugDecl * decl ) {
+	return decl->var_address;
+}
+
+BBString * bmx_debugger_DebugDecl_ConstValue(struct BBDebugDecl * decl) {
+	return decl->const_value;
+}
+
+void * bmx_debugger_DebugDecl_FieldOffset(struct BBDebugDecl * decl, void * inst) {
+	return ((char *)inst) + decl->field_offset;
+}
+
+BBString * bmx_debugger_DebugDecl_StringFromAddress(BBString * p) {
+	return p;
+}
+
+int bmx_debugger_DebugDeclTypeChar(struct BBDebugDecl * decl) {
+	return decl->type_tag[0];
+}
+
+int bmx_debugger_DebugDecl_ArraySize(BBArray * array) {
+	return array->scales[0];
+}
+
+BBClass * bmx_debugger_DebugDecl_clas( BBObject * inst ) {
+	return inst->clas;
+}
+
+int bmx_debugger_DebugDecl_isStringClass(BBClass * clas) {
+	return clas == &bbStringClass;
+}
+
+int bmx_debugger_DebugDecl_isArrayClass(BBClass * clas) {
+	return clas == &bbArrayClass;
+}
+
+int bmx_debugger_DebugDecl_isBaseObject(BBClass * clas) {
+	return clas->super == 0;
+}
+
+struct BBDebugDecl * bmx_debugger_DebugDecl_ArrayDecl(BBArray  * arr) {
+	struct BBDebugDecl * decl = malloc(sizeof(struct BBDebugDecl));
+	
+	decl->kind = BBDEBUGDECL_LOCAL;
+	decl->name = 0;
+	decl->type_tag = arr->type;
+
+	return decl;
+}
+
+void bmx_debugger_DebugDecl_ArrayDeclIndexedPart(struct BBDebugDecl * decl, BBArray  * arr, int index) {
+	
+	int size = 4;
+	switch( arr->type[0] ){
+		case 'b':size=1;break;
+		case 's':size=2;break;
+		case 'l':size=8;break;
+		case 'y':size=8;break;
+		case 'd':size=8;break;
+		case 'h':size=8;break;
+		case 'j':size=16;break;
+		case 'k':size=16;break;
+		case 'm':size=16;break;
+		case '*':size=sizeof(void*);break;
+		case ':':size=sizeof(void*);break;
+		case '$':size=sizeof(void*);break;
+		case '[':size=sizeof(void*);break;
+		case '(':size=sizeof(void*);break;
+		case 'z':size=sizeof(BBSIZET);break;
+#ifdef _WIN32
+		case 'w':size=sizeof(WPARAM);break;
+		case 'x':size=sizeof(LPARAM);break;
+#endif
+	}
+
+	decl->var_address = ((char*)BBARRAYDATA(arr, arr->dims)) + size * index;
+}
+
+void bmx_debugger_DebugDecl_ArrayDeclFree(struct BBDebugDecl * decl) {
+	free(decl);
+}
+
+BBString * bmx_debugger_DebugEnumDeclValue(struct BBDebugDecl * decl, void * val) {
+	BBEnum * bbEnum = bbEnumGetInfo(decl->type_tag);
+
+	switch( bbEnum->type[0] ){
+		case 'b': return bbEnumToString_b(bbEnum, *((BBBYTE*)val));
+		case 's': return bbEnumToString_s(bbEnum, *((BBSHORT*)val));
+		case 'i': return bbEnumToString_i(bbEnum, *((BBINT*)val));
+		case 'u': return bbEnumToString_u(bbEnum, *((BBUINT*)val));
+		case 'l': return bbEnumToString_l(bbEnum, *((BBLONG*)val));
+		case 'y': return bbEnumToString_y(bbEnum, *((BBULONG*)val));
+		case 't': return bbEnumToString_t(bbEnum, *((BBSIZET*)val));
+	}
+
+	return &bbEmptyString;
+}
+
+// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+BBString * bmx_debugger_DebugStmFile(struct BBDebugStm * stmt) {
+	return bbStringFromCString(stmt->source_file);
+}
+
+int bmx_debugger_DebugStmLine(struct BBDebugStm * stmt) {
+	return stmt->line_num;
+}
+
+int bmx_debugger_DebugStmChar(struct BBDebugStm * stmt) {
+	return stmt->char_num;
+}
+
+// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+BBClass * bmx_debugger_DebugClassSuper(BBClass * clas) {
+	return clas->super;
+}
+
+struct BBDebugScope * bmx_debugger_DebugClassScope(BBClass * clas) {
+	return clas->debug_scope;
+}
+
+// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+void * bmx_debugger_ref_bbNullObject() {
+	return &bbNullObject;
+}
+
+void * bmx_debugger_ref_bbEmptyArray() {
+	return &bbEmptyArray;
+}
+
+void * bmx_debugger_ref_bbEmptyString() {
+	return &bbEmptyString;
+}
+
+void * bmx_debugger_ref_brl_blitz_NullFunctionError() {
+	return brl_blitz_NullFunctionError;
+}
+
+// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+int bmx_snprintf(char * txt, size_t size, size_t num) {
+	return snprintf(txt, size, "%7d", num);
+}

+ 1306 - 0
appstub.mod/debugger_mt.stdio.bmx

@@ -0,0 +1,1306 @@
+SuperStrict
+
+Import "debugger.stdio.glue.c"
+
+NoDebug
+
+Import BRL.Socket
+Import BRL.Map
+Import pub.stdc
+
+?win32
+Include "deref_win32.bmx"
+?linux
+Include "deref_linux.bmx"
+?macos
+Include "deref_macos.bmx"
+?
+
+
+Private
+
+Const derefFailure:String = "{?}"
+Const derefSymbol:String = "->"
+
+?Win32
+Extern "Win32"
+Const SW_SHOW:Int=5
+Const SW_RESTORE:Int=9
+Function IsIconic:Int( hwnd:Byte Ptr )="WINBOOL IsIconic( HWND )!"
+Function GetForegroundWindow:Byte Ptr()="HWND GetForegroundWindow()!"
+Function SetForegroundWindow:Int( hwnd:Byte Ptr )="WINBOOL SetForegroundWindow( HWND )!"
+Function ShowWindow:Int( hwnd:Byte Ptr,cmdShow:Int )="WINBOOL ShowWindow( HWND ,int )!"
+Function GetCurrentThreadId:Int()="DWORD GetCurrentThreadId()!"
+End Extern
+?
+
+?MacOS
+Extern
+Function CGDisplayIsCaptured:Int( displayId:Int )
+End Extern
+?
+
+Extern
+	Function bbIsMainThread:Int()="bbIsMainThread"
+	Function bbGCValidate:Int( mem:Byte Ptr ) = "int bbGCValidate( void * )!"
+
+	Function DebugScopeName:String( scope:Int Ptr )="bmx_debugger_DebugScopeName"
+	Function bmx_debugger_DebugScopeKind:UInt( scope:Int Ptr )
+	Function bmx_debugger_DebugScopeDecl:Byte Ptr( scope:Int Ptr )
+
+	Function DebugDeclName:String( decl:Int Ptr )="bmx_debugger_DebugDeclName"
+	Function bmx_debugger_DebugDeclType:String( decl:Int Ptr )
+	Function bmx_debugger_DebugDeclKind:UInt( decl:Int Ptr )
+	Function bmx_debugger_DebugDeclNext:Byte Ptr( decl:Int Ptr )
+	Function bmx_debugger_DebugDecl_VarAddress:Byte Ptr( decl:Int Ptr )
+	Function bmx_debugger_DebugDecl_ConstValue:String( decl:Int Ptr )
+	Function bmx_debugger_DebugDecl_FieldOffset:Byte Ptr(decl:Int Ptr, inst:Byte Ptr)
+	Function bmx_debugger_DebugDecl_StringFromAddress:String( addr:Byte Ptr )
+	Function bmx_debugger_DebugDeclTypeChar:Int( decl:Int Ptr )
+	Function bmx_debugger_DebugDecl_ArraySize:Int( decl:Byte Ptr )
+	Function bmx_debugger_DebugDecl_ArrayDecl:Byte Ptr(inst:Byte Ptr)
+	Function bmx_debugger_DebugDecl_ArrayDeclIndexedPart(decl:Byte Ptr, inst:Byte Ptr, index:Int)
+	Function bmx_debugger_DebugDecl_ArrayDeclFree(decl:Byte Ptr)
+
+	Function bmx_debugger_DebugDecl_clas:Byte Ptr( inst:Byte Ptr )
+	Function bmx_debugger_DebugDecl_isStringClass:Int( clas:Byte Ptr )
+	Function bmx_debugger_DebugDecl_isArrayClass:Int( clas:Byte Ptr )
+	Function bmx_debugger_DebugDecl_isBaseObject:Int( clas:Byte Ptr )
+	
+	Function bmx_debugger_DebugClassSuper:Byte Ptr(clas:Byte Ptr)
+	Function bmx_debugger_DebugClassScope:Byte Ptr(clas:Byte Ptr)
+	
+	Function DebugStmFile:String( stm:Int Ptr )="bmx_debugger_DebugStmFile"
+	Function DebugStmLine:Int( stm:Int Ptr )="bmx_debugger_DebugStmLine"
+	Function DebugStmChar:Int( stm:Int Ptr )="bmx_debugger_DebugStmChar"
+
+	Function bmx_debugger_ref_bbNullObject:Byte Ptr()
+	Function bmx_debugger_ref_bbEmptyArray:Byte Ptr()
+	Function bmx_debugger_ref_bbEmptyString:Byte Ptr()
+	Function bmx_debugger_ref_brl_blitz_NullFunctionError:Byte Ptr()
+	
+	Function bbObjectStructInfo:Byte Ptr(name:Byte Ptr)="BBDebugScope * bbObjectStructInfo( char * )!"
+	Function bmx_debugger_DebugEnumDeclValue:String(decl:Byte Ptr, val:Byte Ptr)
+	
+	Function bmx_snprintf:Int(buf:Byte Ptr, size:Size_T, num:Size_T)
+End Extern
+
+?Not ptr64
+Function ToHex$( val:Int )
+	Local buf:Short[8]
+	For Local k:Int=7 To 0 Step -1
+		Local n:Int=(val&15)+Asc("0")
+		If n>Asc("9") n=n+(Asc("A")-Asc("9")-1)
+		buf[k]=n
+		val:Shr 4
+	Next
+	Return String.FromShorts( buf,8 ).ToLower()
+End Function
+?ptr64
+Function ToHex$( val:Long )
+	Local buf:Short[16]
+	For Local k:Int=15 To 0 Step -1
+		Local n:Int=(val&15)+Asc("0")
+		If n>Asc("9") n=n+(Asc("A")-Asc("9")-1)
+		buf[k]=n
+		val:Shr 4
+	Next
+	Return String.FromShorts( buf,16 ).ToLower()
+End Function
+?
+
+Function IsAlpha:Int( ch:Int )
+	Return (ch>=Asc("a") And ch<=Asc("z")) Or (ch>=Asc("A") And ch<=Asc("Z"))
+End Function
+
+Function IsNumeric:Int( ch:Int )
+	Return ch>=Asc("0") And ch<=Asc("9")
+End Function
+
+Function IsAlphaNumeric:Int( ch:Int )
+	Return IsAlpha(ch) Or IsNumeric(ch)
+End Function
+
+Function IsUnderscore:Int( ch:Int )
+	Return ch=Asc("_")
+End Function
+
+Function Ident$( tag$ Var )
+	If Not tag Return ""
+	If Not IsAlpha( tag[0] ) And Not IsUnderscore( tag[0] ) Return ""
+	Local i:Int=1
+	While i<tag.length And (IsAlphaNumeric(tag[i]) Or IsUnderscore(tag[i]))
+		i:+1
+	Wend
+	Local id$=tag[..i]
+	tag=tag[i..]
+	Return id
+End Function
+
+Function TypeName$( tag$ Var )
+	
+	Local t$=tag[..1]
+	tag=tag[1..]
+
+	Select t
+	Case "b"
+		Return "Byte"
+	Case "s"
+		Return "Short"
+	Case "i"
+		Return "Int"
+	Case "u"
+		Return "UInt"
+	Case "l"
+		Return "Long"
+	Case "y"
+		Return "ULong"
+	Case "j"
+		Return "Int128"
+	Case "f"
+		Return "Float"
+	Case "d"
+		Return "Double"
+	Case "h"
+		Return "Float64"
+	Case "k"
+		Return "Float128"
+	Case "m"
+		Return "Double128"
+	Case "$"
+		Return "String"
+	Case "z"
+		Return "CString"
+	Case "w"
+		Return "WString"
+	Case "t"
+		Return "Size_T"
+	Case "W"
+		Return "WParam"
+	Case "X"
+		Return "LParam"
+	Case ":","?","#","@","/"
+		Local id$=Ident( tag )
+		While tag And tag[0]=Asc(".")
+			tag=tag[1..]
+			id=Ident( tag )
+		Wend
+		If Not id DebugError "Invalid object typetag"
+		Return id
+	Case "*"
+		Return TypeName( tag )+" Ptr"
+	Case "["
+		While tag[..1]=","
+			tag=tag[1..]
+			t:+","
+		Wend
+		If tag[..1]<>"]" DebugError "Invalid array typetag"
+		tag=tag[1..]
+		Return TypeName( tag )+t+"]"
+	Case "("
+		If tag[..1]<>")"
+			t:+TypeName( tag )
+			While tag[..1]=","
+				tag=tag[1..]
+				t:+","+TypeName( tag )
+			Wend
+			If tag[..1]<>")" DebugError "Invalid function typetag"
+		EndIf
+		tag=tag[1..]
+		Return TypeName( tag )+t+")"
+	End Select
+
+	If Not tag.length Return ""
+	DebugError "Invalid debug typetag:"+t
+
+End Function
+
+'int offsets into 12 byte DebugStm struct
+'Const DEBUGSTM_FILE=0
+'Const DEBUGSTM_LINE=1
+'Const DEBUGSTM_CHAR=2
+
+'int offsets into 16 byte DebugDecl struct
+'Const DEBUGDECL_KIND=0
+'Const DEBUGDECL_NAME=1
+'Const DEBUGDECL_TYPE=2
+'Const DEBUGDECL_ADDR:Int=3
+
+'DEBUGDECL_KIND values
+Const DEBUGDECLKIND_END:Int=0
+Const DEBUGDECLKIND_CONST:Int=1
+Const DEBUGDECLKIND_LOCAL:Int=2
+Const DEBUGDECLKIND_FIELD:Int=3
+Const DEBUGDECLKIND_GLOBAL:Int=4
+Const DEBUGDECLKIND_VARPARAM:Int=5
+Const DEBUGDECLKIND_TYPEMETHOD:Int=6
+Const DEBUGDECLKIND_TYPEFUNCTION:Int=7
+
+'int offsets into 12+n_decls*4 byte DebugScope struct
+'Const DEBUGSCOPE_KIND=0
+'Const DEBUGSCOPE_NAME=1
+'Const DEBUGSCOPE_DECLS=2
+
+'DEBUGSCOPE_KIND values
+Const DEBUGSCOPEKIND_FUNCTION:Int=1
+Const DEBUGSCOPEKIND_TYPE:Int=2
+Const DEBUGSCOPEKIND_LOCAL:Int=3
+Const DEBUGSCOPEKIND_INTERFACE:Int=4
+Const DEBUGSCOPEKIND_STRUCT:Int=5
+
+Function DebugError( t$ )
+	WriteStderr "Debugger Error:"+t+"~n"
+	End
+End Function
+
+Function DebugDeclKind$( decl:Int Ptr )
+	Select bmx_debugger_DebugDeclKind(decl)
+	Case DEBUGDECLKIND_CONST Return "Const"
+	Case DEBUGDECLKIND_LOCAL Return "Local"
+	Case DEBUGDECLKIND_FIELD Return "Field"
+	Case DEBUGDECLKIND_GLOBAL Return "Global"
+	Case DEBUGDECLKIND_VARPARAM Return "Local"
+	End Select
+	DebugError "Invalid decl kind"
+End Function
+
+Function DebugDeclType$( decl:Int Ptr )
+	Local t$=bmx_debugger_DebugDeclType(decl)
+	Local ty$=TypeName( t )
+	Return ty
+End Function
+
+Function DebugDeclSize:Int( decl:Int Ptr )
+
+	Local tag:Int=bmx_debugger_DebugDeclTypeChar(decl)
+
+	Select tag
+	Case Asc("b") Return 1
+	Case Asc("s") Return 2
+	Case Asc("i") Return 4
+	Case Asc("u") Return 4
+	Case Asc("f") Return 4
+	Case Asc("l") Return 8
+	Case Asc("y") Return 8
+	Case Asc("d") Return 8
+	Case Asc("h") Return 8
+	Case Asc("j") Return 16
+	Case Asc("k") Return 16
+	Case Asc("m") Return 16
+	' size_t (t) fall-through to ptr64 size below
+	End Select
+
+?Not ptr64
+	Return 4
+?ptr64
+	Return 8
+?
+
+End Function
+
+Function DebugEscapeString$( s$ )
+	If s.length>4096 s=s[..4096]
+	s=s.Replace( "~~","~~~~")
+	s=s.Replace( "~0","~~0" )
+	s=s.Replace( "~t","~~t" )
+	s=s.Replace( "~n","~~n" )
+	s=s.Replace( "~r","~~r" )
+	s=s.Replace( "~q","~~q" )
+	Return "~q"+s+"~q"
+End Function
+
+Function DebugDeclValue$( decl:Int Ptr,inst:Byte Ptr )
+
+	If bmx_debugger_DebugDeclKind(decl)=DEBUGDECLKIND_CONST
+		Return DebugEscapeString(bmx_debugger_DebugDecl_ConstValue(decl))
+	End If
+
+	Local p:Byte Ptr
+	Select bmx_debugger_DebugDeclKind(decl)
+	Case DEBUGDECLKIND_GLOBAL
+		p=bmx_debugger_DebugDecl_VarAddress(decl)
+	Case DEBUGDECLKIND_LOCAL
+		p=bmx_debugger_DebugDecl_VarAddress(decl)
+	Case DEBUGDECLKIND_FIELD
+		p=bmx_debugger_DebugDecl_FieldOffset(decl, inst)
+	Case DEBUGDECLKIND_VARPARAM
+		p=bmx_debugger_DebugDecl_VarAddress(decl)
+?Not ptr64
+		p=Byte Ptr ( (Int Ptr p)[0] )
+?ptr64
+		p=Byte Ptr ( (Long Ptr p)[0] )
+?
+	Default
+		DebugError "Invalid decl kind"
+	End Select
+	
+	Local tag:Int=bmx_debugger_DebugDeclTypeChar(decl)
+	
+	Select tag
+	Case Asc("b")
+		Return String.FromInt( (Byte Ptr p)[0] )
+	Case Asc("s")
+		Return String.FromInt( (Short Ptr p)[0] )
+	Case Asc("i")
+		Return String.FromInt( (Int Ptr p)[0] )
+	Case Asc("u")
+		Return String.FromUInt( (UInt Ptr p)[0] )
+	Case Asc("l")
+		Return String.FromLong( (Long Ptr p)[0] )
+	Case Asc("y")
+		Return String.FromULong( (ULong Ptr p)[0] )
+	Case Asc("f")
+		Return String.FromFloat( (Float Ptr p)[0] )
+	Case Asc("d")
+		Return String.FromDouble( (Double Ptr p)[0] )
+	Case Asc("t")
+		Return String.FromSizet( (Size_T Ptr p)[0] )
+?win32
+	Case Asc("W")
+		Return String.FromWParam( (WParam Ptr p)[0] )
+	Case Asc("X")
+		Return String.FromLParam( (LParam Ptr p)[0] )
+?
+	Case Asc("$")
+		p=(Byte Ptr Ptr p)[0]
+		Return DebugEscapeString( bmx_debugger_DebugDecl_StringFromAddress(p) )
+	Case Asc("z")
+		p=(Byte Ptr Ptr p)[0]
+		If Not p Return "Null"
+		Local s$=String.FromCString( p )
+		Return DebugEscapeString( s )
+	Case Asc("w")
+		p=(Byte Ptr Ptr p)[0]
+		If Not p Return "Null"
+		Local s$=String.FromWString( Short Ptr p )
+		Return DebugEscapeString( s )
+	Case Asc("*"),Asc("?"),Asc("#")
+		Local deref:String
+		If tag = Asc("*") Then deref = DebugDerefPointer(decl, p)
+?Not ptr64
+		Return "$" + ToHex( (Int Ptr p)[0] ) + deref
+?ptr64
+		Return "$" + ToHex( (Long Ptr p)[0] ) + deref
+?
+	Case Asc("(")
+		p=(Byte Ptr Ptr p)[0]
+		If p=bmx_debugger_ref_brl_blitz_NullFunctionError() Return "Null"
+	Case Asc(":")
+		p=(Byte Ptr Ptr p)[0]
+		If p=bmx_debugger_ref_bbNullObject() Return "Null"
+		If p=bmx_debugger_ref_bbEmptyArray() Return "Null[]"
+		If p=bmx_debugger_ref_bbEmptyString() Return "Null$"
+	Case Asc("[")
+		p=(Byte Ptr Ptr p)[0]
+		If Not p Return "Null"
+		If Not bmx_debugger_DebugDecl_ArraySize(p) Return "Null"
+	Case Asc("@")
+?Not ptr64
+		Return "$"+ToHex( Int p ) + "@" + bmx_debugger_DebugDeclType(decl)[1..]
+?ptr64
+		Return "$"+ToHex( Long p ) + "@" + bmx_debugger_DebugDeclType(decl)[1..]
+?
+	Case Asc("h")
+		Return Float Ptr (Varptr p)[0] + "," + Float Ptr (Varptr p)[1]
+	Case Asc("j")
+		Return Int Ptr (Varptr p)[0] + "," + Int Ptr (Varptr p)[1] + "," + Int Ptr (Varptr p)[2] + "," + Int Ptr (Varptr p)[3]
+	Case Asc("k")
+		Return Float Ptr (Varptr p)[0] + "," + Float Ptr (Varptr p)[1] + "," + Float Ptr (Varptr p)[2] + "," + Float Ptr (Varptr p)[3]
+	Case Asc("m")
+		Return Double Ptr(Varptr p)[0] + "," + Double Ptr (Varptr p)[1]
+	Case Asc("/")
+		Return bmx_debugger_DebugEnumDeclValue(decl, p)
+	Default
+		DebugError "Invalid decl typetag:"+Chr(tag)
+	End Select
+	
+?Not ptr64
+	Return "$"+ToHex( Int p )
+?ptr64
+	Return "$"+ToHex( Long p )
+?
+End Function
+
+Function DebugScopeKind$( scope:Int Ptr )
+	Select bmx_debugger_DebugScopeKind(scope)
+	Case DEBUGSCOPEKIND_FUNCTION Return "Function"
+	Case DEBUGSCOPEKIND_TYPE Return "Type"
+	Case DEBUGSCOPEKIND_LOCAL Return "Local"
+	Case DEBUGSCOPEKIND_INTERFACE Return "Interface"
+	Case DEBUGSCOPEKIND_STRUCT Return "Struct"
+	End Select
+	DebugError "Invalid scope kind"
+End Function
+
+Function DebugDerefPointer:String(decl:Int Ptr, pointer:Byte Ptr)
+	Const derefFailure:String = "{?}"
+	Const derefSymbol:String = "->"
+	
+	Local declType:String = DebugDeclType(decl)
+	Local dataType:String = declType
+	Local ptrDepth:Int = 0
+	While dataType.EndsWith(" Ptr")
+		dataType = dataType[..dataType.length - " Ptr".length]
+		ptrDepth :+ 1
+	Wend
+	
+	Local result:String = ""
+	
+	Local dataSize:Size_T
+	Select dataType
+ 		Case "Byte"      dataSize = SizeOf(Byte Null)
+		Case "Short"     dataSize = SizeOf(Short Null)
+		Case "Int"       dataSize = SizeOf(Int Null)
+		Case "UInt"      dataSize = SizeOf(UInt Null)
+		Case "Long"      dataSize = SizeOf(Long Null)
+		Case "ULong"     dataSize = SizeOf(ULong Null)
+		Case "Size_T"    dataSize = SizeOf(Size_T Null)
+		Case "Float"     dataSize = SizeOf(Float Null)
+		Case "Double"    dataSize = SizeOf(Double Null)
+	? Ptr64
+		Case "Float64"   dataSize = SizeOf(Float64 Null)
+		Case "Float128"  dataSize = SizeOf(Float128 Null)
+		Case "Double128" dataSize = SizeOf(Double128 Null)
+		Case "Int128"    dataSize = SizeOf(Int128 Null)
+	? Win32	
+		Case "WParam"    dataSize = SizeOf(WParam Null)
+		Case "LParam"    dataSize = SizeOf(LParam Null)
+	?
+		Default          dataSize = 0 ' cannot dereference this
+	EndSelect
+
+	Local buffer:Byte Ptr = MemAlloc(Size_T(Max(dataSize, SizeOf(Byte Ptr Null))))
+	(Byte Ptr Ptr buffer)[0] = Null
+	
+	Local res:Int
+	?win32
+	result = DebugDerefPointerWin32(dataSize, ptrDepth, pointer, buffer, res)
+	?linux
+	result = DebugDerefPointerLinux(dataSize, ptrDepth, pointer, buffer, res)
+	?macos
+	result = DebugDerefPointerMacos(dataSize, ptrDepth, pointer, buffer, res)
+	?
+
+	If Not res Then
+		Return result
+	End If
+
+	Local value:String
+	Select dataType
+ 		Case "Byte"      value = String((Byte   Ptr buffer)[0])
+		Case "Short"     value = String((Short  Ptr buffer)[0])
+		Case "Int"       value = String((Int    Ptr buffer)[0])
+		Case "UInt"      value = String((UInt   Ptr buffer)[0])
+		Case "Long"      value = String((Long   Ptr buffer)[0])
+		Case "ULong"     value = String((ULong  Ptr buffer)[0])
+		Case "Size_T"    value = String((Size_T Ptr buffer)[0])
+		Case "Float"     value = String((Float  Ptr buffer)[0])
+		Case "Double"    value = String((Double Ptr buffer)[0])
+	? Ptr64
+		Case "Float64"   value = String((Float  Ptr buffer)[0]) + "," + ..
+		                         String((Float  Ptr buffer)[1])
+		Case "Float128"  value = String((Float  Ptr buffer)[0]) + "," + ..
+		                         String((Float  Ptr buffer)[1]) + "," + ..
+		                         String((Float  Ptr buffer)[2]) + "," + ..
+		                         String((Float  Ptr buffer)[3])
+		Case "Double128" value = String((Double Ptr buffer)[0]) + "," + ..
+		                         String((Double Ptr buffer)[1])
+		Case "Int128"    value = String((Int    Ptr buffer)[0]) + "," + ..
+		                         String((Int    Ptr buffer)[1]) + "," + ..
+		                         String((Int    Ptr buffer)[2]) + "," + ..
+		                         String((Int    Ptr buffer)[3])
+	? Win32	
+		Case "WParam"    value = String((WParam Ptr buffer)[0])
+		Case "LParam"    value = String((LParam Ptr buffer)[0])
+	?
+		Default
+			MemFree buffer
+			result :+ derefSymbol + derefFailure
+			Return result
+	EndSelect
+	MemFree buffer
+	result :+ derefSymbol + "{" + value + "}"
+	?
+	
+	Return result
+EndFunction
+
+Extern
+Global bbOnDebugStop()="void bbOnDebugStop()!"
+Global bbOnDebugLog( message$ )="void bbOnDebugLog( BBString * )!"
+Global bbOnDebugEnterStm( stm:Int Ptr )="void bbOnDebugEnterStm( BBDebugStm * )!"
+Global bbOnDebugEnterScope( scope:Int Ptr)="void bbOnDebugEnterScope( BBDebugScope * )!"
+Global bbOnDebugLeaveScope()="void bbOnDebugLeaveScope()!"
+Global bbOnDebugPushExState()="void bbOnDebugPushExState()!"
+Global bbOnDebugPopExState()="void bbOnDebugPopExState()!"
+Global bbOnDebugUnhandledEx( ex:Object )="void bbOnDebugUnhandledEx( BBObject * )!"
+End Extern
+
+bbOnDebugStop=OnDebugStop
+bbOnDebugLog=OnDebugLog
+bbOnDebugEnterStm=OnDebugEnterStm
+bbOnDebugEnterScope=OnDebugEnterScope
+bbOnDebugLeaveScope=OnDebugLeaveScope
+bbOnDebugPushExState=OnDebugPushExState
+bbOnDebugPopExState=OnDebugPopExState
+bbOnDebugUnhandledEx=OnDebugUnhandledEx
+
+?Win32
+Global _ideHwnd:Byte Ptr=GetForegroundWindow();
+Global _appHwnd:Byte Ptr
+?
+
+'********** Debugger code here **********
+
+Const MODE_RUN:Int=0
+Const MODE_STEP:Int=1
+Const MODE_STEPIN:Int=2
+Const MODE_STEPOUT:Int=3
+Const MODE_BREAK:Int=4
+
+Type TScope
+	Field scope:Int Ptr,inst:Byte Ptr,stm:Int Ptr
+End Type
+
+Type TExState
+	Field scopeStackTop:Int
+End Type
+
+Type TDbgState
+	Field Mode:Int,debugLevel:Int,funcLevel:Int
+	Field currentScope:TScope=New TScope
+	Field scopeStack:TScope[],scopeStackTop:Int
+	Field exStateStack:TExState[],exStateStackTop:Int
+End Type
+
+?Threaded
+Extern
+Function bbThreadAllocData:Int()
+Function bbThreadSetData( index:Int,data:Object )
+Function bbThreadGetData:TDbgState( index:Int )="BBObject* bbThreadGetData(int )!"
+End Extern
+?
+
+Global _breakpointsMap:TMap = New TMap
+Global _bpCount:Int
+Global _debugPort:Int = 31666
+Global _debugWait:Int = True
+Global _debugSetup:Int = True
+
+Global _debugData:TDebugData = New TDebugData
+
+_initDebugger()
+
+Const STATEMENT_TIMER:Int = 50
+Global _debugSocket:TSocket, _serverSocket:TSocket
+Global _debugConnected:Int
+
+_debugSocket = TSocket.CreateTCP()
+If Not _debugSocket Then
+	_debugSocket = TSocket.CreateTCP()
+End If
+
+If _debugSocket Then
+	OnDebugLog("Listening on " + _debugPort)
+
+	_debugSocket.Bind(_debugPort)
+	_debugSocket.Listen(1)
+	_debugSetup = False
+End If
+
+Function _initDebugger()
+
+	' port to accept debugger connections against
+	Local env:String = getenv_("BMX_DEBUG_PORT")
+	If env Then
+		Local port:Int = env.ToInt()
+		If port Then
+			_debugPort = port
+		End If
+	End If
+	' wait for debugger to connect before running?
+	' set to non zero for Yes
+	env = getenv_("BMX_DEBUG_WAIT")
+	If env Then
+		Local wait:Int = env.ToInt()
+		If wait Then
+			_debugWait = True
+		End If
+	End If
+
+End Function
+
+Function GetDbgState:TDbgState()
+	Global dbgStateMain:TDbgState=New TDbgState
+?Threaded
+	If bbIsMainThread() Return dbgStateMain
+	Global dbgStateId:Int=bbThreadAllocData()
+	Local dbgState:TDbgState=bbThreadGetData( dbgStateId )
+	If Not dbgState
+		dbgState = New TDbgState
+		bbThreadSetData( dbgStateId,dbgState )
+	End If
+	Return dbgState
+?Not Threaded
+	Return dbgStateMain
+?
+End Function
+
+Global _WAITING_FOR_INPUT:Int
+
+Function ReadDebug$(skipNotAvail:Int = False)
+
+	If _debugConnected Then
+		If Not _serverSocket.Connected() Then
+			_debugConnected = False
+			Return Null
+		End If
+	
+		If _WAITING_FOR_INPUT Then
+			Return ""
+		End If
+	
+		Const BUFLEN:Int = 64
+		Local buf:Byte Ptr = StackAlloc(BUFLEN)
+		Local p:Int, n:Byte
+		Local s:String
+
+		Local count:Int = _serverSocket.ReadAvail()
+
+		If skipNotAvail And Not count Then
+			Return ""
+		End If
+
+		_WAITING_FOR_INPUT = True
+		
+		Repeat
+			_serverSocket.Recv(Varptr n, 1)
+
+			If n = 10 And s.length > 0 Then
+				Exit
+			End If
+
+			buf[p]=n
+			p:+1
+			If p<>BUFLEN And p < count Then
+				Continue
+			End If
+			s:+ String.FromBytes(buf,p)
+			p=0
+		
+			count = _serverSocket.ReadAvail()
+
+			If n = 13 Then
+				Continue
+			End If
+			
+			Delay 50
+		Forever
+		
+		If p > 0 Then
+			s :+ String.FromBytes(buf,p)
+		End If
+		
+		_WAITING_FOR_INPUT = False
+
+		Return s
+		'Return ReadStdin()
+	End If
+End Function
+
+Function WriteDebug( t$, complete:Int = False )
+
+	If _debugConnected Then
+		If Not _serverSocket.Connected() Then
+			_debugConnected = False
+			Return
+		End If
+
+		_debugData.Append(t)
+
+		If complete Then
+
+			Local n:Size_T = bmx_snprintf(_debugData.data, 8, Size_T(_debugData.length - 8))
+			_debugData.data[_debugData.length] = 0
+			
+			_serverSocket.Send(_debugData.data, Size_T(_debugData.length))
+			
+			_debugData.Reset()
+		End If
+	End If
+End Function
+
+Function DumpScope( scope:Byte Ptr, inst:Byte Ptr )
+	Local decl:Byte Ptr=bmx_debugger_DebugScopeDecl(scope)
+	Local kind$=DebugScopeKind( scope )
+	Local name$=DebugScopeName( scope )
+	
+	If Not name name="<local>"
+	
+	WriteDebug kind+" "+name+"~n"
+	While bmx_debugger_DebugDeclKind(decl)<>DEBUGDECLKIND_END
+		Select bmx_debugger_DebugDeclKind(decl)
+		Case DEBUGDECLKIND_TYPEMETHOD,DEBUGDECLKIND_TYPEFUNCTION
+			decl = bmx_debugger_DebugDeclNext(decl)
+			Continue
+		End Select
+		Local kind$=DebugDeclKind( decl )
+		Local name$=DebugDeclname( decl )
+		Local tipe$=DebugDeclType( decl )
+		Local value$=DebugDeclValue( decl, inst )
+		
+		If tipe.find("[") >= 0 Then
+?Not ptr64
+			Local pointer:Int = Int( value )
+?ptr64
+			Local pointer:Long = Long( value )
+?
+?Not ptr64
+			Local dinst:Int Ptr=Int Ptr pointer
+?ptr64
+			Local dinst:Long Ptr=Long Ptr pointer
+?			
+
+			Local length:Int=bmx_debugger_DebugDecl_ArraySize(dinst)
+			value :+ ":" + length
+
+		End If
+		
+		WriteDebug kind+" "+name+":"+tipe+"="+value+"~n"
+
+		decl = bmx_debugger_DebugDeclNext(decl)
+	Wend
+End Function
+
+Function DumpClassScope( clas:Int Ptr,inst:Byte Ptr )
+
+	Local supa:Int Ptr = bmx_debugger_DebugClassSuper(clas)
+	
+	If Not supa Return
+	
+	DumpClassScope supa,inst
+	
+	DumpScope bmx_debugger_DebugClassScope(clas),inst
+
+End Function
+
+Function DumpObject( inst:Byte Ptr,index:Int, count:Int = 100 )
+
+	Local clas:Byte Ptr=bmx_debugger_DebugDecl_clas(inst)
+	
+	If bmx_debugger_DebugDecl_isStringClass(clas)
+
+		WriteDebug DebugEscapeString(bmx_debugger_DebugDecl_StringFromAddress(inst))+"~n"
+
+		Return
+
+	Else If bmx_debugger_DebugDecl_isArrayClass(clas)
+
+		Local length:Int=bmx_debugger_DebugDecl_ArraySize(inst)
+
+		If Not length Return
+		
+		Local decl:Byte Ptr = bmx_debugger_DebugDecl_ArrayDecl(inst)
+
+		For Local i:Int=1 To count
+
+			If index>=length Exit
+			
+			bmx_debugger_DebugDecl_ArrayDeclIndexedPart(decl, inst, index)
+		
+			Local value$=DebugDeclValue( decl,inst )
+			
+			WriteDebug "["+index+"]="+value+"~n"
+			
+			index:+1
+			
+		Next
+
+		bmx_debugger_DebugDecl_ArrayDeclFree(decl)
+		
+'		If index<length
+'
+'			WriteDebug "...=$"+ToHex(Int inst)+":"+index+"~n"
+'	
+'		EndIf
+		
+	Else
+			
+		If bmx_debugger_DebugDecl_isBaseObject(clas) Then
+			WriteDebug "Object~n"
+			Return
+		EndIf
+	
+		DumpClassScope clas,inst
+	
+	EndIf
+	
+End Function
+
+Function DumpStruct( inst:Byte Ptr,index:Int,structName:String )
+	Local s:Byte Ptr = structName.ToCString()
+	Local scope:Byte Ptr = bbObjectStructInfo(s)
+	If scope Then
+		DumpScope scope,inst
+	End If
+	MemFree s
+End Function
+
+Function DumpScopeStack()
+	Local dbgState:TDbgState = GetDbgState()
+	For Local i:Int=Max(dbgState.scopeStackTop-100,0) Until dbgState.scopeStackTop
+		Local t:TScope=dbgState.scopeStack[i]
+		Local stm:Int Ptr=t.stm
+		If Not stm Continue
+		WriteDebug "@"+DebugStmFile(stm)+"<"+DebugStmLine(stm)+","+DebugStmChar(stm)+">~n"
+		DumpScope t.scope, t.inst
+	Next
+End Function
+
+Function UpdateDebug( msg$ )
+	Global indebug:Int
+	If indebug Return
+	indebug=True
+	
+	Local dbgState:TDbgState = GetDbgState()
+	
+?Win32
+	_appHwnd=GetForegroundWindow();
+	'SetForegroundWindow( _ideHwnd );
+?
+?MacOs
+	'fullscreen debug too hard in MacOS!
+	If CGDisplayIsCaptured( 0 )
+		WriteStdout msg
+		End
+	EndIf
+?
+
+	' we really need a connection...
+	Local timeout:Int = 5000
+	While Not _checkConnection()
+		timeout:-1
+		If Not timeout Then
+			End
+		End If
+	Wend
+
+	WriteDebug msg
+	Repeat
+		WriteDebug "~n", True
+		Local line$=ReadDebug()
+
+		Select line[..1].ToLower()
+		Case "r"
+			dbgState.Mode=MODE_RUN
+			Exit
+		Case "s"
+			dbgState.Mode=MODE_STEP
+			dbgState.debugLevel=dbgState.funcLevel
+			Exit
+		Case "e"
+			dbgState.Mode=MODE_STEPIN
+			Exit
+		Case "l"
+			dbgState.Mode=MODE_STEPOUT
+			dbgState.debugLevel=dbgState.scopeStackTop-1
+			Exit
+		Case "t"
+			WriteDebug "StackTrace{~n"
+			DumpScopeStack
+			WriteDebug "}~n"
+		Case "d"
+			Local t$=line[1..].Trim()
+			Local index:Int
+			Local count:Int = 100
+			Local i:Int=t.Find(":")
+			If i<>-1
+				Local Range:String = t[i+1..]
+				t=t[..i]
+				Local n:Int = Range.Find(",")
+				If n <> -1 Then
+					index=Int(Range[..n])
+					count=Int(Range[n+1..])
+				Else
+					index = Int(Range)
+				End If
+			EndIf
+			
+			Local structType:String
+			Local n:Int = t.Find("@")
+			If n <> -1 Then
+				structType = t[n+1..]
+				t = t[..n]
+			Else
+				If t[..1]="$" t=t[1..].Trim()
+				If t[..2].ToLower()="0x" t=t[2..].Trim()
+			End If
+
+?Not ptr64
+			Local pointer:Int = Int( "$"+t )
+?ptr64
+			Local pointer:Long = Long( "$"+t )
+?
+			If Not structType And Not (pointer And bbGCValidate(Byte Ptr(pointer))) Then Continue
+?Not ptr64
+			Local inst:Int Ptr=Int Ptr pointer
+			Local cmd$="ObjectDump@"+ToHex( Int inst )
+?ptr64
+			Local inst:Long Ptr=Long Ptr pointer
+			Local cmd$="ObjectDump@"+ToHex( Long inst )
+?			
+			If structType Then
+				cmd :+ "@" + structType
+			End If
+			If i<>-1 cmd:+":"+index
+			WriteDebug cmd$+"{~n"
+
+			If structType Then
+				DumpStruct inst,index,structType
+			Else
+				DumpObject inst,index,count
+			End If
+			WriteDebug "}~n"
+		Case "h"
+			WriteDebug "T - Stack trace~n"
+			WriteDebug "R - Run from here~n"
+			WriteDebug "S - Step through source code~n"
+			WriteDebug "E - Step into function call~n"
+			WriteDebug "L - Leave function or local block~n"
+			WriteDebug "Q - Quit~n"
+			WriteDebug "H - This text~n"
+			WriteDebug "Dxxxxxxxx - Dump object at hex address xxxxxxxx~n"
+		Case "q"
+			End
+		End Select
+	Forever
+
+?Win32
+	If _appHwnd And _appHwnd<>_ideHwnd 
+		If IsIconic(_apphwnd)
+			ShowWindow _appHwnd,SW_RESTORE
+		Else
+			ShowWindow _appHwnd,SW_SHOW
+		EndIf		
+		_apphwnd=0
+	EndIf
+?
+	indebug=False
+End Function
+
+Function _checkConnection:Int()
+	If Not _debugConnected Then
+
+		Global checking:Int
+		If _debugSocket And Not checking And Not _debugSetup Then
+		
+			checking = True
+			
+			If _debugWait Then
+				' wait 5 mins
+				_serverSocket = _debugSocket.Accept(300000)
+				
+				' after this, fallback to normal and commence running
+				If Not _serverSocket
+					_debugWait = False
+				End If
+			Else
+				_serverSocket = _debugSocket.Accept(0)
+			End If
+			If _serverSocket Then
+				_debugConnected = True
+			End If
+
+			checking = False
+		End If
+	End If
+	
+	Return _debugConnected
+End Function
+
+Function OnDebugStop()
+	UpdateDebug "DebugStop:~n"
+End Function
+
+Function OnDebugLog( message$ )
+	WriteStdout "DebugLog:"+message+"~n"
+End Function
+
+Function OnDebugEnterStm( stm:Int Ptr )
+	Local dbgState:TDbgState = GetDbgState()
+	dbgState.currentScope.stm=stm
+
+	_checkConnection()
+	
+	Select dbgState.Mode
+	Case MODE_RUN
+		If _debugConnected Then
+
+			' because of the nature of the debugger, we need to switch modes since OnDebugEnterStm is called
+			' during socket access - which screws the stack up.
+			dbgState.Mode = MODE_BREAK
+			Local s:String = ReadDebug(True)
+			
+			If _debugWait Then
+					_debugWait = False
+					
+					' enter debugging mode!
+					OnDebugStop()
+					
+					' done debugging. If we are still in BREAK mode, return to Running mode
+					If dbgState.Mode = MODE_BREAK Then
+						dbgState.Mode = MODE_RUN
+					End If
+					
+					Return
+			End If
+			
+			If s Then
+				If s[..1].ToLower() = "x" Then
+					' enter debugging mode!
+					OnDebugStop()
+					
+					' done debugging. If we are still in BREAK mode, return to Running mode
+					If dbgState.Mode = MODE_BREAK Then
+						dbgState.Mode = MODE_RUN
+					End If
+					
+					Return
+				Else If s[..1].ToLower() = "b" Then
+					' format 
+					'   b<FILENAME@line>
+					Local off:Int = s.find("@")
+					If off >= 1 Then
+						Local file:String = s[2..off]
+						Local line:TDdebugLine = New TDdebugLine
+						line.line = s[off + 1..s.length - 2].ToInt()
+						
+						Local list:TDdebugLine[] = TDdebugLine[](_breakpointsMap.ValueForKey(file))
+						
+						If Not list Then
+							list = New TDdebugLine[0]
+						End If
+						
+						list :+ [line]
+						
+						_breakpointsMap.Insert(file, list)
+						_bpCount :+ 1
+					End If
+				Else If s[..1].ToLower() = "z" Then
+					' format 
+					'   z<FILENAME@line>
+					Local off:Int = s.find("@")
+					If off >= 1 Then
+						Local file:String = s[2..off]
+						Local line:Int = s[off + 1..s.length - 2].ToInt()
+						
+						Local list:TDdebugLine[] = TDdebugLine[](_breakpointsMap.ValueForKey(file))
+						
+						If list Then
+							For Local i:Int = 0 Until list.length
+								If list[i].line = line Then
+									If list.length = 1
+										_breakpointsMap.Remove(file)
+									Else 
+										If i = 0 Then
+											list = list[i + 1..] 
+										Else If i = list.length - 1 Then
+											list = list[..i] 
+										Else
+											list = list[..i] + list[i + 1..]
+										End If
+										
+										_breakpointsMap.Insert(file, list)
+										
+									End If
+									
+									_bpCount :- 1
+
+									Exit
+
+								End If
+							Next
+
+						End If
+					End If
+				End If
+
+				' done debugging. If we are still in BREAK mode, return to Running mode
+				If dbgState.Mode = MODE_BREAK Then
+					dbgState.Mode = MODE_RUN
+				End If
+				
+				Return
+
+			Else If _bpCount Then
+			
+				' breakpoints have been defined
+				Local file:String = DebugStmFile(stm)
+				
+				Local list:TDdebugLine[] = TDdebugLine[](_breakpointsMap.ValueForKey(file))
+				
+				If list Then
+					Local line:Int = DebugStmLine(stm)
+					
+					For Local i:Int = 0 Until list.length
+						If list[i].line = line Then
+							' enter debugging mode!
+							OnDebugStop()
+							
+							' done debugging. If we are still in BREAK mode, return to Running mode
+							If dbgState.Mode = MODE_BREAK Then
+								dbgState.Mode = MODE_RUN
+							End If
+							
+							Return
+						End If
+					Next
+				
+				End If		
+
+				' carry on as before
+				dbgState.Mode = MODE_RUN
+				Return
+			Else
+			
+				' carry on as before
+				dbgState.Mode = MODE_RUN
+				Return
+			End If
+		Else
+			' carry on as before
+			Return
+		End If
+	Case MODE_STEP
+		If dbgState.funcLevel>dbgState.debugLevel 
+			Return
+		EndIf
+	Case MODE_STEPOUT
+		If dbgState.scopeStackTop>dbgState.debugLevel
+			Return
+		EndIf
+	Case MODE_BREAK
+		Return
+	End Select
+	
+	UpdateDebug "Debug:~n"
+End Function
+
+Function OnDebugEnterScope( scope:Int Ptr)',inst:Byte Ptr )
+	Local dbgState:TDbgState = GetDbgState()
+	GCSuspend
+
+	If dbgState.scopeStackTop=dbgState.scopeStack.length 
+		dbgState.scopeStack=dbgState.scopeStack[..dbgState.scopeStackTop * 2 + 32]
+		For Local i:Int=dbgState.scopeStackTop Until dbgState.scopeStack.length
+			dbgState.scopeStack[i]=New TScope
+		Next
+	EndIf
+	
+	dbgState.currentScope=dbgState.scopeStack[dbgState.scopeStackTop]
+
+	dbgState.currentScope.scope=scope
+	dbgState.currentScope.inst=0
+
+	dbgState.scopeStackTop:+1
+
+	If bmx_debugger_DebugScopeKind(dbgState.currentScope.scope)=DEBUGSCOPEKIND_FUNCTION dbgState.funcLevel:+1
+
+	GCResume	
+End Function
+
+Function OnDebugLeaveScope()
+	Local dbgState:TDbgState = GetDbgState()
+	GCSuspend
+
+	If Not dbgState.scopeStackTop DebugError "scope stack underflow"
+
+	If bmx_debugger_DebugScopeKind(dbgState.currentScope.scope)=DEBUGSCOPEKIND_FUNCTION dbgState.funcLevel:-1
+	
+	dbgState.scopeStackTop:-1
+
+	If dbgState.scopeStackTop
+		dbgState.currentScope=dbgState.scopeStack[dbgState.scopeStackTop-1]
+	Else
+		dbgState.currentScope=Null
+	EndIf
+
+	GCResume	
+End Function
+
+Function OnDebugPushExState()
+
+	Local dbgState:TDbgState = GetDbgState()
+	GCSuspend
+
+	If dbgState.exStateStackTop=dbgState.exStateStack.length 
+		dbgState.exStateStack=dbgState.exStateStack[..dbgState.exStateStackTop * 2 + 32]
+		For Local i:Int=dbgState.exStateStackTop Until dbgState.exStateStack.length
+			dbgState.exStateStack[i]=New TExState
+		Next
+	EndIf
+	
+	dbgState.exStateStack[dbgState.exStateStackTop].scopeStackTop=dbgState.scopeStackTop
+	
+	dbgState.exStateStackTop:+1
+
+	GCResume	
+End Function
+
+Function OnDebugPopExState()
+
+	Local dbgState:TDbgState = GetDbgState()
+	GCSuspend
+
+	If Not dbgState.exStateStackTop DebugError "exception stack underflow"
+
+	dbgState.exStateStackTop:-1
+
+	dbgState.scopeStackTop=dbgState.exStateStack[dbgState.exStateStackTop].scopeStackTop
+	
+	If dbgState.scopeStackTop
+		dbgState.currentScope=dbgState.scopeStack[dbgState.scopeStackTop-1]
+	Else
+		dbgState.currentScope=Null
+	EndIf
+
+	GCResume	
+End Function
+
+Function OnDebugUnhandledEx( ex:Object )
+
+	GCSuspend
+	
+	UpdateDebug "Unhandled Exception:"+ex.ToString()+"~n"
+
+	GCResume	
+End Function
+
+Type TDdebugLine
+	Field line:Int
+End Type
+
+Type TDebugData
+	Field length:Int = 8
+	Field data:Byte[1024]
+	
+	Field prefix:Byte Ptr = "~~>".ToUTF8String()
+	
+	Method Append(s:String)
+		Local b:Byte Ptr = s.toUTF8String()
+		Local i:Size_T = strlen_(b)
+		
+		If length + i + 2 >= data.length Then
+			data = data[..length + i + data.length * 2/3]
+		End If
+		
+		MemCopy(Byte Ptr(data) + length, prefix, 2)
+		MemCopy(Byte Ptr(data) + length + 2, b, i)
+		MemFree(b)
+		length :+ i + 2
+	End Method
+	
+	Method Reset()
+		length = 8
+	End Method
+End Type

+ 45 - 0
appstub.mod/deref_linux.bmx

@@ -0,0 +1,45 @@
+
+Extern
+	Function bmx_process_vm_readv:Size_T(dataSize:Size_T, pointer:Byte Ptr, buffer:Byte Ptr)
+End Extern
+
+Function DebugDerefPointerLinux:String(dataSize:Size_T, ptrDepth:Int, pointer:Byte Ptr, buffer:Byte Ptr, res:Int Var)
+	Local result:String
+
+	pointer = (Byte Ptr Ptr pointer)[0]
+	For Local i:Int = 1 To ptrDepth - 1
+
+		Local success:Size_T = bmx_process_vm_readv(Size_T(SizeOf(Byte Ptr Null)), pointer, buffer)
+
+		If success < 0 Then
+			MemFree buffer
+			result :+ derefSymbol + derefFailure
+			Return result
+		End If
+
+		pointer = (Byte Ptr Ptr buffer)[0]
+	? Not Ptr64
+		result :+ derefSymbol + "$" + ToHex(Int pointer)
+	? Ptr64
+		result :+ derefSymbol + "$" + ToHex(Long pointer)
+	?
+	Next
+
+	Local success:Size_T
+	If dataSize > 0 Then
+		success = bmx_process_vm_readv(dataSize, pointer, buffer)
+	Else
+		success = -1
+	End If
+	
+	If success < 0 Then
+		MemFree buffer
+		result :+ derefSymbol + derefFailure
+		Return result
+	Else
+		res = True
+	End If
+
+	Return result
+	
+End Function

+ 7 - 0
appstub.mod/deref_macos.bmx

@@ -0,0 +1,7 @@
+
+
+Function DebugDerefPointerMacos:String(dataSize:Size_T, ptrDepth:Int, pointer:Byte Ptr, buffer:Byte Ptr, res:Int Var)
+
+	Return ""
+	
+End Function

+ 43 - 0
appstub.mod/deref_win32.bmx

@@ -0,0 +1,43 @@
+
+Extern "Win32"
+	Function GetCurrentProcess:Byte Ptr() = "HANDLE GetCurrentProcess(void)!"
+	Function ReadProcessMemory:Int(hProcess:Byte Ptr, lpBaseAddress:Byte Ptr, lpBuffer:Byte Ptr, nSize:Size_T, lpNumberOfBytesRead:Size_T Ptr) = "BOOL ReadProcessMemory(HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T*)!"
+End Extern
+
+Function DebugDerefPointerWin32:String(dataSize:Size_T, ptrDepth:Int, pointer:Byte Ptr, buffer:Byte Ptr, res:Int Var)
+
+	Local result:String
+
+	Local processHandle:Byte Ptr = GetCurrentProcess()
+	
+	pointer = (Byte Ptr Ptr pointer)[0]
+	For Local i:Int = 1 To ptrDepth - 1
+		Local success:Int = ReadProcessMemory(processHandle, pointer, buffer, Size_T SizeOf(Byte Ptr Null), Null)
+		If Not success Then
+			MemFree buffer
+			result :+ derefSymbol + derefFailure
+			Return result
+		End If
+		pointer = (Byte Ptr Ptr buffer)[0]
+	? Not Ptr64
+		result :+ derefSymbol + "$" + ToHex(Int pointer)
+	? Ptr64
+		result :+ derefSymbol + "$" + ToHex(Long pointer)
+	?
+	Next
+	
+	Local success:Int
+	If dataSize > 0 Then
+		success = ReadProcessMemory(processHandle, pointer, buffer, dataSize, Null)
+	Else
+		success = False
+	End If
+	res = success
+	If Not success Then
+		MemFree buffer
+		result :+ derefSymbol + derefFailure
+		Return result
+	End If
+
+	Return result
+End Function