1#!/usr/bin/python 2 3#---------------------------------------------------------------------- 4# This module will enable GDB remote packet logging when the 5# 'start_gdb_log' command is called with a filename to log to. When the 6# 'stop_gdb_log' command is called, it will disable the logging and 7# print out statistics about how long commands took to execute and also 8# will primnt ou 9# Be sure to add the python path that points to the LLDB shared library. 10# 11# To use this in the embedded python interpreter using "lldb" just 12# import it with the full path using the "command script import" 13# command. This can be done from the LLDB command line: 14# (lldb) command script import /path/to/gdbremote.py 15# Or it can be added to your ~/.lldbinit file so this module is always 16# available. 17#---------------------------------------------------------------------- 18 19import binascii 20import commands 21import math 22import optparse 23import os 24import re 25import shlex 26import string 27import sys 28import tempfile 29import xml.etree.ElementTree as ET 30 31#---------------------------------------------------------------------- 32# Global variables 33#---------------------------------------------------------------------- 34g_log_file = '' 35g_byte_order = 'little' 36g_number_regex = re.compile('^(0x[0-9a-fA-F]+|[0-9]+)') 37 38class TerminalColors: 39 '''Simple terminal colors class''' 40 def __init__(self, enabled = True): 41 # TODO: discover terminal type from "file" and disable if 42 # it can't handle the color codes 43 self.enabled = enabled 44 45 def reset(self): 46 '''Reset all terminal colors and formatting.''' 47 if self.enabled: 48 return "\x1b[0m"; 49 return '' 50 51 def bold(self, on = True): 52 '''Enable or disable bold depending on the "on" parameter.''' 53 if self.enabled: 54 if on: 55 return "\x1b[1m"; 56 else: 57 return "\x1b[22m"; 58 return '' 59 60 def italics(self, on = True): 61 '''Enable or disable italics depending on the "on" parameter.''' 62 if self.enabled: 63 if on: 64 return "\x1b[3m"; 65 else: 66 return "\x1b[23m"; 67 return '' 68 69 def underline(self, on = True): 70 '''Enable or disable underline depending on the "on" parameter.''' 71 if self.enabled: 72 if on: 73 return "\x1b[4m"; 74 else: 75 return "\x1b[24m"; 76 return '' 77 78 def inverse(self, on = True): 79 '''Enable or disable inverse depending on the "on" parameter.''' 80 if self.enabled: 81 if on: 82 return "\x1b[7m"; 83 else: 84 return "\x1b[27m"; 85 return '' 86 87 def strike(self, on = True): 88 '''Enable or disable strike through depending on the "on" parameter.''' 89 if self.enabled: 90 if on: 91 return "\x1b[9m"; 92 else: 93 return "\x1b[29m"; 94 return '' 95 96 def black(self, fg = True): 97 '''Set the foreground or background color to black. 98 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 99 if self.enabled: 100 if fg: 101 return "\x1b[30m"; 102 else: 103 return "\x1b[40m"; 104 return '' 105 106 def red(self, fg = True): 107 '''Set the foreground or background color to red. 108 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 109 if self.enabled: 110 if fg: 111 return "\x1b[31m"; 112 else: 113 return "\x1b[41m"; 114 return '' 115 116 def green(self, fg = True): 117 '''Set the foreground or background color to green. 118 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 119 if self.enabled: 120 if fg: 121 return "\x1b[32m"; 122 else: 123 return "\x1b[42m"; 124 return '' 125 126 def yellow(self, fg = True): 127 '''Set the foreground or background color to yellow. 128 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 129 if self.enabled: 130 if fg: 131 return "\x1b[43m"; 132 else: 133 return "\x1b[33m"; 134 return '' 135 136 def blue(self, fg = True): 137 '''Set the foreground or background color to blue. 138 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 139 if self.enabled: 140 if fg: 141 return "\x1b[34m"; 142 else: 143 return "\x1b[44m"; 144 return '' 145 146 def magenta(self, fg = True): 147 '''Set the foreground or background color to magenta. 148 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 149 if self.enabled: 150 if fg: 151 return "\x1b[35m"; 152 else: 153 return "\x1b[45m"; 154 return '' 155 156 def cyan(self, fg = True): 157 '''Set the foreground or background color to cyan. 158 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 159 if self.enabled: 160 if fg: 161 return "\x1b[36m"; 162 else: 163 return "\x1b[46m"; 164 return '' 165 166 def white(self, fg = True): 167 '''Set the foreground or background color to white. 168 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 169 if self.enabled: 170 if fg: 171 return "\x1b[37m"; 172 else: 173 return "\x1b[47m"; 174 return '' 175 176 def default(self, fg = True): 177 '''Set the foreground or background color to the default. 178 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 179 if self.enabled: 180 if fg: 181 return "\x1b[39m"; 182 else: 183 return "\x1b[49m"; 184 return '' 185 186 187def start_gdb_log(debugger, command, result, dict): 188 '''Start logging GDB remote packets by enabling logging with timestamps and 189 thread safe logging. Follow a call to this function with a call to "stop_gdb_log" 190 in order to dump out the commands.''' 191 global g_log_file 192 command_args = shlex.split(command) 193 usage = "usage: start_gdb_log [options] [<LOGFILEPATH>]" 194 description='''The command enables GDB remote packet logging with timestamps. The packets will be logged to <LOGFILEPATH> if supplied, or a temporary file will be used. Logging stops when stop_gdb_log is called and the packet times will 195 be aggregated and displayed.''' 196 parser = optparse.OptionParser(description=description, prog='start_gdb_log',usage=usage) 197 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) 198 try: 199 (options, args) = parser.parse_args(command_args) 200 except: 201 return 202 203 if g_log_file: 204 result.PutCString ('error: logging is already in progress with file "%s"', g_log_file) 205 else: 206 args_len = len(args) 207 if args_len == 0: 208 g_log_file = tempfile.mktemp() 209 elif len(args) == 1: 210 g_log_file = args[0] 211 212 if g_log_file: 213 debugger.HandleCommand('log enable --threadsafe --timestamp --file "%s" gdb-remote packets' % g_log_file); 214 result.PutCString ("GDB packet logging enable with log file '%s'\nUse the 'stop_gdb_log' command to stop logging and show packet statistics." % g_log_file) 215 return 216 217 result.PutCString ('error: invalid log file path') 218 result.PutCString (usage) 219 220def stop_gdb_log(debugger, command, result, dict): 221 '''Stop logging GDB remote packets to the file that was specified in a call 222 to "start_gdb_log" and normalize the timestamps to be relative to the first 223 timestamp in the log file. Also print out statistics for how long each 224 command took to allow performance bottlenecks to be determined.''' 225 global g_log_file 226 # Any commands whose names might be followed by more valid C identifier 227 # characters must be listed here 228 command_args = shlex.split(command) 229 usage = "usage: stop_gdb_log [options]" 230 description='''The command stops a previously enabled GDB remote packet logging command. Packet logging must have been previously enabled with a call to start_gdb_log.''' 231 parser = optparse.OptionParser(description=description, prog='stop_gdb_log',usage=usage) 232 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) 233 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='display verbose debug info', default=False) 234 parser.add_option('-C', '--color', action='store_true', dest='color', help='add terminal colors', default=False) 235 parser.add_option('-c', '--sort-by-count', action='store_true', dest='sort_count', help='display verbose debug info', default=False) 236 parser.add_option('-s', '--symbolicate', action='store_true', dest='symbolicate', help='symbolicate addresses in log using current "lldb.target"', default=False) 237 try: 238 (options, args) = parser.parse_args(command_args) 239 except: 240 return 241 options.colors = TerminalColors(options.color) 242 options.symbolicator = None 243 if options.symbolicate: 244 if lldb.target: 245 import lldb.utils.symbolication 246 options.symbolicator = lldb.utils.symbolication.Symbolicator() 247 options.symbolicator.target = lldb.target 248 else: 249 print "error: can't symbolicate without a target" 250 251 if not g_log_file: 252 result.PutCString ('error: logging must have been previously enabled with a call to "stop_gdb_log"') 253 elif os.path.exists (g_log_file): 254 if len(args) == 0: 255 debugger.HandleCommand('log disable gdb-remote packets'); 256 result.PutCString ("GDB packet logging disabled. Logged packets are in '%s'" % g_log_file) 257 parse_gdb_log_file (g_log_file, options) 258 else: 259 result.PutCString (usage) 260 else: 261 print 'error: the GDB packet log file "%s" does not exist' % g_log_file 262 263def is_hex_byte(str): 264 if len(str) == 2: 265 return str[0] in string.hexdigits and str[1] in string.hexdigits; 266 return False 267 268# global register info list 269g_register_infos = list() 270g_max_register_info_name_len = 0 271 272class RegisterInfo: 273 """Class that represents register information""" 274 def __init__(self, kvp): 275 self.info = dict() 276 for kv in kvp: 277 key = kv[0] 278 value = kv[1] 279 self.info[key] = value 280 def name(self): 281 '''Get the name of the register.''' 282 if self.info and 'name' in self.info: 283 return self.info['name'] 284 return None 285 286 def bit_size(self): 287 '''Get the size in bits of the register.''' 288 if self.info and 'bitsize' in self.info: 289 return int(self.info['bitsize']) 290 return 0 291 292 def byte_size(self): 293 '''Get the size in bytes of the register.''' 294 return self.bit_size() / 8 295 296 def get_value_from_hex_string(self, hex_str): 297 '''Dump the register value given a native byte order encoded hex ASCII byte string.''' 298 encoding = self.info['encoding'] 299 bit_size = self.bit_size() 300 packet = Packet(hex_str) 301 if encoding == 'uint': 302 uval = packet.get_hex_uint(g_byte_order) 303 if bit_size == 8: 304 return '0x%2.2x' % (uval) 305 elif bit_size == 16: 306 return '0x%4.4x' % (uval) 307 elif bit_size == 32: 308 return '0x%8.8x' % (uval) 309 elif bit_size == 64: 310 return '0x%16.16x' % (uval) 311 bytes = list(); 312 uval = packet.get_hex_uint8() 313 while uval != None: 314 bytes.append(uval) 315 uval = packet.get_hex_uint8() 316 value_str = '0x' 317 if g_byte_order == 'little': 318 bytes.reverse() 319 for byte in bytes: 320 value_str += '%2.2x' % byte 321 return '%s' % (value_str) 322 323 def __str__(self): 324 '''Dump the register info key/value pairs''' 325 s = '' 326 for key in self.info.keys(): 327 if s: 328 s += ', ' 329 s += "%s=%s " % (key, self.info[key]) 330 return s 331 332class Packet: 333 """Class that represents a packet that contains string data""" 334 def __init__(self, packet_str): 335 self.str = packet_str 336 337 def peek_char(self): 338 ch = 0 339 if self.str: 340 ch = self.str[0] 341 return ch 342 343 def get_char(self): 344 ch = 0 345 if self.str: 346 ch = self.str[0] 347 self.str = self.str[1:] 348 return ch 349 350 def skip_exact_string(self, s): 351 if self.str and self.str.startswith(s): 352 self.str = self.str[len(s):] 353 return True 354 else: 355 return False 356 357 def get_hex_uint8(self): 358 if self.str and len(self.str) >= 2 and self.str[0] in string.hexdigits and self.str[1] in string.hexdigits: 359 uval = int(self.str[0:2], 16) 360 self.str = self.str[2:] 361 return uval 362 return None 363 364 def get_hex_uint16(self, byte_order): 365 uval = 0 366 if byte_order == 'big': 367 uval |= self.get_hex_uint8() << 8 368 uval |= self.get_hex_uint8() 369 else: 370 uval |= self.get_hex_uint8() 371 uval |= self.get_hex_uint8() << 8 372 return uval 373 374 def get_hex_uint32(self, byte_order): 375 uval = 0 376 if byte_order == 'big': 377 uval |= self.get_hex_uint8() << 24 378 uval |= self.get_hex_uint8() << 16 379 uval |= self.get_hex_uint8() << 8 380 uval |= self.get_hex_uint8() 381 else: 382 uval |= self.get_hex_uint8() 383 uval |= self.get_hex_uint8() << 8 384 uval |= self.get_hex_uint8() << 16 385 uval |= self.get_hex_uint8() << 24 386 return uval 387 388 def get_hex_uint64(self, byte_order): 389 uval = 0 390 if byte_order == 'big': 391 uval |= self.get_hex_uint8() << 56 392 uval |= self.get_hex_uint8() << 48 393 uval |= self.get_hex_uint8() << 40 394 uval |= self.get_hex_uint8() << 32 395 uval |= self.get_hex_uint8() << 24 396 uval |= self.get_hex_uint8() << 16 397 uval |= self.get_hex_uint8() << 8 398 uval |= self.get_hex_uint8() 399 else: 400 uval |= self.get_hex_uint8() 401 uval |= self.get_hex_uint8() << 8 402 uval |= self.get_hex_uint8() << 16 403 uval |= self.get_hex_uint8() << 24 404 uval |= self.get_hex_uint8() << 32 405 uval |= self.get_hex_uint8() << 40 406 uval |= self.get_hex_uint8() << 48 407 uval |= self.get_hex_uint8() << 56 408 return uval 409 410 def get_number(self, fail_value=-1): 411 '''Get a number from the packet. The number must be in big endian format and should be parsed 412 according to its prefix (starts with "0x" means hex, starts with "0" means octal, starts with 413 [1-9] means decimal, etc)''' 414 match = g_number_regex.match (self.str) 415 if match: 416 number_str = match.group(1) 417 self.str = self.str[len(number_str):] 418 return int(number_str, 0) 419 else: 420 return fail_value 421 422 423 def get_hex_ascii_str(self, n=0): 424 hex_chars = self.get_hex_chars(n) 425 if hex_chars: 426 return binascii.unhexlify(hex_chars) 427 else: 428 return None 429 430 def get_hex_chars(self, n = 0): 431 str_len = len(self.str) 432 if n == 0: 433 # n was zero, so we need to determine all hex chars and 434 # stop when we hit the end of the string of a non-hex character 435 while n < str_len and self.str[n] in string.hexdigits: 436 n = n + 1 437 else: 438 if n > str_len: 439 return None # Not enough chars 440 # Verify all chars are hex if a length was specified 441 for i in range(n): 442 if self.str[i] not in string.hexdigits: 443 return None # Not all hex digits 444 if n == 0: 445 return None 446 hex_str = self.str[0:n] 447 self.str = self.str[n:] 448 return hex_str 449 450 def get_hex_uint(self, byte_order, n = 0): 451 if byte_order == 'big': 452 hex_str = self.get_hex_chars(n) 453 if hex_str == None: 454 return None 455 return int(hex_str, 16) 456 else: 457 uval = self.get_hex_uint8() 458 if uval == None: 459 return None 460 uval_result = 0 461 shift = 0 462 while uval != None: 463 uval_result |= (uval << shift) 464 shift += 8 465 uval = self.get_hex_uint8() 466 return uval_result 467 468 def get_key_value_pairs(self): 469 kvp = list() 470 if ';' in self.str: 471 key_value_pairs = string.split(self.str, ';') 472 for key_value_pair in key_value_pairs: 473 if len(key_value_pair): 474 kvp.append(string.split(key_value_pair, ':')) 475 return kvp 476 477 def split(self, ch): 478 return string.split(self.str, ch) 479 480 def split_hex(self, ch, byte_order): 481 hex_values = list() 482 strings = string.split(self.str, ch) 483 for str in strings: 484 hex_values.append(Packet(str).get_hex_uint(byte_order)) 485 return hex_values 486 487 def __str__(self): 488 return self.str 489 490 def __len__(self): 491 return len(self.str) 492 493g_thread_suffix_regex = re.compile(';thread:([0-9a-fA-F]+);') 494def get_thread_from_thread_suffix(str): 495 if str: 496 match = g_thread_suffix_regex.match (str) 497 if match: 498 return int(match.group(1), 16) 499 return None 500 501def cmd_stop_reply(options, cmd, args): 502 print "get_last_stop_info()" 503 return False 504 505def rsp_stop_reply(options, cmd, cmd_args, rsp): 506 global g_byte_order 507 packet = Packet(rsp) 508 stop_type = packet.get_char() 509 if stop_type == 'T' or stop_type == 'S': 510 signo = packet.get_hex_uint8() 511 key_value_pairs = packet.get_key_value_pairs() 512 for key_value_pair in key_value_pairs: 513 key = key_value_pair[0] 514 if is_hex_byte(key): 515 reg_num = Packet(key).get_hex_uint8() 516 if reg_num < len(g_register_infos): 517 reg_info = g_register_infos[reg_num] 518 key_value_pair[0] = reg_info.name() 519 key_value_pair[1] = reg_info.get_value_from_hex_string (key_value_pair[1]) 520 elif key == 'jthreads' or key == 'jstopinfo': 521 key_value_pair[1] = binascii.unhexlify(key_value_pair[1]) 522 key_value_pairs.insert(0, ['signal', signo]) 523 print 'Stop reply:' 524 dump_key_value_pairs (key_value_pairs) 525 elif stop_type == 'W': 526 exit_status = packet.get_hex_uint8() 527 print 'exit (status=%i)' % exit_status 528 elif stop_type == 'O': 529 print 'stdout = %s' % packet.str 530 531 532def cmd_unknown_packet(options, cmd, args): 533 if args: 534 print "cmd: %s, args: %s", cmd, args 535 else: 536 print "cmd: %s", cmd 537 return False 538 539def cmd_qSymbol(options, cmd, args): 540 if args == ':': 541 print 'ready to serve symbols' 542 else: 543 packet = Packet(args) 544 symbol_addr = packet.get_hex_uint('big') 545 if symbol_addr is None: 546 if packet.skip_exact_string(':'): 547 symbol_name = packet.get_hex_ascii_str() 548 print 'lookup_symbol("%s") -> symbol not available yet' % (symbol_name) 549 else: 550 print 'error: bad command format' 551 else: 552 if packet.skip_exact_string(':'): 553 symbol_name = packet.get_hex_ascii_str() 554 print 'lookup_symbol("%s") -> 0x%x' % (symbol_name, symbol_addr) 555 else: 556 print 'error: bad command format' 557 558def rsp_qSymbol(options, cmd, cmd_args, rsp): 559 if len(rsp) == 0: 560 print "Unsupported" 561 else: 562 if rsp == "OK": 563 print "No more symbols to lookup" 564 else: 565 packet = Packet(rsp) 566 if packet.skip_exact_string("qSymbol:"): 567 symbol_name = packet.get_hex_ascii_str() 568 print 'lookup_symbol("%s")' % (symbol_name) 569 else: 570 print 'error: response string should start with "qSymbol:": respnse is "%s"' % (rsp) 571 572def cmd_qXfer(options, cmd, args): 573 # $qXfer:features:read:target.xml:0,1ffff#14 574 print "read target special data %s" % (args) 575 return True 576 577def rsp_qXfer(options, cmd, cmd_args, rsp): 578 data = string.split(cmd_args, ':') 579 if data[0] == 'features': 580 if data[1] == 'read': 581 filename, extension = os.path.splitext(data[2]) 582 if extension == '.xml': 583 response = Packet(rsp) 584 xml_string = response.get_hex_ascii_str() 585 ch = xml_string[0] 586 if ch == 'l': 587 xml_string = xml_string[1:] 588 xml_root = ET.fromstring(xml_string) 589 for reg_element in xml_root.findall("./feature/reg"): 590 if not 'value_regnums' in reg_element.attrib: 591 reg_info = RegisterInfo([]) 592 if 'name' in reg_element.attrib: 593 reg_info.info['name'] = reg_element.attrib['name'] 594 else: 595 reg_info.info['name'] = 'unspecified' 596 if 'encoding' in reg_element.attrib: 597 reg_info.info['encoding'] = reg_element.attrib['encoding'] 598 else: 599 reg_info.info['encoding'] = 'uint' 600 if 'offset' in reg_element.attrib: 601 reg_info.info['offset'] = reg_element.attrib['offset'] 602 if 'bitsize' in reg_element.attrib: 603 reg_info.info['bitsize'] = reg_element.attrib['bitsize'] 604 g_register_infos.append(reg_info) 605 print 'XML for "%s":' % (data[2]) 606 ET.dump(xml_root) 607 608def cmd_query_packet(options, cmd, args): 609 if args: 610 print "%s%s" % (cmd, args) 611 else: 612 print "%s" % (cmd) 613 return False 614 615def rsp_ok_error(rsp): 616 print "rsp: ", rsp 617 618def rsp_ok_means_supported(options, cmd, cmd_args, rsp): 619 if rsp == 'OK': 620 print "%s%s is supported" % (cmd, cmd_args) 621 elif rsp == '': 622 print "%s%s is not supported" % (cmd, cmd_args) 623 else: 624 print "%s%s -> %s" % (cmd, cmd_args, rsp) 625 626def rsp_ok_means_success(options, cmd, cmd_args, rsp): 627 if rsp == 'OK': 628 print "success" 629 elif rsp == '': 630 print "%s%s is not supported" % (cmd, cmd_args) 631 else: 632 print "%s%s -> %s" % (cmd, cmd_args, rsp) 633 634def dump_key_value_pairs(key_value_pairs): 635 max_key_len = 0 636 for key_value_pair in key_value_pairs: 637 key_len = len(key_value_pair[0]) 638 if max_key_len < key_len: 639 max_key_len = key_len 640 for key_value_pair in key_value_pairs: 641 key = key_value_pair[0] 642 value = key_value_pair[1] 643 print "%*s = %s" % (max_key_len, key, value) 644 645def rsp_dump_key_value_pairs(options, cmd, cmd_args, rsp): 646 if rsp: 647 print '%s response:' % (cmd) 648 packet = Packet(rsp) 649 key_value_pairs = packet.get_key_value_pairs() 650 dump_key_value_pairs(key_value_pairs) 651 else: 652 print "not supported" 653 654def cmd_c(options, cmd, args): 655 print "continue()" 656 return False 657 658def cmd_s(options, cmd, args): 659 print "step()" 660 return False 661 662def cmd_vCont(options, cmd, args): 663 if args == '?': 664 print "%s: get supported extended continue modes" % (cmd) 665 else: 666 got_other_threads = 0 667 s = '' 668 for thread_action in string.split(args[1:], ';'): 669 (short_action, thread) = string.split(thread_action, ':') 670 tid = int(thread, 16) 671 if short_action == 'c': 672 action = 'continue' 673 elif short_action == 's': 674 action = 'step' 675 elif short_action[0] == 'C': 676 action = 'continue with signal 0x%s' % (short_action[1:]) 677 elif short_action == 'S': 678 action = 'step with signal 0x%s' % (short_action[1:]) 679 else: 680 action = short_action 681 if s: 682 s += ', ' 683 if tid == -1: 684 got_other_threads = 1 685 s += 'other-threads:' 686 else: 687 s += 'thread 0x%4.4x: %s' % (tid, action) 688 if got_other_threads: 689 print "extended_continue (%s)" % (s) 690 else: 691 print "extended_continue (%s, other-threads: suspend)" % (s) 692 return False 693 694def rsp_vCont(options, cmd, cmd_args, rsp): 695 if cmd_args == '?': 696 # Skip the leading 'vCont;' 697 rsp = rsp[6:] 698 modes = string.split(rsp, ';') 699 s = "%s: supported extended continue modes include: " % (cmd) 700 701 for i, mode in enumerate(modes): 702 if i: 703 s += ', ' 704 if mode == 'c': 705 s += 'continue' 706 elif mode == 'C': 707 s += 'continue with signal' 708 elif mode == 's': 709 s += 'step' 710 elif mode == 'S': 711 s += 'step with signal' 712 else: 713 s += 'unrecognized vCont mode: ', mode 714 print s 715 elif rsp: 716 if rsp[0] == 'T' or rsp[0] == 'S' or rsp[0] == 'W' or rsp[0] == 'X': 717 rsp_stop_reply (options, cmd, cmd_args, rsp) 718 return 719 if rsp[0] == 'O': 720 print "stdout: %s" % (rsp) 721 return 722 else: 723 print "not supported (cmd = '%s', args = '%s', rsp = '%s')" % (cmd, cmd_args, rsp) 724 725def cmd_vAttach(options, cmd, args): 726 (extra_command, args) = string.split(args, ';') 727 if extra_command: 728 print "%s%s(%s)" % (cmd, extra_command, args) 729 else: 730 print "attach(pid = %u)" % int(args, 16) 731 return False 732 733 734def cmd_qRegisterInfo(options, cmd, args): 735 print 'query_register_info(reg_num=%i)' % (int(args, 16)) 736 return False 737 738def rsp_qRegisterInfo(options, cmd, cmd_args, rsp): 739 global g_max_register_info_name_len 740 print 'query_register_info(reg_num=%i):' % (int(cmd_args, 16)), 741 if len(rsp) == 3 and rsp[0] == 'E': 742 g_max_register_info_name_len = 0 743 for reg_info in g_register_infos: 744 name_len = len(reg_info.name()) 745 if g_max_register_info_name_len < name_len: 746 g_max_register_info_name_len = name_len 747 print' DONE' 748 else: 749 packet = Packet(rsp) 750 reg_info = RegisterInfo(packet.get_key_value_pairs()) 751 g_register_infos.append(reg_info) 752 print reg_info 753 return False 754 755def cmd_qThreadInfo(options, cmd, args): 756 if cmd == 'qfThreadInfo': 757 query_type = 'first' 758 else: 759 query_type = 'subsequent' 760 print 'get_current_thread_list(type=%s)' % (query_type) 761 return False 762 763def rsp_qThreadInfo(options, cmd, cmd_args, rsp): 764 packet = Packet(rsp) 765 response_type = packet.get_char() 766 if response_type == 'm': 767 tids = packet.split_hex(';', 'big') 768 for i, tid in enumerate(tids): 769 if i: 770 print ',', 771 print '0x%x' % (tid), 772 print 773 elif response_type == 'l': 774 print 'END' 775 776def rsp_hex_big_endian(options, cmd, cmd_args, rsp): 777 packet = Packet(rsp) 778 uval = packet.get_hex_uint('big') 779 print '%s: 0x%x' % (cmd, uval) 780 781def cmd_read_mem_bin(options, cmd, args): 782 # x0x7fff5fc39200,0x200 783 packet = Packet(args) 784 addr = packet.get_number() 785 comma = packet.get_char() 786 size = packet.get_number() 787 print 'binary_read_memory (addr = 0x%16.16x, size = %u)' % (addr, size) 788 return False 789 790def rsp_mem_bin_bytes(options, cmd, cmd_args, rsp): 791 packet = Packet(cmd_args) 792 addr = packet.get_number() 793 comma = packet.get_char() 794 size = packet.get_number() 795 print 'memory:' 796 if size > 0: 797 dump_hex_memory_buffer (addr, rsp) 798 799def cmd_read_memory(options, cmd, args): 800 packet = Packet(args) 801 addr = packet.get_hex_uint('big') 802 comma = packet.get_char() 803 size = packet.get_hex_uint('big') 804 print 'read_memory (addr = 0x%16.16x, size = %u)' % (addr, size) 805 return False 806 807def dump_hex_memory_buffer(addr, hex_byte_str): 808 packet = Packet(hex_byte_str) 809 idx = 0 810 ascii = '' 811 uval = packet.get_hex_uint8() 812 while uval != None: 813 if ((idx % 16) == 0): 814 if ascii: 815 print ' ', ascii 816 ascii = '' 817 print '0x%x:' % (addr + idx), 818 print '%2.2x' % (uval), 819 if 0x20 <= uval and uval < 0x7f: 820 ascii += '%c' % uval 821 else: 822 ascii += '.' 823 uval = packet.get_hex_uint8() 824 idx = idx + 1 825 if ascii: 826 print ' ', ascii 827 ascii = '' 828 829def cmd_write_memory(options, cmd, args): 830 packet = Packet(args) 831 addr = packet.get_hex_uint('big') 832 if packet.get_char() != ',': 833 print 'error: invalid write memory command (missing comma after address)' 834 return 835 size = packet.get_hex_uint('big') 836 if packet.get_char() != ':': 837 print 'error: invalid write memory command (missing colon after size)' 838 return 839 print 'write_memory (addr = 0x%16.16x, size = %u, data:' % (addr, size) 840 dump_hex_memory_buffer (addr, packet.str) 841 return False 842 843def cmd_alloc_memory(options, cmd, args): 844 packet = Packet(args) 845 byte_size = packet.get_hex_uint('big') 846 if packet.get_char() != ',': 847 print 'error: invalid allocate memory command (missing comma after address)' 848 return 849 print 'allocate_memory (byte-size = %u (0x%x), permissions = %s)' % (byte_size, byte_size, packet.str) 850 return False 851 852def rsp_alloc_memory(options, cmd, cmd_args, rsp): 853 packet = Packet(rsp) 854 addr = packet.get_hex_uint('big') 855 print 'addr = 0x%x' % addr 856 857def cmd_dealloc_memory(options, cmd, args): 858 packet = Packet(args) 859 addr = packet.get_hex_uint('big') 860 if packet.get_char() != ',': 861 print 'error: invalid allocate memory command (missing comma after address)' 862 else: 863 print 'deallocate_memory (addr = 0x%x, permissions = %s)' % (addr, packet.str) 864 return False 865def rsp_memory_bytes(options, cmd, cmd_args, rsp): 866 addr = Packet(cmd_args).get_hex_uint('big') 867 dump_hex_memory_buffer (addr, rsp) 868 869def get_register_name_equal_value(options, reg_num, hex_value_str): 870 if reg_num < len(g_register_infos): 871 reg_info = g_register_infos[reg_num] 872 value_str = reg_info.get_value_from_hex_string (hex_value_str) 873 s = reg_info.name() + ' = ' 874 if options.symbolicator: 875 symbolicated_addresses = options.symbolicator.symbolicate (int(value_str, 0)) 876 if symbolicated_addresses: 877 s += options.colors.magenta() 878 s += '%s' % symbolicated_addresses[0] 879 s += options.colors.reset() 880 return s 881 s += value_str 882 return s 883 else: 884 reg_value = Packet(hex_value_str).get_hex_uint(g_byte_order) 885 return 'reg(%u) = 0x%x' % (reg_num, reg_value) 886 887def cmd_read_one_reg(options, cmd, args): 888 packet = Packet(args) 889 reg_num = packet.get_hex_uint('big') 890 tid = get_thread_from_thread_suffix (packet.str) 891 name = None 892 if reg_num < len(g_register_infos): 893 name = g_register_infos[reg_num].name () 894 if packet.str: 895 packet.get_char() # skip ; 896 thread_info = packet.get_key_value_pairs() 897 tid = int(thread_info[0][1], 16) 898 s = 'read_register (reg_num=%u' % reg_num 899 if name: 900 s += ' (%s)' % (name) 901 if tid != None: 902 s += ', tid = 0x%4.4x' % (tid) 903 s += ')' 904 print s 905 return False 906 907def rsp_read_one_reg(options, cmd, cmd_args, rsp): 908 packet = Packet(cmd_args) 909 reg_num = packet.get_hex_uint('big') 910 print get_register_name_equal_value (options, reg_num, rsp) 911 912def cmd_write_one_reg(options, cmd, args): 913 packet = Packet(args) 914 reg_num = packet.get_hex_uint('big') 915 if packet.get_char() != '=': 916 print 'error: invalid register write packet' 917 else: 918 name = None 919 hex_value_str = packet.get_hex_chars() 920 tid = get_thread_from_thread_suffix (packet.str) 921 s = 'write_register (reg_num=%u' % reg_num 922 if name: 923 s += ' (%s)' % (name) 924 s += ', value = ' 925 s += get_register_name_equal_value(options, reg_num, hex_value_str) 926 if tid != None: 927 s += ', tid = 0x%4.4x' % (tid) 928 s += ')' 929 print s 930 return False 931 932def dump_all_regs(packet): 933 for reg_info in g_register_infos: 934 nibble_size = reg_info.bit_size() / 4 935 hex_value_str = packet.get_hex_chars(nibble_size) 936 if hex_value_str != None: 937 value = reg_info.get_value_from_hex_string (hex_value_str) 938 print '%*s = %s' % (g_max_register_info_name_len, reg_info.name(), value) 939 else: 940 return 941 942def cmd_read_all_regs(cmd, cmd_args): 943 packet = Packet(cmd_args) 944 packet.get_char() # toss the 'g' command character 945 tid = get_thread_from_thread_suffix (packet.str) 946 if tid != None: 947 print 'read_all_register(thread = 0x%4.4x)' % tid 948 else: 949 print 'read_all_register()' 950 return False 951 952def rsp_read_all_regs(options, cmd, cmd_args, rsp): 953 packet = Packet(rsp) 954 dump_all_regs (packet) 955 956def cmd_write_all_regs(options, cmd, args): 957 packet = Packet(args) 958 print 'write_all_registers()' 959 dump_all_regs (packet) 960 return False 961 962g_bp_types = [ "software_bp", "hardware_bp", "write_wp", "read_wp", "access_wp" ] 963 964def cmd_bp(options, cmd, args): 965 if cmd == 'Z': 966 s = 'set_' 967 else: 968 s = 'clear_' 969 packet = Packet (args) 970 bp_type = packet.get_hex_uint('big') 971 packet.get_char() # Skip , 972 bp_addr = packet.get_hex_uint('big') 973 packet.get_char() # Skip , 974 bp_size = packet.get_hex_uint('big') 975 s += g_bp_types[bp_type] 976 s += " (addr = 0x%x, size = %u)" % (bp_addr, bp_size) 977 print s 978 return False 979 980def cmd_mem_rgn_info(options, cmd, args): 981 packet = Packet(args) 982 packet.get_char() # skip ':' character 983 addr = packet.get_hex_uint('big') 984 print 'get_memory_region_info (addr=0x%x)' % (addr) 985 return False 986 987def cmd_kill(options, cmd, args): 988 print 'kill_process()' 989 return False 990 991gdb_remote_commands = { 992 '\\?' : { 'cmd' : cmd_stop_reply , 'rsp' : rsp_stop_reply , 'name' : "stop reply pacpket"}, 993 'QStartNoAckMode' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if no ack mode is supported"}, 994 'QThreadSuffixSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if thread suffix is supported" }, 995 'QListThreadsInStopReply' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if threads in stop reply packets are supported" }, 996 'QSetDetachOnError' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if we should detach on error" }, 997 'qVAttachOrWaitSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if threads attach with optional wait is supported" }, 998 'qHostInfo' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get host information" }, 999 'vCont' : { 'cmd' : cmd_vCont , 'rsp' : rsp_vCont , 'name' : "extended continue command" }, 1000 'vAttach' : { 'cmd' : cmd_vAttach , 'rsp' : rsp_stop_reply , 'name' : "attach to process" }, 1001 'c' : { 'cmd' : cmd_c , 'rsp' : rsp_stop_reply , 'name' : "continue" }, 1002 's' : { 'cmd' : cmd_s , 'rsp' : rsp_stop_reply , 'name' : "step" }, 1003 'qRegisterInfo' : { 'cmd' : cmd_qRegisterInfo , 'rsp' : rsp_qRegisterInfo , 'name' : "query register info" }, 1004 'qfThreadInfo' : { 'cmd' : cmd_qThreadInfo , 'rsp' : rsp_qThreadInfo , 'name' : "get current thread list" }, 1005 'qsThreadInfo' : { 'cmd' : cmd_qThreadInfo , 'rsp' : rsp_qThreadInfo , 'name' : "get current thread list" }, 1006 'qShlibInfoAddr' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_hex_big_endian , 'name' : "get shared library info address" }, 1007 'qMemoryRegionInfo' : { 'cmd' : cmd_mem_rgn_info , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get memory region information" }, 1008 'qProcessInfo' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get process info" }, 1009 'qSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query supported" }, 1010 'qXfer:' : { 'cmd' : cmd_qXfer , 'rsp' : rsp_qXfer , 'name' : "qXfer" }, 1011 'qSymbol:' : { 'cmd' : cmd_qSymbol , 'rsp' : rsp_qSymbol , 'name' : "qSymbol" }, 1012 'x' : { 'cmd' : cmd_read_mem_bin , 'rsp' : rsp_mem_bin_bytes , 'name' : "read memory binary" }, 1013 'X' : { 'cmd' : cmd_write_memory , 'rsp' : rsp_ok_means_success , 'name' : "write memory binary" }, 1014 'm' : { 'cmd' : cmd_read_memory , 'rsp' : rsp_memory_bytes , 'name' : "read memory" }, 1015 'M' : { 'cmd' : cmd_write_memory , 'rsp' : rsp_ok_means_success , 'name' : "write memory" }, 1016 '_M' : { 'cmd' : cmd_alloc_memory , 'rsp' : rsp_alloc_memory , 'name' : "allocate memory" }, 1017 '_m' : { 'cmd' : cmd_dealloc_memory, 'rsp' : rsp_ok_means_success , 'name' : "deallocate memory" }, 1018 'p' : { 'cmd' : cmd_read_one_reg , 'rsp' : rsp_read_one_reg , 'name' : "read single register" }, 1019 'P' : { 'cmd' : cmd_write_one_reg , 'rsp' : rsp_ok_means_success , 'name' : "write single register" }, 1020 'g' : { 'cmd' : cmd_read_all_regs , 'rsp' : rsp_read_all_regs , 'name' : "read all registers" }, 1021 'G' : { 'cmd' : cmd_write_all_regs, 'rsp' : rsp_ok_means_success , 'name' : "write all registers" }, 1022 'z' : { 'cmd' : cmd_bp , 'rsp' : rsp_ok_means_success , 'name' : "clear breakpoint or watchpoint" }, 1023 'Z' : { 'cmd' : cmd_bp , 'rsp' : rsp_ok_means_success , 'name' : "set breakpoint or watchpoint" }, 1024 'k' : { 'cmd' : cmd_kill , 'rsp' : rsp_stop_reply , 'name' : "kill process" }, 1025} 1026 1027def calculate_mean_and_standard_deviation(floats): 1028 sum = 0.0 1029 count = len(floats) 1030 if count == 0: 1031 return (0.0, 0.0) 1032 for f in floats: 1033 sum += f 1034 mean = sum / count 1035 accum = 0.0 1036 for f in floats: 1037 delta = f - mean 1038 accum += delta * delta 1039 1040 std_dev = math.sqrt(accum / (count-1)); 1041 return (mean, std_dev) 1042 1043def parse_gdb_log_file(path, options): 1044 f = open(path) 1045 parse_gdb_log(f, options) 1046 f.close() 1047 1048def parse_gdb_log(file, options): 1049 '''Parse a GDB log file that was generated by enabling logging with: 1050 (lldb) log enable --threadsafe --timestamp --file <FILE> gdb-remote packets 1051 This log file will contain timestamps and this function will then normalize 1052 those packets to be relative to the first value timestamp that is found and 1053 show delta times between log lines and also keep track of how long it takes 1054 for GDB remote commands to make a send/receive round trip. This can be 1055 handy when trying to figure out why some operation in the debugger is taking 1056 a long time during a preset set of debugger commands.''' 1057 1058 tricky_commands = [ 'qRegisterInfo' ] 1059 timestamp_regex = re.compile('(\s*)([1-9][0-9]+\.[0-9]+)([^0-9].*)$') 1060 packet_name_regex = re.compile('([A-Za-z_]+)[^a-z]') 1061 packet_transmit_name_regex = re.compile('(?P<direction>send|read) packet: (?P<packet>.*)') 1062 packet_contents_name_regex = re.compile('\$([^#]+)#[0-9a-fA-F]{2}') 1063 packet_names_regex_str = '(' + '|'.join(gdb_remote_commands.keys()) + ')(.*)'; 1064 packet_names_regex = re.compile(packet_names_regex_str); 1065 1066 base_time = 0.0 1067 last_time = 0.0 1068 packet_send_time = 0.0 1069 packet_total_times = {} 1070 packet_times = [] 1071 packet_count = {} 1072 lines = file.read().splitlines() 1073 last_command = None 1074 last_command_args = None 1075 last_command_packet = None 1076 hide_next_response = False 1077 for line in lines: 1078 m = packet_transmit_name_regex.search(line) 1079 is_command = False 1080 direction = None 1081 if m: 1082 direction = m.group('direction') 1083 is_command = direction == 'send' 1084 packet = m.group('packet') 1085 sys.stdout.write(options.colors.green()) 1086 if not options.quiet and not hide_next_response: 1087 print '# ', line 1088 sys.stdout.write(options.colors.reset()) 1089 1090 #print 'direction = "%s", packet = "%s"' % (direction, packet) 1091 1092 if is_command: 1093 print '-->', 1094 else: 1095 print '<--', 1096 if packet[0] == '+': 1097 if not options.quiet: print 'ACK' 1098 continue 1099 elif packet[0] == '-': 1100 if not options.quiet: print 'NACK' 1101 continue 1102 elif packet[0] == '$': 1103 m = packet_contents_name_regex.match(packet) 1104 if m: 1105 contents = m.group(1) 1106 if is_command: 1107 hide_next_response = False 1108 m = packet_names_regex.match (contents) 1109 if m: 1110 last_command = m.group(1) 1111 packet_name = last_command 1112 last_command_args = m.group(2) 1113 last_command_packet = contents 1114 hide_next_response = gdb_remote_commands[last_command]['cmd'](options, last_command, last_command_args) 1115 else: 1116 packet_match = packet_name_regex.match (contents) 1117 if packet_match: 1118 packet_name = packet_match.group(1) 1119 for tricky_cmd in tricky_commands: 1120 if packet_name.find (tricky_cmd) == 0: 1121 packet_name = tricky_cmd 1122 else: 1123 packet_name = contents 1124 last_command = None 1125 last_command_args = None 1126 last_command_packet = None 1127 elif last_command: 1128 gdb_remote_commands[last_command]['rsp'](options, last_command, last_command_args, contents) 1129 else: 1130 print 'error: invalid packet: "', packet, '"' 1131 else: 1132 print '???' 1133 else: 1134 print '## ', line 1135 match = timestamp_regex.match (line) 1136 if match: 1137 curr_time = float (match.group(2)) 1138 if last_time and not is_command: 1139 delta = curr_time - last_time 1140 packet_times.append(delta) 1141 delta = 0.0 1142 if base_time: 1143 delta = curr_time - last_time 1144 else: 1145 base_time = curr_time 1146 1147 if is_command: 1148 packet_send_time = curr_time 1149 elif line.find('read packet: $') >= 0 and packet_name: 1150 if packet_name in packet_total_times: 1151 packet_total_times[packet_name] += delta 1152 packet_count[packet_name] += 1 1153 else: 1154 packet_total_times[packet_name] = delta 1155 packet_count[packet_name] = 1 1156 packet_name = None 1157 1158 if not options or not options.quiet: 1159 print '%s%.6f %+.6f%s' % (match.group(1), curr_time - base_time, delta, match.group(3)) 1160 last_time = curr_time 1161 # else: 1162 # print line 1163 (average, std_dev) = calculate_mean_and_standard_deviation(packet_times) 1164 if average and std_dev: 1165 print '%u packets with average packet time of %f and standard deviation of %f' % (len(packet_times), average, std_dev) 1166 if packet_total_times: 1167 total_packet_time = 0.0 1168 total_packet_count = 0 1169 for key, vvv in packet_total_times.items(): 1170 # print ' key = (%s) "%s"' % (type(key), key) 1171 # print 'value = (%s) %s' % (type(vvv), vvv) 1172 # if type(vvv) == 'float': 1173 total_packet_time += vvv 1174 for key, vvv in packet_count.items(): 1175 total_packet_count += vvv 1176 1177 print '#---------------------------------------------------' 1178 print '# Packet timing summary:' 1179 print '# Totals: time = %6f, count = %6d' % (total_packet_time, total_packet_count) 1180 print '#---------------------------------------------------' 1181 print '# Packet Time (sec) Percent Count ' 1182 print '#------------------------- ---------- ------- ------' 1183 if options and options.sort_count: 1184 res = sorted(packet_count, key=packet_count.__getitem__, reverse=True) 1185 else: 1186 res = sorted(packet_total_times, key=packet_total_times.__getitem__, reverse=True) 1187 1188 if last_time > 0.0: 1189 for item in res: 1190 packet_total_time = packet_total_times[item] 1191 packet_percent = (packet_total_time / total_packet_time)*100.0 1192 if packet_percent >= 10.0: 1193 print " %24s %.6f %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item]) 1194 else: 1195 print " %24s %.6f %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item]) 1196 1197 1198 1199if __name__ == '__main__': 1200 usage = "usage: gdbremote [options]" 1201 description='''The command disassembles a GDB remote packet log.''' 1202 parser = optparse.OptionParser(description=description, prog='gdbremote',usage=usage) 1203 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) 1204 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='display verbose debug info', default=False) 1205 parser.add_option('-C', '--color', action='store_true', dest='color', help='add terminal colors', default=False) 1206 parser.add_option('-c', '--sort-by-count', action='store_true', dest='sort_count', help='display verbose debug info', default=False) 1207 parser.add_option('--crashlog', type='string', dest='crashlog', help='symbolicate using a darwin crash log file', default=False) 1208 try: 1209 (options, args) = parser.parse_args(sys.argv[1:]) 1210 except: 1211 print 'error: argument error' 1212 sys.exit(1) 1213 1214 options.colors = TerminalColors(options.color) 1215 options.symbolicator = None 1216 if options.crashlog: 1217 import lldb 1218 lldb.debugger = lldb.SBDebugger.Create() 1219 import lldb.macosx.crashlog 1220 options.symbolicator = lldb.macosx.crashlog.CrashLog(options.crashlog) 1221 print '%s' % (options.symbolicator) 1222 1223 # This script is being run from the command line, create a debugger in case we are 1224 # going to use any debugger functions in our function. 1225 if len(args): 1226 for file in args: 1227 print '#----------------------------------------------------------------------' 1228 print "# GDB remote log file: '%s'" % file 1229 print '#----------------------------------------------------------------------' 1230 parse_gdb_log_file (file, options) 1231 if options.symbolicator: 1232 print '%s' % (options.symbolicator) 1233 else: 1234 parse_gdb_log(sys.stdin, options) 1235 1236else: 1237 import lldb 1238 if lldb.debugger: 1239 # This initializer is being run from LLDB in the embedded command interpreter 1240 # Add any commands contained in this module to LLDB 1241 lldb.debugger.HandleCommand('command script add -f gdbremote.start_gdb_log start_gdb_log') 1242 lldb.debugger.HandleCommand('command script add -f gdbremote.stop_gdb_log stop_gdb_log') 1243 print 'The "start_gdb_log" and "stop_gdb_log" commands are now installed and ready for use, type "start_gdb_log --help" or "stop_gdb_log --help" for more information' 1244