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