diff -r 000000000000 -r a41df078684a kerneltest/e32utils/d_exc/d_exc.cpp --- /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 +#include +#include +#include +#include +#include "minkda.h" + +RNotifier Notifier; // The "UI" +RMinKda Trapper; +RFs FileSession; +TBuf16 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 KMaxCount) + { + aLine.Format(KFmtOverflow, c); + DumpLine(aLine); + c = KMaxCount; + } + + for (TInt i=0; i 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 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 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 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 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 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 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 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