crashanalysercmd/Libraries/File Formats/Plugins/CrashInfoFilePlugin/FileFormat/CCrashInfoHashBuilder.cs
changeset 2 0c91f0baec58
parent 1 7a31f7298d8f
child 3 045ade241ef5
equal deleted inserted replaced
1:7a31f7298d8f 2:0c91f0baec58
     1 /*
       
     2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description: 
       
    15 *
       
    16 */
       
    17 using System;
       
    18 using System.IO;
       
    19 using System.Text;
       
    20 using System.Security.Cryptography;
       
    21 using System.Collections.Generic;
       
    22 using CrashItemLib.Crash;
       
    23 using CrashItemLib.Crash.Base;
       
    24 using CrashItemLib.Crash.Processes;
       
    25 using CrashItemLib.Crash.Registers;
       
    26 using CrashItemLib.Crash.Registers.Special;
       
    27 using CrashItemLib.Crash.Stacks;
       
    28 using CrashItemLib.Crash.Threads;
       
    29 using CrashItemLib.Crash.Symbols;
       
    30 using CrashItemLib.Crash.Summarisable;
       
    31 using SymbianStructuresLib.Arm.Registers;
       
    32 
       
    33 
       
    34 namespace CrashInfoFilePlugin.PluginImplementations.FileFormat
       
    35 {
       
    36     public class CCrashInfoHashBuilder
       
    37     {
       
    38         #region Enumerations
       
    39         [Flags]
       
    40         public enum TConfiguration
       
    41         {
       
    42             // <summary>
       
    43             // Include stack data in hash
       
    44             // </summary>
       
    45             EIncludeStack = 1,
       
    46 
       
    47             // <summary>
       
    48             // Force inclusion of stack data in hash, even if hash builder
       
    49             // believes that it's inclusion is not needed in order to uniquely
       
    50             // pin-point the crash.
       
    51             // </summary>
       
    52             EIncludeStackForced = 2,
       
    53 
       
    54             // <summary>
       
    55             // By default, offset is only included in program counter symbol. 
       
    56             // This will force it to be incldued for all symbols (i.e. even
       
    57             // those within stack trace).
       
    58             // </summary>
       
    59             EIncludeOffsetsForAllSymbols = 4,
       
    60 
       
    61             // <summary>
       
    62             // Include the processor mode at the time of the crash within the hash.
       
    63             // </summary>
       
    64             EIncludeProcessorMode = 8,
       
    65 
       
    66             // <summary>
       
    67             // By default we output an MD5 hash, but you can enable plain text
       
    68             // output by using this option.
       
    69             // </summary>
       
    70             EOutputAsText = 16,
       
    71             
       
    72             // <summary>
       
    73             // A general purpose default configuration
       
    74             // </summary>
       
    75             EDefault = EIncludeStack | EIncludeStackForced | EIncludeProcessorMode
       
    76         }
       
    77         #endregion
       
    78 
       
    79         #region Factory
       
    80         public static CCrashInfoHashBuilder New( TConfiguration aConfig, CISummarisableEntity aEntity )
       
    81         {
       
    82             CCrashInfoHashBuilder ret = CCrashInfoHashBuilder.New( aConfig, aEntity, KDefaultNumberOfStackEntriesToCheckForSymbols );
       
    83             return ret;
       
    84         }
       
    85 
       
    86         public static CCrashInfoHashBuilder New( TConfiguration aConfig, CISummarisableEntity aEntity, int aNumberOfStackEntriesToCheck )
       
    87         {
       
    88             // By default we'll return an empty string for the hash if mandatory input
       
    89             // elements are not available
       
    90             CCrashInfoHashBuilder ret = null;
       
    91             //
       
    92             CIStack stack = aEntity.Stack;
       
    93             CIThread thread = aEntity.Thread;
       
    94             CIProcess process = aEntity.Process;
       
    95             CIRegisterList regs = aEntity.Registers;
       
    96             //
       
    97             if ( stack != null && regs != null )
       
    98             {
       
    99                 if ( process == null || thread == null )
       
   100                 {
       
   101                     ret = new CCrashInfoHashBuilder( aConfig, regs, stack, aNumberOfStackEntriesToCheck );
       
   102                 }
       
   103                 else
       
   104                 {
       
   105                     ret = new CCrashInfoHashBuilder( aConfig, regs, stack, thread, process, aNumberOfStackEntriesToCheck );
       
   106                 }
       
   107             }
       
   108             else
       
   109             {
       
   110                 throw new ArgumentException( LibResources.KRes_CCrashInfoHashBuilder_BadSummarisable );
       
   111             }
       
   112             //
       
   113             return ret;
       
   114         }
       
   115         #endregion
       
   116 
       
   117         #region Constructors
       
   118         private CCrashInfoHashBuilder( TConfiguration aConfig, CIRegisterList aRegisters, CIStack aStack, int aNumberOfStackEntriesToCheck )
       
   119             : this( aConfig, aRegisters, aStack, null, null, aNumberOfStackEntriesToCheck )
       
   120         {
       
   121         }
       
   122 
       
   123         private CCrashInfoHashBuilder( TConfiguration aConfig, CIRegisterList aRegisters, CIStack aStack, CIThread aThread, CIProcess aProcess, int aNumberOfStackEntriesToCheck )
       
   124         {
       
   125             iConfig = aConfig;
       
   126             iRegisters = aRegisters;
       
   127             iStack = aStack;
       
   128             iProcess = aProcess;
       
   129             iThread = aThread;
       
   130             iNumberOfStackEntriesToCheck = aNumberOfStackEntriesToCheck;
       
   131         }
       
   132         #endregion
       
   133 
       
   134         #region API
       
   135         public string GetHash()
       
   136         {
       
   137             StringBuilder hash = new StringBuilder();
       
   138             //
       
   139             if ( ( iConfig & TConfiguration.EIncludeProcessorMode ) != 0 )
       
   140             {
       
   141                 string processorMode = GetProcessorMode();
       
   142                 hash.AppendFormat( "MODE: [{0}] ", processorMode );
       
   143             }
       
   144             //
       
   145             string moduleName = GetAppropriateBinaryModuleName();
       
   146             hash.AppendFormat( "MODN: [{0}] ", moduleName );
       
   147             //
       
   148             string programCounter = GetProgramCounter();
       
   149             hash.AppendFormat( "PC: [{0}] ", programCounter );
       
   150             //
       
   151             if ( ( iConfig & TConfiguration.EIncludeStack ) != 0 || ( iConfig & TConfiguration.EIncludeStackForced ) != 0 )
       
   152             {
       
   153                 string stackSymbols = GetStackSymbols();
       
   154                 hash.AppendFormat( "STK: [{0}] ", stackSymbols );
       
   155             }
       
   156             
       
   157             // Final stage is to MD5 hash the text, unless the caller requested
       
   158             // plain text output.
       
   159             string ret = hash.ToString();            
       
   160             if ( ( iConfig & TConfiguration.EOutputAsText ) == 0 )
       
   161             {
       
   162                 ret = GetMD5( ret );
       
   163             }
       
   164             //
       
   165             return ret;
       
   166         }
       
   167         #endregion
       
   168 
       
   169         #region Internal constants
       
   170         private const int KDefaultNumberOfStackEntriesToCheckForSymbols = 4;
       
   171         #endregion
       
   172 
       
   173         #region Internal methods
       
   174         private string GetProcessorMode()
       
   175         {
       
   176             string ret = LibResources.KRes_CCrashInfoHashBuilder_NoCPUMode;
       
   177             //
       
   178             if ( iRegisters.IsCurrentProcessorMode )
       
   179             {
       
   180                 CIRegisterCPSR cpsr = iRegisters[ TArmRegisterType.EArmReg_CPSR ] as CIRegisterCPSR;
       
   181                 if ( cpsr != null )
       
   182                 {
       
   183                     ret = ArmRegisterBankUtils.BankAsString( cpsr.ProcessorMode );
       
   184                 }
       
   185             }
       
   186             //
       
   187             return ret;
       
   188         }
       
   189 
       
   190         private string GetAppropriateBinaryModuleName()
       
   191         {
       
   192             // We'll use the name of the binary associated with the program 
       
   193             // counter symbol (if present) or then if not, we'll fall back
       
   194             // to using the process name.
       
   195             string ret = string.Empty;
       
   196             //
       
   197             bool fallBack = false;
       
   198             if ( iRegisters.Contains( TArmRegisterType.EArmReg_PC ) )
       
   199             {
       
   200                 CIRegister pc = iRegisters[ TArmRegisterType.EArmReg_PC ];
       
   201                 if ( pc.Symbol.IsNull == false )
       
   202                 {
       
   203                     // Symbol available - use the associated binary name
       
   204                     string binName = pc.Symbol.Symbol.Collection.FileName.EitherFullNameButDevicePreferred;
       
   205                     ret = Path.GetFileName( binName );
       
   206                 }
       
   207                 else
       
   208                 {
       
   209                     fallBack = true;
       
   210                 }
       
   211             }
       
   212             else
       
   213             {
       
   214                 fallBack = true;
       
   215             }
       
   216 
       
   217             // Do we need to fallback because symbol et al was unavailable?
       
   218             if ( fallBack )
       
   219             {
       
   220                 // No Symbol, then in this case we'll try to fall back
       
   221                 // to the process name. 
       
   222                 if ( iProcess != null )
       
   223                 {
       
   224                     ret = iProcess.Name;
       
   225                 }
       
   226                 else
       
   227                 {
       
   228                     // Must be e.g. IRQ, FIQ, ABT, etc
       
   229                     ret = LibResources.KRes_CCrashInfoHashBuilder_AbortModeStack;
       
   230                 }
       
   231             }
       
   232             //
       
   233             return ret;
       
   234         }
       
   235 
       
   236         private string GetProgramCounter()
       
   237         {
       
   238             string ret = string.Empty;
       
   239             //
       
   240             if ( iRegisters.Contains( TArmRegisterType.EArmReg_PC ) )
       
   241             {
       
   242                 CIRegister pc = iRegisters[ TArmRegisterType.EArmReg_PC ];
       
   243                 ret = CleanSymbol( pc.Value, pc.Symbol, true );
       
   244             }
       
   245             //
       
   246             return ret;
       
   247         }
       
   248 
       
   249         private string GetStackSymbols()
       
   250         {
       
   251             StringBuilder ret = new StringBuilder();
       
   252             //
       
   253             if ( iStack.IsStackOutputAvailable )
       
   254             {
       
   255                 bool isOverflow = iStack.IsOverflow;
       
   256                 bool isForced = ( iConfig & TConfiguration.EIncludeStackForced ) != 0;
       
   257                 bool isNullDereference = ( iStack.Registers.Contains( TArmRegisterType.EArmReg_00 ) && iStack.Registers[ TArmRegisterType.EArmReg_00 ].Value == 0 );
       
   258 
       
   259                 // If dealing with a stack overflow, then we don't, by default, include any
       
   260                 // symbols from the stack, as they are likely to be entirely dirty or "ghosts" for
       
   261                 // the most part. 
       
   262                 //
       
   263                 // Furthermore, if the 'crash' was caused by dereferencing a NULL this pointer
       
   264                 // (which is somewhat of a guess on our part, but we ascertain this by inspecting
       
   265                 // the value of R0) then we don't include stack either.
       
   266                 //
       
   267                 // However, the above behaviour can be overriden by forcing stack processing.
       
   268                 bool processEntries = ( ( isOverflow == false && isNullDereference == false ) || isForced );
       
   269                 if ( processEntries )
       
   270                 {
       
   271                     // Get the entries and work out the number of items we should check for
       
   272                     // associated symbols.
       
   273                     CIElementList<CIStackEntry> stackEntries = iStack.ChildrenByType<CIStackEntry>();
       
   274 
       
   275                     // Discard all the entries that are outside of the stack pointer range.
       
   276                     // Once we see a register-based entry (for accurate decoding) or the current
       
   277                     // stack pointer entry, we've reached the start of the interesting stack data.
       
   278                     //
       
   279                     // However, if there has been a stack overflow then don't discard anything
       
   280                     // or else we'll end up with nothing left!
       
   281                     if ( iStack.IsOverflow == false && stackEntries.Count > 0 )
       
   282                     {
       
   283                         int i = 0;
       
   284                         while ( stackEntries.Count > 0 )
       
   285                         {
       
   286                             // If we see a register based entry then the stack reconstruction almost certainly
       
   287                             // used the accurate algorithm.
       
   288                             //
       
   289                             // Since we have already included the program counter in the hash text, it doesn't
       
   290                             // make sense to include it twice, so skip that.
       
   291                             // However, link register might be useful.
       
   292                             bool remove = true;
       
   293                             CIStackEntry entry = stackEntries[ i ];
       
   294 
       
   295                             if ( entry.IsCurrentStackPointerEntry )
       
   296                             {
       
   297                                 break;
       
   298                             }
       
   299                             else if ( entry.IsRegisterBasedEntry )
       
   300                             {
       
   301                                 // Preserve LR, skip PC
       
   302                                 if ( entry.Register.Type == TArmRegisterType.EArmReg_LR )
       
   303                                 {
       
   304                                     ++i;
       
   305                                     remove = false;
       
   306                                 }
       
   307                             }
       
   308 
       
   309                             if ( remove )
       
   310                             {
       
   311                                 stackEntries.RemoveAt( 0 );
       
   312                             }
       
   313                         }
       
   314                     }
       
   315 
       
   316                     // Did the caller also want offsets within the output? By default only the program
       
   317                     // counter receives this treatment, but it can be overridden.
       
   318                     bool includeOffset = ( iConfig & TConfiguration.EIncludeOffsetsForAllSymbols ) != 0;
       
   319 
       
   320                     // We should now have the stack entries directly relating to the crash call stack.
       
   321                     // Process them in turn, but only look at entries which happen to have associated
       
   322                     // symbols.
       
   323                     
       
   324                     int SymbolsNeeded = iNumberOfStackEntriesToCheck;
       
   325                     foreach (CIStackEntry entry in stackEntries)
       
   326                     {                       
       
   327                         if ( entry.Symbol != null && entry.Symbol.IsNull == false )
       
   328                         {
       
   329                             string txt = CleanSymbol( entry.Data, entry.Symbol, includeOffset );
       
   330                             ret.AppendFormat( "{0}, ", txt );
       
   331                             SymbolsNeeded--;
       
   332                         }     
       
   333                         if (SymbolsNeeded == 0)
       
   334                         {
       
   335                             break;
       
   336                         }
       
   337                     }
       
   338                    
       
   339                     // Remove trailing comma
       
   340                     string t = ret.ToString();
       
   341                     if ( t.EndsWith( ", " ) )
       
   342                     {
       
   343                         ret = ret.Remove( ret.Length - 2, 2 );
       
   344                     }
       
   345                 }
       
   346             }
       
   347             //
       
   348             string final = ret.ToString().TrimEnd();
       
   349             return final;
       
   350         }
       
   351 
       
   352         private string GetMD5( string aMakeHash )
       
   353         {
       
   354             MD5 md5 = MD5.Create();
       
   355             byte[] inputBytes = Encoding.ASCII.GetBytes( aMakeHash );
       
   356             byte[] hash = md5.ComputeHash( inputBytes );
       
   357             //
       
   358             StringBuilder sb = new StringBuilder();
       
   359             //
       
   360             for ( int i = 0; i < hash.Length; i++ )
       
   361             {
       
   362                 sb.Append( hash[ i ].ToString( "x2" ) );
       
   363             }
       
   364             //
       
   365             return sb.ToString();
       
   366         }
       
   367 
       
   368         private static string CleanSymbol( uint aAddress, CISymbol aSymbol, bool aAddOffset )
       
   369         {
       
   370             string ret = string.Empty;
       
   371             //
       
   372             if ( aSymbol.IsNull == false )
       
   373             {
       
   374                 if ( aAddOffset )
       
   375                 {
       
   376                     // Only include the name and offset, not the address
       
   377                     uint offset = aSymbol.Symbol.Offset( aAddress );
       
   378                     ret = string.Format( "|0x{0:x4}| {1}", offset, aSymbol.Symbol.Name );
       
   379                 }
       
   380                 else
       
   381                 {
       
   382                     ret = aSymbol.Symbol.Name;
       
   383                 }
       
   384             }
       
   385             //
       
   386             return ret;
       
   387         }
       
   388         #endregion
       
   389 
       
   390         #region Data members
       
   391         private readonly TConfiguration iConfig;
       
   392         private readonly CIRegisterList iRegisters;
       
   393         private readonly CIStack iStack;
       
   394         private readonly CIThread iThread;
       
   395         private readonly CIProcess iProcess;
       
   396         private readonly int iNumberOfStackEntriesToCheck;
       
   397         #endregion
       
   398     }
       
   399 }