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