diff -r 000000000000 -r a41df078684a kerneltest/e32utils/d_exc/minkda.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kerneltest/e32utils/d_exc/minkda.cpp Mon Oct 19 15:55:17 2009 +0100 @@ -0,0 +1,641 @@ +// Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of the License "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: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// e32utils\d_exc\minkda.cpp +// Example of LDD implementing a minimal kernel-side debug agent. +// +// + +#include +#ifdef __MARM__ +#include +#endif +#include "minkda.h" + +// Uncomment following lines to enable traces in UREL builds +//#undef __KTRACE_OPT +//#define __KTRACE_OPT(c,b) b + +#ifdef _DEBUG +// Panic category used for internal errors +static const char KFault[] = "MINKDA-ERROR, line:"; +#endif + +// Panic category and codes used when detecting programming error in +// user-side clients. +_LIT(KClientPanic, "MINKDA"); +enum TPanic + { + EPanicTrapWhileRequestPending, + EPanicNoCrashedThread, + EPanicUnsupportedRequest, + }; + +// As this LDD allows to bypass platform security, we need to restrict +// access to a few trusted clients. +const TUint32 KDexecSid = 0x101F7770; + +////////////////////////////////////////////////////////////////////////////// + +// +// Callback invoked on thread panic/exception and associated state. +// + +class DCrashHandler : public DKernelEventHandler + { +public: + // construction & destruction + inline DCrashHandler(); + TInt Create(DLogicalDevice* aDevice); + ~DCrashHandler(); +public: + void Trap(TRequestStatus* aRs, TAny* aCrashInfo); + void CancelTrap(); + void KillCrashedThread(); +private: + static TUint EventHandler(TKernelEvent aEvent, TAny* a1, TAny* a2, TAny* aThis); + void HandleCrash(TAny* aContext); + void GetCpuExcInfo(const TAny* aContext, TDbgCpuExcInfo& aInfo); +private: + DMutex* iHandlerMutex; // serialise access to crash handler + NFastSemaphore iSuspendSem; // for suspending crashed thread + DMutex* iDataMutex; // serialise access to following members + DThread* iClient; // client to signal on crash or NULL + TRequestStatus* iTrapRq; // signalled on crash (NULL if none) + TAny* iCrashInfo; // user-side buffer filled when crash trapped + DThread* iCrashedThread; // thread which took exception (NULL if none) + DLogicalDevice* iDevice; // open reference to LDD for avoiding lifetime issues + }; + +inline DCrashHandler::DCrashHandler() + : DKernelEventHandler(EventHandler, this) + { + } + +// +// second-phase c'tor. Called in thread critical section. +// + +TInt DCrashHandler::Create(DLogicalDevice* aDevice) + { + TInt r; + r = aDevice->Open(); + if (r != KErrNone) + return r; + iDevice = aDevice; + _LIT(KHandlerMutexName, "CtHandlerMutex"); + r = Kern::MutexCreate(iHandlerMutex, KHandlerMutexName, KMutexOrdDebug); + if (r != KErrNone) + return r; + _LIT(KDataMutexName, "CtDataMutex"); + r = Kern::MutexCreate(iDataMutex, KDataMutexName, KMutexOrdDebug-1); + if (r != KErrNone) + return r; + return Add(); + } + + +// +// Called when reference count reaches zero. At that point no threads +// are in the handler anymore and the handler has been removed from +// the queue. +// + +DCrashHandler::~DCrashHandler() + { + __KTRACE_OPT(KDEBUGGER, Kern::Printf("DCrashHandler::~DCrashHandler")); + if (iDataMutex) + iDataMutex->Close(NULL); + if (iHandlerMutex) + iHandlerMutex->Close(NULL); + if (iDevice) + iDevice->Close(NULL); + } + +inline TBool TookException(const DThread* aThread) + { + return aThread->iExitType == EExitPanic && + aThread->iExitReason == ECausedException && + aThread->iExitCategory == KLitKernExec; + } + +// +// Called by kernel when various kinds of events occur. In thread critical +// section. +// + +TUint DCrashHandler::EventHandler(TKernelEvent aEvent, TAny* a1, TAny* /*a2*/, TAny* aThis) + { + DThread* pC = &Kern::CurrentThread(); + switch (aEvent) + { + case EEventHwExc: + ((DCrashHandler*)aThis)->HandleCrash(a1); + break; + case EEventKillThread: + if (pC->iExitType == EExitPanic && ! TookException(pC)) + ((DCrashHandler*)aThis)->HandleCrash(NULL); + break; + default: + // ignore other events + break; + } + return ERunNext; + } + +// +// Called when an exception or panic occurs in context of thread which +// took the exception/panicked. In thread critical section. +// + +void DCrashHandler::HandleCrash(TAny* aContext) + { + DThread* pC = &Kern::CurrentThread(); + __KTRACE_OPT(KDEBUGGER, Kern::Printf("HandleCrash context=0x%08X thread=%O", aContext, pC)); + + // Quick exit if crashed thread is debugger (i.e. client thread + // which issued trap request). + if (pC == iClient) + { + __KTRACE_OPT(KDEBUGGER, Kern::Printf("ignoring debugger crash")); + return; + } + + // Set realtime state to off to allow us to write to possibly paged debugger thread. This is + // reasonable as this thread has already crashed. + Kern::SetRealtimeState(ERealtimeStateOff); + + // Ensure that, at any time, at most one thread executes the following + // code. This simplifies user-side API. + Kern::MutexWait(*iHandlerMutex); + __ASSERT_DEBUG(iCrashedThread == NULL, Kern::Fault(KFault, __LINE__)); + + // If there is a pending trap request, store basic information + // about the panic/exception in user-supplied buffer and + // freeze the crashed thread so it can be inspected. + + Kern::MutexWait(*iDataMutex); + if (iTrapRq != NULL) + { + iCrashedThread = pC; + iSuspendSem.iOwningThread = &(iCrashedThread->iNThread); + + TDbgCrashInfo info; + info.iTid = iCrashedThread->iId; + if (aContext) + { + GetCpuExcInfo(aContext, info.iCpu); + info.iType = TDbgCrashInfo::EException; + } + else + info.iType = TDbgCrashInfo::EPanic; + TInt r = Kern::ThreadRawWrite(iClient, iCrashInfo, &info, sizeof(info)); + Kern::RequestComplete(iClient, iTrapRq, r); + iClient = NULL; + } + Kern::MutexSignal(*iDataMutex); + + if (iCrashedThread) + { + __KTRACE_OPT(KDEBUGGER, Kern::Printf("freezing crashed thread")); + NKern::FSWait(&(iSuspendSem)); + __KTRACE_OPT(KDEBUGGER, Kern::Printf("resuming crashed thread")); + Kern::MutexWait(*iDataMutex); + // Must protect in case a cancel executes concurrently. + iCrashedThread = NULL; + Kern::MutexSignal(*iDataMutex); + } + + Kern::MutexSignal(*iHandlerMutex); + } + + +void DCrashHandler::Trap(TRequestStatus* aRs, TAny* aCrashInfo) + { + if (iTrapRq != NULL) + Kern::PanicCurrentThread(KClientPanic, EPanicTrapWhileRequestPending); + NKern::ThreadEnterCS(); + Kern::MutexWait(*iDataMutex); + iClient = &Kern::CurrentThread(); + iCrashInfo = aCrashInfo; + iTrapRq = aRs; + Kern::MutexSignal(*iDataMutex); + NKern::ThreadLeaveCS(); + } + + +void DCrashHandler::CancelTrap() + { + __KTRACE_OPT(KDEBUGGER, Kern::Printf(">DCrashHandler::CancelTrap")); + NKern::ThreadEnterCS(); + Kern::MutexWait(*iDataMutex); + + __KTRACE_OPT(KDEBUGGER, Kern::Printf("cancel request (0x%08X)", iTrapRq)); + Kern::RequestComplete(iClient, iTrapRq, KErrCancel); + iClient = NULL; + + if (iCrashedThread != NULL) + { + __KTRACE_OPT(KDEBUGGER, Kern::Printf("resume crashed thread")); + NKern::FSSignal(&(iSuspendSem)); + } + + Kern::MutexSignal(*iDataMutex); + NKern::ThreadLeaveCS(); + __KTRACE_OPT(KDEBUGGER, Kern::Printf("iR15; + aInfo.iFaultAddress = pE->iFaultAddress; + aInfo.iFaultStatus = pE->iFaultStatus; + aInfo.iExcCode = (TDbgCpuExcInfo::TExcCode)pE->iExcCode; + aInfo.iR13Svc = pE->iR13Svc; + aInfo.iR14Svc = pE->iR14Svc; + aInfo.iSpsrSvc = pE->iSpsrSvc; +#else + (void) aContext; // silence warnings + (void) aInfo; +#endif + } + +////////////////////////////////////////////////////////////////////////////// + +// +// Channel initialisation and cleanup. Dispatcher for user-side +// requests. Crash-related requests are forwarded to DCrashHandler, +// others are implemented here. +// + +class DKdaChannel : public DLogicalChannelBase + { +public: + ~DKdaChannel(); +protected: + // from DLogicalChannelBase + virtual TInt DoCreate(TInt aUnit, const TDesC8* anInfo, const TVersion &aVer); + virtual TInt Request(TInt aFunction, TAny* a1, TAny* a2); +private: + TInt ReadMem(RMinKda::TReadMemParams* aParams); + TInt GetThreadInfo(TUint aTid, TAny* aInfo); + void GetThreadCpuInfo(DThread* aThread, TDbgRegSet& aInfo); + TInt GetCodeSegs(RMinKda::TCodeSnapshotParams* aParams); + TInt GetCodeSegInfo(RMinKda::TCodeInfoParams* aParams); + TInt OpenTempObject(TUint aId, TObjectType aType); + void CloseTempObject(); +private: + DCrashHandler* iCrashHandler; + DObject* iTempObj; // automagically closed if abnormal termination + }; + + +// +// Called when user-side thread create new channel with LDD. Called +// in context of that thread, in thread critical section. +// + +TInt DKdaChannel::DoCreate(TInt /*aUnit*/, const TDesC8* /*aInfo*/, const TVersion &aVer) + { + if (Kern::CurrentThread().iOwningProcess->iS.iSecureId != KDexecSid) + return KErrPermissionDenied; + if (! Kern::QueryVersionSupported(KKdaLddVersion(), aVer)) + return KErrNotSupported; + + iCrashHandler = new DCrashHandler; + if (iCrashHandler == NULL) + return KErrNoMemory; + return iCrashHandler->Create(iDevice); + } + + +// +// Called when last reference to channel is closed, in context of +// closing thread, in thread critical section. +// + +DKdaChannel::~DKdaChannel() + { + __KTRACE_OPT(KDEBUGGER, Kern::Printf("DKdaChannel::~DKdaChannel")); + Kern::SafeClose(iTempObj, NULL); + if (iCrashHandler) + { + iCrashHandler->CancelTrap(); + iCrashHandler->Close(); + } + } + + +// +// Request dispatcher. Called in context of requesting thread. +// + +TInt DKdaChannel::Request(TInt aFunction, TAny* a1, TAny* a2) + { + __KTRACE_OPT(KDEBUGGER, Kern::Printf(">DKdaChannel::Request function=%d a1=0x%08X a2=0x%08X", aFunction, a1, a2)); + + TInt r = KErrNone; + switch (aFunction) + { + case RMinKda::ETrap: + iCrashHandler->Trap((TRequestStatus*)a1, a2); + break; + case RMinKda::ECancelTrap: + iCrashHandler->CancelTrap(); + break; + case RMinKda::EKillCrashedThread: + iCrashHandler->KillCrashedThread(); + break; + case RMinKda::EGetThreadInfo: + r = GetThreadInfo((TUint)a1, a2); + break; + case RMinKda::EReadMem: + r = ReadMem((RMinKda::TReadMemParams*)a1); + break; + case RMinKda::EGetCodeSegs: + r = GetCodeSegs((RMinKda::TCodeSnapshotParams*)a1); + break; + case RMinKda::EGetCodeSegInfo: + r = GetCodeSegInfo((RMinKda::TCodeInfoParams*)a1); + break; + default: + Kern::PanicCurrentThread(KClientPanic, EPanicUnsupportedRequest); + break; + } + + __KTRACE_OPT(KDEBUGGER, Kern::Printf("FullName(info.iFullName); + info.iPid = pT->iOwningProcess->iId; + info.iStackBase = pT->iUserStackRunAddress; + info.iStackSize = pT->iUserStackSize; + info.iExitCategory = pT->iExitCategory; + info.iExitReason = pT->iExitReason; + GetThreadCpuInfo(pT, info.iCpu); + umemput32(aInfo, &info, sizeof(info)); + CloseTempObject(); + } + return r; + } + +// :FIXME: improve API +TInt DKdaChannel::GetCodeSegs(RMinKda::TCodeSnapshotParams* aParams) + { + RMinKda::TCodeSnapshotParams params; + umemget32(¶ms, aParams, sizeof(params)); + + TInt maxcount; + umemget32(&maxcount, params.iCountPtr, sizeof(maxcount)); + + __KTRACE_OPT(KDEBUGGER, Kern::Printf(">DKdaChannel::GetCodeSegs pid=%d maxcount=%d", params.iPid, maxcount)); + + __ASSERT_DEBUG(! iTempObj, Kern::Fault(KFault, __LINE__)); + TInt r = OpenTempObject(params.iPid, EProcess); + if (r != KErrNone) + { + __KTRACE_OPT(KDEBUGGER, Kern::Printf("TraverseCodeSegs(&q, NULL, DCodeSeg::EMarkDebug, DProcess::ETraverseFlagAdd); + + CloseTempObject(); + + TInt n = Min(actcount, maxcount); + SDblQueLink* pL = q.iA.iNext; + r = KErrNone; + for (TInt i=0; iiNext) + { + DCodeSeg* pS = _LOFF(pL, DCodeSeg, iTempLink); + XTRAP(r, XT_DEFAULT, umemput32(params.iHandles + i, &pS, sizeof(TAny*))); + if (r != KErrNone) + break; + } + + DCodeSeg::EmptyQueue(q, DCodeSeg::EMarkDebug); + + Kern::EndAccessCode(); + + if (r == KErrBadDescriptor) + Kern::PanicCurrentThread(KLitKernExec, ECausedException); + umemput32(params.iCountPtr, &actcount, sizeof(actcount)); + + __KTRACE_OPT(KDEBUGGER, Kern::Printf("GetMemoryInfo(mmi, pP); + if (r == KErrNone) + { + params.iCodeBase = mmi.iCodeBase; + params.iCodeSize = mmi.iCodeSize; + XTRAP(r, XT_DEFAULT, nameBuffer.Append(*(pS->iFileName))); + } + } + Kern::EndAccessCode(); + Kern::KUDesPut(*(params.iPathPtr), nameBuffer); + if (r == KErrBadDescriptor) + Kern::PanicCurrentThread(KLitKernExec, ECausedException); + + if (r == KErrNone) + umemput32(aParams, ¶ms, sizeof(params)); + + return r; + } + +// +// Lookup a thread or process id and open the corresponding object. +// +// The object is stored in DKdaChannel::iTempObj to ensure it will be +// closed even if the client thread terminates unexpectedly. The +// caller must call CloseTempObject() when it is finished with it. +// + +TInt DKdaChannel::OpenTempObject(TUint aId, TObjectType aType) + { + __ASSERT_DEBUG(aType == EProcess || aType == EThread, Kern::Fault(KFault, __LINE__)); + __ASSERT_DEBUG(! iTempObj, Kern::Fault(KFault, __LINE__)); + + DObjectCon* pC = Kern::Containers()[aType]; + NKern::ThreadEnterCS(); + pC->Wait(); + DObject* tempObj = (aType == EProcess) ? (DObject*)Kern::ProcessFromId(aId) : (DObject*)Kern::ThreadFromId(aId); + NKern::LockSystem(); + iTempObj = tempObj; + TInt r = KErrNotFound; + if (iTempObj) + r = iTempObj->Open(); + + NKern::UnlockSystem(); + pC->Signal(); + NKern::ThreadLeaveCS(); + return r; + } + +void DKdaChannel::CloseTempObject() + { + __ASSERT_DEBUG(iTempObj, Kern::Fault(KFault, __LINE__)); + NKern::ThreadEnterCS(); + iTempObj->Close(NULL); + iTempObj = NULL; + NKern::ThreadLeaveCS(); + } + +#ifdef __MARM__ + +void DKdaChannel::GetThreadCpuInfo(DThread* aThread, TDbgRegSet& aInfo) + { + __ASSERT_DEBUG(aThread != &Kern::CurrentThread(), Kern::Fault(KFault, __LINE__)); + + TArmRegSet regSet; + TUint32 unused; + NKern::ThreadGetUserContext(&(aThread->iNThread), ®Set, unused); + aInfo.iRn[0] = regSet.iR0; + aInfo.iRn[1] = regSet.iR1; + aInfo.iRn[2] = regSet.iR2; + aInfo.iRn[3] = regSet.iR3; + aInfo.iRn[4] = regSet.iR4; + aInfo.iRn[5] = regSet.iR5; + aInfo.iRn[6] = regSet.iR6; + aInfo.iRn[7] = regSet.iR7; + aInfo.iRn[8] = regSet.iR8; + aInfo.iRn[9] = regSet.iR9; + aInfo.iRn[10] = regSet.iR10; + aInfo.iRn[11] = regSet.iR11; + aInfo.iRn[12] = regSet.iR12; + aInfo.iRn[13] = regSet.iR13; + aInfo.iRn[14] = regSet.iR14; + aInfo.iRn[15] = regSet.iR15; + aInfo.iCpsr = regSet.iFlags; + } + +#else + +void DKdaChannel::GetThreadCpuInfo(DThread* /*aThread*/, TDbgRegSet& /*aInfo*/) + { + } + +#endif + + +////////////////////////////////////////////////////////////////////////////// + +class DCtDevice : public DLogicalDevice + { +public: + DCtDevice(); + // from DLogicalDevice + virtual TInt Install(); + virtual void GetCaps(TDes8& aDes) const; + virtual TInt Create(DLogicalChannelBase*& aChannel); + }; + +DCtDevice::DCtDevice() + { + // iParseMask = 0; + // iUnitsMask = 0; + iVersion = KKdaLddVersion(); + } + +TInt DCtDevice::Install() + { + return SetName(&KKdaLddName); + } + +void DCtDevice::GetCaps(TDes8& /*aDes*/) const + { + } + +TInt DCtDevice::Create(DLogicalChannelBase*& aChannel) + { + aChannel = new DKdaChannel; + return (aChannel != NULL) ? KErrNone : KErrNoMemory; + } + +////////////////////////////////////////////////////////////////////////////// + +DECLARE_STANDARD_LDD() + { + return new DCtDevice; + } +