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