commands/fdb/fdb.cpp
changeset 0 7f656887cf89
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/fdb/fdb.cpp	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,1564 @@
+// fdb.cpp
+// 
+// Copyright (c) 2009 - 2010 Accenture. All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+// 
+// Initial Contributors:
+// Accenture - Initial contribution
+//
+
+#include <fshell/ioutils.h>
+#include <fshell/memoryaccesscmd.h>
+#include <fshell/ltkutils.h>
+#include <fshell/bsym.h>
+#include <fshell/descriptorutils.h>
+#include <fshell/iocons_writer.h>
+#include <fshell/line_editor.h>
+#include <fshell/qr3dll.h>
+#include <u32std.h> // Is this available in all environments? If not, need to define TDesType ourselves
+#include <babitflags.h>
+#include <badesca.h>
+_LIT(KCrLf, "\r\n");
+_LIT(KPrompt, "fdb>");
+
+using namespace IoUtils;
+using namespace LtkUtils;
+
+const TInt KMaxRegisters = 32;
+
+class CCmdFdb : public CMemoryAccessCommandBase, public MLineEditorObserver, public MLineCompleter
+	{
+public:
+	static CCommandBase* NewLC();
+	~CCmdFdb();
+private:
+	struct SThreadContext;
+	CCmdFdb();
+	void InstallL();
+	void AttachL(TUint aThreadId);
+	void Detach(SThreadContext* aContext);
+	void PrintThreadInfo(SThreadContext& aContext);
+	void PrintRegistersL(SThreadContext& aThread);
+	void PrintRegistersL(SThreadContext& aThread, TBool aUserMode);
+	void PrintRegistersLRPC(SThreadContext& aThread, TBool aUserMode);
+	void RefreshL(SThreadContext& aContext);
+	void RefreshIfRunningL(SThreadContext& aContext);
+	TPtrC LookupSymbol(TUint32 aAddress);
+	void StartCommandParsingL();
+	void ProcessLineL(const TDesC& aLine);
+	void FocusL(TUint aThreadId);
+	SThreadContext* ContextForThread(TUint aThreadId) const;
+	SThreadContext& CurrentL();
+	SThreadContext& CurrentNoRefreshL();
+	enum TPrintMode { EJustSymbols, EAllData, EHexDump };
+	void PrintMemL(TUint aThreadId, TUint32 aStart, TUint32 aEnd, TPrintMode aMode);
+	void BrrrrrainsL();
+	void ShowHelpTextL();
+	TBool IsSymbol(TUint aAddress) const;
+	void StartInteractiveViewL(TLinAddr aAddress);
+	void DrawMemViewL(TBool aUpdate);
+	void UpdateMemForMemViewL();
+	static TInt IsDescriptorHeader(TUint8 const* aHeaderPtr, TInt aMaxLen);
+	void ShowBreakpointsL();
+	static TInt BreakTest(TAny* aPtr);
+	static TInt BreakTestLtk(TAny* aPtr);
+	static TInt BreakTestCond(TAny* aPtr);
+	void ClearAllBreakpointsL();
+	void BreakpointHit(const RMemoryAccess::TBreakpointNotification& aNotif);
+	void CompleteLineWithSymbolL(TConsoleLine& aLine, TLex& aLex);
+	void CheckForConditionL(TLex& aLex, RMemoryAccess::TPredicate& aCondition);
+	
+private: // From MLineCompleter
+	void LcCompleteLineL(TConsoleLine& aLine, const TChar& aEscapeChar);
+
+private: // From MLineEditorObserver
+	void LeoHandleLine(const TDesC& aLine);
+
+private: // From CCommandBase.
+	virtual const TDesC& Name() const;
+	virtual void DoRunL();
+	virtual void ArgumentsL(RCommandArgumentList& aArguments);
+	virtual void OptionsL(RCommandOptionList& aOptions);
+	void RunL();
+	void DoCancel();
+
+private:
+	// Command line stuff
+	TBool iAllThreads;
+	TUint iThreadId;
+	TFileName2 iBsymFile;
+	TFileName2 iMapFileDir;
+
+	// Thread context
+	struct SThreadContext
+		{
+		SThreadContext() : iUserValidRegisters(0), iSupervisorValidRegisters(0), iFlags(0) {}
+
+		RThread iThread;
+		TUint32 iUserRegisters[KMaxRegisters];
+		TUint32 iUserValidRegisters;
+		TUint32 iSupervisorRegisters[KMaxRegisters];
+		TUint32 iSupervisorValidRegisters;
+		TThreadKernelInfo iKernelInfo;
+		enum TFlags { ERunning = 1, };
+		TBitFlags32 iFlags;
+		};
+	SThreadContext* iCurrent;
+	RPointerArray<SThreadContext> iThreads;
+
+	// Other stuff
+	CSymbolics* iSymbols;
+	TBuf<256> iTempNameBuf;
+
+	// Line editor support
+	TIoConsWriterAdaptor iConsoleAdapter;
+	CLineEditor* iLineEditor;
+	TBuf<256> iCommandLineBuf;
+	TBool iFinishedCommand;
+	TBool iShouldExit;
+
+	// Interactive wossname support
+	enum TViewType
+		{
+		EUnspecified,
+		EStack,
+		EHeap,
+		};
+	TViewType iMemoryViewType;
+	TLinAddr iMemStart;
+	RBuf8 iMemBuf;
+	/*
+	class TCrumb
+		{
+		enum TType { EAddress, EHeapCell, };
+		TType iType;
+		TLinAddr iAddress;
+		TInt iIndex;
+		}
+	RArray<TCrumb> iBreadcrumbs;
+	RArray<TLinAddr> iMarks;
+	RArray<TLinAddr> iLinks;
+	*/
+
+	// Breakpoint support
+	RMemoryAccess::TBreakpointNotification iBreakpointNotification;
+	TPckg<RMemoryAccess::TBreakpointNotification> iBreakpointNotificationPkg;
+	class CBreakpointNotifier : public CActive
+		{
+	public:
+		CBreakpointNotifier(CCmdFdb& aCmd);
+		~CBreakpointNotifier();
+
+	private:
+		void Request();
+		void RunL();
+		void DoCancel();
+
+	private:
+		CCmdFdb& iCmd;
+		};
+	friend class CBreakpointNotifier; // Declaration needed for gcc 2.9
+	CBreakpointNotifier* iBreakpointNotifier;
+	};
+
+EXE_BOILER_PLATE(CCmdFdb)
+
+CCommandBase* CCmdFdb::NewLC()
+	{
+	CCmdFdb* self = new(ELeave) CCmdFdb();
+	CleanupStack::PushL(self);
+	self->BaseConstructL();
+	return self;
+	}
+
+CCmdFdb::~CCmdFdb()
+	{
+	Cancel();
+	delete iLineEditor;
+	for (TInt i = 0; i < iThreads.Count(); i++)
+		{
+		SThreadContext* thread = iThreads[i];
+		thread->iThread.Close();
+		delete thread;
+		}
+	iThreads.Close();
+	delete iSymbols;
+	iMemBuf.Close();
+	delete iBreakpointNotifier;
+	/*
+	iLinks.Close();
+	iBreadcrumbs.Close();
+	iMarks.Close();
+	*/
+	}
+
+CCmdFdb::CCmdFdb()
+	: CMemoryAccessCommandBase(EManualComplete), iConsoleAdapter(Stdout()), iBreakpointNotificationPkg(iBreakpointNotification)
+	{
+	}
+
+const TDesC& CCmdFdb::Name() const
+	{
+	_LIT(KName, "fdb");	
+	return KName;
+	}
+
+void CCmdFdb::ArgumentsL(RCommandArgumentList& aArguments)
+	{
+	aArguments.AppendUintL(iThreadId, _L("threadid"));
+	}
+
+void CCmdFdb::OptionsL(RCommandOptionList& aOptions)
+	{
+	aOptions.AppendBoolL(iAllThreads, _L("all"));
+	aOptions.AppendFileNameL(iBsymFile, _L("symbols"));
+	aOptions.AppendFileNameL(iMapFileDir, _L("mapfiles"));
+	}
+
+struct STestData
+	{
+	STestData() : iPtr8(NULL, 0) {}
+
+	char iPad1[12];
+	TBuf8<6> iBuf8;
+	TInt iPad2;
+	TBufC8<9> iBufC8;
+	char iPad3;
+	TPtrC8 iPtrC8;
+	TUint64 iPad4;
+	TPtr8 iPtr8;
+	char iPad5[3];
+	};
+
+
+void CCmdFdb::DoRunL()
+	{
+	LoadMemoryAccessL();
+	TInt mode = iMemAccess.GetZombieDebugMode();
+	LeaveIfErr(mode, _L("Couldn't get zombie mode from driver"));
+
+	iSymbols = new(ELeave) CSymbolics(FsL());
+	if (iBsymFile.Length())
+		{
+		//Printf(_L("Loading symbolics... ")); // It can take a while...
+		iSymbols->AddBsymFileL(iBsymFile);
+		//Printf(_L("done.\r\n"));
+		}
+	if (iMapFileDir.Length())
+		{
+		iSymbols->SetFallbackMapFileDirL(iMapFileDir);
+		}
+
+	// Testing
+	//CMapFile* map = CMapFile::NewL(FsL(), _L("C:\\symbols\\QResources3.exe.map"));
+	//iSymbols->AddMapFileL(map);
+	//const TDesC& symName = iSymbols->LookupL(_L("QResources3.exe"), 0x10abc);
+	//Printf(_L("Found symbol = %S\r\n"), &symName);
+	
+	if (Env().IsDefined(_L("FDB_TEST")))
+		{
+		STestData test;
+		test.iBuf8 = _L8("12345");
+		test.iBufC8 = _L8("abcdefg");
+		test.iPtrC8.Set(test.iBufC8);
+		TPckg<STestData> pkg(test);
+		iMemBuf.Assign(pkg.AllocL());
+		DrawMemViewL(EFalse);
+		return;
+		}
+	
+	TBool install = ETrue;
+	// Figure out something sensible to do
+	if (mode != 0 || iThreadId != 0) install = EFalse;
+
+	if (iAllThreads) Printf(_L("Note that because --all-threads was specified, this and all future commands will hang rather than exiting!\r\n"));
+
+	InstallL(); // Always do this, in case user has changed the --all-threads setting
+	if (install)
+		{
+		// This will really mess up evalid, but the toolkit doesn't really use it so I don't feel *too* guilty
+		Printf(_L8("Welcome to fdb (build " __DATE__ " " __TIME__ ").\r\nDebugger hook installed and will stay running until you type 'uninstall'.\r\nYou can exit fdb if required and it will stay running in the background.\r\nType 'help' for command info.\r\n"));
+		}
+	else
+		{
+		AttachL(iThreadId);
+		}
+
+	iBreakpointNotifier = new(ELeave) CBreakpointNotifier(*this);
+	TInt err = iMemAccess.RegisterPersistantBreakpoint(LtkUtils::BreakpointAddr());
+	if (err != KErrNone && err != KErrAlreadyExists)
+		{
+		LeaveIfErr(err, _L("Couldn't install the LtkUtils::Breakpoint() breakpoint"));
+		}
+	StartCommandParsingL();
+	}
+
+void CCmdFdb::InstallL()
+	{
+	LeaveIfErr(iMemAccess.SetZombieDebugMode(iAllThreads ? 2 : 1), _L("Couldn't install thread monitor"));
+	const TUint32 KAllThreadsSystem = 1; // Word 2, bit 0
+	TUint32 dbgmask = UserSvr::DebugMask(2);
+	if (dbgmask & KAllThreadsSystem)
+		{
+		Printf(_L("(Clearing KAllThreadsSystem attribute)\r\n"));
+		User::SetDebugMask(dbgmask & ~KAllThreadsSystem, 2);
+		}
+	}
+
+void CCmdFdb::AttachL(TUint aThreadId)
+	{
+	if (aThreadId == 0)
+		{
+		// Need to figure out something to attach to
+		TBuf8<64> buf; // TODO handle more than this many zombies
+		TInt res = iMemAccess.GetZombies(buf);
+		LeaveIfErr(res, _L("Couldn't get zombie info from driver"));
+		//TInt leftOver = 0;
+		//if (res > buf.MaxLength()) leftOver = (res - buf.MaxLength()) / sizeof(TUint);
+		RMemoryAccess::TZombieInfo* ptr = (RMemoryAccess::TZombieInfo*)buf.Ptr();
+		TInt zombiecount = buf.Length() / sizeof(RMemoryAccess::TZombieInfo);
+		for (TInt i = 0; i < zombiecount; i++)
+			{
+			TUint id = ptr[i].iThreadId;
+			if (ContextForThread(id)) continue; // already attached to this thread, keep looking
+			RThread thread;
+			TInt err = iMemAccess.RThreadForceOpen(thread, id);
+			if (err)
+				{
+				PrintWarning(_L("Couldn't open thread %u"), id);
+				continue;
+				}
+			iTempNameBuf = thread.FullName();
+			Printf(_L("(Found thread id %u %S)\r\n"), id, &iTempNameBuf);
+			if (aThreadId == 0) aThreadId = id;
+			thread.Close();
+			}
+		if (aThreadId == 0)
+			{
+			if (buf.Length())
+				Printf(_L("No zombied threads found that aren't already attached.\r\n"));
+			else
+				PrintWarning(_L("No zombied threads found to attach to."));
+			return;
+			}
+		else
+			{
+			Printf(_L("(Attaching to thread %u)\r\n"), aThreadId);
+			}
+		}
+
+	SThreadContext* context = ContextForThread(aThreadId);
+	if (context)
+		{
+		PrintWarning(_L("Already attached to thread %u, focussing it instead."), aThreadId);
+		iCurrent = context;
+		return;
+		}
+
+	context = new(ELeave) SThreadContext;
+	CleanupStack::PushL(context);
+	RThread& thread(context->iThread);
+	CleanupClosePushL(thread);
+	LeaveIfErr(iMemAccess.RThreadForceOpen(thread, aThreadId), _L("Couldn't open thread %u"), aThreadId);
+	RefreshL(*context);
+	iThreads.AppendL(context);
+	CleanupStack::Pop(2, context); // context->iThread, context
+	iCurrent = context;
+
+	PrintThreadInfo(*context);
+	// Don't print any more than this - it makes the output too wordy
+	//PrintRegistersL(*context);
+	}
+
+void CCmdFdb::PrintThreadInfo(SThreadContext& aContext)
+	{
+	RThread& aThread = aContext.iThread;
+	TFullName name = aThread.FullName();
+	Write(_L("Name: "));
+	Write(name);
+
+	Printf(_L("\r\nThread Id: %d, exit type: "), (TUint)aThread.Id());
+	TExitType exitType = aThread.ExitType();
+	if (exitType == EExitPending)
+		{
+		Printf(_L("Still running\r\n"));
+		}
+	else if (exitType == EExitKill)
+		{
+		Printf(_L("Kill %d\r\n"), aThread.ExitReason());
+		}
+	else if (exitType == EExitTerminate)
+		{
+		Printf(_L("Terminate %d\r\n"), aThread.ExitReason());
+		}
+	else if (exitType == EExitPanic)
+		{
+		TExitCategoryName exitCategory = aThread.ExitCategory();
+		Printf(_L("Panic %S %d\r\n"), &exitCategory, aThread.ExitReason());
+		}
+
+	Printf(_L("User stack: base=%08x limit=%08x\r\n"), aContext.iKernelInfo.UserStackBase(), aContext.iKernelInfo.iUserStackLimit);
+	Printf(_L("Kern stack: base=%08x limit=%08x\r\n"), aContext.iKernelInfo.iSupervisorStack + aContext.iKernelInfo.iSupervisorStackSize, aContext.iKernelInfo.iSupervisorStack);
+	}
+
+void CCmdFdb::PrintRegistersL(SThreadContext& aThread)
+	{
+	PrintRegistersL(aThread, ETrue);
+	PrintRegistersL(aThread, EFalse);
+	}
+
+void CCmdFdb::PrintRegistersL(SThreadContext& aThread, TBool aUserMode)
+	{
+	if (aUserMode)
+		{
+		Printf(_L("User mode registers:\r\n"));
+		}
+	else
+		{
+		Printf(_L("Supervisor mode registers:\r\n"));
+		}
+
+	TUint32 valid = aThread.iUserValidRegisters;
+	TUint32* ptr = aThread.iUserRegisters;
+	if (!aUserMode)
+		{
+		valid = aThread.iSupervisorValidRegisters;
+		ptr = aThread.iSupervisorRegisters;
+		}
+
+	if (valid == 0) Printf(_L("(No valid registers)\r\n"));
+	// From here down is ARM specific, but since no other platforms implement this API there's no pressing need to ifdef it
+	// See TArmRegSet for the ordering
+	for (TInt i = 0; i < 16; i++)
+		{
+		if (valid & (1<<i))
+			{
+			if (i < 10) Write(_L(" ")); // extra space for padding
+			Printf(_L("  R%d: "), i);
+			Printf(_L("%08x "), ptr[i]);
+			Write(LookupSymbol(ptr[i]));
+			Write(KCrLf);
+			}
+		}
+	if (valid & (1<<16))
+		{
+		Printf(_L("Flags (%cPSR): %08x\r\n"), aUserMode ? 'C' : 'S', ptr[16]); // CPSR or SPSR
+		}
+	if (valid & (1<<17))
+		{
+		// Domain Access Control Register
+		Printf(_L(" DACR: %08x\r\n"), ptr[16]);
+		}
+	}
+
+void CCmdFdb::PrintRegistersLRPC(SThreadContext& aThread, TBool aUserMode)
+	{
+	// Print these in the format of the stack trace
+	TUint32 valid = aThread.iUserValidRegisters;
+	TUint32* ptr = aThread.iUserRegisters;
+	if (!aUserMode)
+		{
+		valid = aThread.iSupervisorValidRegisters;
+		ptr = aThread.iSupervisorRegisters;
+		}
+	
+	if (valid & 1<<15)
+		{
+		Printf(_L("     R15: %08x "), ptr[15]);
+		Write(LookupSymbol(ptr[15]));
+		Write(KCrLf);
+		}
+	if (valid & 1<<14)
+		{
+		Printf(_L("     R14: %08x "), ptr[14]);
+		Write(LookupSymbol(ptr[14]));
+		Write(KCrLf);
+		}
+	}
+
+void CCmdFdb::RefreshIfRunningL(SThreadContext& aContext)
+	{
+	if (iCurrent->iFlags.IsSet(SThreadContext::ERunning))
+		{
+		// Thread is still running, we should refresh our info
+		RefreshL(aContext);
+		}
+	}
+
+void CCmdFdb::RefreshL(SThreadContext& aContext)
+	{
+	aContext.iFlags.Assign(SThreadContext::ERunning, aContext.iThread.ExitType() == EExitPending);
+	TUint tid = aContext.iThread.Id();
+
+	// Get registers
+	TPtr8 userreg((TUint8*)&aContext.iUserRegisters[0], KMaxRegisters*sizeof(TUint32), KMaxRegisters*sizeof(TUint32));
+	userreg.FillZ();
+	aContext.iUserValidRegisters = 0;
+	TInt err = iMemAccess.GetRegisters(aContext.iThread, ETrue, userreg, aContext.iUserValidRegisters);
+	if (err) PrintError(err, _L("Couldn't read user registers for thread %u"), tid);
+
+	TPtr8 supreg((TUint8*)&aContext.iSupervisorRegisters[0], KMaxRegisters*sizeof(TUint32), KMaxRegisters*sizeof(TUint32));
+	supreg.FillZ();
+	aContext.iSupervisorValidRegisters = 0;
+	err = iMemAccess.GetRegisters(aContext.iThread, EFalse, supreg, aContext.iSupervisorValidRegisters);
+	if (err) PrintError(err, _L("Couldn't read supervisor registers for thread %u\r\n"), tid);
+
+	// And memaccess info
+	TPckg<TThreadKernelInfo> kerninfo(aContext.iKernelInfo);
+	err = iMemAccess.GetObjectInfoByHandle(EThread, RThread().Id(), aContext.iThread.Handle(), kerninfo);
+	if (err) PrintError(err, _L("Couldn't read thread info from memoryaccess for thread %u\r\n"), tid);
+	//aContext.iFlags.Assign(SThreadContext::ESuspended, aContext.iKernelInfo.iNThreadSuspendCount != 0);
+	}
+	
+TPtrC CCmdFdb::LookupSymbol(TUint32 aAddress)
+	{
+	TPtrC name;
+	if (iSymbols)
+		{
+		// Try straight ROM address lookup
+		TRAPD(err, name.Set(iSymbols->LookupL(aAddress)));
+		if (err) PrintError(err, _L("Failed to lookup symbol"));
+		if (name.Length()) return name;
+		}
+	// Try getting a codeseg from memaccess
+	TFullName8 codesegname;
+	TInt res = iMemAccess.FindAddressInCodeSegments(codesegname, (TAny*)aAddress);
+	if (res >= 0)
+		{
+		iTempNameBuf.Copy(codesegname);
+		if (iSymbols)
+			{
+			// Try codeseg lookup in CSymbolics (ie in a CMapFile)
+			TParsePtrC parse(iTempNameBuf);
+			TRAPD(err, name.Set(iSymbols->LookupL(parse.NameAndExt(), res)));
+			if (err) PrintError(err, _L("Failed to lookup symbol"));
+			if (name.Length()) return name;
+			}
+		// Otherwise fallback to just doing codeseg+offset
+		iTempNameBuf.AppendFormat(_L(" + 0x%x"), res);
+		return iTempNameBuf;
+		}
+	// Last ditch, check if euser thinks it's in ROM
+	if (RFs::IsRomAddress((TAny*)aAddress))
+		{
+		_LIT(KSomewhere, "[Somewhere in ROM]");
+		return KSomewhere();
+		}
+	return KNullDesC();
+	}
+
+void CCmdFdb::StartCommandParsingL()
+	{
+	TBuf<64> historyFile;
+	User::LeaveIfError(FsL().PrivatePath(historyFile));
+	historyFile.Insert(0, _L("c:"));
+	historyFile.Append(_L("fdb_history"));
+	Fs().CreatePrivatePath(EDriveC);
+
+	iLineEditor = CLineEditor::NewL(Fs(), iConsoleAdapter, *this, *this, historyFile);
+	iLineEditor->Start(KPrompt, iCommandLineBuf);
+	iLineEditor->ReinstatePromptAndUserInput();
+	SetErrorReported(EFalse);
+	Stdin().WaitForKey(iStatus);
+	SetActive();
+	}
+
+void CCmdFdb::RunL()
+	{
+	if (iStatus.Int() < 0)
+		{
+		// iosrv dead?
+		Complete(iStatus.Int());
+		return;
+		}
+
+	iLineEditor->HandleKey(Stdin().KeyCode(), Stdin().KeyModifiers());
+	if (iShouldExit)
+		{
+		Complete(KErrNone);
+		}
+	else if (iFinishedCommand)
+		{
+		iLineEditor->Start(KPrompt, iCommandLineBuf);
+		iLineEditor->ReinstatePromptAndUserInput();
+		iFinishedCommand = EFalse;
+		SetErrorReported(EFalse); // We need to clear this each time through
+		}
+	
+	if (!iShouldExit)
+		{
+		Stdin().WaitForKey(iStatus);
+		SetActive();
+		}
+	}
+
+void CCmdFdb::DoCancel()
+	{
+	Stdin().WaitForKeyCancel();
+	}
+
+void CCmdFdb::LcCompleteLineL(TConsoleLine& aLine, const TChar& /*aEscapeChar*/)
+	{
+	TPtrC line = aLine.ContentsToCursor();
+	TLex lex(line);
+	TPtrC cmd = lex.NextToken();
+	if (cmd != _L("b") && cmd != _L("break")) return; // We only currently do completion on the 'break' command
+
+	CompleteLineWithSymbolL(aLine, lex);
+	}
+
+void CCmdFdb::CompleteLineWithSymbolL(TConsoleLine& aLine, TLex& aLex)
+	{
+	// Have we got as far as a codeseg? We don't support completing on codeseg yet
+	TPtrC codeseg = aLex.NextToken();
+	TPtrC ext = codeseg.Right(4);
+	if (ext != _L(".dll") && ext != _L(".exe")) return;
+
+	aLex.SkipSpace();
+	TInt symbolStartPos = aLex.Offset();
+	TPtrC symbol = aLex.Remainder();
+	RLtkBuf buf;
+	buf.CreateLC(512);
+	buf.Copy(symbol);
+	CDesC16Array* suggestions = new(ELeave) CDesC16ArrayFlat(32);
+	CleanupStack::PushL(suggestions);
+	//TODO this can be slow first time, need some UI to warn the user
+	iSymbols->CompleteL(codeseg, buf, *suggestions);
+	if (buf.Length() > symbol.Length())
+		{
+		aLine.Replace(symbolStartPos, buf);
+		}
+	// If the tab added any chars we don't show choices
+	else if (suggestions->Count() > 1)
+		{
+		buf.Zero();
+		for (TInt i = 0; i < suggestions->Count(); i++)
+			{
+			if (i > 0) buf.AppendL('\t');
+			buf.AppendL((*suggestions)[i]);
+			}
+		aLine.PrintCompletionPossibilitiesL(buf);
+		}
+
+	CleanupStack::PopAndDestroy(suggestions);
+	CleanupStack::PopAndDestroy(&buf);
+	}
+
+void CCmdFdb::LeoHandleLine(const TDesC& aLine)
+	{
+	iFinishedCommand = ETrue;
+	if (aLine.Length())
+		{
+		TRAPD(err, ProcessLineL(aLine));
+		if (err)
+			{
+			PrintError(err, _L("Error executing command"));
+			}
+		}
+	}
+
+void CCmdFdb::ProcessLineL(const TDesC& aLine)
+	{
+	TLex lex(aLine);
+	TPtrC cmd = lex.NextToken();
+	lex.SkipSpace();
+
+	char ch = 0;
+	if (cmd.Length() == 1)
+		{
+		ch = cmd[0];
+		}
+
+	_LIT(KHelp, "help");
+	_LIT(KExit, "exit");
+	_LIT(KAttach, "attach");
+	_LIT(KDetach, "detach");
+	_LIT(KFocus, "focus");
+	_LIT(KRegisters, "registers");
+	_LIT(KLookup, "lookup");
+	_LIT(KStack, "stack");
+	_LIT(KKstack, "kstack");
+	_LIT(KList, "list");
+	_LIT(KMem, "mem");
+	_LIT(KUninstall, "uninstall");
+	_LIT(KBrowse, "browse");
+	_LIT(KBreak, "break");
+	_LIT(KContinue, "continue");
+	_LIT(KClear, "clear");
+	_LIT(KLoad, "load");
+	if (cmd == KHelp || ch == 'h')
+		{
+		ShowHelpTextL();
+		}
+	else if (cmd == KExit || ch == 'x')
+		{
+		iShouldExit = ETrue; // TODO check whether a detach is desired
+		}
+	else if (cmd == KUninstall || ch == 'u')
+		{
+		Printf(_L("Uninstalling thread hook, clearing all breakpoints, freeing all zombie threads. Undertakers will now get notified of the threads' exits.\r\n"));
+		iMemAccess.SetZombieDebugMode(0);
+		iShouldExit = ETrue;
+		}
+	else if (cmd == KAttach || ch == 'a')
+		{
+		TUint threadId = 0;
+		lex.Val(threadId);
+		AttachL(threadId);
+		}
+	else if (cmd == KFocus || ch == 'f')
+		{
+		TUint threadId = 0;
+		lex.Val(threadId);
+		if (threadId == 0)
+			{
+			PrintThreadInfo(CurrentL());
+			}
+		else
+			{
+			FocusL(threadId);
+			}
+		}
+	else if (cmd == KRegisters || ch == 'r')
+		{
+		PrintRegistersL(CurrentL());
+		}
+	else if (cmd == KLookup || ch == '?')
+		{
+		TUint32 addr = LtkUtils::HexLexL(lex);
+		Printf(_L("%08x: "), addr);
+		Write(LookupSymbol(addr));
+		Write(KCrLf);
+		}
+	else if (cmd == KDetach || ch == 'd')
+		{
+		TUint threadId = 0;
+		lex.Val(threadId);
+		SThreadContext* thread = NULL;
+		if (threadId == 0)
+			{
+			// No point refreshing, all it will do is possibly complain about reading the registers (which looks confusing)
+			thread = &CurrentNoRefreshL();
+			}
+		else
+			{
+			thread = ContextForThread(threadId);
+			if (!thread) LeaveIfErr(KErrNotFound, _L("Thread %u is not currently attached."), threadId);
+			}
+		Detach(thread);
+		}
+	else if (cmd == KStack || ch == 't')
+		{
+		TBool all = lex.NextToken() == _L("all");
+		SThreadContext& c = CurrentL();
+		TUint32 start = c.iKernelInfo.iUserStackLimit;
+		TUint32 end = c.iKernelInfo.UserStackBase();
+		if (start == 0)
+			{
+			Printf(_L("No user stack for this thread.\r\n"));
+			}
+		else
+			{
+			PrintRegistersLRPC(c, ETrue);
+			if ((c.iUserValidRegisters & (1 << 13)) && Rng(start, c.iUserRegisters[13], end)) start = c.iUserRegisters[13];
+			PrintMemL(c.iThread.Id(), start, end, all ? EAllData : EJustSymbols);
+			}
+		}
+	else if (cmd == KKstack || ch == 'k')
+		{
+		TBool all = lex.NextToken() == _L("all");
+		SThreadContext& c = CurrentL();
+		TUint32 start = c.iKernelInfo.iSupervisorStack;
+		TUint32 end = start + c.iKernelInfo.iSupervisorStackSize;
+		if (start == 0)
+			{
+			Printf(_L("Couldn't find kernel stack for this thread (!?).\r\n"));
+			}
+		else
+			{
+			PrintRegistersLRPC(c, EFalse);
+			if ((c.iSupervisorValidRegisters & (1 << 13)) && Rng(start, c.iSupervisorRegisters[13], end)) start = c.iSupervisorRegisters[13];
+			PrintMemL(0, start, end, all ? EAllData : EJustSymbols); // zero is the null thread, which is a kernel thread thus has the address space we're interested in
+			}
+		}
+	else if (cmd == KList || ch == 'l')
+		{
+		BrrrrrainsL();
+		}
+	else if (cmd == KMem || ch == 'm')
+		{
+		TUint32 start = LtkUtils::HexLexL(lex);
+		lex.SkipSpace();
+		TUint32 len = LtkUtils::HexLexL(lex);
+		PrintMemL(CurrentL().iThread.Id(), start, start+len, EHexDump);
+		}
+	else if (cmd == KBrowse)
+		{
+		TPtrC remainder = lex.Remainder();
+		TLinAddr addr = 0;
+		if (remainder.Length() == 0 || remainder == _L("heap"))
+			{
+			//TODO
+			}
+		else if (remainder == _L("stack"))
+			{
+			//TODO
+			}
+		else
+			{
+			addr = LtkUtils::HexLexL(lex);
+			}
+		StartInteractiveViewL(addr);
+		}
+	else if (cmd == KBreak || ch == 'b')
+		{
+		TInt res = KErrNone;
+		TBool set = ETrue;
+		TPtrC remainder = lex.Remainder();
+		if (remainder.Length() == 0)
+			{
+			ShowBreakpointsL();
+			set = EFalse;
+			}
+		else if (remainder == _L("test"))
+			{
+			// This is undocumented, for testing only
+			RThread me;
+			res = iMemAccess.SetBreakpoint(me, (TLinAddr)&LtkUtils::RawPrint);
+			LeaveIfErr(res, _L("Couldn't set test breakpoint"));
+			RThread testThread;
+			LeaveIfErr(testThread.Create(_L("BreakpointTestThread"), &BreakTest, 8192, NULL, NULL), _L("Couldn't create test thread"));
+			testThread.Resume();
+			testThread.Close();
+			}
+		else if (remainder == _L("testltk"))
+			{
+			// This is undocumented, for testing only
+			RThread testThread;
+			LeaveIfErr(testThread.Create(_L("BreakpointLtkTestThread"), &BreakTestLtk, 8192, NULL, NULL), _L("Couldn't create test thread"));
+			testThread.Resume();
+			testThread.Close();
+			set = EFalse;
+			}
+		else if (remainder == _L("testhw"))
+			{
+			// This is undocumented, for testing only
+			RThread testThread;
+			LeaveIfErr(testThread.Create(_L("BreakpointTestThread"), &BreakTest, 8192, NULL, NULL), _L("Couldn't create test thread"));
+			res = iMemAccess.SetBreakpoint(testThread, (TLinAddr)&LtkUtils::RawPrint);
+			LeaveIfErr(res, _L("Couldn't set test breakpoint"));
+			testThread.Resume();
+			testThread.Close();
+			}
+		else if (remainder == _L("testcond"))
+			{
+			// This is undocumented, for testing only
+			RThread testThread;
+			LeaveIfErr(testThread.Create(_L("BreakpointTestThread"), &BreakTestCond, 8192, NULL, NULL), _L("Couldn't create test thread"));
+			RMemoryAccess::TPredicate condition;
+			LeaveIfErr(condition.AddCondition(RMemoryAccess::TPredicate::ESignedEq, 0, (TUint)-5), _L("Couldn't add condition"));
+			res = iMemAccess.SetBreakpoint(testThread, (TLinAddr)&User::Leave, &condition);
+			LeaveIfErr(res, _L("Couldn't set test breakpoint"));
+			testThread.Resume();
+			testThread.Close();
+			}
+		else
+			{
+			TUint addr;
+			TInt err = HexLex(lex, addr);
+			if (err)
+				{
+				// Try codeseg and symbol name
+				TPtrC codeseg = lex.NextToken();
+				lex.SkipSpace();
+				TPtrC name = lex.Remainder();
+				// Hmm symbols can have spaces in, how to distinguish the condition? Assume if the last word starts with an 'r' it's a condition. Not very nice.
+				TInt space = name.LocateReverse(' ');
+				RMemoryAccess::TPredicate condition;
+				if (space >= 0 && space+1 < name.Length() && name[space+1] == 'r')
+					{
+					name.Set(name.Left(space));
+					lex.Inc(name.Length());
+					while (!lex.Eos())
+						{
+						CheckForConditionL(lex, condition);
+						}
+					}
+
+				TUint offset = 0;
+				TRAPL(offset = iSymbols->CodesegOffsetFromSymbolNameL(codeseg, name), _L("Couldn't find offset of symbol '%S' in codeseg '%S'"), &name, &codeseg);
+				RLtkBuf8 codeseg8;
+				CleanupClosePushL(codeseg8);
+				codeseg8.AppendL(codeseg);
+				res = iMemAccess.SetSymbolicBreakpoint(CurrentL().iThread, codeseg8, offset, &condition);
+				LeaveIfErr(res, _L("Couldn't create symbolic breakpoint"));
+				CleanupStack::PopAndDestroy(&codeseg8);
+				}
+			else
+				{
+				RMemoryAccess::TPredicate condition;
+				while (!lex.Eos())
+					{
+					CheckForConditionL(lex, condition);
+					}
+				res = iMemAccess.SetBreakpoint(CurrentL().iThread, addr, &condition);
+				LeaveIfErr(res, _L("Couldn't create breakpoint"));
+				Printf(_L("Breakpoint created at 0x%08x "), addr);
+				Write(LookupSymbol(addr));
+				Write(KCrLf);
+				}
+			}
+
+		if (set)
+			{
+			if (res & RMemoryAccess::TBreakpointInfo::EHardware)
+				{
+				Printf(_L("Hardware breakpoint %d set.\r\n"), res & ~RMemoryAccess::TBreakpointInfo::EHardware);
+				}
+			else if (res == 0)
+				{
+				Printf(_L("Pending breakpoint set (Note these don't work yet!)\r\n"));
+				}
+			else
+				{
+				Printf(_L("Breakpoint %d set.\r\n"), res);
+				}
+			}
+		}
+	else if (cmd == KContinue || ch =='c')
+		{
+		TInt err = iMemAccess.ContinueFromBreakpoint(CurrentL().iThread);
+		LeaveIfErr(err, _L("Couldn't continue - is the thread definitely stopped on a breakpoint?"));
+		}
+	else if (cmd == KClear)
+		{
+		TPtrC remainder = lex.Remainder();
+		if (remainder.Length() == 0)
+			{
+			ClearAllBreakpointsL();
+			}
+		else
+			{
+			TInt n;
+			User::LeaveIfError(lex.Val(n));
+			LeaveIfErr(iMemAccess.ClearBreakpoint(n), _L("Couldn't clear breakpoint %d"), n);
+			}
+		}
+	else if (cmd == KLoad)
+		{
+		TPtrC path = lex.Remainder();
+		if (path.Right(5).CompareF(_L(".bsym")) == 0)
+			{
+			//Printf(_L("Loading symbolics... ")); // It can take a while...
+			iSymbols->AddBsymFileL(path);
+			//Printf(_L("done.\r\n"));
+			}
+		else
+			{
+			iSymbols->SetFallbackMapFileDirL(path);
+			}
+		}
+	else
+		{
+		PrintError(KErrNotFound, _L("Unrecognised command '%S'. Try 'help'."), &cmd);
+		}
+	}
+
+CCmdFdb::SThreadContext* CCmdFdb::ContextForThread(TUint aThreadId) const
+	{
+	for (TInt i = 0; i < iThreads.Count(); i++)
+		{
+		if ((TUint)iThreads[i]->iThread.Id() == aThreadId)
+			{
+			return iThreads[i];
+			}
+		}
+	return NULL;
+	}
+
+CCmdFdb::SThreadContext& CCmdFdb::CurrentL()
+	{
+	CCmdFdb::SThreadContext& current = CurrentNoRefreshL();
+	RefreshIfRunningL(current);
+	return current;
+	}
+
+CCmdFdb::SThreadContext& CCmdFdb::CurrentNoRefreshL()
+	{
+	if (!iCurrent)
+		{
+		LeaveIfErr(KErrNotReady, _L("No currently focussed thread"));
+		}
+	return *iCurrent;
+	}
+
+void CCmdFdb::FocusL(TUint aThreadId)
+	{
+	SThreadContext* c = ContextForThread(aThreadId);
+	if (c) iCurrent = c;
+	else
+		{
+		LeaveIfErr(KErrNotFound, _L("Couldn't find thread id %u in the attached threads. Do you need to do 'attach %u'?"), aThreadId, aThreadId);
+		}
+	}
+
+void CCmdFdb::Detach(SThreadContext* aContext)
+	{
+	TInt err = iMemAccess.ReleaseZombie(aContext->iThread);
+	if (err && aContext->iFlags.IsSet(SThreadContext::ERunning))
+		{
+		// Don't complain about driver if the thread wasn't actually zombied
+		PrintError(err, _L("Driver couldn't find zombie thread"));
+		}
+
+	TInt arrayPos = iThreads.Find(aContext);
+	iThreads.Remove(arrayPos);
+	if (iCurrent == aContext) iCurrent = NULL;
+	aContext->iThread.Close();
+	delete aContext;
+	}
+
+void CCmdFdb::PrintMemL(TUint aThreadId, TUint32 aStart, TUint32 aEnd, TPrintMode aMode)
+	{
+	// word-align start and end
+	aStart &= ~3;
+	aEnd = (aEnd+3) & (~3);
+
+	TInt size = aEnd - aStart;
+	RBuf8 mem;
+	CleanupClosePushL(mem);
+	mem.CreateL(size);
+
+	TThreadMemoryAccessParamsBuf params;
+	params().iId = aThreadId;
+	params().iAddr = (TUint8*)aStart;
+	params().iSize = size;
+
+	TInt err = iMemAccess.GetThreadMem(params, mem);
+	LeaveIfErr(err, _L("Couldn't read thread memory %08x-%08x"), aStart, aEnd);
+
+	if (aMode == EHexDump)
+		{
+		TInt offset = (TInt)aStart;
+		LtkUtils::HexDumpToOutput(mem, Stdout(), offset);
+		}
+	else
+		{
+		const TUint32* ptr = (const TUint32*)mem.Ptr();
+		const TInt count = mem.Size() / 4;
+		for (TInt i = 0; i < count; i++)
+			{
+			TUint32 word = ptr[i];
+			TBool print = aMode == EAllData || IsSymbol(word);
+			if (print)
+				{
+				Printf(_L("%08x: %08x "), aStart + i*4, word);
+				Write(LookupSymbol(word));
+				Write(KCrLf);
+				}
+			}
+		}
+	CleanupStack::PopAndDestroy(&mem);
+	}
+
+void CCmdFdb::BrrrrrainsL()
+	{
+	RBuf8 buf;
+	CleanupClosePushL(buf);
+	buf.CreateL(1024);
+	TInt res = iMemAccess.GetZombies(buf);
+	LeaveIfErr(res, _L("Couldn't get zombie info from driver"));
+	RMemoryAccess::TZombieInfo* ptr = (RMemoryAccess::TZombieInfo*)buf.Ptr();
+	const TInt zombiecount = buf.Length()/sizeof(RMemoryAccess::TZombieInfo);
+
+	// Go through all the zombies
+	for (TInt i = 0; i < zombiecount; i++)
+		{
+		TUint id = ptr[i].iThreadId;
+		SThreadContext* context = ContextForThread(id);
+		char stat = '-';
+		char suspended = '-';
+		if (context)
+			{
+			stat = 'a';
+			if (context == iCurrent) stat = '*';
+			iTempNameBuf = context->iThread.FullName();
+			}
+		else
+			{
+			RThread thread;
+			TInt err = iMemAccess.RThreadForceOpen(thread, id);
+			if (err)
+				{
+				PrintWarning(_L("Couldn't open thread %u"), id);
+				continue;
+				}
+			iTempNameBuf = thread.FullName();
+			thread.Close();
+			}
+		if (ptr[i].iFlags & RMemoryAccess::TZombieInfo::ESuspended) suspended = 's';
+		if (ptr[i].iFlags & RMemoryAccess::TZombieInfo::EBreakpoint) suspended = 'b';
+
+		Printf(_L("%c%c %u "), stat, suspended, id);
+		Write(iTempNameBuf);
+		Write(KCrLf);
+		}
+
+	// Now do any attached threads that aren't zombied
+	for (TInt i = 0; i < iThreads.Count(); i++)
+		{
+		SThreadContext* c = iThreads[i];
+		TUint id = (TUint)c->iThread.Id();
+		RMemoryAccess::TZombieInfo dummy; dummy.iThreadId = id;
+		TBool foundInZombies = EFalse;
+		if (zombiecount) foundInZombies = RArray<RMemoryAccess::TZombieInfo>(sizeof(RMemoryAccess::TZombieInfo), ptr, zombiecount).Find(dummy) != KErrNotFound;
+		if (!foundInZombies)
+			{
+			char stat = 'a';
+			if (c == iCurrent) stat = '*';
+			iTempNameBuf = c->iThread.FullName();
+			Printf(_L("%c- %u "), stat, id);
+			Write(iTempNameBuf);
+			Write(KCrLf);
+			}
+		}
+
+	CleanupStack::PopAndDestroy(&buf);
+	}
+
+void CCmdFdb::ShowHelpTextL()
+	{
+	// Possible TODOs: hEap, Save, Ymodem?
+	_LIT(KStartOfCommands, "SUPPORTED COMMANDS\r\n\r\n");
+	CTextBuffer* helpText = const_cast<CTextBuffer*>(GetHelpTextL());
+	TInt found = helpText->Descriptor().Find(KStartOfCommands);
+	helpText->Delete(0, found + KStartOfCommands().Length());
+	helpText->Write(Stdout());
+	delete helpText;
+	}
+
+TBool CCmdFdb::IsSymbol(TUint aAddress) const
+	{
+	// Ranges probably not perfect, seem to be roughly ok though
+	TBool okRange = Rng(0x70000000u, aAddress, 0xA0000000u) || Rng(0xC0000000u, aAddress, 0xFC000000u);
+	return okRange && aAddress != 0xDEDEDEDE && aAddress != 0xAAAAAAAA && aAddress != 0xBBBBBBBB && aAddress != 0xCCCCCCCC; 
+	}
+
+void CCmdFdb::StartInteractiveViewL(TLinAddr aAddress)
+	{
+	iMemStart = aAddress & (~3);
+	if (iMemStart >= iCurrent->iKernelInfo.iUserStackLimit && iMemStart <= iCurrent->iKernelInfo.UserStackBase())
+		{
+		iMemoryViewType = EStack;
+		}
+	else //TODO EHeap
+		{
+		iMemoryViewType = EUnspecified;
+		}
+
+	iMemBuf.Zero();
+	TSize consoleSize(80,24);
+	Stdout().GetScreenSize(consoleSize);
+	iMemBuf.ReAllocL(consoleSize.iHeight * 16); // At most we display 16 bytes per line
+	TInt numBytesOnLine = 16; // By default
+	if (iMemoryViewType == EStack) numBytesOnLine = 4;
+	TInt numBytesOnScreen = numBytesOnLine * consoleSize.iHeight;
+
+	TLinAddr lastAddr = 0;
+	for (;;)
+		{
+		iMemStart &= ~3; // Just checking
+		TBool update = (iMemStart != lastAddr);
+		lastAddr = iMemStart;
+		DrawMemViewL(update);
+		TUint key = Stdin().ReadKey();
+		switch (key)
+			{
+			case EKeyPageUp:
+				iMemStart -= numBytesOnScreen;
+				break;
+			case EKeyPageDown:
+				iMemStart += numBytesOnScreen;
+				break;
+			case EKeyUpArrow:
+				iMemStart -= numBytesOnLine;
+				break;
+			case EKeyDownArrow:
+				iMemStart += numBytesOnLine;
+				break;
+			case 'q':
+			case 'x':
+			case EKeyEscape:
+				//Stdout().SetAttributesL(ConsoleAttributes::ENone);
+				return;
+			default:
+				break;
+			}
+		}
+	}
+
+void CCmdFdb::UpdateMemForMemViewL()
+	{
+	iMemBuf.Zero();
+	
+	TThreadMemoryAccessParamsBuf params;
+	params().iId = iCurrent->iThread.Id();
+	params().iAddr = (TUint8*)iMemStart;
+	params().iSize = iMemBuf.MaxSize();
+
+	TInt err = iMemAccess.GetThreadMem(params, iMemBuf);
+	if (err) PrintError(err, _L("Couldn't read thread memory %08x-%08x"), params().iAddr, params().iAddr+params().iSize);
+	}
+
+/*
+class TFocusable
+	{
+public:
+	TLinAddr iAddr;
+	enum TType
+		{
+		EHeapPtr,
+		}
+	}
+*/
+
+void CCmdFdb::DrawMemViewL(TBool aUpdate)
+	{
+	Stdout().ClearScreen();
+	if (aUpdate) UpdateMemForMemViewL();
+
+
+	// This is a really messy function. Can't seem to find a way of making it easier to read.
+	const ConsoleAttributes::TAttributes KNormal(ConsoleAttributes::ENone, ConsoleAttributes::EBlack, ConsoleAttributes::EWhite);
+	const ConsoleAttributes::TAttributes KSymbol(0, ConsoleAttributes::ERed, ConsoleAttributes::EUnchanged);
+	const ConsoleAttributes::TAttributes KDescriptor(0, ConsoleAttributes::EUnchanged, ConsoleAttributes::EYellow);
+	//const ConsoleAttributes::TAttributes KHeap(0, ConsoleAttributes::EUnchanged, ConsoleAttributes::ECyan);
+	//const ConsoleAttributes::TAttributes KHeapAlternate(0, ConsoleAttributes::EUnchanged, ConsoleAttributes::EBlue);
+	enum { ENoColor = 0, ESymbol = 1, EDescriptor = 2 };
+
+	TSize consoleSize(80,24);
+	Stdout().GetScreenSize(consoleSize);
+	TUint8 const*const bptr = iMemBuf.Ptr();
+	TUint32 const*const ptr = (TUint32 const*)bptr;
+	//iLinks.Reset();
+	CTextBuffer* text = CTextBuffer::NewLC(1024);
+	text->SetAttributesL(KNormal);
+	
+	TInt numBytesOnLine = 16; // By default
+	if (iMemoryViewType == EStack)
+		{
+		numBytesOnLine = 4;
+		}
+	//TInt numBytesOnScreen = numBytesOnLine * consoleSize.iHeight;
+	TInt remainingInDescriptor = 0; // Not in descriptor initially
+	//TInt remainingInHeapCell = 0; // Not worrying about heap cells right now
+
+	for (TInt line = 0; line < consoleSize.iHeight - 1 && line*numBytesOnLine < iMemBuf.Length(); line++)
+		{
+		TBuf8<16> colorBuf; colorBuf.SetLength(colorBuf.MaxLength());
+		TUint8* colorBufPtr = (TUint8*)colorBuf.Ptr();
+		Mem::Fill(colorBufPtr, 16, ENoColor);
+		text->AppendFormatL(_L("%08x: "), iMemStart + line*numBytesOnLine);
+
+		const TInt idxForLine = line * numBytesOnLine;
+		TInt i;
+		for (i = 0; i < numBytesOnLine; i += 4, colorBufPtr += 4)
+			{
+			const TInt idxInBuf = idxForLine + i;
+			if (idxInBuf >= iMemBuf.Length()) break;
+
+			if (remainingInDescriptor == 0) remainingInDescriptor = IsDescriptorHeader(bptr + idxInBuf, 256);
+			TInt runLen = 0;
+			if (remainingInDescriptor > 0)
+				{
+				text->SetAttributesL(KDescriptor);
+				runLen = Min(remainingInDescriptor, numBytesOnLine-i);
+				if (runLen > 4) runLen = 4; // We only do up to 4 bytes at a time
+				Mem::Fill(colorBufPtr, runLen, EDescriptor);
+				}
+
+			// Check for symbols
+			TUint32 word = ptr[idxInBuf / 4];
+			if (IsSymbol(word))
+				{
+				text->SetAttributesL(KSymbol);
+				colorBufPtr[0] |= ESymbol;
+				colorBufPtr[1] |= ESymbol;
+				colorBufPtr[2] |= ESymbol;
+				colorBufPtr[3] |= ESymbol;
+				if (iMemoryViewType == EStack)
+					{
+					TPtrC symb = LookupSymbol(word);
+					text->AppendFormatL(_L("%08x %S"), word, &symb);
+					text->SetAttributesL(KNormal);
+					text->AppendL(KCrLf);
+					continue;
+					}
+				}
+
+			// Actually print the hex bytes
+			for (TInt ch = 0; ch < 4; ch++)
+				{
+				text->AppendFormatL(_L("%02X"), bptr[idxInBuf+ch]);
+				if (runLen)
+					{
+					remainingInDescriptor--;
+					runLen--;
+					if (remainingInDescriptor == 0)	text->SetAttributesL(KNormal); // If we've just finished a run we clear the formatting before the space
+					}
+				text->AppendL(' ');
+				}
+			if (((i+4) % 16) == 0) text->SetAttributesL(KNormal); // In preparation for printing the ascii
+			if (((i+4) % 8) == 0) text->AppendL(' '); // Extra space every 8th char
+			}
+
+		TInt rem = numBytesOnLine - i;
+		if (rem > 0)
+			{
+			// Need to fill in spaces for the hex bytes we don't have
+			_LIT(K3Space, "   ");
+			while (rem--) text->AppendL(K3Space);
+			// And the final 2 spaces before the ascii
+			text->AppendL(' ');
+			text->AppendL(' ');
+			}
+
+
+		// Time to print the ascii
+		const TInt max = Min(numBytesOnLine, iMemBuf.Length() - idxForLine);
+		for (TInt j = 0; j < max; j++)
+			{
+			char ch = bptr[idxForLine + j];
+			if (ch < 32 || ch >= 128) ch = '.';
+			text->SetAttributesL(KNormal);
+			if (colorBuf[j] & EDescriptor) text->SetAttributesL(KDescriptor);
+			if (colorBuf[j] & ESymbol) text->SetAttributesL(KSymbol);
+			text->AppendL(ch);
+			}
+		text->SetAttributesL(KNormal);
+		text->AppendL(KCrLf);
+		}
+
+	text->SetAttributesL(ConsoleAttributes::ENone);
+	text->Write(Stdout());
+	CleanupStack::PopAndDestroy(text);
+	}
+
+/*static*/ TInt CCmdFdb::IsDescriptorHeader(TUint8 const* aHeaderPtr, TInt aMaxLen)
+	{
+	if (((TLinAddr)aHeaderPtr & 0x3) != 0) return 0; // Not aligned
+	TInt type = *(TUint32*)aHeaderPtr >> 28;
+
+	if ((type == EPtr || type == EBufCPtr) && aMaxLen >= 12) return 12; // Len + maxlen + ptr
+	else if (type == EPtrC && aMaxLen >= 8) return 8; // Len + ptr
+	else if (type == EBuf || type == EBufC)
+		{
+		TInt len = (*(TInt32 const*)aHeaderPtr) & 0xfffffff;
+		if (len > aMaxLen || (type == 0 && len == 0)) return 0;
+		// Take a stab at whether it's a 16-bit descriptor
+		TInt wideness = 1;
+		TInt bufOffset = (type == EBuf ? 8 : 4);
+		TUint16 const* wptr = (TUint16 const*)(aHeaderPtr + bufOffset);
+		if (len > 4 && wptr[0] < 256 && wptr[1] < 256) wideness = 2;
+		return bufOffset + len * wideness; // Add 4 so the header itself is included in the calculation
+		}
+	else
+		{
+		return 0;
+		}
+	}
+
+void CCmdFdb::ShowBreakpointsL()
+	{
+	RBuf8 buf;
+	CleanupClosePushL(buf);
+	buf.CreateL(1024);
+	LeaveIfErr(iMemAccess.GetBreakpoints(buf), _L("Couldn't read breakpoint information"));
+	RMemoryAccess::TBreakpointInfo* bs = (RMemoryAccess::TBreakpointInfo*)buf.Ptr();
+	RLtkBuf desc;
+	desc.CreateL(256);
+	TInt count = buf.Length() / sizeof(RMemoryAccess::TBreakpointInfo);
+	if (count)
+		{
+		for (TInt i = 0; i < count; i++)
+			{
+			RMemoryAccess::TBreakpointInfo& b = bs[i];
+			Printf(_L("Breakpoint %d (thread id %u): "), b.iBreakpointId, b.iThreadId);
+			Write(LookupSymbol(b.iAddress));
+			TBool brackets = b.iFlags & (RMemoryAccess::TBreakpointInfo::EPending | b.iFlags & RMemoryAccess::TBreakpointInfo::EHardware) || !(b.iFlags & RMemoryAccess::TBreakpointInfo::EEnabled) || b.iCondition.HasConditions();
+			if (brackets) Write(_L(" ("));
+			desc.Zero();
+			if (b.iFlags & RMemoryAccess::TBreakpointInfo::EPending) desc.Append(_L("PENDING "));
+			if (!(b.iFlags & RMemoryAccess::TBreakpointInfo::EEnabled)) desc.Append(_L("DISABLED "));
+			if (b.iFlags & RMemoryAccess::TBreakpointInfo::EHardware) desc.Append(_L("HARDWARE "));
+			b.iCondition.Description(desc);
+			if (desc.Length() && desc[desc.Length()-1] == ' ') desc.SetLength(desc.Length()-1);
+			Write(desc);
+			if (brackets) Write(_L(")"));
+			Write(KCrLf);
+			}
+		}
+	else
+		{
+		Printf(_L("No breakpoints defined.\r\n"));
+		}
+	CleanupStack::PopAndDestroy(&buf);
+	}
+
+void CCmdFdb::CBreakpointNotifier::RunL()
+	{
+	if (iStatus.Int() < 0)
+		{
+		iCmd.PrintError(iStatus.Int(), _L("Error returned from NotifyBreakpoint"));
+		return;
+		}
+	
+	RMemoryAccess::TBreakpointNotification notif = iCmd.iBreakpointNotification;
+	iCmd.iMemAccess.NotifyBreakpoint(iCmd.iBreakpointNotificationPkg, iStatus);
+	SetActive();
+
+	iCmd.BreakpointHit(notif);
+	}
+
+void CCmdFdb::CBreakpointNotifier::DoCancel()
+	{
+	iCmd.iMemAccess.CancelNotifyBreakpoint();
+	}
+
+CCmdFdb::CBreakpointNotifier::CBreakpointNotifier(CCmdFdb& aCmd)
+: CActive(CActive::EPriorityStandard), iCmd(aCmd)
+	{
+	CActiveScheduler::Add(this);
+	iCmd.iMemAccess.NotifyBreakpoint(iCmd.iBreakpointNotificationPkg, iStatus);
+	SetActive();
+	}
+
+CCmdFdb::CBreakpointNotifier::~CBreakpointNotifier()
+	{
+	Cancel();
+	}
+
+void CCmdFdb::BreakpointHit(const RMemoryAccess::TBreakpointNotification& aNotif)
+	{
+	iLineEditor->RemovePromptAndUserInput();
+	Printf(_L("Breakpoint %d hit in thread %u: "), aNotif.iBreakpointId, aNotif.iThreadId);
+	Write(LookupSymbol(aNotif.iAddress));
+	Write(KCrLf);
+	if (iCurrent == NULL)
+		{
+		Printf(_L("(Attaching to thread %u)\r\n"), aNotif.iThreadId);
+		TRAP_IGNORE(AttachL(aNotif.iThreadId));
+		}
+	iLineEditor->ReinstatePromptAndUserInput();
+	}
+
+TInt CCmdFdb::BreakTest(TAny* /*aPtr*/)
+	{
+	//LtkUtils::Breakpoint();
+	LtkUtils::RawPrint(_L8("Breaktest has completed\r\n"));
+	return 5;
+	}
+
+TInt CCmdFdb::BreakTestLtk(TAny* /*aPtr*/)
+	{
+	LtkUtils::Breakpoint();
+	return 6;
+	}
+
+TInt CCmdFdb::BreakTestCond(TAny* /*aPtr*/)
+	{
+	CTrapCleanup* trap = CTrapCleanup::New();
+	TRAPD(err, User::Leave(-1)); // This shouldn't trigger the break
+	TRAP(err, User::Leave(-5)); // This should
+	delete trap;
+	return 0;
+	}
+
+void CCmdFdb::ClearAllBreakpointsL()
+	{
+	RBuf8 buf;
+	CleanupClosePushL(buf);
+	buf.CreateL(1024);
+	LeaveIfErr(iMemAccess.GetBreakpoints(buf), _L("Couldn't read breakpoint information"));
+	RMemoryAccess::TBreakpointInfo* bs = (RMemoryAccess::TBreakpointInfo*)buf.Ptr();
+	TInt count = buf.Length() / sizeof(RMemoryAccess::TBreakpointInfo);
+	for (TInt i = 0; i < count; i++)
+		{
+		RMemoryAccess::TBreakpointInfo& b = bs[i];
+		TInt err = iMemAccess.ClearBreakpoint(b.iBreakpointId);
+		if (err) PrintWarning(_L("Couldn't clear breakpoint %d, err=%d"), b.iBreakpointId, err);
+		}
+	CleanupStack::PopAndDestroy(&buf);
+	}
+
+void CCmdFdb::CheckForConditionL(TLex& aLex, RMemoryAccess::TPredicate& aCondition)
+	{
+	aLex.SkipSpace();
+	if (aLex.Eos()) return;
+	_LIT(KRegErr, "First argument in a conditional must be a register r0-r15");
+	if (aLex.Get() != 'r') LeaveIfErr(KErrArgument, KRegErr);
+	TInt reg;
+	LeaveIfErr(aLex.Val(reg), KRegErr);
+	if (reg < 0 || reg > 15) LeaveIfErr(KErrArgument, KRegErr);
+	RMemoryAccess::TPredicate::TOp op = RMemoryAccess::TPredicate::ENothing;
+	TUint opchar = aLex.Get();
+	switch (opchar)
+		{
+		case '<':
+			op = RMemoryAccess::TPredicate::ELt;
+			if (aLex.Peek() == '=')
+				{
+				op = RMemoryAccess::TPredicate::ELe;
+				aLex.Get();
+				}
+			break;
+		case '>':
+			op = RMemoryAccess::TPredicate::EGt;
+			if (aLex.Peek() == '=')
+				{
+				op = RMemoryAccess::TPredicate::EGe;
+				aLex.Get();
+				}
+			break;
+		case '!':
+			if (aLex.Get() != '=') LeaveIfErr(KErrArgument, _L("Unrecognised operand"));
+			op = RMemoryAccess::TPredicate::ENe;
+			break;
+		case '=':
+			op = RMemoryAccess::TPredicate::EEq;
+			if (aLex.Peek() == '=') aLex.Get(); // We allow == as well as =
+			break;
+		default:
+			LeaveIfErr(KErrArgument, _L("Unrecognised operand"));
+			break;
+		}
+	TInt val;
+	TInt err = LtkUtils::HexLex(aLex, (TUint&)val);
+	if (err)
+		{
+		// Try normal - this handles signed negative numbers, which HexLex doesn't afaik
+		err = aLex.Val(val);
+		}
+	LeaveIfErr(err, _L("Couldn't parse value"));
+	TBool signedCompare = ETrue;
+	if (aLex.Peek() == 'u' || aLex.Peek() == 'U')
+		{
+		aLex.Get();
+		signedCompare = EFalse;
+		}
+	if (signedCompare)
+		{
+		op = (RMemoryAccess::TPredicate::TOp)(op + 6); // ELt -> ESignedLt etc
+		//Printf(_L("Op=%d reg=%d val=%d\r\n"), op, reg, val);
+		}
+	//else Printf(_L("Op=%d reg=%d val=%uu\r\n"), op, reg, (TUint)val);
+	if (aLex.Peek() == ',')
+		{
+		// Eat comma
+		aLex.Get();
+		}
+	LeaveIfErr(aCondition.AddCondition(op, reg, (TUint32)val), _L("Couldn't add condition to TPredicate"));
+	}