commands/fdb/fdb.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Wed, 23 Jun 2010 15:52:26 +0100
changeset 0 7f656887cf89
permissions -rw-r--r--
First submission to Symbian Foundation staging server.

// 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"));
	}