view src/cli/gdbcli.d @ 1:4a9dcbd9e54f

-files of 0.13 beta -fixes so that it now compiles with the current dmd version
author marton@basel.hu
date Tue, 05 Apr 2011 20:44:01 +0200
parents
children
line wrap: on
line source

/*  Ddbg - Win32 Debugger for the D programming language
 *  Copyright (c) 2007 Jascha Wetzel
 *  All rights reserved. See LICENSE.TXT for details.
 */
module cli.gdbcli;

import std.string;
static import std.regexp;
import std.c.stdio : sscanf;
import std.c.string;
import std.demangle;

import util;
import codeview.codeview;
import breakpoint;
import debugger;
import disasm;
import callstack;
import dbgprocess;
import dbgthread;
import expression.expression_apd;
import expression.datahandler : SymbolValue;
import expression.evaluationcontext;
import cli.userinterface;

import win32.winbase;
import win32.winuser;
import win32.winnt;

/**************************************************************************************************

**************************************************************************************************/
class GDBCLI : UserInterfaceBase
{
	string[]    lastcmd;

    uint        current_frame_level;

	string			prompt,
					command_line,
					debuggee;
	bool			fullname = false,
					quit;

    /**********************************************************************************************

    **********************************************************************************************/
	this()
	{
		prompt = "(gdb) ";
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void init(string[] args)
	{
		DbgIO.println(WELCOME_STRING);
		if ( args.length < 2 )
			throw new DebuggerException("Usage: ddbg -cli=gdb [-cmd=<commands>] [-fullname] -args <exe file> [arguments]");

		bool	read_args = false;
		Lfe: foreach ( int i, a; args )
		{
			switch ( a )
			{
				case "-fullname":
				case "--fullname":
                    fullname = true;
                    break;
				case "-args":
				case "--args":
					read_args=true;
					break;
				default:
                    debuggee = a;
					if ( read_args )
					{
                        if ( find(a, ' ') >= 0 )
                            command_line = "\""~a~"\"";
                        else
                            command_line = a;
						if ( args.length > i+1 )
                            command_line ~= " "~join(args[i+1..$], " ");
						break Lfe;
					}
			}
		}

		dbg = new Debugger(debuggee, this);
		dbg.create_new_console = true;
	}

	int start()
	{
		while ( !quit )
		{
            try readCommand();
            catch ( Exception e )
                DbgIO.println(e.msg);
		}
		return 0;
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void singleStep()
	{
		DbgIO.println(describeLocation(dbg.current_address));
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void exitProcess()
	{
		DbgIO.println("Program exited");
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void loadedDLL(DLL dll)
	{
		if( dll !is null && dll.image.name.length )
			DbgIO.writeln(dll.image.name~" loaded");
		else
			DbgIO.writeln("unknown DLL loaded");
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void win32exception(uint threadId, EXCEPTION_RECORD* exrec)
	{
		DbgIO.println(
			"Program received signal SIG Unhandled Exception: %s(0x%x) at 0x%x",
			getExceptionName(exrec.ExceptionCode), exrec.ExceptionCode, exrec.ExceptionAddress //describeLocation(dbg.current_address)
		);
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void exception(uint threadId, string class_name, string msg, size_t obj_ptr)
	{
		DbgIO.println(
			"Program received signal SIG Unhandled D Exception (%s%s) at %s",
			class_name, msg.length>0?" \""~msg~"\"":"", describeLocation(dbg.current_address)
		);
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void userInterrupt()
	{
		DbgIO.println("User interrupt at %s", describeLocation(dbg.current_address));
	}

    /**********************************************************************************************

    **********************************************************************************************/
	bool breakpoint(int index, Breakpoint bp, DbgThread thread)
	{
		if ( bp.file is null || bp.line == 0 )
			DbgIO.println("Unknown breakpoint hit at %s", describeLocation(bp.address));
		else
			DbgIO.println("\032\032%s:%d:0:begmidl:0x%08x", fullname?dbg.getFullSourcePath(bp.file):bp.file, bp.line, bp.address);
		return true;
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void debugString(string str)
	{
		printf("Error OUTPUT DEBUG STRING:\n%s\n", toStringz(str));
	}

    /**********************************************************************************************
        Command line parser. Gets called when debuggee is suspended.
    **********************************************************************************************/
	bool parseCommand(string input)
	{
		if ( strip(input).length > 0 )
		{
			auto r = std.regexp.RegExp("(([^\" \\t]+)|(\"[^\"]+\"))+");
			lastcmd.length = 0;
			foreach ( m; r.search(strip(input)) )
				lastcmd ~= r.match(0);
		}
		if ( lastcmd.length <= 0 )
			return false;

		int slash_pos = find(lastcmd[0], '/');
		if ( slash_pos >= 0 ) {
			lastcmd ~= lastcmd[0];
			lastcmd[0] = lastcmd[0][0..slash_pos];
		}

		switch ( lastcmd[0] )
		{
			case "help":
				DbgIO.println("GDB emulation mode - no help available, yet");
				break;
			case "delete":
				if ( lastcmd.length < 2 )
					DbgIO.println("Warning: too few arguments for set");
				else switch ( lastcmd[1] )
				{
					case "breakpoints":
						if ( lastcmd.length < 3 )
						{
							foreach ( uint i; dbg.breakpoints.keys )
								dbg.removeBreakpoint(i);
						}
						else if ( !isNumeric(lastcmd[2]) )
							DbgIO.println("Warning: invalid breakpoint index '%s'", lastcmd[2]);
						else
							dbg.removeBreakpoint(cast(uint)atoi(lastcmd[2]));
						break;
					default:
						DbgIO.println("Warning: unknown argument %s", lastcmd[1]);
				}
				break;
			case "set":
				if ( lastcmd.length < 2 )
					DbgIO.println("Warning: too few arguments for set");
				else switch ( lastcmd[1] )
				{
					case "prompt":
						if ( lastcmd.length < 3 )
							DbgIO.println("Warning: too few arguments for set prompt");
						else
							prompt = lastcmd[2];
						break;
					case "args":
						if ( dbg.process_loaded )
							DbgIO.println("Warning: process already started");
						command_line = debuggee~" "~join(lastcmd[2..$], " ");
						break;
					default:
						DbgIO.println("Warning: unknown variable %s", lastcmd[1]);
				}
				break;
			case "info":
				if ( lastcmd.length < 2 )
					DbgIO.println("Warning: too few arguments for info");
				else
				{
					switch ( lastcmd[1] )
					{
						case "locals":
                            if ( dbg.process_loaded )
                            {
                                uint scope_address = dbg.getScopeAddress(current_frame_level);
                                ScopeSymbol scope_sym = dbg.images.findProcedureSymbol(scope_address);
                                for ( ; scope_sym !is null; scope_sym = scope_sym.parent_scope )
                                {
                                    if ( scope_sym is null )
                                        DbgIO.println("No valid scope active");
                                    else
                                        evalStackSymbols(scope_sym.symbols.stack_symbols);
                                }
                            }
							break;
						case "args":
                            if ( dbg.process_loaded )
                            {
                                uint scope_address = dbg.getScopeAddress(current_frame_level);
                                ScopeSymbol scope_sym = dbg.images.findProcedureSymbol(scope_address);
                                for ( ; scope_sym !is null; scope_sym = scope_sym.parent_scope )
                                {
                                    if ( scope_sym is null )
                                        DbgIO.println("No valid scope active");
                                    else
                                    {
                                        auto psym = cast(ProcedureSymbol)scope_sym;
                                        if ( psym !is null )
                                            evalStackSymbols(psym.arguments.stack_symbols);
                                    }
                                }
                            }
							break;
						case "registers":
							printRegisterDump(dbg.process.threads[dbg.thread_id]);
							break;
						case "frame":
                            uint scope_address = dbg.getScopeAddress(current_frame_level);
                            ubyte[] frame = dbg.stack.getFrame(current_frame_level);
							Location loc = dbg.images.findLocation(scope_address);
							DbgIO.print("Stack level %d, frame at ", current_frame_level);
							if ( loc.scope_sym !is null ) {
								ProcedureSymbol psym = cast(ProcedureSymbol)loc.scope_sym;
								DbgIO.println("0x%x:", loc.getCodeBase + psym.cvdata.offset + psym.cvdata.debug_start);
							}
							else
								DbgIO.println("0x%x:", dbg.current_address);

							DbgIO.print(" eip = 0x%x in %s (",
								loc.address, loc.scope_sym is null?"??":loc.scope_sym.name_notype
							);

							if ( loc.codeblock !is null )
								DbgIO.print("%s:%d", loc.file, loc.line);
							else if ( loc.mod !is null )
								DbgIO.write(loc.mod.name);
							else
							{
								DLL dll = dbg.process.findDLL(loc.address);
								if ( dll !is null )
									DbgIO.write(dll.image.name);
							}

							DbgIO.println("); saved eip 0x%x", (cast(uint[])frame)[1]);
							break;
						default:
							DbgIO.println("Warning: unknown argument %s", lastcmd[1]);
					}
				}
				break;
            // select frame
            case "select-frame":
                if ( lastcmd.length > 1 )
                    current_frame_level = cast(uint)atoi(lastcmd[1]);
                DbgIO.println("Current frame level is %d", current_frame_level);
                break;
			// add breakpoint
			case "tbreak":
			case "clear":
			case "break":
				int index;
				if ( lastcmd.length < 2 ) {
					DbgIO.println("invalid syntax - see help for details");
					break;
				}
                Location loc = new Location(lastcmd[1]);
                loc.bind(dbg.images, dbg.source_search_paths);
                if ( loc is null )
                    break;
				if ( lastcmd.length > 2 )
					index = cast(int)atoi(lastcmd[2]);

				if ( lastcmd[0] == "clear" )
				{
					Breakpoint bp = dbg.getBreakpoint(loc, index);
					dbg.removeBreakpoint(index);
				}
				else
				{
                    Breakpoint bp;
				    if ( lastcmd[0] == "tbreak" )
                        index = -1;
                    else if ( index <= 0 && dbg.breakpoints.length > 0 )
                        index = dbg.breakpoints.keys.dup.sort[$-1]+1;
                    bp = dbg.setBreakpoint(loc, index, 0);
					DbgIO.println("Breakpoint %d at 0x%08x", index, bp.address);
				}
				break;
			// add source search path
			case "directory":
				if ( lastcmd.length > 1 )
				{
					string sp = lastcmd[1].dup;
					if ( find(sp, '/') >= 0 )
						sp = replace(sp, "/", "\\");
					if ( sp[$-1] != '\\' )
						sp ~= '\\';
					dbg.source_search_paths ~= sp;
				}
				else
					DbgIO.println("usage: ssp <search_path>");
				break;
			// quit
			case "quit":
			case "q":
				dbg.abort = true;
				quit = true;
				return true;
			// run/continue
			case "cont":
			case "run":
			case "start":
				if ( dbg.process_loaded ) {
					dbg.single_step = false;
					dbg.paused = false;
					return true;
				}
				else {
					dbg.start(command_line);
					return true;
				}
			// single-step
			case "nexti":
				dbg.single_step = true;
				dbg.activateSingleStep(true);
				return true;
			// step over
			case "next":
				if ( dbg.step(StepMode.e_over) )
					return true;
				break;
			// step into
			case "step":
				if ( dbg.step(StepMode.e_in) )
					return true;
				break;
			// step out
			case "finish":
				if ( dbg.step(StepMode.e_out) )
					return true;
				break;
			// unwind stack
			case "bt":
				unwindStack();
				break;
			case "print":
			case "output":
				if ( dbg.process_loaded && lastcmd.length > 1 )
				{
					string expr;
					if ( lastcmd[1][0] == '/' )
						expr = lastcmd[2];
					else
						expr = lastcmd[1];
					try DbgIO.println(symbolValueToString(dbg.handleData(dbg.evaluateExpression(expr), false)));
					catch ( EvaluationException e ) {
						DbgIO.writeln(e.toString);
					}
				}
				break;
			case "disassemble":
                if ( !dbg.process_loaded )
                    break;

                uint start_address = dbg.current_address;
				if ( lastcmd.length > 1 )
					sscanf(toStringz(lastcmd[1]), "%x", &start_address);
				else
				{
					Location loc = dbg.images.findLocation(dbg.current_address);
					if ( loc.scope_sym !is null ) {
						ProcedureSymbol psym = cast(ProcedureSymbol)loc.scope_sym;
						start_address = loc.getCodeBase + psym.cvdata.offset;
						DisAsm.disasm(dbg.process, start_address, start_address+psym.cvdata.proc_length, &printDisassembly);
						break;
					}
				}
				DisAsm.disasm(dbg.process, start_address, 0, &printDisassembly);
				break;
			case "x":
				uint start_address;
				if ( lastcmd.length > 2 )
				{
					if ( lastcmd[1].length > 2 && lastcmd[1][0..2] == "0x" )
						lastcmd[1] = lastcmd[1][2..$];
					sscanf(toStringz(lastcmd[1]), "%x", &start_address);
					string count_str = lastcmd[$-1][2..$-2];
					uint count = cast(uint)atoi(count_str);

					dumpMemory(start_address, count);
				}
				else
					DbgIO.println("Warning: too few arguments for x");
				break;
            case "whatis":
				if ( dbg.process_loaded && lastcmd.length > 1 )
				{
				    SymbolData sd = dbg.evaluateExpression(lastcmd[1], current_frame_level);
				    string type = demangle("_D0"~sd.type);
					try DbgIO.println("type = %s", type);
					catch ( EvaluationException e ) {
						DbgIO.writeln(e.toString);
					}
				}
                break;


			// list source search paths
			case "lsp":
				foreach ( s; dbg.source_search_paths )
					DbgIO.writeln(s);
				break;
			// list breakpoints
			case "lbp":
				if ( dbg.breakpoints.length <= 0 )
					DbgIO.println("no breakpoints set");
				foreach ( uint i, bp; dbg.breakpoints )
					DbgIO.println("%d %s", i, bp.toString);
				break;
			// list temporary breakpoints
			case "ltbp":
				if ( dbg.temp_breakpoints.empty )
					DbgIO.println("no temporary breakpoints set");
				foreach ( bp; dbg.temp_breakpoints )
					DbgIO.writeln(bp.value.toString);
				break;
			// list debug modules
			case "lm":
				string[]	modules_noinfo;
                foreach ( img; dbg.images.images )
                {
                    if ( img.codeView is null )
                        continue;
                    foreach ( m; img.codeView.modulesByIndex )
                    {
                        string name = m.name;

                        if ( m.header.iLib > 0 )
                            name ~= " from "~img.codeView.libraries[m.header.iLib];

                        if ( lastcmd.length > 1 && find(name, lastcmd[1]) < 0 )
                            continue;

                        bool has_info = false;
                        with ( m.symbols ) if ( proc_symbols.length+stack_symbols.length+data_symbols.length > 0 )
                        {
                            DbgIO.println(
                                "%s\n\tSymbols: %d proc %d stack %d data",
                                name, proc_symbols.length, stack_symbols.length, data_symbols.length
                            );
                            has_info = true;
                        }
                        if ( m.source_module !is null )
                        {
                            DbgIO.println("\tSource files:");
                            has_info = true;
                            foreach ( sf; m.source_module.files )
                                DbgIO.println("\t\t%s", sf.name);
                        }
                        if ( !has_info )
                            modules_noinfo ~= name;
                    }
                }
				if ( modules_noinfo.length > 0 )
				{
					DbgIO.println("Modules without debug information:");
					foreach ( m; modules_noinfo )
						DbgIO.println("%s ", m);
				}
				break;
			// list source modules
			case "lsm":
                foreach ( img; dbg.images.images )
                {
                    if ( img.codeView is null )
                        continue;
                    foreach ( m; img.codeView.modulesByIndex )
                    {
                        if ( m.source_module !is null )
                        {
                            foreach ( sf; m.source_module.files )
                            {
                                if ( lastcmd.length > 1 && find(sf.name, lastcmd[1]) < 0 )
                                    continue;
                                DbgIO.writeln(sf.name);
                            }
                        }
                    }
                }
				break;
			// list all symbols
			case "ls":
                foreach ( img; dbg.images.images )
                {
                    if ( img.codeView is null )
                        continue;
                    printSymbols(img.codeView, img.codeView.global_pub.named_symbols, lastcmd.length>1?lastcmd[1]:null);
                    printSymbols(img.codeView, img.codeView.global_sym.named_symbols, lastcmd.length>1?lastcmd[1]:null);
                    printSymbols(img.codeView, img.codeView.static_sym.named_symbols, lastcmd.length>1?lastcmd[1]:null);
                    foreach ( m; img.codeView.modulesByIndex )
                        printSymbols(img.codeView, m.symbols.named_symbols, lastcmd.length>1?lastcmd[1]:null);
                }
				break;
			// list function symbols
			case "lf":
                foreach ( img; dbg.images.images )
                {
                    if ( img.codeView is null )
                        continue;
                    printSymbols(img.codeView, img.codeView.global_pub.proc_symbols, lastcmd.length>1?lastcmd[1]:null);
                    printSymbols(img.codeView, img.codeView.global_sym.proc_symbols, lastcmd.length>1?lastcmd[1]:null);
                    printSymbols(img.codeView, img.codeView.static_sym.proc_symbols, lastcmd.length>1?lastcmd[1]:null);
                    foreach ( m; img.codeView.modulesByIndex )
                        printSymbols(img.codeView, m.symbols.proc_symbols, lastcmd.length>1?lastcmd[1]:null);
                }
				break;
			// list data symbols
			case "ld":
                foreach ( img; dbg.images.images )
                {
                    if ( img.codeView is null )
                        continue;
                    printSymbols(img.codeView, img.codeView.global_pub.data_symbols, lastcmd.length>1?lastcmd[1]:null);
                    printSymbols(img.codeView, img.codeView.global_sym.data_symbols, lastcmd.length>1?lastcmd[1]:null);
                    printSymbols(img.codeView, img.codeView.static_sym.data_symbols, lastcmd.length>1?lastcmd[1]:null);
                    foreach ( m; img.codeView.modulesByIndex )
                        printSymbols(img.codeView, m.symbols.data_symbols, lastcmd.length>1?lastcmd[1]:null);
                }
				break;
			// list global symbols
			case "lg":
                foreach ( img; dbg.images.images )
                {
                    if ( img.codeView is null )
                        continue;
                    printSymbols(img.codeView, img.codeView.global_pub.named_symbols, lastcmd.length>1?lastcmd[1]:null);
                    printSymbols(img.codeView, img.codeView.global_sym.named_symbols, lastcmd.length>1?lastcmd[1]:null);
                }
				break;
			// list global publics
			case "lp":
                foreach ( img; dbg.images.images )
                {
                    if ( img.codeView is null )
                        continue;
                    printSymbols(img.codeView, img.codeView.global_pub.named_symbols, lastcmd.length>1?lastcmd[1]:null);
                }
				break;
			// delete breakpoint
			case "dbp":
				if ( lastcmd.length > 1 )
				{
					if ( lastcmd[1] == "*" )
					{
						foreach ( uint i, bp; dbg.breakpoints )
						{
							bp.deactivate(dbg.process);
							dbg.breakpoints.remove(i);
						}
					}
					else if ( isNumeric(lastcmd[1]) )
					{
						if ( !dbg.removeBreakpoint(cast(uint)atoi(lastcmd[1])) )
							DbgIO.println("invalid breakpoint index: %s",lastcmd[1]);
					}
					else
					{
                        Location loc = new Location(lastcmd[1]);
                        loc.bind(dbg.images, dbg.source_search_paths);
						if ( loc !is null )
						{
							int bp_index;
							dbg.getBreakpoint(loc, bp_index);
							if ( bp_index >= 0 && !dbg.removeBreakpoint(bp_index) )
								DbgIO.println("No breakpoint set at "~lastcmd[1]);
						}
						else
							DbgIO.println("Usage: dbp [<bp index>|<file:line>]");
					}
				}
				else
					DbgIO.println("Usage: bp <sourc file>:<line>");
				break;
			// dump stack
			case "ds":
				int dump_length;
				if ( lastcmd.length > 1 )
					dump_length = cast(int)atoi(lastcmd[1])*4;
				else
				{
					CONTEXT ctx;
					if ( dbg.process.threads[dbg.thread_id].getContext(ctx, CONTEXT_CONTROL) )
						dump_length = ctx.Ebp-ctx.Esp+8;
					if ( dump_length <= 0 )
						dump_length = 16*4;
				}
				int top = dbg.stack.data.length>dump_length?dump_length:dbg.stack.data.length;
				dumpMemory(dbg.stack.top_ptr, top, dbg.stack.data);
				break;
			case "dm":
				if ( lastcmd.length < 3 ) {
					DbgIO.println("usage: dm <start> <length>");
					break;
				}
				uint start;
				sscanf(toStringz(lastcmd[1]), "%x", &start);
				dumpMemory(start, cast(uint)atoi(lastcmd[2]));
				break;

			// unknown command
			default:
				DbgIO.println("Warning: Unknown command '%s' ignored!", lastcmd[0]);
				break;
		}

		return false;
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void dumpMemory(uint start, uint length, ubyte[] _data=null)
	{
		ubyte[]	data;
		if ( _data is null )
		{
			data.length = length;
			if ( !dbg.process.readProcessMemory(start, data.ptr, data.length) )
				return;
		}
		else
			data = _data;
		for ( uint i = 0; i < length; ++i )
		{
			if ( i % (8*4) == 0 )
			{
				if ( i > 0 )
					DbgIO.println("");
				DbgIO.print("0x%08x:", start+i);
			}
			DbgIO.print(" 0x%02x", data[i]);
		}
		DbgIO.println("");
	}

    /**********************************************************************************************

    **********************************************************************************************/
	void evalStackSymbols(StackSymbol[] symbols)
	{
		foreach ( sym; symbols )
		{
            string name = sym.mangled_name;
            DbgIO.print("%s = ", name);
            try DbgIO.writeln(symbolValueToString(dbg.handleData(dbg.evaluateExpression(name, current_frame_level, sym), true)));
            catch ( EvaluationException e ) {
                DbgIO.println(e.msg);
            }
		}
	}

    /**********************************************************************************************

    **********************************************************************************************/
    void printAsmLine(uint address, string bytes, string asmsource, string symbol, string location, string source)
	{
	    bool nl = false;
        if ( location !is null ) {
            DbgIO.print("0x%08x <??>: // ", address);
            DbgIO.write(location);
            nl = true;
        }
        if ( source !is null )
        {
            if ( !nl )
				DbgIO.print("0x%08x <??>: // ", address);
            DbgIO.write(source);
            nl = true;
        }
        if ( nl )
            DbgIO.println;

		assert(asmsource !is null);
		DbgIO.print("0x%08x <??>: ", address, asmsource);

		if ( symbol !is null )
			DbgIO.write(symbol);
		DbgIO.println;
	}

    /**********************************************************************************************
        Prints the register contents of the given thread's context.
    **********************************************************************************************/
	void printRegisterDump(DbgThread thread)
	{
		CONTEXT ctx;
		if ( thread.getContext(ctx) )
		{
			DbgIO.println("eax\t0x%x -\nebx\t0x%x -\necx\t0x%x -\nedx\t0x%x -", ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx);
			DbgIO.println("cs\t0x%x -\nds\t0x%x -\nes\t0x%x -\nfs\t0x%x -", ctx.SegCs, ctx.SegDs, ctx.SegEs, ctx.SegFs);
			DbgIO.println("gs\t0x%x -\nss\t0x%x -\nedi\t0x%x -\nesi\t0x%x -", ctx.SegGs, ctx.SegSs, ctx.Edi, ctx.Esi);
			DbgIO.println("ebp\t0x%x -\nesp\t0x%x -\neip\t0x%x -\neflags\t0x%x -", ctx.Ebp, ctx.Esp, ctx.Eip, ctx.EFlags);
		}
		else
			DbgIO.println("Warning: Couldn't get main thread's context");
	}

    /**********************************************************************************************

    **********************************************************************************************/
	string symbolValueToString(SymbolValue val)
	{
	    string str;
	    if ( val.name !is null )
            str = val.name~" = ";
        if ( val.value !is null )
            str ~= val.value;
        else
        {
            if ( val.children.length > 0 )
            {
                str ~= "{";
                bool first = true;
                foreach ( c; val.children )
                {
                    if ( first )
                        first = false;
                    else
                        str ~= ",";
                    str ~= symbolValueToString(c);
                }
                str ~= "}";
            }
            else
                str ~= "{}";
        }
        return str;
	}

    /**********************************************************************************************
        Read command and call CLI supplied parser function.
        Gets called when debuggee is suspended.
    **********************************************************************************************/
	bool readCommand()
	{
	    if ( cmdQueue.length > 0 )
	    {
	        string cmd = strip(cmdQueue[0]);
	        cmdQueue = cmdQueue[1..$];
            DbgIO.writeln(prompt~cmd);
	        return parseCommand(cmd);
	    }
	    else {
            DbgIO.write(prompt);
            string input = DbgIO.readln();
            return parseCommand(input);
	    }
	}
}