Update contrib.
1 // Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
2 // All rights reserved.
3 // This component and the accompanying materials are made available
4 // under the terms of the License "Eclipse Public License v1.0"
5 // which accompanies this distribution, and is available
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
8 // Initial Contributors:
9 // Nokia Corporation - initial contribution.
14 // e32utils\d_exc\d_exc.cpp
15 // Trap and log user-side exceptions and panics.
18 // Trap panics and exceptions forever. Prompt whether to log.
19 // Logs go on C: drive.
20 // d_exc [-m] [-nN] [-pN] [-b] [-d log_path]
21 // -m minimal logging (no stack dump)
22 // -nN stop after N exceptions/panics
23 // -pN log to serial port N instead of C: drive
24 // -b do not prompt; always log
25 // -d specify the path for log files. If not given, logs are
26 // written to the root of the system drive. If just a path
27 // name is given, logs are written to that directory (must
28 // start with a \) on the system drive.
33 #include <e32std_private.h>
39 RNotifier Notifier; // The "UI"
42 TBuf16<KMaxFileName> LogPath; // to specify log file location
44 // Possible outputs where crash information can be dumped
45 enum TOutputType{ EFile, ESerial };
47 // Variables shared between DumpLine() and the various functions used
48 // to format crash info.
49 TOutputType ActiveOutput = EFile;
50 TBool IoError; // ETrue after I/O error
51 RBusDevComm CommPort; // Handle to serial port used
52 RFile File; // Handle to text file used
54 // Maximum length in characters of a line in the file containing
55 // textual information about the crash.
56 const TInt KMaxLineLength = KMaxFullName + 32;
58 class TLexNew : public TLex16
61 inline TLexNew(const TDesC16& aDes) {Assign(aDes);}
62 TInt ExtractParameter(TDes16 &aParam);
65 TInt TLexNew::ExtractParameter(TDes16 &aParam)
70 TBool GetNext = EFalse;
72 //exit..if it's empty (empty option at the end of command)
76 // remove any space between option and the rest of param..
79 // just see, what's next..
80 // if there this a param with spaces- should be in "quotes"
84 Inc(); // skip this quote " and move to next position..
87 // remove spaces after quotes (" param...")
90 // ..mark next character position as a start of our token
93 // move until the end of our token (next space)..
97 token.Copy(MarkedToken());
99 // if.. there was one-word param.. with quotes..shrink it..and don't try to search next one..
100 if (*(token.MidTPtr(token.Length()-1).Ptr()) == '"')
102 // just shrink it by that ending quote..
103 token.SetLength(token.Length()-1);
107 // This is at least beginning of our param.. let's use it!
108 // add this to beginning of our param..
111 // if this was param specified in quotes..search for the ending quote..
117 // before taking next one..check it - if '-' on the beginning..
118 // it's either next param specifier..(no ending quote at all)
122 // get the next one..
123 token.Copy(NextToken());
125 // was there any token more? ..if not- we're at the end..
126 // so the ending quote still wasn't found...
130 // is this the last one - with quote" at the end?
131 if (*(token.MidTPtr(token.Length()-1).Ptr()) == '"')
133 // just shrink it by that ending quote..
134 token.SetLength(token.Length()-1);
138 param.Append(_L(" ")); // there was space in orig. param..restore it..
139 param.Append(token); // and append this token to our param..
142 // if there was any space at the end..(e.g. if specified: -d"c:\logs ")
146 //finally - copy param to the referenced descriptor
152 TInt ValidatePath(TDes16 &aLogPath)
155 // check the length first.. (20 chars for file name..)
156 if (aLogPath.Length() >(KMaxFileName - 20))
158 Notifier.InfoPrint(_L("directory name too long.."));
162 // if it hasn't drive letter (colon wasn't second..)
163 if (*(aLogPath.MidTPtr(1).Ptr()) != ':')
165 // if it starts with "\" use system drive..
166 if (*(aLogPath.MidTPtr(0).Ptr()) == '\\')
168 // if someone specified param like: "\ path\" ...obviously..
169 if (*(aLogPath.MidTPtr(1).Ptr()) == ' ')
173 drive.Append(RFs::GetSystemDriveChar());
175 drive.Append(_L(":"));
176 aLogPath.Insert(0, drive);
178 else //otherwise -path not valid..
184 // and add backslash if needed
185 if (*(aLogPath.MidTPtr(aLogPath.Length()-1).Ptr()) != '\\')
186 aLogPath.Append(_L("\\"));
188 //open file session..
189 if (FileSession.Connect() != KErrNone)
194 if (dir.Open(FileSession, aLogPath, KEntryAttMatchExclusive) != KErrNone)
196 Notifier.InfoPrint(_L("specified directory doesn't exist"));
197 LogPath.Zero(); //clear global path..
205 // close file session..
212 // Open specified serial port and push handle on the cleanup stack.
214 void OpenCommPortLC(TInt aPortNum)
222 _LIT(KErrPdd, "Failed to load serial PDD");
223 _LIT(KErrLdd, "Failed to load serial LDD");
224 _LIT(KErrOpen, "Failed to open comm port");
225 _LIT(KErrCfg, "Failed to configure comm port");
227 TInt r = User::LoadPhysicalDevice(KPdd);
228 if (r != KErrNone && r != KErrAlreadyExists)
230 Notifier.InfoPrint(KErrPdd);
234 r = User::LoadLogicalDevice(KLdd);
235 if (r != KErrNone && r != KErrAlreadyExists)
237 Notifier.InfoPrint(KErrLdd);
241 r = CommPort.Open(aPortNum);
244 Notifier.InfoPrint(KErrOpen);
247 CleanupClosePushL(CommPort);
250 TCommConfigV01& cfg=cfgBuf();
251 CommPort.Config(cfgBuf);
252 cfg.iRate=EBps115200;
253 cfg.iDataBits=EData8;
254 cfg.iStopBits=EStop1;
255 cfg.iParity=EParityNone;
256 cfg.iHandshake=KConfigObeyXoff|KConfigSendXoff;
257 cfg.iFifo=EFifoEnable;
258 cfg.iTerminatorCount=0;
259 cfg.iSIREnable=ESIRDisable;
260 r = CommPort.SetConfig(cfgBuf);
263 Notifier.InfoPrint(KErrCfg);
269 void ParseCmdLineL(TInt& aPortNum, TInt& aMaxTrapCount, TBool& aInteractive, TBool& aDumpStack)
271 _LIT(KInvalidArg, "Invalid command-line");
273 HBufC* cl = HBufC::NewLC(User::CommandLineLength());
274 TPtr clp = cl->Des();
275 User::CommandLine(clp);
277 // If started from UIKON shell, ignore command-line and use defaults
278 if (clp.Match(_L("?:\\*")) == 0)
285 TInt r = KErrArgument;
286 if (lex.Get() == '-')
291 r = lex.Val(aMaxTrapCount);
294 r = lex.Val(aPortNum);
296 ActiveOutput = ESerial;
299 aInteractive = EFalse;
307 //try to extract path and store it in global buffer
308 r = lex.ExtractParameter(LogPath);
309 // check, if specified path is valid
311 r = ValidatePath(LogPath);
317 Notifier.InfoPrint(KInvalidArg);
318 User::Leave(KErrArgument);
323 CleanupStack::PopAndDestroy(cl);
327 // Dump specified line + CRLF on the selected output. Set IoError to
328 // ETrue if an error occurs.
330 void DumpLine(TDes8& aLine)
333 _LIT8(KCrLf, "\r\n");
335 if (ActiveOutput == ESerial)
338 CommPort.Write(s, aLine);
339 User::WaitForRequest(s);
343 r = File.Write(aLine);
349 void DumpExcInfo(const TDbgCpuExcInfo& aInfo, TDes8& aLine)
351 _LIT8(KHdr, "\r\nUNHANDLED EXCEPTION:");
355 _LIT8(KFmt1, "code=%d PC=%08x FAR=%08x FSR=%08x");
356 aLine.Format(KFmt1, aInfo.iExcCode, aInfo.iFaultPc, aInfo.iFaultAddress, aInfo.iFaultStatus);
358 _LIT8(KFmt2, "R13svc=%08x R14svc=%08x SPSRsvc=%08x");
359 aLine.Format(KFmt2, aInfo.iR13Svc, aInfo.iR14Svc, aInfo.iSpsrSvc);
362 (void) aInfo; // silence warning
367 void DumpRegisters(const TDbgRegSet& aRegs, TDes8& aLine)
369 #if defined(__MARM__)
370 _LIT8(KHdr, "\r\nUSER REGISTERS:");
373 _LIT8(KFmtCpsr, "CPSR=%08x");
374 aLine.Format(KFmtCpsr, aRegs.iCpsr);
376 for (TInt i=0; i<TDbgRegSet::KRegCount; i+=4)
378 _LIT8(KFmtReg, "r%02d=%08x %08x %08x %08x");
379 aLine.Format(KFmtReg, i, aRegs.iRn[i], aRegs.iRn[i+1], aRegs.iRn[i+2], aRegs.iRn[i+3]);
383 (void) aRegs; // silence warnings
389 void DumpCodeSegs(TUint aPid, TDes8& aLine)
391 _LIT(KPanicCodeMods, "DEXC-CODEMOD");
392 _LIT8(KHdr, "\r\nCODE SEGMENTS:");
393 _LIT8(KFmtOverflow, "Only first %d code modules displayed");
394 _LIT8(KFmtMod, "%08X-%08X %S");
399 // :FIXME: improve API
400 // :FIXME: suspend/resume all threads in process
401 const TInt KMaxCount = 128;
402 TAny* handles[KMaxCount];
405 TInt r = Trapper.GetCodeSegs(aPid, handles, c);
406 __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicCodeMods, r));
410 aLine.Format(KFmtOverflow, c);
415 for (TInt i=0; i<c; i++)
417 TDbgCodeSegInfo info;
418 r = Trapper.GetCodeSegInfo(handles[i], aPid, info);
421 TBuf8<KMaxFileName> path;
422 path.Copy(info.iPath);
423 aLine.Format(KFmtMod, info.iCodeBase, info.iCodeBase+info.iCodeSize, &path);
430 void DumpTextInfo(const TDbgCrashInfo& aCrashInfo, const TDbgThreadInfo& aThreadInfo)
432 _LIT(KFmtTextFile, "d_exc_%d.txt");
433 _LIT(KErrTextOpen, "text file open error");
434 _LIT(KErrTextWrite, "text file write error");
436 if (ActiveOutput == EFile)
438 TBuf16<KMaxFileName> name;
439 name.Format(KFmtTextFile, aCrashInfo.iTid);
441 // if -d param wasn't specified, use default location..(root dir on system drive)
442 if(!LogPath.Length())
444 LogPath.Append(RFs::GetSystemDriveChar());
446 LogPath.Append(_L(":\\"));
449 TBuf16<KMaxFileName> filename;
450 filename.Copy(LogPath);
451 filename.Append(name);
453 TInt r = File.Replace(FileSession, filename, EFileWrite+EFileShareAny+EFileStream);
456 Notifier.InfoPrint(KErrTextOpen);
463 // Note that following buffer is passed to callee functions and
464 // reuse to minimise stack footprint.
465 TBuf8<KMaxLineLength> line;
469 _LIT8(KHdr, "EKA2 USER CRASH LOG");
472 line.Copy(aThreadInfo.iFullName);
473 _LIT8(KName, "Thread Name: ");
474 line.Insert(0, KName);
476 _LIT8(KFmtTid, "Thread ID: %u");
477 line.Format(KFmtTid, aCrashInfo.iTid);
479 _LIT8(KFmtStack, "User Stack %08X-%08X");
480 line.Format(KFmtStack, aThreadInfo.iStackBase,
481 aThreadInfo.iStackBase+aThreadInfo.iStackSize);
484 if (aCrashInfo.iType == TDbgCrashInfo::EPanic)
486 TBuf8<KMaxExitCategoryName> cat;
487 cat.Copy(aThreadInfo.iExitCategory);
488 _LIT8(KFmtPanic, "Panic: %S-%d");
489 line.Format(KFmtPanic, &cat, aThreadInfo.iExitReason);
493 DumpExcInfo(aCrashInfo.iCpu, line);
495 DumpRegisters(aThreadInfo.iCpu, line);
496 DumpCodeSegs(aThreadInfo.iPid, line);
502 Notifier.InfoPrint(KErrTextWrite);
504 if (ActiveOutput == EFile)
509 // Output stack on selected output. If serial port, use
510 // human-readable format. If file, use binary format.
512 void DumpStack(TUint aTid, const TDbgThreadInfo& aInfo)
514 _LIT(KFmtStackFile, "d_exc_%d.stk");
515 _LIT(KErrStackOpen, "stack file open error");
516 _LIT(KErrStackWrite, "stack file write error");
517 _LIT(KPanicReadStack, "DEXC-READSTACK");
523 if (ActiveOutput == EFile)
525 TBuf16<KMaxFileName> name;
526 name.Format(KFmtStackFile, aTid);
528 // if -d param wasn't specified, use default location..(root dir on system drive)
529 if(!LogPath.Length())
531 LogPath.Append(RFs::GetSystemDriveChar());
533 LogPath.Append(_L(":\\"));
536 TBuf16<KMaxFileName> filename;
537 filename.Copy(LogPath);
538 filename.Append(name);
540 r = file.Replace(FileSession, filename, EFileWrite+EFileShareAny+EFileStream);
543 Notifier.InfoPrint(KErrStackOpen);
548 const TInt KBufSize = 256;
550 TLinAddr top = aInfo.iStackBase + aInfo.iStackSize;
551 for (TLinAddr base = aInfo.iStackBase; base < top; base += KBufSize)
553 // Read chunk of stack. Should always succeeds as thread has
554 // been suspended by LDD.
555 r = Trapper.ReadMem(aTid, base, buf);
556 __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicReadStack, r));
558 if (ActiveOutput == ESerial)
563 TInt len = buf.Length();
569 out.AppendNumFixedWidth(a,EHex,8);
570 out.Append(_L8(": "));
574 TUint8 c=*(buf.Ptr()+offset+b);
575 out.AppendNumFixedWidth(c,EHex,2);
577 if (c<0x20 || c>=0x7f)
579 ascii.Append(TChar(c));
590 if (file.Write(buf) != KErrNone)
596 Notifier.InfoPrint(KErrStackWrite);
597 if (ActiveOutput == EFile)
602 // Display a dialog box containing basic facts about the crash and ask
603 // the user whether to dump detailed information or skip this crash.
605 enum TDebugChoice { EDoDebug, EDoNotDebug };
607 TDebugChoice CrashDialog(TDbgCrashInfo::TType aCrashType, const TDbgThreadInfo& aInfo)
609 _LIT(KExc, "Exception");
610 _LIT(KPanic, "Panic %S:%d");
611 _LIT(KBut1, "Do Not Debug");
612 _LIT(KBut2, "Debug");
615 if (aCrashType == TDbgCrashInfo::EException)
618 line1.Format(KPanic, &aInfo.iExitCategory, aInfo.iExitReason);
621 Notifier.Notify(line1, aInfo.iFullName, KBut1, KBut2, r, s);
622 User::WaitForRequest(s);
623 return r == 0 ? EDoNotDebug : EDoDebug;
629 _LIT(KErrFs, "Failed to connect to file server");
630 _LIT(KErrLoadLdd, "Failed to load KDA LDD");
631 _LIT(KErrOpenLdd, "Failed to open KDA LDD");
632 _LIT(KLddPath, "MINKDA");
633 _LIT(KStarted, "D_EXC started");
634 _LIT(KCrash, "Crash detected");
635 _LIT(KPanicThreadInfo, "DEXC-THREADINFO");
638 TInt maxTrapCount = -1;
639 TBool isInteractive = ETrue;
640 TBool dumpStack = ETrue;
641 ParseCmdLineL(portNum, maxTrapCount, isInteractive, dumpStack);
643 // Open selected output and push resulting handle on cleanup
646 if (ActiveOutput == EFile)
648 if ((r = FileSession.Connect()) != KErrNone)
650 Notifier.InfoPrint(KErrFs);
653 CleanupClosePushL(FileSession);
656 OpenCommPortLC(portNum);
658 r = User::LoadLogicalDevice(KLddPath);
659 if (r != KErrNone && r != KErrAlreadyExists)
661 Notifier.InfoPrint(KErrLoadLdd);
665 // See comment near __KHEAP_MARKEND
671 Notifier.InfoPrint(KErrOpenLdd);
674 CleanupClosePushL(Trapper);
676 Notifier.InfoPrint(KStarted);
680 TDbgCrashInfo crashInfo;
681 Trapper.Trap(s, crashInfo);
682 for (TInt crashCount = 0; maxTrapCount<0 || crashCount<maxTrapCount; ++crashCount)
684 User::WaitForRequest(s);
686 // Get more info about crashed thread. Should always succeeds
687 // as the thread has been suspended by LDD.
688 TDbgThreadInfo threadInfo;
689 TInt r = Trapper.GetThreadInfo(crashInfo.iTid, threadInfo);
690 __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicThreadInfo, r));
693 Notifier.InfoPrint(KCrash);
694 if (! isInteractive || CrashDialog(crashInfo.iType, threadInfo) == EDoDebug)
696 DumpTextInfo(crashInfo, threadInfo);
698 DumpStack(crashInfo.iTid, threadInfo);
700 Trapper.Trap(s, crashInfo);
701 Trapper.KillCrashedThread();
704 Trapper.CancelTrap();
706 CleanupStack::PopAndDestroy(&Trapper);
707 CleanupStack::PopAndDestroy(); // FileSession or CommPort
709 // Commented out because the InfoPrint thread may or may not have
710 // terminated when we reach this point. It if hasn't a spurious
711 // memory leak will be reported.
713 // User::After(3000000);
717 User::FreeLogicalDevice(KKdaLddName);
723 _LIT(KPanicNtf, "DEXC-NO-NTF");
724 _LIT(KPanicLeave, "DEXC-LEAVE");
725 _LIT(KPanicOom, "DEXC-NO-CLEANUP");
727 // :FIXME: remove when platform security is always on
728 RProcess().DataCaging(RProcess::EDataCagingOn);
733 RThread().HandleCount(phcStart, thcStart);
736 TInt r = Notifier.Connect();
737 __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicNtf, r));
740 CTrapCleanup* cleanup = CTrapCleanup::New();
741 __ASSERT_ALWAYS(cleanup, User::Panic(KPanicOom, KErrNoMemory));
743 __ASSERT_ALWAYS(r == KErrNone, User::Panic(KPanicLeave, r));
752 RThread().HandleCount(phcEnd, thcEnd);
753 __ASSERT_DEBUG(phcStart == phcEnd, User::Panic(_L("DEXC-PHC"), phcEnd-phcStart));
754 __ASSERT_DEBUG(thcStart == thcEnd, User::Panic(_L("DEXC-THC"), thcEnd-thcStart));