crashanalysercmd/Libraries/File Formats/Plugins/CrashInfoFilePlugin/FileFormat/CCrashInfoHashBuilder.cs
author Matti Laitinen <matti.t.laitinen@nokia.com>
Thu, 11 Feb 2010 15:50:58 +0200
changeset 0 818e61de6cd1
permissions -rw-r--r--
Add initial version of Crash Analyser cmdline under EPL

/*
* 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
    }
}