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

Source Code for Module winappdbg.textio

   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  Functions for text input, logging or text output. 
  30   
  31  @group Input: 
  32      HexInput 
  33  @group Output: 
  34      HexOutput 
  35  @group Logging: 
  36      HexDump, 
  37      CrashDump, 
  38      DebugLog 
  39  """ 
  40   
  41  __revision__ = "$Id: textio.py 495 2009-12-05 06:54:05Z qvasimodo $" 
  42   
  43  __all__ =   [ 
  44                  'HexDump', 
  45                  'HexInput', 
  46                  'HexOutput', 
  47                  'Table', 
  48                  'CrashDump', 
  49                  'DebugLog', 
  50                  'Logger', 
  51              ] 
  52   
  53  import win32 
  54   
  55  import time 
  56  import struct 
  57  import traceback 
58 59 #------------------------------------------------------------------------------ 60 61 -class HexInput (object):
62 """ 63 Static functions for user input parsing. 64 The counterparts for each method are in the L{HexOutput} class. 65 """ 66 67 @staticmethod
68 - def integer(token):
69 """ 70 Convert numeric strings into integers. 71 72 @type token: str 73 @param token: String to parse. 74 75 @rtype: int 76 @return: Parsed integer value. 77 """ 78 token = token.strip() 79 if token.startswith('0x'): 80 result = int(token, 16) # hexadecimal 81 elif token.startswith('0b'): 82 result = int(token[2:], 2) # binary 83 ## elif token.startswith('0'): 84 ## result = int(token, 8) # octal 85 else: 86 try: 87 result = int(token) # decimal 88 except ValueError: 89 result = int(token, 16) # hexadecimal (no "0x" prefix) 90 return result
91 92 @staticmethod
93 - def address(token):
94 """ 95 Convert numeric strings into memory addresses. 96 97 @type token: str 98 @param token: String to parse. 99 100 @rtype: int 101 @return: Parsed integer value. 102 """ 103 return int(token, 16)
104 105 @staticmethod
106 - def hexadecimal(token):
107 """ 108 Convert a strip of hexadecimal numbers into binary data. 109 110 @type token: str 111 @param token: String to parse. 112 113 @rtype: str 114 @return: Parsed string value. 115 """ 116 token = ''.join([ c for c in token if c.isalnum() ]) 117 if len(token) % 2 != 0: 118 raise ValueError, "Missing characters in hex data" 119 data = '' 120 for i in xrange(0, len(token), 2): 121 x = token[i:i+2] 122 d = int(x, 16) 123 s = struct.pack('<B', d) 124 data += s 125 return data
126 127 @staticmethod
128 - def pattern(token):
129 """ 130 Convert an hexadecimal search pattern into a POSIX regular expression. 131 132 For example, the following pattern:: 133 134 "B8 0? ?0 ?? ??" 135 136 Would match the following data:: 137 138 "B8 0D F0 AD BA" # mov eax, 0xBAADF00D 139 140 @type token: str 141 @param token: String to parse. 142 143 @rtype: str 144 @return: Parsed string value. 145 """ 146 token = ''.join([ c for c in token if c == '?' or c.isalnum() ]) 147 if len(token) % 2 != 0: 148 raise ValueError, "Missing characters in hex data" 149 regexp = '' 150 for i in xrange(0, len(token), 2): 151 x = token[i:i+2] 152 if x == '??': 153 regexp += '.' 154 elif x[0] == '?': 155 f = '\\x%%.1x%s' % x[1] 156 x = ''.join([ f % c for c in xrange(0, 0x10) ]) 157 regexp = '%s[%s]' % (regexp, x) 158 elif x[1] == '?': 159 f = '\\x%s%%.1x' % x[0] 160 x = ''.join([ f % c for c in xrange(0, 0x10) ]) 161 regexp = '%s[%s]' % (regexp, x) 162 else: 163 regexp = '%s\\x%s' % (regexp, x) 164 return regexp
165 166 @classmethod
167 - def integer_list_file(cls, filename):
168 """ 169 Read a list of integers from a file. 170 171 The file format is: 172 173 - # anywhere in the line begins a comment 174 - leading and trailing spaces are ignored 175 - empty lines are ignored 176 - integers can be specified as: 177 - decimal numbers ("100" is 100) 178 - hexadecimal numbers ("0x100" is 256) 179 - binary numbers ("0b100" is 4) 180 - octal numbers ("0100" is 64) 181 182 @type filename: str 183 @param filename: Name of the file to read. 184 185 @rtype: list( int ) 186 @return: List of integers read from the file. 187 """ 188 count = 0 189 result = list() 190 fd = open(filename, 'r') 191 for line in fd: 192 count = count + 1 193 if '#' in line: 194 line = line[ : line.find('#') ] 195 line = line.strip() 196 if line: 197 try: 198 value = cls.integer(line) 199 except ValueError, e: 200 msg = "Error in line %d of %s: %s" 201 msg = msg % (count, filename, str(e)) 202 raise ValueError, msg 203 result.append(value) 204 return result
205 206 @classmethod
207 - def string_list_file(cls, filename):
208 """ 209 Read a list of string values from a file. 210 211 The file format is: 212 213 - # anywhere in the line begins a comment 214 - leading and trailing spaces are ignored 215 - empty lines are ignored 216 - strings cannot span over a single line 217 218 @type filename: str 219 @param filename: Name of the file to read. 220 221 @rtype: list 222 @return: List of integers and strings read from the file. 223 """ 224 count = 0 225 result = list() 226 fd = open(filename, 'r') 227 for line in fd: 228 count = count + 1 229 if '#' in line: 230 line = line[ : line.find('#') ] 231 line = line.strip() 232 if line: 233 result.append(line) 234 return result
235 236 @classmethod
237 - def mixed_list_file(cls, filename):
238 """ 239 Read a list of mixed values from a file. 240 241 The file format is: 242 243 - # anywhere in the line begins a comment 244 - leading and trailing spaces are ignored 245 - empty lines are ignored 246 - strings cannot span over a single line 247 - integers can be specified as: 248 - decimal numbers ("100" is 100) 249 - hexadecimal numbers ("0x100" is 256) 250 - binary numbers ("0b100" is 4) 251 - octal numbers ("0100" is 64) 252 253 @type filename: str 254 @param filename: Name of the file to read. 255 256 @rtype: list 257 @return: List of integers and strings read from the file. 258 """ 259 count = 0 260 result = list() 261 fd = open(filename, 'r') 262 for line in fd: 263 count = count + 1 264 if '#' in line: 265 line = line[ : line.find('#') ] 266 line = line.strip() 267 if line: 268 try: 269 value = cls.integer(line) 270 except ValueError, e: 271 value = line 272 result.append(value) 273 return result
274
275 #------------------------------------------------------------------------------ 276 277 -class HexOutput (object):
278 """ 279 Static functions for user output parsing. 280 The counterparts for each method are in the L{HexInput} class. 281 282 @type integer_size: int 283 @cvar integer_size: Size in characters of an outputted integer. 284 This value is platform dependent. 285 286 @type address_size: int 287 @cvar address_size: Size in characters of an outputted address. 288 This value is platform dependent. 289 """ 290 291 integer_size = len('%x' % (win32.DWORD(-1).value))+2 292 address_size = len('%x' % (win32.SIZE_T(-1).value))+2 293 294 @classmethod
295 - def integer(cls, integer):
296 """ 297 @type integer: int 298 @param integer: Integer. 299 300 @rtype: str 301 @return: Text output. 302 """ 303 return ('0x%%.%dx' % (cls.integer_size - 2)) % integer
304 305 @classmethod
306 - def address(cls, address):
307 """ 308 @type address: int 309 @param address: Memory address. 310 311 @rtype: str 312 @return: Text output. 313 """ 314 return ('0x%%.%dx' % (cls.address_size - 2)) % address
315 316 @staticmethod
317 - def hexadecimal(data):
318 """ 319 Convert binary data to a string of hexadecimal numbers. 320 321 @type data: str 322 @param data: Binary data. 323 324 @rtype: str 325 @return: Hexadecimal representation. 326 """ 327 return HexDump.hexadecimal(data, separator = '')
328 329 @classmethod
330 - def integer_list_file(cls, filename, values):
331 """ 332 Write a list of integers to a file. 333 If a file of the same name exists, it's contents are replaced. 334 335 See L{HexInput.integer_list_file} for a description of the file format. 336 337 @type filename: str 338 @param filename: Name of the file to write. 339 340 @type values: list( int ) 341 @param values: List of integers to write to the file. 342 """ 343 fd = open(filename, 'w') 344 for integer in values: 345 print >> fd, cls.integer(integer) 346 fd.close()
347 348 @classmethod
349 - def string_list_file(cls, filename, values):
350 """ 351 Write a list of strings to a file. 352 If a file of the same name exists, it's contents are replaced. 353 354 See L{HexInput.string_list_file} for a description of the file format. 355 356 @type filename: str 357 @param filename: Name of the file to write. 358 359 @type values: list( int ) 360 @param values: List of strings to write to the file. 361 """ 362 fd = open(filename, 'w') 363 for string in values: 364 print >> fd, string 365 fd.close()
366 367 @classmethod
368 - def mixed_list_file(cls, filename, values):
369 """ 370 Write a list of mixed values to a file. 371 If a file of the same name exists, it's contents are replaced. 372 373 See L{HexInput.mixed_list_file} for a description of the file format. 374 375 @type filename: str 376 @param filename: Name of the file to write. 377 378 @type values: list( int ) 379 @param values: List of mixed values to write to the file. 380 """ 381 fd = open(filename, 'w') 382 for original in values: 383 try: 384 parsed = cls.integer(original) 385 except TypeError: 386 parsed = repr(original) 387 print >> fd, parsed 388 fd.close()
389
390 #------------------------------------------------------------------------------ 391 392 -class HexDump (object):
393 """ 394 Static functions for hexadecimal dumps. 395 396 @type integer_size: int 397 @cvar integer_size: Size in characters of an outputted integer. 398 This value is platform dependent. 399 400 @type address_size: int 401 @cvar address_size: Size in characters of an outputted address. 402 This value is platform dependent. 403 """ 404 405 integer_size = len('%x' % (win32.DWORD(-1).value)) 406 address_size = len('%x' % (win32.SIZE_T(-1).value)) 407 408 @classmethod
409 - def integer(cls, integer):
410 """ 411 @type integer: int 412 @param integer: Integer. 413 414 @rtype: str 415 @return: Text output. 416 """ 417 return ('%%.%dX' % cls.integer_size) % integer
418 419 @classmethod
420 - def address(cls, address):
421 """ 422 @type address: int 423 @param address: Memory address. 424 425 @rtype: str 426 @return: Text output. 427 """ 428 return ('%%.%dX' % cls.address_size) % address
429 430 @staticmethod
431 - def printable(data):
432 """ 433 Replace unprintable characters with dots. 434 435 @type data: str 436 @param data: Binary data. 437 438 @rtype: str 439 @return: Printable text. 440 """ 441 result = '' 442 for c in data: 443 if 32 < ord(c) < 128: 444 result += c 445 else: 446 result += '.' 447 return result
448 449 @staticmethod
450 - def hexadecimal(data, separator = ''):
451 """ 452 Convert binary data to a string of hexadecimal numbers. 453 454 @type data: str 455 @param data: Binary data. 456 457 @type separator: str 458 @param separator: 459 Separator between the hexadecimal representation of each character. 460 461 @rtype: str 462 @return: Hexadecimal representation. 463 """ 464 return separator.join( [ '%.2x' % ord(c) for c in data ] )
465 466 @staticmethod
467 - def hexa_word(data, separator = ' '):
468 """ 469 Convert binary data to a string of hexadecimal WORDs. 470 471 @type data: str 472 @param data: Binary data. 473 474 @type separator: str 475 @param separator: 476 Separator between the hexadecimal representation of each WORD. 477 478 @rtype: str 479 @return: Hexadecimal representation. 480 """ 481 if len(data) & 1 != 0: 482 data += '\0' 483 return separator.join( [ '%.4x' % struct.unpack('<H', data[i:i+2])[0] \ 484 for i in xrange(0, len(data), 2) ] )
485 486 @staticmethod
487 - def hexa_dword(data, separator = ' '):
488 """ 489 Convert binary data to a string of hexadecimal DWORDs. 490 491 @type data: str 492 @param data: Binary data. 493 494 @type separator: str 495 @param separator: 496 Separator between the hexadecimal representation of each DWORD. 497 498 @rtype: str 499 @return: Hexadecimal representation. 500 """ 501 if len(data) & 3 != 0: 502 data += '\0' * (4 - (len(data) & 3)) 503 return separator.join( [ '%.8x' % struct.unpack('<L', data[i:i+4])[0] \ 504 for i in xrange(0, len(data), 4) ] )
505 506 @staticmethod
507 - def hexa_qword(data, separator = ' '):
508 """ 509 Convert binary data to a string of hexadecimal QWORDs. 510 511 @type data: str 512 @param data: Binary data. 513 514 @type separator: str 515 @param separator: 516 Separator between the hexadecimal representation of each QWORD. 517 518 @rtype: str 519 @return: Hexadecimal representation. 520 """ 521 if len(data) & 7 != 0: 522 data += '\0' * (8 - (len(data) & 7)) 523 return separator.join( [ '%.16x' % struct.unpack('<Q', data[i:i+8])[0]\ 524 for i in xrange(0, len(data), 8) ] )
525 526 @classmethod
527 - def hexline(cls, data, separator = ' ', width = None):
528 """ 529 Dump a line of hexadecimal numbers from binary data. 530 531 @type data: str 532 @param data: Binary data. 533 534 @type separator: str 535 @param separator: 536 Separator between the hexadecimal representation of each character. 537 538 @type width: int 539 @param width: 540 (Optional) Maximum number of characters to convert per text line. 541 This value is also used for padding. 542 543 @rtype: str 544 @return: Multiline output text. 545 """ 546 if width is None: 547 fmt = '%s %s' 548 else: 549 fmt = '%%-%ds %%-%ds' % ((len(separator)+2)*width-1, width) 550 return fmt % (cls.hexadecimal(data, separator), cls.printable(data))
551 552 @classmethod
553 - def hexblock(cls, data, address = None, separator = ' ', width = 8):
554 """ 555 Dump a block of hexadecimal numbers from binary data. 556 Also show a printable text version of the data. 557 558 @type data: str 559 @param data: Binary data. 560 561 @type address: str 562 @param address: Memory address where the data was read from. 563 564 @type separator: str 565 @param separator: 566 Separator between the hexadecimal representation of each character. 567 568 @type width: int 569 @param width: 570 (Optional) Maximum number of characters to convert per text line. 571 572 @rtype: str 573 @return: Multiline output text. 574 """ 575 return cls.hexblock_cb(cls.hexline, data, address, width, 576 cb_kwargs = {'width' : width, 'separator' : separator})
577 578 @classmethod
579 - def hexblock_cb(cls, callback, data, address = None, width = 16, 580 cb_args = (), cb_kwargs = {}):
581 """ 582 Dump a block of binary data using a callback function to convert each 583 line of text. 584 585 @type callback: function 586 @param callback: Callback function to convert each line of data. 587 588 @type data: str 589 @param data: Binary data. 590 591 @type address: str 592 @param address: 593 (Optional) Memory address where the data was read from. 594 595 @type cb_args: str 596 @param cb_args: 597 (Optional) Arguments to pass to the callback function. 598 599 @type cb_kwargs: str 600 @param cb_kwargs: 601 (Optional) Keyword arguments to pass to the callback function. 602 603 @type width: int 604 @param width: 605 (Optional) Maximum number of bytes to convert per text line. 606 607 @rtype: str 608 @return: Multiline output text. 609 """ 610 result = '' 611 if address is None: 612 for i in xrange(0, len(data), width): 613 result = '%s%s\n' % ( result, \ 614 callback(data[i:i+width], *cb_args, **cb_kwargs) ) 615 else: 616 for i in xrange(0, len(data), width): 617 result = '%s%s: %s\n' % ( result, cls.address(address), \ 618 callback(data[i:i+width], *cb_args, **cb_kwargs) ) 619 address += width 620 return result
621 622 @classmethod
623 - def hexblock_byte(cls, data, address = None, separator = ' ', width = 16):
624 """ 625 Dump a block of hexadecimal BYTEs from binary data. 626 627 @type data: str 628 @param data: Binary data. 629 630 @type address: str 631 @param address: Memory address where the data was read from. 632 633 @type separator: str 634 @param separator: 635 Separator between the hexadecimal representation of each BYTE. 636 637 @type width: int 638 @param width: 639 (Optional) Maximum number of BYTEs to convert per text line. 640 641 @rtype: str 642 @return: Multiline output text. 643 """ 644 return cls.hexblock_cb(cls.hexadecimal, data, address, width, 645 cb_kwargs = {'separator': separator})
646 647 @classmethod
648 - def hexblock_word(cls, data, address = None, separator = ' ', width = 8):
649 """ 650 Dump a block of hexadecimal WORDs from binary data. 651 652 @type data: str 653 @param data: Binary data. 654 655 @type address: str 656 @param address: Memory address where the data was read from. 657 658 @type separator: str 659 @param separator: 660 Separator between the hexadecimal representation of each WORD. 661 662 @type width: int 663 @param width: 664 (Optional) Maximum number of WORDs to convert per text line. 665 666 @rtype: str 667 @return: Multiline output text. 668 """ 669 return cls.hexblock_cb(cls.hexa_word, data, address, width * 2, 670 cb_kwargs = {'separator': separator})
671 672 @classmethod
673 - def hexblock_dword(cls, data, address = None, separator = ' ', width = 4):
674 """ 675 Dump a block of hexadecimal DWORDs from binary data. 676 677 @type data: str 678 @param data: Binary data. 679 680 @type address: str 681 @param address: Memory address where the data was read from. 682 683 @type separator: str 684 @param separator: 685 Separator between the hexadecimal representation of each DWORD. 686 687 @type width: int 688 @param width: 689 (Optional) Maximum number of DWORDs to convert per text line. 690 691 @rtype: str 692 @return: Multiline output text. 693 """ 694 return cls.hexblock_cb(cls.hexa_dword, data, address, width * 4, 695 cb_kwargs = {'separator': separator})
696 697 @classmethod
698 - def hexblock_qword(cls, data, address = None, separator = ' ', width = 2):
699 """ 700 Dump a block of hexadecimal QWORDs from binary data. 701 702 @type data: str 703 @param data: Binary data. 704 705 @type address: str 706 @param address: Memory address where the data was read from. 707 708 @type separator: str 709 @param separator: 710 Separator between the hexadecimal representation of each QWORD. 711 712 @type width: int 713 @param width: 714 (Optional) Maximum number of QWORDs to convert per text line. 715 716 @rtype: str 717 @return: Multiline output text. 718 """ 719 return cls.hexblock_cb(cls.hexa_qword, data, address, width * 8, 720 cb_kwargs = {'separator': separator})
721
722 #------------------------------------------------------------------------------ 723 724 -class Table (object):
725 """ 726 Text based table. The number of columns and the width of each column 727 is automatically calculated. 728 """ 729
730 - def __init__(self, sep = ' '):
731 """ 732 @type sep: str 733 @param sep: Separator between cells in each row. 734 """ 735 self.__cols = list() 736 self.__width = list() 737 self.__sep = sep
738
739 - def addRow(self, *row):
740 """ 741 Add a row to the table. All items are converted to strings. 742 743 @type row: tuple 744 @keyword row: Each argument is a cell in the table. 745 """ 746 row = [ str(item) for item in row ] 747 len_row = [ len(item) for item in row ] 748 width = self.__width 749 len_old = len(width) 750 len_new = len(row) 751 known = min(len_old, len_new) 752 missing = len_new - len_old 753 if missing > 0: 754 width.extend( len_row[ -missing : ] ) 755 self.__width = [ max( width[i], len_row[i] ) for i in xrange(len_new) ] 756 self.__cols.append(row)
757
758 - def justify(self, column, direction):
759 """ 760 Make the text in a column left or right justified. 761 762 @type column: int 763 @param column: Index of the column. 764 765 @type direction: int 766 @param direction: 767 C{1} to justify left, 768 C{-1} to justify right. 769 770 @raise IndexError: Bad column index. 771 @raise ValueError: Bad direction value. 772 """ 773 if direction == -1: 774 self.__width[column] = abs(self.__width[column]) 775 elif direction == 1: 776 self.__width[column] = - abs(self.__width[column]) 777 else: 778 raise ValueError, "Bad direction value."
779
780 - def getOutput(self):
781 """ 782 Get the text output for the table. 783 784 @rtype: str 785 @return: Text output. 786 """ 787 return '%s\n' % '\n'.join( self.yieldOutput() )
788
789 - def yieldOutput(self):
790 """ 791 Generate the text output for the table. 792 793 @rtype: generator of str 794 @return: Text output. 795 """ 796 width = self.__width 797 if width: 798 num_cols = len(width) 799 fmt = ['%%%ds' % -w for w in width] 800 if width[-1] > 0: 801 fmt[-1] = '%s' 802 fmt = self.__sep.join(fmt) 803 for row in self.__cols: 804 row.extend( [''] * (num_cols - len(row)) ) 805 yield fmt % tuple(row)
806
807 #------------------------------------------------------------------------------ 808 809 -class CrashDump (object):
810 """ 811 Static functions for crash dumps. 812 813 @type reg_template: str 814 @cvar reg_template: Template for the L{dump_registers} method. 815 """ 816 817 # Templates for the dump_registers method. 818 reg_template = { 819 'i386' : ( 820 'eax=%(Eax).8x ebx=%(Ebx).8x ecx=%(Ecx).8x edx=%(Edx).8x esi=%(Esi).8x edi=%(Edi).8x\n' 821 'eip=%(Eip).8x esp=%(Esp).8x ebp=%(Ebp).8x %(efl_dump)s\n' 822 'cs=%(SegCs).4x ss=%(SegSs).4x ds=%(SegDs).4x es=%(SegEs).4x fs=%(SegFs).4x gs=%(SegGs).4x efl=%(EFlags).8x\n' 823 ), 824 'amd64' : ( 825 'rax=%(Rax).16x rbx=%(Rbx).16x rcx=%(Rcx).16x\n' 826 'rdx=%(Rdx).16x rsi=%(Rsi).16x rdi=%(Rdi).16x\n' 827 'rip=%(Rip).16x rsp=%(Rsp).16x rbp=%(Rbp).16x\n' 828 ' r8=%(R8).16x r9=%(R9).16x r10=%(R10).16x\n' 829 'r11=%(R11).16x r12=%(R12).16x r13=%(R13).16x\n' 830 'r14=%(R14).16x r15=%(R15).16x\n' 831 '%(efl_dump)s\n' 832 'cs=%(SegCs).4x ss=%(SegSs).4x ds=%(SegDs).4x es=%(SegEs).4x fs=%(SegFs).4x gs=%(SegGs).4x efl=%(EFlags).8x\n' 833 ), 834 } 835 836 @staticmethod
837 - def dump_flags(efl):
838 """ 839 Dump the x86 processor flags. 840 The output mimics that of the WinDBG debugger. 841 842 @type efl: int 843 @param efl: Value of the eFlags register. 844 845 @rtype: str 846 @return: Text suitable for logging. 847 """ 848 if efl is None: 849 return '' 850 if win32.CONTEXT.arch not in ('i386', 'amd64'): 851 raise NotImplementedError 852 efl_dump = 'iopl=%1d' % ((efl & 0x3000) >> 12) 853 if efl & 0x100000: 854 efl_dump += ' vip' 855 else: 856 efl_dump += ' ' 857 if efl & 0x80000: 858 efl_dump += ' vif' 859 else: 860 efl_dump += ' ' 861 # 0x20000 ??? 862 if efl & 0x800: 863 efl_dump += ' ov' # Overflow 864 else: 865 efl_dump += ' no' # No overflow 866 if efl & 0x400: 867 efl_dump += ' dn' # Downwards 868 else: 869 efl_dump += ' up' # Upwards 870 if efl & 0x200: 871 efl_dump += ' ei' # Enable interrupts 872 else: 873 efl_dump += ' di' # Disable interrupts 874 # 0x100 trap flag 875 if efl & 0x80: 876 efl_dump += ' ng' # Negative 877 else: 878 efl_dump += ' pl' # Positive 879 if efl & 0x40: 880 efl_dump += ' zr' # Zero 881 else: 882 efl_dump += ' nz' # Nonzero 883 if efl & 0x10: 884 efl_dump += ' ac' # Auxiliary carry 885 else: 886 efl_dump += ' na' # No auxiliary carry 887 # 0x8 ??? 888 if efl & 0x4: 889 efl_dump += ' pe' # Parity odd 890 else: 891 efl_dump += ' po' # Parity even 892 # 0x2 ??? 893 if efl & 0x1: 894 efl_dump += ' cy' # Carry 895 else: 896 efl_dump += ' nc' # No carry 897 return efl_dump
898 899 @classmethod
900 - def dump_registers(cls, registers):
901 """ 902 Dump the x86 processor register values. 903 The output mimics that of the WinDBG debugger. 904 905 @type registers: dict( str S{->} int ) 906 @param registers: Dictionary mapping register names to their values. 907 908 @rtype: str 909 @return: Text suitable for logging. 910 """ 911 if registers is None: 912 return '' 913 arch = win32.CONTEXT.arch 914 if arch not in ('i386', 'amd64'): 915 raise NotImplementedError 916 registers = registers.copy() 917 registers['efl_dump'] = cls.dump_flags( registers['EFlags'] ) 918 return cls.reg_template[arch] % registers
919 920 @staticmethod
921 - def dump_registers_peek(registers, data, separator = ' ', width = 16):
922 """ 923 Dump data pointed to by the given registers, if any. 924 925 @type registers: dict( str S{->} int ) 926 @param registers: Dictionary mapping register names to their values. 927 This value is returned by L{Thread.get_context}. 928 929 @type data: dict( str S{->} str ) 930 @param data: Dictionary mapping register names to the data they point to. 931 This value is returned by L{Thread.peek_pointers_in_registers}. 932 933 @rtype: str 934 @return: Text suitable for logging. 935 """ 936 if None in (registers, data): 937 return '' 938 names = data.keys() 939 names.sort() 940 result = '' 941 for reg_name in names: 942 tag = reg_name.lower() 943 dumped = HexDump.hexline(data[reg_name], separator, width) 944 result += '%s -> %s\n' % (tag, dumped) 945 return result
946 947 @staticmethod
948 - def dump_data_peek(data, base = 0, separator = ' ', width = 16):
949 """ 950 Dump data from pointers guessed within the given binary data. 951 952 @type data: str 953 @param data: Dictionary mapping offsets to the data they point to. 954 955 @type base: int 956 @param base: Base offset. 957 958 @rtype: str 959 @return: Text suitable for logging. 960 """ 961 if data is None: 962 return '' 963 pointers = data.keys() 964 pointers.sort() 965 result = '' 966 for offset in pointers: 967 dumped = HexDump.hexline(data[offset], separator, width) 968 result += '%s -> %s' % (HexDump.address(base + offset), dumped) 969 return result
970 971 @staticmethod
972 - def dump_stack_peek(data, separator = ' ', width = 16):
973 """ 974 Dump data from pointers guessed within the given stack dump. 975 976 @type data: str 977 @param data: Dictionary mapping stack offsets to the data they point to. 978 979 @rtype: str 980 @return: Text suitable for logging. 981 """ 982 if data is None: 983 return '' 984 pointers = data.keys() 985 pointers.sort() 986 result = '' 987 if pointers: 988 if win32.CONTEXT.arch == 'i386': 989 spreg = 'esp' 990 elif win32.CONTEXT.arch == 'i386': 991 spreg = 'rsp' 992 else: 993 spreg = 'STACK' # just a generic tag 994 tag_fmt = '[%s+0x%%.%dx]' % (spreg, len( '%x' % pointers[-1] ) ) 995 for offset in pointers: 996 dumped = HexDump.hexline(data[offset], separator, width) 997 tag = tag_fmt % offset 998 result += '%s -> %s\n' % (tag, dumped) 999 return result
1000 1001 @staticmethod
1002 - def dump_stack_trace(stack_trace):
1003 """ 1004 Dump a stack trace, as returned by L{Thread.get_stack_trace} with the 1005 C{bUseLabels} parameter set to C{False}. 1006 1007 @type stack_trace: list( int, int, str ) 1008 @param stack_trace: Stack trace as a list of tuples of 1009 ( return address, frame pointer, module filename ) 1010 1011 @rtype: str 1012 @return: Text suitable for logging. 1013 """ 1014 if stack_trace is None: 1015 return '' 1016 table = Table() 1017 table.addRow('Frame', 'Origin', 'Module') 1018 for (fp, ra, mod) in stack_trace: 1019 table.addRow( HexDump.address(fp), HexDump.address(ra), mod ) 1020 return table.getOutput()
1021 1022 @staticmethod
1023 - def dump_stack_trace_with_labels(stack_trace):
1024 """ 1025 Dump a stack trace, 1026 as returned by L{Thread.get_stack_trace_with_labels}. 1027 1028 @type stack_trace: list( int, int, str ) 1029 @param stack_trace: Stack trace as a list of tuples of 1030 ( return address, frame pointer, module filename ) 1031 1032 @rtype: str 1033 @return: Text suitable for logging. 1034 """ 1035 if stack_trace is None: 1036 return '' 1037 table = Table() 1038 table.addRow('Frame', 'Origin') 1039 for (fp, label) in stack_trace: 1040 table.addRow( HexDump.address(fp), label ) 1041 return table.getOutput()
1042 1043 # TODO 1044 # + Instead of a star when EIP points to, it would be better to show 1045 # any register value (or other values like the exception address) that 1046 # points to a location in the dissassembled code. 1047 # + It'd be very useful to show some labels here. 1048 # + It'd be very useful to show register contents for code at EIP 1049 @staticmethod
1050 - def dump_code(disassembly, pc = None, bLowercase = True):
1051 """ 1052 Dump a disassembly. Optionally mark where the program counter is. 1053 1054 @type disassembly: list of tuple( int, int, str, str ) 1055 @param disassembly: Disassembly dump as returned by 1056 L{Process.disassemble} or L{Thread.disassemble_around_pc}. 1057 1058 @type pc: int 1059 @param pc: (Optional) Program counter. 1060 1061 @type bLowercase: bool 1062 @param bLowercase: (Optional) If C{True} convert the code to lowercase. 1063 1064 @rtype: str 1065 @return: Text suitable for logging. 1066 """ 1067 if disassembly is None: 1068 return '' 1069 table = Table(sep = ' | ') 1070 for (addr, size, code, dump) in disassembly: 1071 if bLowercase: 1072 code = code.lower() 1073 if addr == pc: 1074 addr = ' * %s' % HexDump.address(addr) 1075 else: 1076 addr = ' %s' % HexDump.address(addr) 1077 table.addRow(addr, dump, code) 1078 table.justify(1, 1) 1079 return table.getOutput()
1080 1081 @staticmethod
1082 - def dump_code_line(disassembly_line, bShowAddress = True, 1083 bShowDump = True, 1084 bLowercase = True, 1085 dwDumpWidth = None, 1086 dwCodeWidth = None):
1087 """ 1088 Dump a single line of code. To dump a block of code use L{dump_code}. 1089 1090 @type disassembly_line: tuple( int, int, str, str ) 1091 @param disassembly_line: Single item of the list returned by 1092 L{Process.disassemble} or L{Thread.disassemble_around_pc}. 1093 1094 @type bShowAddress: bool 1095 @param bShowAddress: (Optional) If C{True} show the memory address. 1096 1097 @type bShowDump: bool 1098 @param bShowDump: (Optional) If C{True} show the hexadecimal dump. 1099 1100 @type bLowercase: bool 1101 @param bLowercase: (Optional) If C{True} convert the code to lowercase. 1102 1103 @type dwDumpWidth: int or None 1104 @param dwDumpWidth: (Optional) Width in characters of the hex dump. 1105 1106 @type dwCodeWidth: int or None 1107 @param dwCodeWidth: (Optional) Width in characters of the code. 1108 1109 @rtype: str 1110 @return: Text suitable for logging. 1111 """ 1112 (addr, size, code, dump) = disassembly_line 1113 dump = dump.replace(' ', '') 1114 result = list() 1115 fmt = '' 1116 if bShowAddress: 1117 result.append( HexDump.address(addr) ) 1118 fmt += '%%%ds:' % HexDump.address_size 1119 if bShowDump: 1120 result.append(dump) 1121 if dwDumpWidth: 1122 fmt += ' %%-%ds' % dwDumpWidth 1123 else: 1124 fmt += ' %s' 1125 if bLowercase: 1126 code = code.lower() 1127 result.append(code) 1128 if dwCodeWidth: 1129 fmt += ' %%-%ds' % dwCodeWidth 1130 else: 1131 fmt += ' %s' 1132 return fmt % tuple(result)
1133 1134 @staticmethod
1135 - def dump_memory_map(memoryMap, mappedFilenames = None):
1136 """ 1137 Dump the memory map of a process. Optionally show the filenames for 1138 memory mapped files as well. 1139 1140 @type memoryMap: list( L{win32.MemoryBasicInformation} ) 1141 @param memoryMap: Memory map returned by L{Process.get_memory_map}. 1142 1143 @type mappedFilenames: dict( int S{->} str ) 1144 @param mappedFilenames: (Optional) Memory mapped filenames 1145 returned by L{Process.get_mapped_filenames}. 1146 1147 @rtype: str 1148 @return: Text suitable for logging. 1149 """ 1150 table = Table() 1151 if mappedFilenames: 1152 table.addRow("Address", "Size", "State", "Access", "Type", "File") 1153 else: 1154 table.addRow("Address", "Size", "State", "Access", "Type") 1155 1156 # For each memory block in the map... 1157 for mbi in memoryMap: 1158 1159 # Address and size of memory block. 1160 BaseAddress = HexDump.address(mbi.BaseAddress) 1161 RegionSize = HexDump.address(mbi.RegionSize) 1162 1163 # State (free or allocated). 1164 mbiState = mbi.State 1165 if mbiState == win32.MEM_RESERVE: 1166 State = "Reserved" 1167 elif mbiState == win32.MEM_COMMIT: 1168 State = "Commited" 1169 elif mbiState == win32.MEM_FREE: 1170 State = "Free" 1171 else: 1172 State = "Unknown" 1173 1174 # Page protection bits (R/W/X/G). 1175 if mbiState != win32.MEM_COMMIT: 1176 Protect = "" 1177 else: 1178 mbiProtect = mbi.Protect 1179 if mbiProtect & win32.PAGE_NOACCESS: 1180 Protect = "--- " 1181 elif mbiProtect & win32.PAGE_READONLY: 1182 Protect = "R-- " 1183 elif mbiProtect & win32.PAGE_READWRITE: 1184 Protect = "RW- " 1185 elif mbiProtect & win32.PAGE_WRITECOPY: 1186 Protect = "RC- " 1187 elif mbiProtect & win32.PAGE_EXECUTE: 1188 Protect = "--X " 1189 elif mbiProtect & win32.PAGE_EXECUTE_READ: 1190 Protect = "R-X " 1191 elif mbiProtect & win32.PAGE_EXECUTE_READWRITE: 1192 Protect = "RWX " 1193 elif mbiProtect & win32.PAGE_EXECUTE_WRITECOPY: 1194 Protect = "RCX " 1195 else: 1196 Protect = "??? " 1197 if mbiProtect & win32.PAGE_GUARD: 1198 Protect += "G" 1199 else: 1200 Protect += "-" 1201 if mbiProtect & win32.PAGE_NOCACHE: 1202 Protect += "N" 1203 else: 1204 Protect += "-" 1205 if mbiProtect & win32.PAGE_WRITECOMBINE: 1206 Protect += "W" 1207 else: 1208 Protect += "-" 1209 1210 # Type (file mapping, executable image, or private memory). 1211 mbiType = mbi.Type 1212 if mbiType == win32.MEM_IMAGE: 1213 Type = "Image" 1214 elif mbiType == win32.MEM_MAPPED: 1215 Type = "Mapped" 1216 elif mbiType == win32.MEM_PRIVATE: 1217 Type = "Private" 1218 elif mbiType == 0: 1219 Type = "" 1220 else: 1221 Type = "Unknown" 1222 1223 # Output a row in the table. 1224 if mappedFilenames: 1225 FileName = mappedFilenames.get(mbi.BaseAddress, '') 1226 table.addRow( BaseAddress, RegionSize, State, Protect, Type, FileName ) 1227 else: 1228 table.addRow( BaseAddress, RegionSize, State, Protect, Type ) 1229 1230 # Return the table output. 1231 return table.getOutput()
1232
1233 #------------------------------------------------------------------------------ 1234 1235 -class DebugLog (object):
1236 'Static functions for debug logging.' 1237 1238 @staticmethod
1239 - def log_text(text):
1240 """ 1241 Log lines of text, inserting a timestamp. 1242 1243 @type text: str 1244 @param text: Text to log. 1245 1246 @rtype: str 1247 @return: Log line. 1248 """ 1249 if text.endswith('\n'): 1250 text = text[:-len('\n')] 1251 #text = text.replace('\n', '\n\t\t') # text CSV 1252 ltime = time.strftime("%X") 1253 msecs = (time.time() % 1) * 1000 1254 return '[%s.%04d] %s' % (ltime, msecs, text)
1255 #return '[%s.%04d]\t%s' % (ltime, msecs, text) # text CSV 1256 1257 @classmethod
1258 - def log_event(cls, event, text):
1259 """ 1260 Log lines of text associated with a debug event. 1261 1262 @type event: L{Event} 1263 @param event: Event object. 1264 1265 @type text: str 1266 @param text: Text to log. 1267 1268 @rtype: str 1269 @return: Log line. 1270 """ 1271 text = 'pid %d tid %d: %s' % (event.get_pid(), event.get_tid(), text) 1272 #text = 'pid %d tid %d:\t%s' % (event.get_pid(), event.get_tid(), text) # text CSV 1273 return cls.log_text(text)
1274
1275 #------------------------------------------------------------------------------ 1276 1277 -class Logger(object):
1278 """ 1279 Logs text to standard output and/or a text file. 1280 1281 @type logfile: str or None 1282 @ivar logfile: Append messahes to this text file. 1283 1284 @type verbose: bool 1285 @ivar verbose: C{True} to print messages to standard output. 1286 1287 @type fd: file 1288 @ivar fd: File object where log messages are printed to. 1289 C{None} if no log file is used. 1290 """ 1291
1292 - def __init__(self, logfile = None, verbose = True):
1293 """ 1294 @type logfile: str or None 1295 @param logfile: Append messahes to this text file. 1296 1297 @type verbose: bool 1298 @param verbose: C{True} to print messages to standard output. 1299 """ 1300 self.verbose = verbose 1301 self.logfile = logfile 1302 if self.logfile: 1303 self.fd = open(self.logfile, 'a+')
1304
1305 - def __logfile_error(self, e):
1306 msg = "Warning, error writing log file %s: %s" 1307 msg = msg % (self.logfile, str(e)) 1308 print DebugLog.log_text(msg) 1309 self.logfile = None 1310 self.fd = None
1311
1312 - def __do_log(self, text):
1313 if self.verbose: 1314 print text 1315 if self.logfile: 1316 try: 1317 self.fd.writelines('%s\n' % text) 1318 except IOError, e: 1319 self.__logfile_error(e)
1320
1321 - def log_text(self, text):
1322 """ 1323 Log lines of text, inserting a timestamp. 1324 1325 @type text: str 1326 @param text: Text to log. 1327 """ 1328 self.__do_log( DebugLog.log_text(text) )
1329
1330 - def log_event(self, event, text):
1331 """ 1332 Log lines of text associated with a debug event. 1333 1334 @type event: L{Event} 1335 @param event: Event object. 1336 1337 @type text: str 1338 @param text: Text to log. 1339 """ 1340 self.__do_log( DebugLog.log_event(event, text) )
1341
1342 - def log_exc(self):
1343 """ 1344 Log lines of text associated with the last Python exception. 1345 """ 1346 self.__do_log( 'Exception raised: %s' % traceback.format_exc() )
1347