--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/kerneltest/e32utils/d_exc/d_exc.cpp Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,758 @@
+// Copyright (c) 2008-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\d_exc.cpp
+// Trap and log user-side exceptions and panics.
+// USAGE:
+// d_exc
+// Trap panics and exceptions forever. Prompt whether to log.
+// Logs go on C: drive.
+// d_exc [-m] [-nN] [-pN] [-b] [-d log_path]
+// -m minimal logging (no stack dump)
+// -nN stop after N exceptions/panics
+// -pN log to serial port N instead of C: drive
+// -b do not prompt; always log
+// -d specify the path for log files. If not given, logs are
+// written to the root of the system drive. If just a path
+// name is given, logs are written to that directory (must
+// start with a \) on the system drive.
+//
+//
+
+#include <e32std.h>
+#include <e32std_private.h>
+#include <e32svr.h>
+#include <d32comm.h>
+#include <f32file.h>
+#include "minkda.h"
+
+RNotifier Notifier; // The "UI"
+RMinKda Trapper;
+RFs FileSession;
+TBuf16<KMaxFileName> LogPath; // to specify log file location
+
+// Possible outputs where crash information can be dumped
+enum TOutputType{ EFile, ESerial };
+
+// Variables shared between DumpLine() and the various functions used
+// to format crash info.
+TOutputType ActiveOutput = EFile;
+TBool IoError; // ETrue after I/O error
+RBusDevComm CommPort; // Handle to serial port used
+RFile File; // Handle to text file used
+
+// Maximum length in characters of a line in the file containing
+// textual information about the crash.
+const TInt KMaxLineLength = KMaxFullName + 32;
+
+class TLexNew : public TLex16
+ {
+public:
+ inline TLexNew(const TDesC16& aDes) {Assign(aDes);}
+ TInt ExtractParameter(TDes16 &aParam);
+ };
+
+TInt TLexNew::ExtractParameter(TDes16 &aParam)
+ {
+ TBuf16<512> token;
+ TBuf16<512> param;
+
+ TBool GetNext = EFalse;
+
+ //exit..if it's empty (empty option at the end of command)
+ if (!Peek())
+ return KErrArgument;
+
+ // remove any space between option and the rest of param..
+ SkipSpace();
+
+ // just see, what's next..
+ // if there this a param with spaces- should be in "quotes"
+ if (Peek() == '"')
+ {
+ GetNext = ETrue;
+ Inc(); // skip this quote " and move to next position..
+ }
+
+ // remove spaces after quotes (" param...")
+ SkipSpace();
+
+ // ..mark next character position as a start of our token
+ Mark();
+
+ // move until the end of our token (next space)..
+ SkipCharacters();
+
+ //and get it!!
+ token.Copy(MarkedToken());
+
+ // if.. there was one-word param.. with quotes..shrink it..and don't try to search next one..
+ if (*(token.MidTPtr(token.Length()-1).Ptr()) == '"')
+ {
+ // just shrink it by that ending quote..
+ token.SetLength(token.Length()-1);
+ GetNext=EFalse;
+ }
+
+ // This is at least beginning of our param.. let's use it!
+ // add this to beginning of our param..
+ param.Append(token);
+
+ // if this was param specified in quotes..search for the ending quote..
+ while (GetNext)
+ {
+ // Next is space..
+ SkipSpace();
+
+ // before taking next one..check it - if '-' on the beginning..
+ // it's either next param specifier..(no ending quote at all)
+ if (Peek() == '-')
+ return KErrArgument;
+
+ // get the next one..
+ token.Copy(NextToken());
+
+ // was there any token more? ..if not- we're at the end..
+ // so the ending quote still wasn't found...
+ if (!token.Length())
+ return KErrArgument;
+
+ // is this the last one - with quote" at the end?
+ if (*(token.MidTPtr(token.Length()-1).Ptr()) == '"')
+ {
+ // just shrink it by that ending quote..
+ token.SetLength(token.Length()-1);
+ GetNext=EFalse;
+ }
+
+ param.Append(_L(" ")); // there was space in orig. param..restore it..
+ param.Append(token); // and append this token to our param..
+ }
+
+ // if there was any space at the end..(e.g. if specified: -d"c:\logs ")
+ // - remove it
+ param.TrimRight();
+
+ //finally - copy param to the referenced descriptor
+ aParam.Copy(param);
+
+ return KErrNone;
+ }
+
+TInt ValidatePath(TDes16 &aLogPath)
+ {
+
+ // check the length first.. (20 chars for file name..)
+ if (aLogPath.Length() >(KMaxFileName - 20))
+ {
+ Notifier.InfoPrint(_L("directory name too long.."));
+ return KErrArgument;
+ }
+
+ // if it hasn't drive letter (colon wasn't second..)
+ if (*(aLogPath.MidTPtr(1).Ptr()) != ':')
+ {
+ // if it starts with "\" use system drive..
+ if (*(aLogPath.MidTPtr(0).Ptr()) == '\\')
+ {
+ // if someone specified param like: "\ path\" ...obviously..
+ if (*(aLogPath.MidTPtr(1).Ptr()) == ' ')
+ return KErrArgument;
+
+ TBuf16<2> drive;
+ drive.Append(RFs::GetSystemDriveChar());
+ drive.LowerCase();
+ drive.Append(_L(":"));
+ aLogPath.Insert(0, drive);
+ }
+ else //otherwise -path not valid..
+ {
+ return KErrArgument;
+ }
+ }
+
+ // and add backslash if needed
+ if (*(aLogPath.MidTPtr(aLogPath.Length()-1).Ptr()) != '\\')
+ aLogPath.Append(_L("\\"));
+
+ //open file session..
+ if (FileSession.Connect() != KErrNone)
+ return KErrGeneral;
+
+ RDir dir;
+ TInt err=KErrNone;
+ if (dir.Open(FileSession, aLogPath, KEntryAttMatchExclusive) != KErrNone)
+ {
+ Notifier.InfoPrint(_L("specified directory doesn't exist"));
+ LogPath.Zero(); //clear global path..
+ err = KErrArgument;
+ }
+ else
+ {
+ dir.Close();
+ }
+
+ // close file session..
+ FileSession.Close();
+
+ return err;
+ }
+
+
+// Open specified serial port and push handle on the cleanup stack.
+
+void OpenCommPortLC(TInt aPortNum)
+ {
+#ifdef __WINS__
+ _LIT(KPdd, "ECDRV");
+#else
+ _LIT(KPdd, "EUART");
+#endif
+ _LIT(KLdd, "ECOMM");
+ _LIT(KErrPdd, "Failed to load serial PDD");
+ _LIT(KErrLdd, "Failed to load serial LDD");
+ _LIT(KErrOpen, "Failed to open comm port");
+ _LIT(KErrCfg, "Failed to configure comm port");
+
+ TInt r = User::LoadPhysicalDevice(KPdd);
+ if (r != KErrNone && r != KErrAlreadyExists)
+ {
+ Notifier.InfoPrint(KErrPdd);
+ User::Leave(r);
+ }
+
+ r = User::LoadLogicalDevice(KLdd);
+ if (r != KErrNone && r != KErrAlreadyExists)
+ {
+ Notifier.InfoPrint(KErrLdd);
+ User::Leave(r);
+ }
+
+ r = CommPort.Open(aPortNum);
+ if (r != KErrNone)
+ {
+ Notifier.InfoPrint(KErrOpen);
+ User::Leave(r);
+ }
+ CleanupClosePushL(CommPort);
+
+ TCommConfig cfgBuf;
+ TCommConfigV01& cfg=cfgBuf();
+ CommPort.Config(cfgBuf);
+ cfg.iRate=EBps115200;
+ cfg.iDataBits=EData8;
+ cfg.iStopBits=EStop1;
+ cfg.iParity=EParityNone;
+ cfg.iHandshake=KConfigObeyXoff|KConfigSendXoff;
+ cfg.iFifo=EFifoEnable;
+ cfg.iTerminatorCount=0;
+ cfg.iSIREnable=ESIRDisable;
+ r = CommPort.SetConfig(cfgBuf);
+ if (r != KErrNone)
+ {
+ Notifier.InfoPrint(KErrCfg);
+ User::Leave(r);
+ }
+ }
+
+
+void ParseCmdLineL(TInt& aPortNum, TInt& aMaxTrapCount, TBool& aInteractive, TBool& aDumpStack)
+ {
+ _LIT(KInvalidArg, "Invalid command-line");
+
+ HBufC* cl = HBufC::NewLC(User::CommandLineLength());
+ TPtr clp = cl->Des();
+ User::CommandLine(clp);
+
+ // If started from UIKON shell, ignore command-line and use defaults
+ if (clp.Match(_L("?:\\*")) == 0)
+ return;
+
+ TLexNew lex(*cl);
+
+ while (! lex.Eos())
+ {
+ TInt r = KErrArgument;
+ if (lex.Get() == '-')
+ {
+ switch (lex.Get())
+ {
+ case 'n':
+ r = lex.Val(aMaxTrapCount);
+ break;
+ case 'p':
+ r = lex.Val(aPortNum);
+ if (r == KErrNone)
+ ActiveOutput = ESerial;
+ break;
+ case 'b':
+ aInteractive = EFalse;
+ r = KErrNone;
+ break;
+ case 'm':
+ aDumpStack = EFalse;
+ r = KErrNone;
+ break;
+ case 'd':
+ //try to extract path and store it in global buffer
+ r = lex.ExtractParameter(LogPath);
+ // check, if specified path is valid
+ if (r == KErrNone)
+ r = ValidatePath(LogPath);
+ break;
+ }
+ }
+ if (r != KErrNone)
+ {
+ Notifier.InfoPrint(KInvalidArg);
+ User::Leave(KErrArgument);
+ }
+ lex.SkipSpace();
+ }
+
+ CleanupStack::PopAndDestroy(cl);
+ }
+
+
+// Dump specified line + CRLF on the selected output. Set IoError to
+// ETrue if an error occurs.
+
+void DumpLine(TDes8& aLine)
+ {
+ TInt r;
+ _LIT8(KCrLf, "\r\n");
+ aLine.Append(KCrLf);
+ if (ActiveOutput == ESerial)
+ {
+ TRequestStatus s;
+ CommPort.Write(s, aLine);
+ User::WaitForRequest(s);
+ r = s.Int();
+ }
+ else
+ r = File.Write(aLine);
+ if (r != KErrNone)
+ IoError = ETrue;
+ }
+
+
+void DumpExcInfo(const TDbgCpuExcInfo& aInfo, TDes8& aLine)
+ {
+ _LIT8(KHdr, "\r\nUNHANDLED EXCEPTION:");
+ aLine = KHdr;
+ DumpLine(aLine);
+#ifdef __MARM__
+ _LIT8(KFmt1, "code=%d PC=%08x FAR=%08x FSR=%08x");
+ aLine.Format(KFmt1, aInfo.iExcCode, aInfo.iFaultPc, aInfo.iFaultAddress, aInfo.iFaultStatus);
+ DumpLine(aLine);
+ _LIT8(KFmt2, "R13svc=%08x R14svc=%08x SPSRsvc=%08x");
+ aLine.Format(KFmt2, aInfo.iR13Svc, aInfo.iR14Svc, aInfo.iSpsrSvc);
+ DumpLine(aLine);
+#else
+ (void) aInfo; // silence warning
+#endif
+ }
+
+
+void DumpRegisters(const TDbgRegSet& aRegs, TDes8& aLine)
+ {
+#if defined(__MARM__)
+ _LIT8(KHdr, "\r\nUSER REGISTERS:");
+ aLine = KHdr;
+ DumpLine(aLine);
+ _LIT8(KFmtCpsr, "CPSR=%08x");
+ aLine.Format(KFmtCpsr, aRegs.iCpsr);
+ DumpLine(aLine);
+ for (TInt i=0; i<TDbgRegSet::KRegCount; i+=4)
+ {
+ _LIT8(KFmtReg, "r%02d=%08x %08x %08x %08x");
+ aLine.Format(KFmtReg, i, aRegs.iRn[i], aRegs.iRn[i+1], aRegs.iRn[i+2], aRegs.iRn[i+3]);
+ DumpLine(aLine);
+ }
+#else
+ (void) aRegs; // silence warnings
+ (void) aLine;
+#endif
+ }
+
+
+void DumpCodeSegs(TUint aPid, TDes8& aLine)
+ {
+ _LIT(KPanicCodeMods, "DEXC-CODEMOD");
+ _LIT8(KHdr, "\r\nCODE SEGMENTS:");
+ _LIT8(KFmtOverflow, "Only first %d code modules displayed");
+ _LIT8(KFmtMod, "%08X-%08X %S");
+
+ aLine = KHdr;
+ DumpLine(aLine);
+
+ // :FIXME: improve API
+ // :FIXME: suspend/resume all threads in process
+ const TInt KMaxCount = 128;
+ TAny* handles[KMaxCount];
+ TInt c = KMaxCount;
+
+ TInt r = Trapper.GetCodeSegs(aPid, handles, c);
+ __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicCodeMods, r));
+
+ if (c > KMaxCount)
+ {
+ aLine.Format(KFmtOverflow, c);
+ DumpLine(aLine);
+ c = KMaxCount;
+ }
+
+ for (TInt i=0; i<c; i++)
+ {
+ TDbgCodeSegInfo info;
+ r = Trapper.GetCodeSegInfo(handles[i], aPid, info);
+ if (r == KErrNone)
+ {
+ TBuf8<KMaxFileName> path;
+ path.Copy(info.iPath);
+ aLine.Format(KFmtMod, info.iCodeBase, info.iCodeBase+info.iCodeSize, &path);
+ DumpLine(aLine);
+ }
+ }
+ }
+
+
+void DumpTextInfo(const TDbgCrashInfo& aCrashInfo, const TDbgThreadInfo& aThreadInfo)
+ {
+ _LIT(KFmtTextFile, "d_exc_%d.txt");
+ _LIT(KErrTextOpen, "text file open error");
+ _LIT(KErrTextWrite, "text file write error");
+
+ if (ActiveOutput == EFile)
+ {
+ TBuf16<KMaxFileName> name;
+ name.Format(KFmtTextFile, aCrashInfo.iTid);
+
+ // if -d param wasn't specified, use default location..(root dir on system drive)
+ if(!LogPath.Length())
+ {
+ LogPath.Append(RFs::GetSystemDriveChar());
+ LogPath.LowerCase();
+ LogPath.Append(_L(":\\"));
+ }
+
+ TBuf16<KMaxFileName> filename;
+ filename.Copy(LogPath);
+ filename.Append(name);
+
+ TInt r = File.Replace(FileSession, filename, EFileWrite+EFileShareAny+EFileStream);
+ if (r != KErrNone)
+ {
+ Notifier.InfoPrint(KErrTextOpen);
+ return;
+ }
+ }
+
+ IoError = EFalse;
+
+ // Note that following buffer is passed to callee functions and
+ // reuse to minimise stack footprint.
+ TBuf8<KMaxLineLength> line;
+
+ line.Fill('-', 76);
+ DumpLine(line);
+ _LIT8(KHdr, "EKA2 USER CRASH LOG");
+ line = KHdr;
+ DumpLine(line);
+ line.Copy(aThreadInfo.iFullName);
+ _LIT8(KName, "Thread Name: ");
+ line.Insert(0, KName);
+ DumpLine(line);
+ _LIT8(KFmtTid, "Thread ID: %u");
+ line.Format(KFmtTid, aCrashInfo.iTid);
+ DumpLine(line);
+ _LIT8(KFmtStack, "User Stack %08X-%08X");
+ line.Format(KFmtStack, aThreadInfo.iStackBase,
+ aThreadInfo.iStackBase+aThreadInfo.iStackSize);
+ DumpLine(line);
+
+ if (aCrashInfo.iType == TDbgCrashInfo::EPanic)
+ {
+ TBuf8<KMaxExitCategoryName> cat;
+ cat.Copy(aThreadInfo.iExitCategory);
+ _LIT8(KFmtPanic, "Panic: %S-%d");
+ line.Format(KFmtPanic, &cat, aThreadInfo.iExitReason);
+ DumpLine(line);
+ }
+ else
+ DumpExcInfo(aCrashInfo.iCpu, line);
+
+ DumpRegisters(aThreadInfo.iCpu, line);
+ DumpCodeSegs(aThreadInfo.iPid, line);
+
+ line.Zero();
+ DumpLine(line);
+
+ if (IoError)
+ Notifier.InfoPrint(KErrTextWrite);
+
+ if (ActiveOutput == EFile)
+ File.Close();
+ }
+
+
+// Output stack on selected output. If serial port, use
+// human-readable format. If file, use binary format.
+
+void DumpStack(TUint aTid, const TDbgThreadInfo& aInfo)
+ {
+ _LIT(KFmtStackFile, "d_exc_%d.stk");
+ _LIT(KErrStackOpen, "stack file open error");
+ _LIT(KErrStackWrite, "stack file write error");
+ _LIT(KPanicReadStack, "DEXC-READSTACK");
+
+ TInt r;
+ IoError = EFalse;
+
+ RFile file;
+ if (ActiveOutput == EFile)
+ {
+ TBuf16<KMaxFileName> name;
+ name.Format(KFmtStackFile, aTid);
+
+ // if -d param wasn't specified, use default location..(root dir on system drive)
+ if(!LogPath.Length())
+ {
+ LogPath.Append(RFs::GetSystemDriveChar());
+ LogPath.LowerCase();
+ LogPath.Append(_L(":\\"));
+ }
+
+ TBuf16<KMaxFileName> filename;
+ filename.Copy(LogPath);
+ filename.Append(name);
+
+ r = file.Replace(FileSession, filename, EFileWrite+EFileShareAny+EFileStream);
+ if (r != KErrNone)
+ {
+ Notifier.InfoPrint(KErrStackOpen);
+ return;
+ }
+ }
+
+ const TInt KBufSize = 256;
+ TBuf8<KBufSize> buf;
+ TLinAddr top = aInfo.iStackBase + aInfo.iStackSize;
+ for (TLinAddr base = aInfo.iStackBase; base < top; base += KBufSize)
+ {
+ // Read chunk of stack. Should always succeeds as thread has
+ // been suspended by LDD.
+ r = Trapper.ReadMem(aTid, base, buf);
+ __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicReadStack, r));
+
+ if (ActiveOutput == ESerial)
+ {
+ TBuf8<80> out;
+ TBuf8<20> ascii;
+ TUint a = base;
+ TInt len = buf.Length();
+ TInt offset = 0;
+ while(len>0)
+ {
+ out.Zero();
+ ascii.Zero();
+ out.AppendNumFixedWidth(a,EHex,8);
+ out.Append(_L8(": "));
+ TUint b;
+ for (b=0; b<16; b++)
+ {
+ TUint8 c=*(buf.Ptr()+offset+b);
+ out.AppendNumFixedWidth(c,EHex,2);
+ out.Append(' ');
+ if (c<0x20 || c>=0x7f)
+ c=0x2e;
+ ascii.Append(TChar(c));
+ }
+ out.Append(ascii);
+ DumpLine(out);
+ a+=16;
+ offset += 16;
+ len-=16;
+ }
+ }
+ else
+ {
+ if (file.Write(buf) != KErrNone)
+ IoError = ETrue;
+ }
+ }
+
+ if (IoError)
+ Notifier.InfoPrint(KErrStackWrite);
+ if (ActiveOutput == EFile)
+ file.Close();
+ }
+
+
+// Display a dialog box containing basic facts about the crash and ask
+// the user whether to dump detailed information or skip this crash.
+
+enum TDebugChoice { EDoDebug, EDoNotDebug };
+
+TDebugChoice CrashDialog(TDbgCrashInfo::TType aCrashType, const TDbgThreadInfo& aInfo)
+ {
+ _LIT(KExc, "Exception");
+ _LIT(KPanic, "Panic %S:%d");
+ _LIT(KBut1, "Do Not Debug");
+ _LIT(KBut2, "Debug");
+
+ TBuf<64> line1;
+ if (aCrashType == TDbgCrashInfo::EException)
+ line1 = KExc;
+ else
+ line1.Format(KPanic, &aInfo.iExitCategory, aInfo.iExitReason);
+ TInt r;
+ TRequestStatus s;
+ Notifier.Notify(line1, aInfo.iFullName, KBut1, KBut2, r, s);
+ User::WaitForRequest(s);
+ return r == 0 ? EDoNotDebug : EDoDebug;
+ }
+
+
+void MainL()
+ {
+ _LIT(KErrFs, "Failed to connect to file server");
+ _LIT(KErrLoadLdd, "Failed to load KDA LDD");
+ _LIT(KErrOpenLdd, "Failed to open KDA LDD");
+ _LIT(KLddPath, "MINKDA");
+ _LIT(KStarted, "D_EXC started");
+ _LIT(KCrash, "Crash detected");
+ _LIT(KPanicThreadInfo, "DEXC-THREADINFO");
+
+ TInt portNum;
+ TInt maxTrapCount = -1;
+ TBool isInteractive = ETrue;
+ TBool dumpStack = ETrue;
+ ParseCmdLineL(portNum, maxTrapCount, isInteractive, dumpStack);
+
+ // Open selected output and push resulting handle on cleanup
+ // stack.
+ TInt r;
+ if (ActiveOutput == EFile)
+ {
+ if ((r = FileSession.Connect()) != KErrNone)
+ {
+ Notifier.InfoPrint(KErrFs);
+ User::Leave(r);
+ }
+ CleanupClosePushL(FileSession);
+ }
+ else
+ OpenCommPortLC(portNum);
+
+ r = User::LoadLogicalDevice(KLddPath);
+ if (r != KErrNone && r != KErrAlreadyExists)
+ {
+ Notifier.InfoPrint(KErrLoadLdd);
+ User::Leave(r);
+ }
+
+// See comment near __KHEAP_MARKEND
+// __KHEAP_MARK;
+
+ r = Trapper.Open();
+ if (r != KErrNone)
+ {
+ Notifier.InfoPrint(KErrOpenLdd);
+ User::Leave(r);
+ }
+ CleanupClosePushL(Trapper);
+
+ Notifier.InfoPrint(KStarted);
+
+ // Main loop
+ TRequestStatus s;
+ TDbgCrashInfo crashInfo;
+ Trapper.Trap(s, crashInfo);
+ for (TInt crashCount = 0; maxTrapCount<0 || crashCount<maxTrapCount; ++crashCount)
+ {
+ User::WaitForRequest(s);
+
+ // Get more info about crashed thread. Should always succeeds
+ // as the thread has been suspended by LDD.
+ TDbgThreadInfo threadInfo;
+ TInt r = Trapper.GetThreadInfo(crashInfo.iTid, threadInfo);
+ __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicThreadInfo, r));
+
+ if (! isInteractive)
+ Notifier.InfoPrint(KCrash);
+ if (! isInteractive || CrashDialog(crashInfo.iType, threadInfo) == EDoDebug)
+ {
+ DumpTextInfo(crashInfo, threadInfo);
+ if (dumpStack)
+ DumpStack(crashInfo.iTid, threadInfo);
+ }
+ Trapper.Trap(s, crashInfo);
+ Trapper.KillCrashedThread();
+ }
+
+ Trapper.CancelTrap();
+
+ CleanupStack::PopAndDestroy(&Trapper);
+ CleanupStack::PopAndDestroy(); // FileSession or CommPort
+
+// Commented out because the InfoPrint thread may or may not have
+// terminated when we reach this point. It if hasn't a spurious
+// memory leak will be reported.
+// #ifdef _DEBUG
+// User::After(3000000);
+// __KHEAP_MARKEND;
+// #endif
+
+ User::FreeLogicalDevice(KKdaLddName);
+ }
+
+
+TInt E32Main()
+ {
+ _LIT(KPanicNtf, "DEXC-NO-NTF");
+ _LIT(KPanicLeave, "DEXC-LEAVE");
+ _LIT(KPanicOom, "DEXC-NO-CLEANUP");
+
+ // :FIXME: remove when platform security is always on
+ RProcess().DataCaging(RProcess::EDataCagingOn);
+
+#ifdef _DEBUG
+ TInt phcStart;
+ TInt thcStart;
+ RThread().HandleCount(phcStart, thcStart);
+#endif
+
+ TInt r = Notifier.Connect();
+ __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicNtf, r));
+
+ __UHEAP_MARK;
+ CTrapCleanup* cleanup = CTrapCleanup::New();
+ __ASSERT_ALWAYS(cleanup, User::Panic(KPanicOom, KErrNoMemory));
+ TRAP(r, MainL());
+ __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicLeave, r));
+ delete cleanup;
+ __UHEAP_MARKEND;
+
+ Notifier.Close();
+
+#ifdef _DEBUG
+ TInt phcEnd;
+ TInt thcEnd;
+ RThread().HandleCount(phcEnd, thcEnd);
+ __ASSERT_DEBUG(phcStart == phcEnd, User::Panic(_L("DEXC-PHC"), phcEnd-phcStart));
+ __ASSERT_DEBUG(thcStart == thcEnd, User::Panic(_L("DEXC-THC"), thcEnd-thcStart));
+#endif
+
+ return r;
+ }