diff -r 000000000000 -r 818e61de6cd1 crashanalysercmd/Libraries/File Formats/Plugins/CrashInfoFilePlugin/FileFormat/CCrashInfoHashBuilder.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/crashanalysercmd/Libraries/File Formats/Plugins/CrashInfoFilePlugin/FileFormat/CCrashInfoHashBuilder.cs Thu Feb 11 15:50:58 2010 +0200 @@ -0,0 +1,399 @@ +/* +* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of "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: +* +*/ +using System; +using System.IO; +using System.Text; +using System.Security.Cryptography; +using System.Collections.Generic; +using CrashItemLib.Crash; +using CrashItemLib.Crash.Base; +using CrashItemLib.Crash.Processes; +using CrashItemLib.Crash.Registers; +using CrashItemLib.Crash.Registers.Special; +using CrashItemLib.Crash.Stacks; +using CrashItemLib.Crash.Threads; +using CrashItemLib.Crash.Symbols; +using CrashItemLib.Crash.Summarisable; +using SymbianStructuresLib.Arm.Registers; + + +namespace CrashInfoFilePlugin.PluginImplementations.FileFormat +{ + public class CCrashInfoHashBuilder + { + #region Enumerations + [Flags] + public enum TConfiguration + { + // + // Include stack data in hash + // + EIncludeStack = 1, + + // + // Force inclusion of stack data in hash, even if hash builder + // believes that it's inclusion is not needed in order to uniquely + // pin-point the crash. + // + EIncludeStackForced = 2, + + // + // By default, offset is only included in program counter symbol. + // This will force it to be incldued for all symbols (i.e. even + // those within stack trace). + // + EIncludeOffsetsForAllSymbols = 4, + + // + // Include the processor mode at the time of the crash within the hash. + // + EIncludeProcessorMode = 8, + + // + // By default we output an MD5 hash, but you can enable plain text + // output by using this option. + // + EOutputAsText = 16, + + // + // A general purpose default configuration + // + EDefault = EIncludeStack | EIncludeStackForced | EIncludeProcessorMode + } + #endregion + + #region Factory + public static CCrashInfoHashBuilder New( TConfiguration aConfig, CISummarisableEntity aEntity ) + { + CCrashInfoHashBuilder ret = CCrashInfoHashBuilder.New( aConfig, aEntity, KDefaultNumberOfStackEntriesToCheckForSymbols ); + return ret; + } + + public static CCrashInfoHashBuilder New( TConfiguration aConfig, CISummarisableEntity aEntity, int aNumberOfStackEntriesToCheck ) + { + // By default we'll return an empty string for the hash if mandatory input + // elements are not available + CCrashInfoHashBuilder ret = null; + // + CIStack stack = aEntity.Stack; + CIThread thread = aEntity.Thread; + CIProcess process = aEntity.Process; + CIRegisterList regs = aEntity.Registers; + // + if ( stack != null && regs != null ) + { + if ( process == null || thread == null ) + { + ret = new CCrashInfoHashBuilder( aConfig, regs, stack, aNumberOfStackEntriesToCheck ); + } + else + { + ret = new CCrashInfoHashBuilder( aConfig, regs, stack, thread, process, aNumberOfStackEntriesToCheck ); + } + } + else + { + throw new ArgumentException( LibResources.KRes_CCrashInfoHashBuilder_BadSummarisable ); + } + // + return ret; + } + #endregion + + #region Constructors + private CCrashInfoHashBuilder( TConfiguration aConfig, CIRegisterList aRegisters, CIStack aStack, int aNumberOfStackEntriesToCheck ) + : this( aConfig, aRegisters, aStack, null, null, aNumberOfStackEntriesToCheck ) + { + } + + private CCrashInfoHashBuilder( TConfiguration aConfig, CIRegisterList aRegisters, CIStack aStack, CIThread aThread, CIProcess aProcess, int aNumberOfStackEntriesToCheck ) + { + iConfig = aConfig; + iRegisters = aRegisters; + iStack = aStack; + iProcess = aProcess; + iThread = aThread; + iNumberOfStackEntriesToCheck = aNumberOfStackEntriesToCheck; + } + #endregion + + #region API + public string GetHash() + { + StringBuilder hash = new StringBuilder(); + // + if ( ( iConfig & TConfiguration.EIncludeProcessorMode ) != 0 ) + { + string processorMode = GetProcessorMode(); + hash.AppendFormat( "MODE: [{0}] ", processorMode ); + } + // + string moduleName = GetAppropriateBinaryModuleName(); + hash.AppendFormat( "MODN: [{0}] ", moduleName ); + // + string programCounter = GetProgramCounter(); + hash.AppendFormat( "PC: [{0}] ", programCounter ); + // + if ( ( iConfig & TConfiguration.EIncludeStack ) != 0 || ( iConfig & TConfiguration.EIncludeStackForced ) != 0 ) + { + string stackSymbols = GetStackSymbols(); + hash.AppendFormat( "STK: [{0}] ", stackSymbols ); + } + + // Final stage is to MD5 hash the text, unless the caller requested + // plain text output. + string ret = hash.ToString(); + if ( ( iConfig & TConfiguration.EOutputAsText ) == 0 ) + { + ret = GetMD5( ret ); + } + // + return ret; + } + #endregion + + #region Internal constants + private const int KDefaultNumberOfStackEntriesToCheckForSymbols = 4; + #endregion + + #region Internal methods + private string GetProcessorMode() + { + string ret = LibResources.KRes_CCrashInfoHashBuilder_NoCPUMode; + // + if ( iRegisters.IsCurrentProcessorMode ) + { + CIRegisterCPSR cpsr = iRegisters[ TArmRegisterType.EArmReg_CPSR ] as CIRegisterCPSR; + if ( cpsr != null ) + { + ret = ArmRegisterBankUtils.BankAsString( cpsr.ProcessorMode ); + } + } + // + return ret; + } + + private string GetAppropriateBinaryModuleName() + { + // We'll use the name of the binary associated with the program + // counter symbol (if present) or then if not, we'll fall back + // to using the process name. + string ret = string.Empty; + // + bool fallBack = false; + if ( iRegisters.Contains( TArmRegisterType.EArmReg_PC ) ) + { + CIRegister pc = iRegisters[ TArmRegisterType.EArmReg_PC ]; + if ( pc.Symbol.IsNull == false ) + { + // Symbol available - use the associated binary name + string binName = pc.Symbol.Symbol.Collection.FileName.EitherFullNameButDevicePreferred; + ret = Path.GetFileName( binName ); + } + else + { + fallBack = true; + } + } + else + { + fallBack = true; + } + + // Do we need to fallback because symbol et al was unavailable? + if ( fallBack ) + { + // No Symbol, then in this case we'll try to fall back + // to the process name. + if ( iProcess != null ) + { + ret = iProcess.Name; + } + else + { + // Must be e.g. IRQ, FIQ, ABT, etc + ret = LibResources.KRes_CCrashInfoHashBuilder_AbortModeStack; + } + } + // + return ret; + } + + private string GetProgramCounter() + { + string ret = string.Empty; + // + if ( iRegisters.Contains( TArmRegisterType.EArmReg_PC ) ) + { + CIRegister pc = iRegisters[ TArmRegisterType.EArmReg_PC ]; + ret = CleanSymbol( pc.Value, pc.Symbol, true ); + } + // + return ret; + } + + private string GetStackSymbols() + { + StringBuilder ret = new StringBuilder(); + // + if ( iStack.IsStackOutputAvailable ) + { + bool isOverflow = iStack.IsOverflow; + bool isForced = ( iConfig & TConfiguration.EIncludeStackForced ) != 0; + bool isNullDereference = ( iStack.Registers.Contains( TArmRegisterType.EArmReg_00 ) && iStack.Registers[ TArmRegisterType.EArmReg_00 ].Value == 0 ); + + // If dealing with a stack overflow, then we don't, by default, include any + // symbols from the stack, as they are likely to be entirely dirty or "ghosts" for + // the most part. + // + // Furthermore, if the 'crash' was caused by dereferencing a NULL this pointer + // (which is somewhat of a guess on our part, but we ascertain this by inspecting + // the value of R0) then we don't include stack either. + // + // However, the above behaviour can be overriden by forcing stack processing. + bool processEntries = ( ( isOverflow == false && isNullDereference == false ) || isForced ); + if ( processEntries ) + { + // Get the entries and work out the number of items we should check for + // associated symbols. + CIElementList stackEntries = iStack.ChildrenByType(); + + // Discard all the entries that are outside of the stack pointer range. + // Once we see a register-based entry (for accurate decoding) or the current + // stack pointer entry, we've reached the start of the interesting stack data. + // + // However, if there has been a stack overflow then don't discard anything + // or else we'll end up with nothing left! + if ( iStack.IsOverflow == false && stackEntries.Count > 0 ) + { + int i = 0; + while ( stackEntries.Count > 0 ) + { + // If we see a register based entry then the stack reconstruction almost certainly + // used the accurate algorithm. + // + // Since we have already included the program counter in the hash text, it doesn't + // make sense to include it twice, so skip that. + // However, link register might be useful. + bool remove = true; + CIStackEntry entry = stackEntries[ i ]; + + if ( entry.IsCurrentStackPointerEntry ) + { + break; + } + else if ( entry.IsRegisterBasedEntry ) + { + // Preserve LR, skip PC + if ( entry.Register.Type == TArmRegisterType.EArmReg_LR ) + { + ++i; + remove = false; + } + } + + if ( remove ) + { + stackEntries.RemoveAt( 0 ); + } + } + } + + // Did the caller also want offsets within the output? By default only the program + // counter receives this treatment, but it can be overridden. + bool includeOffset = ( iConfig & TConfiguration.EIncludeOffsetsForAllSymbols ) != 0; + + // We should now have the stack entries directly relating to the crash call stack. + // Process them in turn, but only look at entries which happen to have associated + // symbols. + + int SymbolsNeeded = iNumberOfStackEntriesToCheck; + foreach (CIStackEntry entry in stackEntries) + { + if ( entry.Symbol != null && entry.Symbol.IsNull == false ) + { + string txt = CleanSymbol( entry.Data, entry.Symbol, includeOffset ); + ret.AppendFormat( "{0}, ", txt ); + SymbolsNeeded--; + } + if (SymbolsNeeded == 0) + { + break; + } + } + + // Remove trailing comma + string t = ret.ToString(); + if ( t.EndsWith( ", " ) ) + { + ret = ret.Remove( ret.Length - 2, 2 ); + } + } + } + // + string final = ret.ToString().TrimEnd(); + return final; + } + + private string GetMD5( string aMakeHash ) + { + MD5 md5 = MD5.Create(); + byte[] inputBytes = Encoding.ASCII.GetBytes( aMakeHash ); + byte[] hash = md5.ComputeHash( inputBytes ); + // + StringBuilder sb = new StringBuilder(); + // + for ( int i = 0; i < hash.Length; i++ ) + { + sb.Append( hash[ i ].ToString( "x2" ) ); + } + // + return sb.ToString(); + } + + private static string CleanSymbol( uint aAddress, CISymbol aSymbol, bool aAddOffset ) + { + string ret = string.Empty; + // + if ( aSymbol.IsNull == false ) + { + if ( aAddOffset ) + { + // Only include the name and offset, not the address + uint offset = aSymbol.Symbol.Offset( aAddress ); + ret = string.Format( "|0x{0:x4}| {1}", offset, aSymbol.Symbol.Name ); + } + else + { + ret = aSymbol.Symbol.Name; + } + } + // + return ret; + } + #endregion + + #region Data members + private readonly TConfiguration iConfig; + private readonly CIRegisterList iRegisters; + private readonly CIStack iStack; + private readonly CIThread iThread; + private readonly CIProcess iProcess; + private readonly int iNumberOfStackEntriesToCheck; + #endregion + } +}