Package winappdbg :: Module crash
[hide private]
[frames] | no frames]

Source Code for Module winappdbg.crash

   1  # Copyright (c) 2009, Mario Vilas 
   2  # All rights reserved. 
   3  # 
   4  # Redistribution and use in source and binary forms, with or without 
   5  # modification, are permitted provided that the following conditions are met: 
   6  # 
   7  #     * Redistributions of source code must retain the above copyright notice, 
   8  #       this list of conditions and the following disclaimer. 
   9  #     * Redistributions in binary form must reproduce the above copyright 
  10  #       notice,this list of conditions and the following disclaimer in the 
  11  #       documentation and/or other materials provided with the distribution. 
  12  #     * Neither the name of the copyright holder nor the names of its 
  13  #       contributors may be used to endorse or promote products derived from 
  14  #       this software without specific prior written permission. 
  15  # 
  16  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
  17  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
  18  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
  19  # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
  20  # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
  21  # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
  22  # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
  23  # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
  24  # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
  25  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  26  # POSSIBILITY OF SUCH DAMAGE. 
  27   
  28  """ 
  29  Crash logging module. 
  30  """ 
  31   
  32  __revision__ = "$Id: crash.py 549 2009-12-13 23:33:54Z qvasimodo $" 
  33   
  34  __all__ =   [ 
  35                  # Object that represents a crash in the debugee. 
  36                  'Crash', 
  37   
  38                  # Container that can store Crash objects in a DBM database. 
  39                  'CrashContainer', 
  40   
  41                  # Container that can store Crash objects in a SQLite database. 
  42                  'CrashTable', 
  43   
  44                  # Volatile container that does not store Crash objects. 
  45                  'VolatileCrashContainer', 
  46   
  47                  # Fake Crash container. 
  48                  'DummyCrashContainer', 
  49              ] 
  50   
  51  from system import MemoryAddresses, PathOperations 
  52  from textio import HexDump, CrashDump 
  53  import win32 
  54   
  55  import os 
  56  import time 
  57  import zlib 
  58  import traceback 
  59   
  60  try: 
  61      import cPickle as pickle 
  62  except ImportError: 
  63      import pickle 
  64   
  65  try: 
  66      from pickletools import optimize 
  67  except ImportError: 
68 - def optimize(picklestring):
69 return picklestring
70 71 # lazy imports 72 anydbm = None 73 sqlite = None
74 75 #============================================================================== 76 77 -class Crash (object):
78 """ 79 Represents a crash, bug, or another interesting event in the debugee. 80 81 @group Key: 82 key 83 84 @group Report: 85 briefReport, fullReport, notesReport 86 87 @group Notes: 88 addNote, getNotes, iterNotes, hasNotes, clearNotes 89 90 @type timeStamp: float 91 @ivar timeStamp: Timestamp as returned by time.time(). 92 93 @type notes: list( str ) 94 @ivar notes: List of strings, each string is a note. 95 96 @type eventCode: int 97 @ivar eventCode: Event code as defined by the Win32 API. 98 99 @type eventName: str 100 @ivar eventName: Event code user-friendly name. 101 102 @type pid: int 103 @ivar pid: Process global ID. 104 105 @type tid: int 106 @ivar tid: Thread global ID. 107 108 @type registers: dict( str S{->} int ) 109 @ivar registers: Dictionary mapping register names to their values. 110 111 @type registersPeek: None or dict( str S{->} str ) 112 @ivar registersPeek: Dictionary mapping register names to the data they point to. 113 114 C{None} if unapplicable or unable to retrieve. 115 116 @type labelPC: None or str 117 @ivar labelPC: Label pointing to the program counter. 118 119 C{None} or invalid if unapplicable or unable to retrieve. 120 121 @type debugString: None or str 122 @ivar debugString: Debug string sent by the debugee. 123 124 C{None} if unapplicable or unable to retrieve. 125 126 @type exceptionCode: None or int 127 @ivar exceptionCode: Exception code as defined by the Win32 API. 128 129 C{None} if unapplicable or unable to retrieve. 130 131 @type exceptionName: None or str 132 @ivar exceptionName: Exception code user-friendly name. 133 134 C{None} if unapplicable or unable to retrieve. 135 136 @type exceptionAddress: None or int 137 @ivar exceptionAddress: Memory address where the exception occured. 138 139 C{None} if unapplicable or unable to retrieve. 140 141 @type exceptionLabel: None or str 142 @ivar exceptionLabel: Label pointing to the exception address. 143 144 C{None} or invalid if unapplicable or unable to retrieve. 145 146 @type faultType: None or int 147 @ivar faultType: Access violation type. 148 Only applicable to memory faults. 149 Should be one of the following constants: 150 151 - L{win32.ACCESS_VIOLATION_TYPE_READ} 152 - L{win32.ACCESS_VIOLATION_TYPE_WRITE} 153 - L{win32.ACCESS_VIOLATION_TYPE_DEP} 154 155 C{None} if unapplicable or unable to retrieve. 156 157 @type faultAddress: None or int 158 @ivar faultAddress: Access violation memory address. 159 Only applicable to memory faults. 160 161 C{None} if unapplicable or unable to retrieve. 162 163 @type faultLabel: None or str 164 @ivar faultLabel: Label pointing to the access violation memory address. 165 Only applicable to memory faults. 166 167 C{None} if unapplicable or unable to retrieve. 168 169 @type firstChance: None or bool 170 @ivar firstChance: 171 C{True} for first chance exceptions, C{False} for second chance. 172 173 C{None} if unapplicable or unable to retrieve. 174 175 @type isOurBreakpoint: bool 176 @ivar isOurBreakpoint: 177 C{True} for breakpoints defined by the L{Debug} class, 178 C{False} otherwise. 179 180 C{None} if unapplicable. 181 182 @type isSystemBreakpoint: bool 183 @ivar isSystemBreakpoint: 184 C{True} for known system-defined breakpoints, 185 C{False} otherwise. 186 187 C{None} if unapplicable. 188 189 @type modFileName: None or str 190 @ivar modFileName: File name of module where the program counter points to. 191 192 C{None} or invalid if unapplicable or unable to retrieve. 193 194 @type lpBaseOfDll: None or int 195 @ivar lpBaseOfDll: Base of module where the program counter points to. 196 197 C{None} if unapplicable or unable to retrieve. 198 199 @type stackTrace: None or tuple of tuple( int, int, str ) 200 @ivar stackTrace: 201 Stack trace of the current thread as a tuple of 202 ( frame pointer, return address, module filename ). 203 204 C{None} or empty if unapplicable or unable to retrieve. 205 206 @type stackTracePretty: None or tuple of tuple( int, str ) 207 @ivar stackTracePretty: 208 Stack trace of the current thread as a tuple of 209 ( frame pointer, return location ). 210 211 C{None} or empty if unapplicable or unable to retrieve. 212 213 @type stackTracePC: None or tuple( int... ) 214 @ivar stackTracePC: Tuple of return addresses in the stack trace. 215 216 C{None} or empty if unapplicable or unable to retrieve. 217 218 @type stackTraceLabels: None or tuple( str... ) 219 @ivar stackTraceLabels: 220 Tuple of labels pointing to the return addresses in the stack trace. 221 222 C{None} or empty if unapplicable or unable to retrieve. 223 224 @type stackRange: tuple( int, int ) 225 @ivar stackRange: 226 Stack beginning and end pointers, in memory addresses order. 227 228 C{None} if unapplicable or unable to retrieve. 229 230 @type stackFrame: None or str 231 @ivar stackFrame: Data pointed to by the stack pointer. 232 233 C{None} or empty if unapplicable or unable to retrieve. 234 235 @type stackPeek: None or dict( int S{->} str ) 236 @ivar stackPeek: Dictionary mapping stack offsets to the data they point to. 237 238 C{None} or empty if unapplicable or unable to retrieve. 239 240 @type faultCode: None or str 241 @ivar faultCode: Data pointed to by the program counter. 242 243 C{None} or empty if unapplicable or unable to retrieve. 244 245 @type faultMem: None or str 246 @ivar faultMem: Data pointed to by the exception address. 247 248 C{None} or empty if unapplicable or unable to retrieve. 249 250 @type faultPeek: None or dict( intS{->} str ) 251 @ivar faultPeek: Dictionary mapping guessed pointers at L{faultMem} to the data they point to. 252 253 C{None} or empty if unapplicable or unable to retrieve. 254 255 @type faultDisasm: None or tuple of tuple( long, int, str, str ) 256 @ivar faultDisasm: Dissassembly around the program counter. 257 258 C{None} or empty if unapplicable or unable to retrieve. 259 260 @type memoryMap: None or list of L{win32.MemoryBasicInformation} objects. 261 @ivar memoryMap: Memory snapshot of the program. May contain the actual 262 data from the entire process memory if requested. 263 See L{fetch_extra_data} for more details. 264 265 C{None} or empty if unapplicable or unable to retrieve. 266 """ 267
268 - def __init__(self, event):
269 """ 270 @type event: L{Event} 271 @param event: Event object for crash. 272 """ 273 274 # First of all, take the timestamp. 275 self.timeStamp = time.time() 276 277 # Notes are initially empty. 278 self.notes = list() 279 280 # Get the process and thread, but dont't store them in the DB. 281 process = event.get_process() 282 thread = event.get_thread() 283 284 # The following properties are always retrieved for all events. 285 self.eventCode = event.get_code() 286 self.eventName = event.get_event_name() 287 self.pid = event.get_pid() 288 self.tid = event.get_tid() 289 self.registers = dict(thread.get_context()) 290 self.labelPC = process.get_label_at_address(self.pc) 291 292 # The following properties are only retrieved for some events. 293 self.registersPeek = None 294 self.debugString = None 295 self.exceptionCode = None 296 self.exceptionName = None 297 self.exceptionAddress = None 298 self.faultType = None 299 self.faultAddress = None 300 self.faultLabel = None 301 self.firstChance = None 302 self.isOurBreakpoint = None 303 self.isSystemBreakpoint = None 304 self.modFileName = None 305 self.lpBaseOfDll = None 306 self.exceptionLabel = None 307 self.stackLimits = None 308 self.stackTrace = None 309 self.stackTracePC = None 310 self.stackTraceLabels = None 311 self.stackTracePretty = None 312 self.stackRange = None 313 self.stackFrame = None 314 self.stackPeek = None 315 self.faultCode = None 316 self.faultMem = None 317 self.faultPeek = None 318 self.faultDisasm = None 319 self.memoryMap = None 320 321 # Get information for debug string events. 322 if self.eventCode == win32.OUTPUT_DEBUG_STRING_EVENT: 323 self.debugString = event.get_debug_string() 324 325 # Get information for module load and unload events. 326 # For create and exit process events, get the information 327 # for the main module. 328 elif self.eventCode in (win32.CREATE_PROCESS_DEBUG_EVENT, 329 win32.EXIT_PROCESS_DEBUG_EVENT, 330 win32.LOAD_DLL_DEBUG_EVENT, 331 win32.UNLOAD_DLL_DEBUG_EVENT): 332 aModule = event.get_module() 333 self.modFileName = event.get_filename() 334 if not self.modFileName: 335 self.modFileName = aModule.get_filename() 336 self.lpBaseOfDll = event.get_module_base() 337 if not self.lpBaseOfDll: 338 self.lpBaseOfDll = aModule.get_base() 339 340 # Get some information for exception events. 341 # To get the remaining information call fetch_extra_data(). 342 elif self.eventCode == win32.EXCEPTION_DEBUG_EVENT: 343 344 # Exception information. 345 self.exceptionCode = event.get_exception_code() 346 self.exceptionName = event.get_exception_name() 347 self.exceptionDescription = event.get_exception_description() 348 self.exceptionAddress = event.get_exception_address() 349 self.firstChance = event.is_first_chance() 350 self.exceptionLabel = process.get_label_at_address( 351 self.exceptionAddress) 352 if self.exceptionCode in (win32.EXCEPTION_ACCESS_VIOLATION, 353 win32.EXCEPTION_GUARD_PAGE, 354 win32.EXCEPTION_IN_PAGE_ERROR): 355 self.faultType = event.get_fault_type() 356 self.faultAddress = event.get_fault_address() 357 self.faultLabel = process.get_label_at_address( 358 self.faultAddress) 359 elif self.exceptionCode in (win32.EXCEPTION_BREAKPOINT, 360 win32.EXCEPTION_SINGLE_STEP): 361 self.isOurBreakpoint = hasattr(event, 'breakpoint') \ 362 and event.breakpoint 363 self.isSystemBreakpoint = \ 364 process.is_system_defined_breakpoint(self.exceptionAddress) 365 366 # Stack trace. 367 try: 368 self.stackTracePretty = thread.get_stack_trace_with_labels() 369 except Exception, e: 370 pass 371 try: 372 self.stackTrace = thread.get_stack_trace() 373 stackTracePC = [ ra for (_,ra,_) in self.stackTrace ] 374 self.stackTracePC = tuple(stackTracePC) 375 stackTraceLabels = [ process.get_label_at_address(ra) \ 376 for ra in self.stackTracePC ] 377 self.stackTraceLabels = tuple(stackTraceLabels) 378 except Exception, e: 379 pass
380
381 - def fetch_extra_data(self, event, takeMemorySnapshot = 0):
382 """ 383 Fetch extra data from the L{Event} object. 384 385 @note: This is only needed for exceptions. Since this method may take 386 a little longer to run, it's best to call it only after you've 387 determined the crash is interesting and you want to save it. 388 389 @type event: L{Event} 390 @param event: Event object for crash. 391 392 @type takeMemorySnapshot: int 393 @param takeMemorySnapshot: 394 Memory snapshot behavior: 395 - C{0} to take no memory information (default). 396 - C{1} to take only the memory map. 397 - C{2} to take a full memory snapshot. 398 """ 399 400 # Get the process and thread, but dont't store them in the DB. 401 process = event.get_process() 402 thread = event.get_thread() 403 404 # Get some information for exception events. 405 if self.eventCode == win32.EXCEPTION_DEBUG_EVENT: 406 407 # Data pointed to by registers. 408 self.registersPeek = thread.peek_pointers_in_registers() 409 410 # Module that raised the exception. 411 aModule = process.get_module_at_address(self.pc) 412 if aModule is not None: 413 self.modFileName = aModule.get_filename() 414 self.lpBaseOfDll = aModule.get_base() 415 416 # Contents of the stack frame. 417 try: 418 self.stackRange = thread.get_stack_range() 419 except Exception, e: 420 pass 421 try: 422 self.stackFrame = thread.get_stack_frame() 423 stackFrame = self.stackFrame 424 except Exception, e: 425 self.stackFrame = thread.peek_stack_data() 426 stackFrame = self.stackFrame[:64] 427 if stackFrame: 428 self.stackPeek = process.peek_pointers_in_data(stackFrame) 429 430 # Code that raised the exception. 431 self.faultCode = thread.peek_code_bytes() 432 try: 433 self.faultDisasm = thread.disassemble_around_pc(32) 434 except Exception, e: 435 pass 436 437 # For memory related exceptions, get the memory contents 438 # of the location that caused the exception to be raised. 439 if self.pc != self.exceptionAddress and self.exceptionCode in ( 440 win32.EXCEPTION_ACCESS_VIOLATION, 441 win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 442 win32.EXCEPTION_DATATYPE_MISALIGNMENT, 443 win32.EXCEPTION_IN_PAGE_ERROR, 444 win32.EXCEPTION_STACK_OVERFLOW, 445 win32.EXCEPTION_GUARD_PAGE, 446 ): 447 self.faultMem = process.peek(self.exceptionAddress, 64) 448 if self.faultMem: 449 self.faultPeek = process.peek_pointers_in_data(self.faultMem) 450 451 # Take a snapshot of the process memory. Additionally get the 452 # memory contents if requested. 453 if takeMemorySnapshot == 2: 454 self.memoryMap = process.take_memory_snapshot() 455 elif takeMemorySnapshot == 1: 456 self.memoryMap = process.get_memory_map() 457 mappedFilenames = process.get_mapped_filenames(self.memoryMap) 458 for mbi in self.memoryMap: 459 mbi.filename = mappedFilenames.get(mbi.BaseAddress, None) 460 mbi.content = None
461 462 @property
463 - def pc(self):
464 """ 465 Value of the program counter register. 466 467 @rtype: int 468 """ 469 try: 470 return self.registers['Eip'] # i386 471 except KeyError: 472 return self.registers['Rip'] # amd64
473 474 @property
475 - def sp(self):
476 """ 477 Value of the stack pointer register. 478 479 @rtype: int 480 """ 481 try: 482 return self.registers['Esp'] # i386 483 except KeyError: 484 return self.registers['Rsp'] # amd64
485 486 @property
487 - def fp(self):
488 """ 489 Value of the frame pointer register. 490 491 @rtype: int 492 """ 493 try: 494 return self.registers['Ebp'] # i386 495 except KeyError: 496 return self.registers['Rbp'] # amd64
497
498 - def __str__(self):
499 return self.fullReport()
500
501 - def key(self):
502 """ 503 Generates an approximately unique key for the Crash object. 504 505 This key can be used as an heuristic to determine if two crashes were 506 caused by the same software error. Ideally it should be treated as an 507 opaque object. 508 509 @see: U{http://apps.sourceforge.net/trac/winappdbg/wiki/CrashKey} 510 511 @rtype: (opaque) 512 @return: Crash unique key. 513 """ 514 if self.labelPC: 515 eip = self.labelPC 516 else: 517 eip = self.pc 518 if self.stackTraceLabels: 519 trace = self.stackTraceLabels 520 else: 521 trace = self.stackTracePC 522 return ( 523 self.eventCode, 524 self.exceptionCode, 525 eip, 526 trace, 527 self.debugString, 528 )
529
530 - def isExploitable(self):
531 """ 532 Guess how likely is it that the bug causing the crash can be leveraged 533 into an exploitable vulnerability. 534 535 @note: Don't take this as an equivalent of a real exploitability 536 analysis, that can only be done by a human being! This is only 537 a guideline, useful for example to sort crashes - placing the most 538 interesting ones at the top. 539 540 @see: The heuristics are similar to those of the B{!exploitable} 541 extension for I{WinDBG}, which can be downloaded from here: 542 543 U{http://www.codeplex.com/msecdbg} 544 545 @rtype: tuple( str, str ) 546 @return: The first element of the tuple is the result of the analysis, 547 being one of the following: 548 549 - Not an exception 550 - Not exploitable 551 - Not likely exploitable 552 - Unknown 553 - Probably exploitable 554 - Exploitable 555 556 The second element of the tuple is a code to identify the matched 557 heuristic rule. 558 559 The second element of the tuple is a description string of the 560 reason behind the result. 561 """ 562 563 # Terminal rules 564 565 if self.eventCode != win32.EXCEPTION_DEBUG_EVENT: 566 return ("Not an exception", "NotAnException", "The event is not an exception.") 567 568 if self.stackRange and self.pc is not None and self.stackRange[0] <= self.pc < self.stackRange[1]: 569 return ("Exploitable", "StackCodeExecution", "Code execution from the stack is considered exploitable.") 570 571 # This rule is NOT from !exploitable 572 if self.stackRange and self.sp is not None and not (self.stackRange[0] <= self.sp < self.stackRange[1]): 573 return ("Exploitable", "StackPointerCorruption", "Stack pointer corruption is considered exploitable.") 574 575 # XXX add rule to check if code is in writeable memory 576 577 if self.exceptionCode == win32.EXCEPTION_ILLEGAL_INSTRUCTION: 578 return ("Exploitable", "IllegalInstruction", "An illegal instruction exception indicates that the attacker controls execution flow.") 579 580 if self.exceptionCode == win32.EXCEPTION_PRIV_INSTRUCTION: 581 return ("Exploitable", "PrivilegedInstruction", "A privileged instruction exception indicates that the attacker controls execution flow.") 582 583 if self.exceptionCode == win32.EXCEPTION_GUARD_PAGE: 584 return ("Exploitable", "GuardPage", "A guard page violation indicates a stack overflow has occured, and the stack of another thread was reached (possibly the overflow length is not controlled by the attacker).") 585 586 if self.exceptionCode == win32.STATUS_STACK_BUFFER_OVERRUN: 587 return ("Exploitable", "GSViolation", "An overrun of a protected stack buffer has been detected. This is considered exploitable, and must be fixed.") 588 589 if self.exceptionCode == win32.STATUS_HEAP_CORRUPTION: 590 return ("Exploitable", "HeapCorruption", "Heap Corruption has been detected. This is considered exploitable, and must be fixed.") 591 592 if self.exceptionCode == win32.EXCEPTION_ACCESS_VIOLATION: 593 nearNull = self.faultAddress is None or MemoryAddresses.align_address_to_page_start(self.faultAddress) == win32.NULL 594 controlFlow = self.__is_control_flow() 595 blockDataMove = self.__is_block_data_move() 596 if self.faultType == win32.EXCEPTION_EXECUTE_FAULT: 597 if nearNull: 598 return ("Probably exploitable", "DEPViolation", "User mode DEP access violations are probably exploitable if near NULL.") 599 else: 600 return ("Exploitable", "DEPViolation", "User mode DEP access violations are exploitable.") 601 elif self.faultType == win32.EXCEPTION_WRITE_FAULT: 602 if nearNull: 603 return ("Probably exploitable", "WriteAV", "User mode write access violations that are near NULL are probably exploitable.") 604 else: 605 return ("Exploitable", "WriteAV", "User mode write access violations that are not near NULL are exploitable.") 606 elif self.faultType == win32.EXCEPTION_READ_FAULT: 607 if self.faultAddress == self.pc: 608 if nearNull: 609 return ("Probably exploitable", "ReadAVonIP", "Access violations at the instruction pointer are probably exploitable if near NULL.") 610 else: 611 return ("Exploitable", "ReadAVonIP", "Access violations at the instruction pointer are exploitable if not near NULL.") 612 if controlFlow: 613 if nearNull: 614 return ("Probably exploitable", "ReadAVonControlFlow", "Access violations near null in control flow instructions are considered probably exploitable.") 615 else: 616 return ("Exploitable", "ReadAVonControlFlow", "Access violations not near null in control flow instructions are considered exploitable.") 617 if blockDataMove: 618 return ("Probably exploitable", "ReadAVonBlockMove", "This is a read access violation in a block data move, and is therefore classified as probably exploitable.") 619 620 # Rule: Tainted information used to control branch addresses is considered probably exploitable 621 # Rule: Tainted information used to control the target of a later write is probably exploitable 622 623 # Non terminal rules 624 625 # XXX TODO maybe we should be returning a list of tuples instead? 626 627 result = ("Unknown", "Unknown", "Exploitability unknown.") 628 629 if self.exceptionCode == win32.EXCEPTION_ACCESS_VIOLATION: 630 if self.faultType == win32.EXCEPTION_READ_FAULT: 631 if nearNull: 632 result = ("Not likely exploitable", "ReadAVNearNull", "This is a user mode read access violation near null, and is probably not exploitable.") 633 634 elif self.exceptionCode == win32.EXCEPTION_INT_DIVIDE_BY_ZERO: 635 result = ("Not likely exploitable", "DivideByZero", "This is an integer divide by zero, and is probably not exploitable.") 636 637 elif self.exceptionCode == win32.EXCEPTION_FLT_DIVIDE_BY_ZERO: 638 result = ("Not likely exploitable", "DivideByZero", "This is a floating point divide by zero, and is probably not exploitable.") 639 640 elif self.exceptionCode in (win32.EXCEPTION_BREAKPOINT, win32.STATUS_WX86_BREAKPOINT): 641 result = ("Unknown", "Breakpoint", "While a breakpoint itself is probably not exploitable, it may also be an indication that an attacker is testing a target. In either case breakpoints should not exist in production code.") 642 643 # Rule: If the stack contains unknown symbols in user mode, call that out 644 645 # Rule: Tainted information used to control the source of a later block move unknown, but called out explicitly 646 647 # Rule: Tainted information used as an argument to a function is an unknown risk, but called out explicitly 648 649 # Rule: Tainted information used to control branch selection is an unknown risk, but called out explicitly 650 651 return result
652
653 - def __is_control_flow(self):
654 jump_instructions = ( 655 'jmp', 'jecxz', 'jcxz', 656 'ja', 'jnbe', 'jae', 'jnb', 'jb', 'jnae', 'jbe', 'jna', 'jc', 'je', 657 'jz', 'jnc', 'jne', 'jnz', 'jnp', 'jpo', 'jp', 'jpe', 'jg', 'jnle', 658 'jge', 'jnl', 'jl', 'jnge', 'jle', 'jng', 'jno', 'jns', 'jo', 'js' 659 ) 660 call_instructions = ( 'call', 'ret', 'retn' ) 661 loop_instructions = ( 'loop', 'loopz', 'loopnz', 'loope', 'loopne' ) 662 control_flow_instructions = call_instructions + loop_instructions + \ 663 jump_instructions 664 isControlFlow = False 665 instruction = None 666 if self.pc is not None and self.faultDisasm: 667 for disasm in self.faultDisasm: 668 if disasm[0] == self.pc: 669 instruction = disasm[2].lower().strip() 670 break 671 if instruction: 672 for x in control_flow_instructions: 673 if x in instruction: 674 isControlFlow = True 675 break 676 return isControlFlow
677
678 - def __is_block_data_move(self):
679 block_data_move_instructions = ('movs', 'stos', 'lods') 680 isBlockDataMove = False 681 instruction = None 682 if self.pc is not None and self.faultDisasm: 683 for disasm in self.faultDisasm: 684 if disasm[0] == self.pc: 685 instruction = disasm[2].lower().strip() 686 break 687 if instruction: 688 for x in block_data_move_instructions: 689 if x in instruction: 690 isBlockDataMove = True 691 break 692 return isBlockDataMove
693
694 - def briefReport(self):
695 """ 696 @rtype: str 697 @return: Short description of the event. 698 """ 699 if self.exceptionCode is not None: 700 if self.exceptionCode == win32.EXCEPTION_BREAKPOINT: 701 if self.isOurBreakpoint: 702 what = "Breakpoint hit" 703 elif self.isSystemBreakpoint: 704 what = "System breakpoint hit" 705 else: 706 what = "Assertion failed" 707 elif self.exceptionDescription: 708 what = self.exceptionDescription 709 elif self.exceptionName: 710 what = self.exceptionName 711 else: 712 what = "Exception %s" % HexDump.integer(self.exceptionCode) 713 if self.firstChance: 714 chance = 'first' 715 else: 716 chance = 'second' 717 if self.exceptionLabel: 718 where = self.exceptionLabel 719 elif self.exceptionAddress: 720 where = HexDump.address(self.exceptionAddress) 721 elif self.labelPC: 722 where = self.labelPC 723 else: 724 where = HexDump.address(self.pc) 725 msg = "%s (%s chance) at %s" % (what, chance, where) 726 elif self.debugString is not None: 727 if self.labelPC: 728 where = self.labelPC 729 else: 730 where = HexDump.address(self.pc) 731 msg = "Debug string from %s: %r" % (where, self.debugString) 732 else: 733 if self.labelPC: 734 where = self.labelPC 735 else: 736 where = HexDump.address(self.pc) 737 msg = "%s (%s) at %s" % ( 738 self.eventName, 739 HexDump.integer(self.eventCode), 740 where 741 ) 742 return msg
743
744 - def fullReport(self, bShowNotes = True):
745 """ 746 @type bShowNotes: bool 747 @param bShowNotes: C{True} to show the user notes, C{False} otherwise. 748 749 @rtype: str 750 @return: Long description of the event. 751 """ 752 msg = self.briefReport() 753 msg += '\n' 754 755 if win32.sizeof(win32.LPVOID) == 4: 756 width = 16 757 else: 758 width = 8 759 760 if self.eventCode == win32.EXCEPTION_DEBUG_EVENT: 761 (exploitability, expcode, expdescription) = self.isExploitable() 762 msg += '\nSecurity risk level: %s\n' % exploitability 763 msg += ' %s\n' % expdescription 764 765 if bShowNotes and self.notes: 766 msg += '\nNotes:\n' 767 msg += self.notesReport() 768 769 if not self.labelPC: 770 base = HexDump.address(self.lpBaseOfDll) 771 if self.modFileName: 772 fn = PathOperations.pathname_to_filename(self.modFileName) 773 msg += '\nRunning in %s (%s)\n' % (fn, base) 774 else: 775 msg += '\nRunning in module at %s\n' % base 776 777 if self.registers: 778 msg += '\nRegisters:\n' 779 msg += CrashDump.dump_registers(self.registers) 780 if self.registersPeek: 781 msg += '\n' 782 msg += CrashDump.dump_registers_peek(self.registers, 783 self.registersPeek, 784 width = width) 785 786 if self.faultDisasm: 787 msg += '\nCode disassembly:\n' 788 msg += CrashDump.dump_code(self.faultDisasm, self.pc) 789 790 if self.stackTrace: 791 msg += '\nStack trace:\n' 792 if self.stackTracePretty: 793 msg += CrashDump.dump_stack_trace_with_labels( 794 self.stackTracePretty) 795 else: 796 msg += CrashDump.dump_stack_trace(self.stackTrace) 797 798 if self.stackFrame: 799 if self.stackPeek: 800 msg += '\nStack pointers:\n' 801 msg += CrashDump.dump_stack_peek(self.stackPeek, width = width) 802 msg += '\nStack dump:\n' 803 msg += HexDump.hexblock(self.stackFrame, self.sp, width = width) 804 805 if self.faultCode and not self.modFileName: 806 msg += '\nCode dump:\n' 807 msg += HexDump.hexblock(self.faultCode, self.pc, width = width) 808 809 if self.faultMem: 810 if self.faultPeek: 811 msg += '\nException address pointers:\n' 812 msg += CrashDump.dump_data_peek(self.faultPeek, 813 self.exceptionAddress, 814 width = width) 815 msg += '\nException address dump:\n' 816 msg += HexDump.hexblock(self.faultMem, self.exceptionAddress, 817 width = width) 818 819 if self.memoryMap: 820 msg += '\nMemory map:\n' 821 mappedFileNames = dict() 822 for mbi in self.memoryMap: 823 if hasattr(mbi, 'filename') and mbi.filename: 824 mappedFileNames[mbi.BaseAddress] = mbi.filename 825 msg += CrashDump.dump_memory_map(self.memoryMap, mappedFileNames) 826 827 if not msg.endswith('\n\n'): 828 if not msg.endswith('\n'): 829 msg += '\n' 830 msg += '\n' 831 return msg
832
833 - def notesReport(self):
834 """ 835 @rtype: str 836 @return: All notes, merged and formatted for a report. 837 """ 838 msg = '' 839 if self.notes: 840 for n in self.notes: 841 n = n.strip('\n') 842 if '\n' in n: 843 n = n.strip('\n') 844 msg += ' * %s\n' % n.pop(0) 845 for x in n: 846 msg += ' %s\n' % x 847 else: 848 msg += ' * %s\n' % n 849 return msg
850
851 - def addNote(self, msg):
852 """ 853 Add a note to the crash event. 854 855 @type msg: str 856 @param msg: Note text. 857 """ 858 self.notes.append(msg)
859
860 - def clearNotes(self):
861 """ 862 Clear the notes of this crash event. 863 """ 864 self.notes = list()
865
866 - def getNotes(self):
867 """ 868 Get the list of notes of this crash event. 869 870 @rtype: list( str ) 871 @return: List of notes. 872 """ 873 return self.notes
874
875 - def iterNotes(self):
876 """ 877 Iterate the notes of this crash event. 878 879 @rtype: listiterator 880 @return: Iterator of the list of notes. 881 """ 882 return self.notes.__iter__()
883
884 - def hasNotes(self):
885 """ 886 @rtype: bool 887 @return: C{True} if there are notes for this crash event. 888 """ 889 return bool( self.notes )
890
891 #============================================================================== 892 893 -class CrashContainer (object):
894 """ 895 Manages a database of persistent Crash objects, trying to avoid duplicates. 896 897 Uses a DBM database file for persistency. 898 899 @see: L{Crash.key} 900 """ 901 902 # The interface is meant to be similar to a Python set. 903 # However it may not be necessary to implement all of the set methods. 904 # Other methods like get, has_key, iterkeys and itervalues 905 # are dictionary-like. 906
907 - class __CrashContainerIterator (object):
908 """ 909 Iterator of Crash objects. Returned by L{CrashContainer.__iter__}. 910 """ 911
912 - def __init__(self, container):
913 """ 914 @type container: L{CrashContainer} 915 @param container: Crash set to iterate. 916 """ 917 # It's important to keep a reference to the CrashContainer, 918 # rather than it's underlying database. 919 # Otherwise the destructor of CrashContainer may close the 920 # database while we're still iterating it. 921 # 922 # TODO: lock the database when iterating it. 923 # 924 self.__container = container 925 self.__keys_iter = container.iterkeys()
926
927 - def next(self):
928 """ 929 @rtype: L{Crash} 930 @return: A B{copy} of a Crash object in the L{CrashContainer}. 931 @raise StopIteration: No more items left. 932 """ 933 key = self.__keys_iter.next() 934 return self.__container.get(key)
935
936 - def __init__(self, filename = None, allowRepeatedKeys = False):
937 """ 938 @type filename: str 939 @param filename: (Optional) File name for crash database. 940 If no filename is specified, the container is volatile. 941 942 Volatile containers are stored only in memory and 943 destroyed when they go out of scope. 944 """ 945 ## 946 ## @type allowRepeatedKeys: bool 947 ## @param allowRepeatedKeys: 948 ## If C{True} all L{Crash} objects are stored. 949 ## 950 ## If C{False} any L{Crash} object with the same key as a 951 ## previously existing object will be ignored. 952 if allowRepeatedKeys: 953 raise NotImplementedError 954 self.__filename = filename 955 if filename: 956 global anydbm 957 if not anydbm: 958 import anydbm 959 self.__db = anydbm.open(filename, 'c') 960 self.__keys = dict([ (self.__unmarshall_key(mk), mk) \ 961 for mk in self.__db.keys() ]) 962 else: 963 self.__db = dict() 964 self.__keys = dict()
965
966 - def __del__(self):
967 try: 968 if self.__filename: 969 self.__db.close() 970 except: 971 pass
972
973 - def __contains__(self, crash):
974 """ 975 @type crash: L{Crash} 976 @param crash: Crash object. 977 978 @rtype: bool 979 @return: C{True} if the Crash object is in the container. 980 """ 981 return self.__keys.has_key( crash.key() )
982
983 - def __iter__(self):
984 """ 985 @see: L{itervalues} 986 @rtype: iterator 987 @return: Iterator of the contained L{Crash} objects. 988 """ 989 return self.itervalues()
990
991 - def __len__(self):
992 """ 993 @rtype: int 994 @return: Count of L{Crash} elements in the container. 995 """ 996 return len(self.__keys)
997
998 - def __bool__(self):
999 """ 1000 @rtype: bool 1001 @return: C{False} if the container is empty. 1002 """ 1003 return bool(self.__keys)
1004
1005 - def has_key(self, key):
1006 """ 1007 @type key: L{Crash} unique key. 1008 @param key: Key of the crash to get. 1009 1010 @rtype: bool 1011 @return: C{True} if a matching L{Crash} object is in the container. 1012 """ 1013 return key in self.__keys
1014
1015 - def iterkeys(self):
1016 """ 1017 @rtype: iterator 1018 @return: Iterator of the contained L{Crash} object keys. 1019 1020 @see: L{get} 1021 @warning: A B{copy} of each object is returned, 1022 so any changes made to them will be lost. 1023 1024 To preserve changes do the following: 1025 1. Keep a reference to the object. 1026 2. Delete the object from the set. 1027 3. Modify the object and add it again. 1028 """ 1029 return self.__keys.iterkeys()
1030
1031 - def itervalues(self):
1032 """ 1033 @rtype: iterator 1034 @return: Iterator of the contained L{Crash} objects. 1035 1036 @warning: A B{copy} of each object is returned, 1037 so any changes made to them will be lost. 1038 1039 To preserve changes do the following: 1040 1. Keep a reference to the object. 1041 2. Delete the object from the set. 1042 3. Modify the object and add it again. 1043 """ 1044 return self.__CrashContainerIterator(self)
1045
1046 - def add(self, crash):
1047 """ 1048 Adds a new crash to the container. 1049 If the crash appears to be already known, it's ignored. 1050 1051 @see: L{Crash.key} 1052 1053 @type crash: L{Crash} 1054 @param crash: Crash object to add. 1055 """ 1056 if crash not in self: 1057 key = crash.key() 1058 skey = self.__marshall_key(key) 1059 data = self.__marshall_value(crash) 1060 self.__db[skey] = data 1061 self.__keys[key] = skey
1062
1063 - def remove(self, crash):
1064 """ 1065 Removes a crash from the container. 1066 1067 @type crash: L{Crash} 1068 @param crash: Crash object to remove. 1069 """ 1070 key = crash.key() 1071 skey = self.__keys[key] 1072 del self.__db[skey] 1073 del self.__keys[key]
1074
1075 - def get(self, key):
1076 """ 1077 Retrieves a crash from the container. 1078 1079 @type key: L{Crash} unique key. 1080 @param key: Key of the crash to get. 1081 1082 @rtype: L{Crash} object. 1083 @return: Crash matching the given key. 1084 1085 @see: L{iterkeys} 1086 @warning: A B{copy} of each object is returned, 1087 so any changes made to them will be lost. 1088 1089 To preserve changes do the following: 1090 1. Keep a reference to the object. 1091 2. Delete the object from the set. 1092 3. Modify the object and add it again. 1093 """ 1094 skey = self.__keys[key] 1095 data = self.__db[skey] 1096 crash = self.__unmarshall_value(data) 1097 return crash
1098
1099 - def __marshall_key(self, key):
1100 """ 1101 Marshalls a Crash key to be used in the database. 1102 1103 @type key: (opaque object) 1104 @param key: Key to convert. 1105 1106 @rtype: str 1107 @return: Converted key. 1108 """ 1109 if key in self.__keys: 1110 return self.__keys[key] 1111 key = pickle.dumps(key, protocol = pickle.HIGHEST_PROTOCOL) 1112 key = optimize(key) 1113 return key
1114
1115 - def __unmarshall_key(self, key):
1116 """ 1117 Unmarshalls a Crash key read from the database. 1118 1119 @type key: str 1120 @param key: Key to convert. 1121 1122 @rtype: (opaque object) 1123 @return: Converted key. 1124 """ 1125 return pickle.loads(key)
1126
1127 - def __marshall_value(self, value):
1128 """ 1129 Marshalls a Crash object to be used in the database. 1130 1131 @type value: L{Crash} 1132 @param value: Object to convert. 1133 1134 @rtype: str 1135 @return: Converted object. 1136 """ 1137 value = pickle.dumps(value, protocol = pickle.HIGHEST_PROTOCOL) 1138 value = optimize(value) 1139 return zlib.compress(value, zlib.Z_BEST_COMPRESSION)
1140
1141 - def __unmarshall_value(self, value):
1142 """ 1143 Unmarshalls a Crash object read from the database. 1144 1145 @type value: str 1146 @param value: Object to convert. 1147 1148 @rtype: L{Crash} 1149 @return: Converted object. 1150 """ 1151 value = zlib.decompress(value) 1152 return pickle.loads(value)
1153
1154 #============================================================================== 1155 1156 -class CrashTable (object):
1157 """ 1158 Manages a database of persistent Crash objects, trying to avoid duplicates 1159 only when requested. 1160 1161 Uses a SQLite database file for persistency. 1162 1163 @see: L{Crash.key} 1164 """ 1165 1166 __table_definition = ( 1167 "CREATE TABLE Crashes (" 1168 1169 # Sequential row IDs. 1170 "id INTEGER PRIMARY KEY," 1171 1172 # These are the bare minimum columns required to store the objects. 1173 # The rest are just for convenience. 1174 "timeStamp TIMESTAMP," # float converted to GMT timestamp 1175 "key BLOB," # the pickled key 1176 "pickle BLOB," # the pickled object 1177 1178 # Exploitability test. 1179 "isExploitable TEXT," # the result 1180 "isExploitableRule TEXT," # the matched rule 1181 1182 # Event description. 1183 "eventCode INTEGER," 1184 "pid INTEGER," 1185 "tid INTEGER," 1186 "pc INTEGER," 1187 "sp INTEGER," 1188 "fp INTEGER," 1189 "labelPC TEXT," 1190 1191 # Exception description. 1192 "exceptionCode INTEGER," 1193 "exceptionAddress INTEGER," 1194 "exceptionLabel TEXT," 1195 "firstChance INTEGER," # 0 or 1 1196 "faultType INTEGER," 1197 "faultAddress INTEGER," 1198 "faultLabel TEXT," 1199 "faultDisasm TEXT," # dumped 1200 "stackTrace TEXT," # dumped stackTracePretty 1201 1202 # Additional notes. 1203 "notes TEXT" # joined 1204 ")" 1205 ) 1206 __insert_row = ( 1207 "INSERT INTO Crashes VALUES (null%s)" 1208 % (',?' * (len(__table_definition.split(',')) - 1)) 1209 ) 1210 1211 __select_pickle = "SELECT pickle FROM Crashes" 1212 __select_key = "SELECT key FROM Crashes" 1213 __select_count = "SELECT COUNT(*) FROM Crashes" 1214 1215 __memory_table_definition = ( 1216 "CREATE TABLE Memory (" 1217 "id INTEGER PRIMARY KEY," # Sequential row IDs. 1218 "Crash INTEGER," # Row ID in the Crashes table. 1219 "Address INTEGER," # Value of mbi.BaseAddress. 1220 "Size INTEGER," # Value of mbi.RegionSize. 1221 "State TEXT," # Value of mbi.State. 1222 "Access TEXT," # Value of mbi.Protect. 1223 "Type TEXT," # Value of mbi.Type. 1224 "File TEXT," # Value of mbi.filename. 1225 "Data BLOB" # Value of mbi.content (compressed?). 1226 ")" 1227 ) 1228 __memory_insert_row = ( 1229 "INSERT INTO Memory VALUES " 1230 "(null, ?, ?, ?, ?, ?, ?, ?, ?)" 1231 ) 1232
1233 - def __get_row_values(self, crash):
1234 timeStamp = time.asctime( time.gmtime( crash.timeStamp ) ) 1235 key = self.__marshall_key(crash.key()) 1236 pickle = self.__marshall_value(crash) 1237 isExploitable, isExploitableRule, _ = crash.isExploitable() 1238 eventCode = crash.eventCode 1239 pid = crash.pid 1240 tid = crash.tid 1241 pc = crash.pc 1242 sp = crash.sp 1243 fp = crash.fp 1244 labelPC = crash.labelPC 1245 exceptionCode = crash.exceptionCode 1246 exceptionAddress = crash.exceptionAddress 1247 exceptionLabel = crash.exceptionLabel 1248 firstChance = crash.firstChance # int(bool(crash.firstChance)) 1249 faultType = crash.faultType 1250 faultAddress = crash.faultAddress 1251 faultLabel = crash.faultLabel 1252 faultDisasm = CrashDump.dump_code(crash.faultDisasm, crash.pc) 1253 stackTrace = CrashDump.dump_stack_trace_with_labels( 1254 crash.stackTracePretty) 1255 notes = crash.notesReport() 1256 return ( 1257 timeStamp, 1258 key, 1259 pickle, 1260 isExploitable, 1261 isExploitableRule, 1262 eventCode, 1263 pid, 1264 tid, 1265 pc, 1266 sp, 1267 fp, 1268 labelPC, 1269 exceptionCode, 1270 exceptionAddress, 1271 exceptionLabel, 1272 firstChance, 1273 faultType, 1274 faultAddress, 1275 faultLabel, 1276 faultDisasm, 1277 stackTrace, 1278 notes, 1279 )
1280
1281 - def __memory_get_row_values(self, CrashID, mbi):
1282 1283 # State (free or allocated). 1284 if mbi.State == win32.MEM_RESERVE: 1285 State = "Reserved" 1286 elif mbi.State == win32.MEM_COMMIT: 1287 State = "Commited" 1288 elif mbi.State == win32.MEM_FREE: 1289 State = "Free" 1290 else: 1291 State = "Unknown" 1292 1293 # Page protection bits (R/W/X/G). 1294 if mbi.State != win32.MEM_COMMIT: 1295 Protect = "" 1296 else: 1297 if mbi.Protect & win32.PAGE_NOACCESS: 1298 Protect = "--- " 1299 elif mbi.Protect & win32.PAGE_READONLY: 1300 Protect = "R-- " 1301 elif mbi.Protect & win32.PAGE_READWRITE: 1302 Protect = "RW- " 1303 elif mbi.Protect & win32.PAGE_WRITECOPY: 1304 Protect = "RC- " 1305 elif mbi.Protect & win32.PAGE_EXECUTE: 1306 Protect = "--X " 1307 elif mbi.Protect & win32.PAGE_EXECUTE_READ: 1308 Protect = "R-- " 1309 elif mbi.Protect & win32.PAGE_EXECUTE_READWRITE: 1310 Protect = "RW- " 1311 elif mbi.Protect & win32.PAGE_EXECUTE_WRITECOPY: 1312 Protect = "RCX " 1313 else: 1314 Protect = "??? " 1315 if mbi.Protect & win32.PAGE_GUARD: 1316 Protect += "G" 1317 else: 1318 Protect += "-" 1319 if mbi.Protect & win32.PAGE_NOCACHE: 1320 Protect += "N" 1321 else: 1322 Protect += "-" 1323 if mbi.Protect & win32.PAGE_WRITECOMBINE: 1324 Protect += "W" 1325 else: 1326 Protect += "-" 1327 1328 # Type (file mapping, executable image, or private memory). 1329 if mbi.Type == win32.MEM_IMAGE: 1330 Type = "Image" 1331 elif mbi.Type == win32.MEM_MAPPED: 1332 Type = "Mapped" 1333 elif mbi.Type == win32.MEM_PRIVATE: 1334 Type = "Private" 1335 elif mbi.Type == 0: 1336 Type = "" 1337 else: 1338 Type = "Unknown" 1339 1340 # Memory contents. 1341 content = mbi.content 1342 if not content: 1343 content = '' 1344 else: 1345 ## content = zlib.compress(content, zlib.Z_BEST_COMPRESSION) 1346 pass 1347 content = sqlite.Binary(content) 1348 1349 # Return a tuple to pass to Cursor.execute(). 1350 return ( 1351 CrashID, 1352 mbi.BaseAddress, 1353 mbi.RegionSize, 1354 State, 1355 Protect, 1356 Type, 1357 mbi.filename, 1358 content, 1359 )
1360
1361 - def __init__(self, location = None, allowRepeatedKeys = True):
1362 """ 1363 @type location: str 1364 @param location: (Optional) Location of the crash database. 1365 If no location is specified, the container is volatile. 1366 1367 If the location is a filename, it's an SQLite database file. 1368 1369 Volatile containers are stored only in memory and 1370 destroyed when they go out of scope. 1371 1372 @type allowRepeatedKeys: bool 1373 @param allowRepeatedKeys: 1374 If C{True} all L{Crash} objects are stored. 1375 1376 If C{False} any L{Crash} object with the same key as a 1377 previously existing object will be ignored. 1378 """ 1379 1380 # Import sqlite if needed. 1381 global sqlite 1382 if sqlite is None: 1383 try: 1384 import sqlite3 as sqlite 1385 except ImportError: 1386 from pysqlite2 import dbapi2 as sqlite 1387 1388 # If no location is given store the database in memory. 1389 if not location: 1390 location = ':memory:' 1391 self.__location = location 1392 1393 # Connect to the database and get a cursor. 1394 self.__db = sqlite.connect(self.__location) 1395 self.__cursor = self.__db.cursor() 1396 1397 # Create the tables if needed. 1398 try: 1399 self.__cursor.execute(self.__table_definition) 1400 self.__cursor.execute(self.__memory_table_definition) 1401 self.__db.commit() 1402 except Exception: 1403 pass 1404 1405 # Populate the cache of existing keys. 1406 self.__allowRepeatedKeys = allowRepeatedKeys 1407 self.__keys = dict() 1408 self.__cursor.execute(self.__select_key) 1409 for row in self.__cursor: 1410 marshalled_key = row[0] 1411 unmarshalled_key = self.__unmarshall_key(marshalled_key) 1412 self.__keys[unmarshalled_key] = marshalled_key 1413 1414 # Get the number of crashes stored in the database. 1415 self.__cursor.execute(self.__select_count) 1416 count = 0 1417 for row in self.__cursor: 1418 count = long(row[0]) 1419 self.__count = count
1420
1421 - def add(self, crash):
1422 """ 1423 Adds a new crash to the container. 1424 1425 @note: 1426 When the C{allowRepeatedKeys} parameter of the constructor 1427 is set to C{False}, duplicated crashes are ignored. 1428 1429 @see: L{Crash.key} 1430 1431 @type crash: L{Crash} 1432 @param crash: Crash object to add. 1433 """ 1434 1435 # Add the key to the keys cache. 1436 # Filter out by key if requested. 1437 key = crash.key() 1438 if self.__allowRepeatedKeys or key not in self.__keys: 1439 self.__keys[key] = self.__marshall_key(key) 1440 1441 # Insert the row into the table. 1442 self.__cursor.execute(self.__insert_row, 1443 self.__get_row_values(crash)) 1444 1445 # Save the memory snapshot, if any. 1446 if hasattr(crash, 'memoryMap') and crash.memoryMap: 1447 cid = self.__cursor.lastrowid 1448 for mbi in crash.memoryMap: 1449 self.__cursor.execute(self.__memory_insert_row, 1450 self.__memory_get_row_values(cid, mbi)) 1451 1452 # Commit the changes to the database. 1453 self.__db.commit() 1454 1455 # Increment the counter of crashes. 1456 self.__count += 1
1457
1458 - def __iter__(self):
1459 """ 1460 @rtype: iterator 1461 @return: Iterator of the contained L{Crash} objects. 1462 """ 1463 self.__cursor.execute(self.__select_pickle) 1464 for row in self.__cursor: 1465 crash = row[0] 1466 crash = self.__unmarshall_value(crash) 1467 yield crash
1468
1469 - def __contains__(self, crash):
1470 """ 1471 @type crash: L{Crash} 1472 @param crash: Crash object. 1473 1474 @rtype: bool 1475 @return: C{True} if the Crash object is in the container. 1476 """ 1477 return self.__keys.has_key( crash.key() )
1478
1479 - def __len__(self):
1480 """ 1481 @rtype: int 1482 @return: Count of L{Crash} elements in the container. 1483 """ 1484 return self.__count
1485
1486 - def __bool__(self):
1487 """ 1488 @rtype: bool 1489 @return: C{False} if the container is empty. 1490 """ 1491 # XXX HACK 1492 # Check if the keys cache is empty instead of querying the database. 1493 return bool(self.__keys)
1494
1495 - def __marshall_key(self, key):
1496 """ 1497 Marshalls a Crash key to be used in the database. 1498 1499 @type key: (opaque object) 1500 @param key: Key to convert. 1501 1502 @rtype: BLOB 1503 @return: Converted key. 1504 """ 1505 if key in self.__keys: 1506 return self.__keys[key] 1507 key = pickle.dumps(key, protocol = pickle.HIGHEST_PROTOCOL) 1508 key = optimize(key) 1509 key = sqlite.Binary(key) 1510 return key
1511
1512 - def __unmarshall_key(self, key):
1513 """ 1514 Unmarshalls a Crash key read from the database. 1515 1516 @type key: str 1517 @param key: Key to convert. 1518 1519 @rtype: (opaque object) 1520 @return: Converted key. 1521 """ 1522 key = str(key) 1523 key = pickle.loads(key) 1524 return key
1525
1526 - def __marshall_value(self, value):
1527 """ 1528 Marshalls a Crash object to be used in the database. 1529 The C{memoryMap} member is B{NOT} stored here. 1530 1531 @type value: L{Crash} 1532 @param value: Object to convert. 1533 1534 @rtype: BLOB 1535 @return: Converted object. 1536 """ 1537 crash = value 1538 memoryMap = crash.memoryMap 1539 try: 1540 crash.memoryMap = None 1541 value = pickle.dumps(value, protocol = pickle.HIGHEST_PROTOCOL) 1542 finally: 1543 crash.memoryMap = memoryMap 1544 del memoryMap 1545 del crash 1546 value = optimize(value) 1547 value = zlib.compress(value, zlib.Z_BEST_COMPRESSION) 1548 value = sqlite.Binary(value) 1549 return value
1550
1551 - def __unmarshall_value(self, value):
1552 """ 1553 Unmarshalls a Crash object read from the database. 1554 1555 @type value: str 1556 @param value: Object to convert. 1557 1558 @rtype: L{Crash} 1559 @return: Converted object. 1560 """ 1561 value = str(value) 1562 value = zlib.decompress(value) 1563 value = pickle.loads(value) 1564 return value
1565
1566 #============================================================================== 1567 1568 -class VolatileCrashContainer(CrashContainer):
1569 """ 1570 Manages a database of volatile Crash objects, 1571 trying to avoid duplicates if requested. 1572 1573 @see: L{Crash.key} 1574 """ 1575
1576 - def __init__(self, allowRepeatedKeys = True):
1577 """ 1578 Volatile containers are stored only in memory and 1579 destroyed when they go out of scope. 1580 1581 @type allowRepeatedKeys: bool 1582 @param allowRepeatedKeys: 1583 If C{True} all L{Crash} objects are stored. 1584 1585 If C{False} any L{Crash} object with the same key as a 1586 previously existing object will be ignored. 1587 """ 1588 super(VolatileCrashContainer, self).__init__() 1589 self.__allowRepeatedKeys = allowRepeatedKeys 1590 self.__dict = dict() 1591 self.__set = set()
1592
1593 - def __contains__(self, crash):
1594 """ 1595 @type crash: L{Crash} 1596 @param crash: Crash object. 1597 1598 @rtype: bool 1599 @return: C{True} if the Crash object is in the container. 1600 """ 1601 return self.__dict.has_key( crash.key() )
1602
1603 - def __iter__(self):
1604 """ 1605 @see: L{itervalues} 1606 @rtype: iterator 1607 @return: Iterator of the contained L{Crash} objects. 1608 """ 1609 return self.itervalues()
1610
1611 - def __len__(self):
1612 """ 1613 @rtype: int 1614 @return: Count of L{Crash} elements in the container. 1615 """ 1616 return len(self.__set)
1617
1618 - def __bool__(self):
1619 """ 1620 @rtype: bool 1621 @return: C{False} if the container is empty. 1622 """ 1623 return bool(len(self))
1624
1625 - def add(self, crash):
1626 """ 1627 Adds a new crash to the container. 1628 1629 @note: 1630 When the C{allowRepeatedKeys} parameter of the constructor 1631 is set to C{False}, duplicated crashes are ignored. 1632 1633 @see: L{Crash.key} 1634 1635 @type crash: L{Crash} 1636 @param crash: Crash object to add. 1637 """ 1638 key = crash.key() 1639 if self.__allowRepeatedKeys or key not in self.__dict: 1640 self.__dict[key] = crash 1641 self.__set.add(crash)
1642
1643 - def has_key(self, key):
1644 """ 1645 @type key: L{Crash} unique key. 1646 @param key: Key of the crash to get. 1647 1648 @rtype: bool 1649 @return: C{True} if a matching L{Crash} object is in the container. 1650 """ 1651 return self.__dict.has_key(key)
1652
1653 - def iterkeys(self):
1654 """ 1655 @rtype: iterator 1656 @return: Iterator of the contained L{Crash} object keys. 1657 1658 @see: L{get} 1659 @warning: A B{copy} of each object is returned, 1660 so any changes made to them will be lost. 1661 1662 To preserve changes do the following: 1663 1. Keep a reference to the object. 1664 2. Delete the object from the set. 1665 3. Modify the object and add it again. 1666 """ 1667 return self.__dict.iterkeys()
1668
1669 - def itervalues(self):
1670 """ 1671 @rtype: iterator 1672 @return: Iterator of the contained L{Crash} objects. 1673 1674 @warning: A B{copy} of each object is returned, 1675 so any changes made to them will be lost. 1676 1677 To preserve changes do the following: 1678 1. Keep a reference to the object. 1679 2. Delete the object from the set. 1680 3. Modify the object and add it again. 1681 """ 1682 return iter(self.__set)
1683
1684 #============================================================================== 1685 1686 -class DummyCrashContainer(CrashContainer):
1687 """ 1688 Fakes a database of volatile Crash objects, 1689 trying to mimic part of it's interface. 1690 1691 @see: L{Crash.key} 1692 """ 1693
1694 - def __init__(self, allowRepeatedKeys = True):
1695 """ 1696 Fake containers don't store L{Crash} objects, but they implement the 1697 interface properly. 1698 1699 @type allowRepeatedKeys: bool 1700 @param allowRepeatedKeys: 1701 If C{True} the len() of the container returns the total number 1702 of L{Crash} objects added. 1703 1704 If C{False} the len() of the container returns the total number 1705 of L{Crash} objects keys added. 1706 """ 1707 super(DummyCrashContainer, self).__init__() 1708 self.__keys = set() 1709 self.__count = 0 1710 self.__allowRepeatedKeys = allowRepeatedKeys
1711
1712 - def __contains__(self, crash):
1713 """ 1714 @type crash: L{Crash} 1715 @param crash: Crash object. 1716 1717 @rtype: bool 1718 @return: C{True} if the Crash object is in the container. 1719 """ 1720 return crash.key() in self.__keys
1721
1722 - def __len__(self):
1723 """ 1724 @rtype: int 1725 @return: Count of L{Crash} elements in the container. 1726 """ 1727 if self.__allowRepeatedKeys: 1728 return self.__count 1729 return len(self.__keys)
1730
1731 - def __bool__(self):
1732 """ 1733 @rtype: bool 1734 @return: C{False} if the container is empty. 1735 """ 1736 return bool(len(self))
1737
1738 - def add(self, crash):
1739 """ 1740 Adds a new crash to the container. 1741 1742 @note: 1743 When the C{allowRepeatedKeys} parameter of the constructor 1744 is set to C{False}, duplicated crashes are ignored. 1745 1746 @see: L{Crash.key} 1747 1748 @type crash: L{Crash} 1749 @param crash: Crash object to add. 1750 """ 1751 self.__keys.add( crash.key() ) 1752 self.__count += 1
1753
1754 - def has_key(self, key):
1755 """ 1756 @type key: L{Crash} unique key. 1757 @param key: Key of the crash to get. 1758 1759 @rtype: bool 1760 @return: C{True} if a matching L{Crash} object is in the container. 1761 """ 1762 return self.__dict.has_key(key)
1763
1764 - def iterkeys(self):
1765 """ 1766 @rtype: iterator 1767 @return: Iterator of the contained L{Crash} object keys. 1768 1769 @see: L{get} 1770 @warning: A B{copy} of each object is returned, 1771 so any changes made to them will be lost. 1772 1773 To preserve changes do the following: 1774 1. Keep a reference to the object. 1775 2. Delete the object from the set. 1776 3. Modify the object and add it again. 1777 """ 1778 return iter(self.__dict)
1779