crashanalysercmd/Libraries/File Formats/Plugins/CrashInfoFilePlugin/FileFormat/CCrashInfoHashBuilder.cs
changeset 0 818e61de6cd1
--- /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
+        {
+            // <summary>
+            // Include stack data in hash
+            // </summary>
+            EIncludeStack = 1,
+
+            // <summary>
+            // 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.
+            // </summary>
+            EIncludeStackForced = 2,
+
+            // <summary>
+            // 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).
+            // </summary>
+            EIncludeOffsetsForAllSymbols = 4,
+
+            // <summary>
+            // Include the processor mode at the time of the crash within the hash.
+            // </summary>
+            EIncludeProcessorMode = 8,
+
+            // <summary>
+            // By default we output an MD5 hash, but you can enable plain text
+            // output by using this option.
+            // </summary>
+            EOutputAsText = 16,
+            
+            // <summary>
+            // A general purpose default configuration
+            // </summary>
+            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<CIStackEntry> stackEntries = iStack.ChildrenByType<CIStackEntry>();
+
+                    // 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
+    }
+}