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 key_value_pairs.insert(0, ['signal', signo]) 499 dump_key_value_pairs (key_value_pairs) 500 elif stop_type == 'W': 501 exit_status = packet.get_hex_uint8() 502 print 'exit (status=%i)' % exit_status 503 elif stop_type == 'O': 504 print 'stdout = %s' % packet.str 505 506 507def cmd_unknown_packet(options, cmd, args): 508 if args: 509 print "cmd: %s, args: %s", cmd, args 510 else: 511 print "cmd: %s", cmd 512 513def cmd_qXfer(options, cmd, args): 514 # $qXfer:features:read:target.xml:0,1ffff#14 515 print "read target special data %s" % (args) 516 517def rsp_qXfer(options, cmd, cmd_args, rsp): 518 data = string.split(cmd_args, ':') 519 if data[0] == 'features': 520 if data[1] == 'read': 521 filename, extension = os.path.splitext(data[2]) 522 if extension == '.xml': 523 response = Packet(rsp) 524 xml_string = response.get_hex_ascii_str() 525 ch = xml_string[0] 526 if ch == 'l': 527 xml_string = xml_string[1:] 528 xml_root = ET.fromstring(xml_string) 529 for reg_element in xml_root.findall("./feature/reg"): 530 if not 'value_regnums' in reg_element.attrib: 531 reg_info = RegisterInfo([]) 532 if 'name' in reg_element.attrib: 533 reg_info.info['name'] = reg_element.attrib['name'] 534 else: 535 reg_info.info['name'] = 'unspecified' 536 if 'encoding' in reg_element.attrib: 537 reg_info.info['encoding'] = reg_element.attrib['encoding'] 538 else: 539 reg_info.info['encoding'] = 'uint' 540 if 'offset' in reg_element.attrib: 541 reg_info.info['offset'] = reg_element.attrib['offset'] 542 if 'bitsize' in reg_element.attrib: 543 reg_info.info['bitsize'] = reg_element.attrib['bitsize'] 544 g_register_infos.append(reg_info) 545 546 547def cmd_query_packet(options, cmd, args): 548 if args: 549 print "query: %s, args: %s" % (cmd, args) 550 else: 551 print "query: %s" % (cmd) 552 553def rsp_ok_error(rsp): 554 print "rsp: ", rsp 555 556def rsp_ok_means_supported(options, cmd, cmd_args, rsp): 557 if rsp == 'OK': 558 print "%s%s is supported" % (cmd, cmd_args) 559 elif rsp == '': 560 print "%s%s is not supported" % (cmd, cmd_args) 561 else: 562 print "%s%s -> %s" % (cmd, cmd_args, rsp) 563 564def rsp_ok_means_success(options, cmd, cmd_args, rsp): 565 if rsp == 'OK': 566 print "success" 567 elif rsp == '': 568 print "%s%s is not supported" % (cmd, cmd_args) 569 else: 570 print "%s%s -> %s" % (cmd, cmd_args, rsp) 571 572def dump_key_value_pairs(key_value_pairs): 573 max_key_len = 0 574 for key_value_pair in key_value_pairs: 575 key_len = len(key_value_pair[0]) 576 if max_key_len < key_len: 577 max_key_len = key_len 578 for key_value_pair in key_value_pairs: 579 key = key_value_pair[0] 580 value = key_value_pair[1] 581 print "%*s = %s" % (max_key_len, key, value) 582 583def rsp_dump_key_value_pairs(options, cmd, cmd_args, rsp): 584 if rsp: 585 packet = Packet(rsp) 586 key_value_pairs = packet.get_key_value_pairs() 587 dump_key_value_pairs(key_value_pairs) 588 else: 589 print "not supported" 590 591def cmd_vCont(options, cmd, args): 592 if args == '?': 593 print "%s: get supported extended continue modes" % (cmd) 594 else: 595 got_other_threads = 0 596 s = '' 597 for thread_action in string.split(args[1:], ';'): 598 (short_action, thread) = string.split(thread_action, ':') 599 tid = int(thread, 16) 600 if short_action == 'c': 601 action = 'continue' 602 elif short_action == 's': 603 action = 'step' 604 elif short_action[0] == 'C': 605 action = 'continue with signal 0x%s' % (short_action[1:]) 606 elif short_action == 'S': 607 action = 'step with signal 0x%s' % (short_action[1:]) 608 else: 609 action = short_action 610 if s: 611 s += ', ' 612 if tid == -1: 613 got_other_threads = 1 614 s += 'other-threads:' 615 else: 616 s += 'thread 0x%4.4x: %s' % (tid, action) 617 if got_other_threads: 618 print "extended_continue (%s)" % (s) 619 else: 620 print "extended_continue (%s, other-threads: suspend)" % (s) 621 622def rsp_vCont(options, cmd, cmd_args, rsp): 623 if cmd_args == '?': 624 # Skip the leading 'vCont;' 625 rsp = rsp[6:] 626 modes = string.split(rsp, ';') 627 s = "%s: supported extended continue modes include: " % (cmd) 628 629 for i, mode in enumerate(modes): 630 if i: 631 s += ', ' 632 if mode == 'c': 633 s += 'continue' 634 elif mode == 'C': 635 s += 'continue with signal' 636 elif mode == 's': 637 s += 'step' 638 elif mode == 'S': 639 s += 'step with signal' 640 else: 641 s += 'unrecognized vCont mode: ', mode 642 print s 643 elif rsp: 644 if rsp[0] == 'T' or rsp[0] == 'S' or rsp[0] == 'W' or rsp[0] == 'X': 645 rsp_stop_reply (options, cmd, cmd_args, rsp) 646 return 647 if rsp[0] == 'O': 648 print "stdout: %s" % (rsp) 649 return 650 else: 651 print "not supported (cmd = '%s', args = '%s', rsp = '%s')" % (cmd, cmd_args, rsp) 652 653def cmd_vAttach(options, cmd, args): 654 (extra_command, args) = string.split(args, ';') 655 if extra_command: 656 print "%s%s(%s)" % (cmd, extra_command, args) 657 else: 658 print "attach_pid(%s)" % args 659 660def cmd_qRegisterInfo(options, cmd, args): 661 print 'query_register_info(reg_num=%i)' % (int(args, 16)) 662 663def rsp_qRegisterInfo(options, cmd, cmd_args, rsp): 664 global g_max_register_info_name_len 665 print 'query_register_info(reg_num=%i):' % (int(cmd_args, 16)), 666 if len(rsp) == 3 and rsp[0] == 'E': 667 g_max_register_info_name_len = 0 668 for reg_info in g_register_infos: 669 name_len = len(reg_info.name()) 670 if g_max_register_info_name_len < name_len: 671 g_max_register_info_name_len = name_len 672 print' DONE' 673 else: 674 packet = Packet(rsp) 675 reg_info = RegisterInfo(packet.get_key_value_pairs()) 676 g_register_infos.append(reg_info) 677 print reg_info 678 679 680def cmd_qThreadInfo(options, cmd, args): 681 if cmd == 'qfThreadInfo': 682 query_type = 'first' 683 else: 684 query_type = 'subsequent' 685 print 'get_current_thread_list(type=%s)' % (query_type) 686 687def rsp_qThreadInfo(options, cmd, cmd_args, rsp): 688 packet = Packet(rsp) 689 response_type = packet.get_char() 690 if response_type == 'm': 691 tids = packet.split_hex(';', 'big') 692 for i, tid in enumerate(tids): 693 if i: 694 print ',', 695 print '0x%x' % (tid), 696 print 697 elif response_type == 'l': 698 print 'END' 699 700def rsp_hex_big_endian(options, cmd, cmd_args, rsp): 701 packet = Packet(rsp) 702 uval = packet.get_hex_uint('big') 703 print '%s: 0x%x' % (cmd, uval) 704 705def cmd_read_memory(options, cmd, args): 706 packet = Packet(args) 707 addr = packet.get_hex_uint('big') 708 comma = packet.get_char() 709 size = packet.get_hex_uint('big') 710 print 'read_memory (addr = 0x%x, size = %u)' % (addr, size) 711 712def dump_hex_memory_buffer(addr, hex_byte_str): 713 packet = Packet(hex_byte_str) 714 idx = 0 715 ascii = '' 716 uval = packet.get_hex_uint8() 717 while uval != None: 718 if ((idx % 16) == 0): 719 if ascii: 720 print ' ', ascii 721 ascii = '' 722 print '0x%x:' % (addr + idx), 723 print '%2.2x' % (uval), 724 if 0x20 <= uval and uval < 0x7f: 725 ascii += '%c' % uval 726 else: 727 ascii += '.' 728 uval = packet.get_hex_uint8() 729 idx = idx + 1 730 if ascii: 731 print ' ', ascii 732 ascii = '' 733 734def cmd_write_memory(options, cmd, args): 735 packet = Packet(args) 736 addr = packet.get_hex_uint('big') 737 if packet.get_char() != ',': 738 print 'error: invalid write memory command (missing comma after address)' 739 return 740 size = packet.get_hex_uint('big') 741 if packet.get_char() != ':': 742 print 'error: invalid write memory command (missing colon after size)' 743 return 744 print 'write_memory (addr = 0x%x, size = %u, data:' % (addr, size) 745 dump_hex_memory_buffer (addr, packet.str) 746 747def cmd_alloc_memory(options, cmd, args): 748 packet = Packet(args) 749 byte_size = packet.get_hex_uint('big') 750 if packet.get_char() != ',': 751 print 'error: invalid allocate memory command (missing comma after address)' 752 return 753 print 'allocate_memory (byte-size = %u (0x%x), permissions = %s)' % (byte_size, byte_size, packet.str) 754 755def rsp_alloc_memory(options, cmd, cmd_args, rsp): 756 packet = Packet(rsp) 757 addr = packet.get_hex_uint('big') 758 print 'addr = 0x%x' % addr 759 760def cmd_dealloc_memory(options, cmd, args): 761 packet = Packet(args) 762 addr = packet.get_hex_uint('big') 763 if packet.get_char() != ',': 764 print 'error: invalid allocate memory command (missing comma after address)' 765 return 766 print 'deallocate_memory (addr = 0x%x, permissions = %s)' % (addr, packet.str) 767 768def rsp_memory_bytes(options, cmd, cmd_args, rsp): 769 addr = Packet(cmd_args).get_hex_uint('big') 770 dump_hex_memory_buffer (addr, rsp) 771 772def get_register_name_equal_value(options, reg_num, hex_value_str): 773 if reg_num < len(g_register_infos): 774 reg_info = g_register_infos[reg_num] 775 value_str = reg_info.get_value_from_hex_string (hex_value_str) 776 s = reg_info.name() + ' = ' 777 if options.symbolicator: 778 symbolicated_addresses = options.symbolicator.symbolicate (int(value_str, 0)) 779 if symbolicated_addresses: 780 s += options.colors.magenta() 781 s += '%s' % symbolicated_addresses[0] 782 s += options.colors.reset() 783 return s 784 s += value_str 785 return s 786 else: 787 reg_value = Packet(hex_value_str).get_hex_uint(g_byte_order) 788 return 'reg(%u) = 0x%x' % (reg_num, reg_value) 789 790def cmd_read_one_reg(options, cmd, args): 791 packet = Packet(args) 792 reg_num = packet.get_hex_uint('big') 793 tid = get_thread_from_thread_suffix (packet.str) 794 name = None 795 if reg_num < len(g_register_infos): 796 name = g_register_infos[reg_num].name () 797 if packet.str: 798 packet.get_char() # skip ; 799 thread_info = packet.get_key_value_pairs() 800 tid = int(thread_info[0][1], 16) 801 s = 'read_register (reg_num=%u' % reg_num 802 if name: 803 s += ' (%s)' % (name) 804 if tid != None: 805 s += ', tid = 0x%4.4x' % (tid) 806 s += ')' 807 print s 808 809def rsp_read_one_reg(options, cmd, cmd_args, rsp): 810 packet = Packet(cmd_args) 811 reg_num = packet.get_hex_uint('big') 812 print get_register_name_equal_value (options, reg_num, rsp) 813 814def cmd_write_one_reg(options, cmd, args): 815 packet = Packet(args) 816 reg_num = packet.get_hex_uint('big') 817 if packet.get_char() != '=': 818 print 'error: invalid register write packet' 819 else: 820 name = None 821 hex_value_str = packet.get_hex_chars() 822 tid = get_thread_from_thread_suffix (packet.str) 823 s = 'write_register (reg_num=%u' % reg_num 824 if name: 825 s += ' (%s)' % (name) 826 s += ', value = ' 827 s += get_register_name_equal_value(options, reg_num, hex_value_str) 828 if tid != None: 829 s += ', tid = 0x%4.4x' % (tid) 830 s += ')' 831 print s 832 833def dump_all_regs(packet): 834 for reg_info in g_register_infos: 835 nibble_size = reg_info.bit_size() / 4 836 hex_value_str = packet.get_hex_chars(nibble_size) 837 if hex_value_str != None: 838 value = reg_info.get_value_from_hex_string (hex_value_str) 839 print '%*s = %s' % (g_max_register_info_name_len, reg_info.name(), value) 840 else: 841 return 842 843def cmd_read_all_regs(cmd, cmd_args): 844 packet = Packet(cmd_args) 845 packet.get_char() # toss the 'g' command character 846 tid = get_thread_from_thread_suffix (packet.str) 847 if tid != None: 848 print 'read_all_register(thread = 0x%4.4x)' % tid 849 else: 850 print 'read_all_register()' 851 852def rsp_read_all_regs(options, cmd, cmd_args, rsp): 853 packet = Packet(rsp) 854 dump_all_regs (packet) 855 856def cmd_write_all_regs(options, cmd, args): 857 packet = Packet(args) 858 print 'write_all_registers()' 859 dump_all_regs (packet) 860 861g_bp_types = [ "software_bp", "hardware_bp", "write_wp", "read_wp", "access_wp" ] 862 863def cmd_bp(options, cmd, args): 864 if cmd == 'Z': 865 s = 'set_' 866 else: 867 s = 'clear_' 868 packet = Packet (args) 869 bp_type = packet.get_hex_uint('big') 870 packet.get_char() # Skip , 871 bp_addr = packet.get_hex_uint('big') 872 packet.get_char() # Skip , 873 bp_size = packet.get_hex_uint('big') 874 s += g_bp_types[bp_type] 875 s += " (addr = 0x%x, size = %u)" % (bp_addr, bp_size) 876 print s 877 878def cmd_mem_rgn_info(options, cmd, args): 879 packet = Packet(args) 880 packet.get_char() # skip ':' character 881 addr = packet.get_hex_uint('big') 882 print 'get_memory_region_info (addr=0x%x)' % (addr) 883 884def cmd_kill(options, cmd, args): 885 print 'kill_process()' 886 887gdb_remote_commands = { 888 '\\?' : { 'cmd' : cmd_stop_reply , 'rsp' : rsp_stop_reply , 'name' : "stop reply pacpket"}, 889 'QStartNoAckMode' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if no ack mode is supported"}, 890 'QThreadSuffixSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if thread suffix is supported" }, 891 'QListThreadsInStopReply' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if threads in stop reply packets are supported" }, 892 'QSetDetachOnError' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if we should detach on error" }, 893 'qVAttachOrWaitSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query if threads attach with optional wait is supported" }, 894 'qHostInfo' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get host information" }, 895 'vCont' : { 'cmd' : cmd_vCont , 'rsp' : rsp_vCont , 'name' : "extended continue command" }, 896 'vAttach' : { 'cmd' : cmd_vAttach , 'rsp' : rsp_stop_reply , 'name' : "attach to process" }, 897 'qRegisterInfo' : { 'cmd' : cmd_qRegisterInfo , 'rsp' : rsp_qRegisterInfo , 'name' : "query register info" }, 898 'qfThreadInfo' : { 'cmd' : cmd_qThreadInfo , 'rsp' : rsp_qThreadInfo , 'name' : "get current thread list" }, 899 'qsThreadInfo' : { 'cmd' : cmd_qThreadInfo , 'rsp' : rsp_qThreadInfo , 'name' : "get current thread list" }, 900 'qShlibInfoAddr' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_hex_big_endian , 'name' : "get shared library info address" }, 901 'qMemoryRegionInfo' : { 'cmd' : cmd_mem_rgn_info , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get memory region information" }, 902 'qProcessInfo' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_dump_key_value_pairs, 'name' : "get process info" }, 903 'qSupported' : { 'cmd' : cmd_query_packet , 'rsp' : rsp_ok_means_supported , 'name' : "query supported" }, 904 'qXfer:' : { 'cmd' : cmd_qXfer , 'rsp' : rsp_qXfer , 'name' : "qXfer" }, 905 'm' : { 'cmd' : cmd_read_memory , 'rsp' : rsp_memory_bytes , 'name' : "read memory" }, 906 'M' : { 'cmd' : cmd_write_memory , 'rsp' : rsp_ok_means_success , 'name' : "write memory" }, 907 '_M' : { 'cmd' : cmd_alloc_memory , 'rsp' : rsp_alloc_memory , 'name' : "allocate memory" }, 908 '_m' : { 'cmd' : cmd_dealloc_memory, 'rsp' : rsp_ok_means_success , 'name' : "deallocate memory" }, 909 'p' : { 'cmd' : cmd_read_one_reg , 'rsp' : rsp_read_one_reg , 'name' : "read single register" }, 910 'P' : { 'cmd' : cmd_write_one_reg , 'rsp' : rsp_ok_means_success , 'name' : "write single register" }, 911 'g' : { 'cmd' : cmd_read_all_regs , 'rsp' : rsp_read_all_regs , 'name' : "read all registers" }, 912 'G' : { 'cmd' : cmd_write_all_regs, 'rsp' : rsp_ok_means_success , 'name' : "write all registers" }, 913 'z' : { 'cmd' : cmd_bp , 'rsp' : rsp_ok_means_success , 'name' : "clear breakpoint or watchpoint" }, 914 'Z' : { 'cmd' : cmd_bp , 'rsp' : rsp_ok_means_success , 'name' : "set breakpoint or watchpoint" }, 915 'k' : { 'cmd' : cmd_kill , 'rsp' : rsp_stop_reply , 'name' : "kill process" }, 916} 917 918def calculate_mean_and_standard_deviation(floats): 919 sum = 0.0 920 count = len(floats) 921 for f in floats: 922 sum += f 923 mean = sum / count 924 accum = 0.0 925 for f in floats: 926 delta = f - mean 927 accum += delta * delta 928 929 std_dev = math.sqrt(accum / (count-1)); 930 return (mean, std_dev) 931 932def parse_gdb_log_file(file, options): 933 '''Parse a GDB log file that was generated by enabling logging with: 934 (lldb) log enable --threadsafe --timestamp --file <FILE> gdb-remote packets 935 This log file will contain timestamps and this function will then normalize 936 those packets to be relative to the first value timestamp that is found and 937 show delta times between log lines and also keep track of how long it takes 938 for GDB remote commands to make a send/receive round trip. This can be 939 handy when trying to figure out why some operation in the debugger is taking 940 a long time during a preset set of debugger commands.''' 941 942 tricky_commands = [ 'qRegisterInfo' ] 943 timestamp_regex = re.compile('(\s*)([1-9][0-9]+\.[0-9]+)([^0-9].*)$') 944 packet_name_regex = re.compile('([A-Za-z_]+)[^a-z]') 945 packet_transmit_name_regex = re.compile('(?P<direction>send|read) packet: (?P<packet>.*)') 946 packet_contents_name_regex = re.compile('\$([^#]+)#[0-9a-fA-F]{2}') 947 packet_names_regex_str = '(' + '|'.join(gdb_remote_commands.keys()) + ')(.*)'; 948 packet_names_regex = re.compile(packet_names_regex_str); 949 950 base_time = 0.0 951 last_time = 0.0 952 packet_send_time = 0.0 953 packet_total_times = {} 954 packet_times = [] 955 packet_count = {} 956 file = open(file) 957 lines = file.read().splitlines() 958 last_command = None 959 last_command_args = None 960 last_command_packet = None 961 for line in lines: 962 m = packet_transmit_name_regex.search(line) 963 is_command = False 964 direction = None 965 if m: 966 direction = m.group('direction') 967 is_command = direction == 'send' 968 packet = m.group('packet') 969 sys.stdout.write(options.colors.green()) 970 if not options.quiet: 971 print '# ', line 972 sys.stdout.write(options.colors.reset()) 973 974 #print 'direction = "%s", packet = "%s"' % (direction, packet) 975 976 if packet[0] == '+': 977 if not options.quiet: print 'ACK' 978 continue 979 elif packet[0] == '-': 980 if not options.quiet: print 'NACK' 981 continue 982 elif packet[0] == '$': 983 m = packet_contents_name_regex.match(packet) 984 if m: 985 contents = m.group(1) 986 if is_command: 987 m = packet_names_regex.match (contents) 988 if m: 989 last_command = m.group(1) 990 packet_name = last_command 991 last_command_args = m.group(2) 992 last_command_packet = contents 993 gdb_remote_commands[last_command]['cmd'](options, last_command, last_command_args) 994 else: 995 packet_match = packet_name_regex.match (contents) 996 if packet_match: 997 packet_name = packet_match.group(1) 998 for tricky_cmd in tricky_commands: 999 if packet_name.find (tricky_cmd) == 0: 1000 packet_name = tricky_cmd 1001 else: 1002 packet_name = contents 1003 last_command = None 1004 last_command_args = None 1005 last_command_packet = None 1006 elif last_command: 1007 gdb_remote_commands[last_command]['rsp'](options, last_command, last_command_args, contents) 1008 else: 1009 print 'error: invalid packet: "', packet, '"' 1010 else: 1011 print '???' 1012 else: 1013 print '## ', line 1014 match = timestamp_regex.match (line) 1015 if match: 1016 curr_time = float (match.group(2)) 1017 if last_time and not is_command: 1018 delta = curr_time - last_time 1019 packet_times.append(delta) 1020 delta = 0.0 1021 if base_time: 1022 delta = curr_time - last_time 1023 else: 1024 base_time = curr_time 1025 1026 if is_command: 1027 packet_send_time = curr_time 1028 elif line.find('read packet: $') >= 0 and packet_name: 1029 if packet_name in packet_total_times: 1030 packet_total_times[packet_name] += delta 1031 packet_count[packet_name] += 1 1032 else: 1033 packet_total_times[packet_name] = delta 1034 packet_count[packet_name] = 1 1035 packet_name = None 1036 1037 if not options or not options.quiet: 1038 print '%s%.6f %+.6f%s' % (match.group(1), curr_time - base_time, delta, match.group(3)) 1039 last_time = curr_time 1040 # else: 1041 # print line 1042 (average, std_dev) = calculate_mean_and_standard_deviation(packet_times) 1043 print '%u packets with average packet time of %f and standard deviation of %f' % (len(packet_times), average, std_dev) 1044 if packet_total_times: 1045 total_packet_time = 0.0 1046 total_packet_count = 0 1047 for key, vvv in packet_total_times.items(): 1048 # print ' key = (%s) "%s"' % (type(key), key) 1049 # print 'value = (%s) %s' % (type(vvv), vvv) 1050 # if type(vvv) == 'float': 1051 total_packet_time += vvv 1052 for key, vvv in packet_count.items(): 1053 total_packet_count += vvv 1054 1055 print '#---------------------------------------------------' 1056 print '# Packet timing summary:' 1057 print '# Totals: time = %6f, count = %6d' % (total_packet_time, total_packet_count) 1058 print '#---------------------------------------------------' 1059 print '# Packet Time (sec) Percent Count ' 1060 print '#------------------------- ---------- ------- ------' 1061 if options and options.sort_count: 1062 res = sorted(packet_count, key=packet_count.__getitem__, reverse=True) 1063 else: 1064 res = sorted(packet_total_times, key=packet_total_times.__getitem__, reverse=True) 1065 1066 if last_time > 0.0: 1067 for item in res: 1068 packet_total_time = packet_total_times[item] 1069 packet_percent = (packet_total_time / total_packet_time)*100.0 1070 if packet_percent >= 10.0: 1071 print " %24s %.6f %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item]) 1072 else: 1073 print " %24s %.6f %.2f%% %6d" % (item, packet_total_time, packet_percent, packet_count[item]) 1074 1075 1076 1077if __name__ == '__main__': 1078 usage = "usage: gdbremote [options]" 1079 description='''The command disassembles a GDB remote packet log.''' 1080 parser = optparse.OptionParser(description=description, prog='gdbremote',usage=usage) 1081 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', help='display verbose debug info', default=False) 1082 parser.add_option('-q', '--quiet', action='store_true', dest='quiet', help='display verbose debug info', default=False) 1083 parser.add_option('-C', '--color', action='store_true', dest='color', help='add terminal colors', default=False) 1084 parser.add_option('-c', '--sort-by-count', action='store_true', dest='sort_count', help='display verbose debug info', default=False) 1085 parser.add_option('--crashlog', type='string', dest='crashlog', help='symbolicate using a darwin crash log file', default=False) 1086 try: 1087 (options, args) = parser.parse_args(sys.argv[1:]) 1088 except: 1089 print 'error: argument error' 1090 sys.exit(1) 1091 1092 options.colors = TerminalColors(options.color) 1093 options.symbolicator = None 1094 if options.crashlog: 1095 import lldb 1096 lldb.debugger = lldb.SBDebugger.Create() 1097 import lldb.macosx.crashlog 1098 options.symbolicator = lldb.macosx.crashlog.CrashLog(options.crashlog) 1099 print '%s' % (options.symbolicator) 1100 1101 # This script is being run from the command line, create a debugger in case we are 1102 # going to use any debugger functions in our function. 1103 for file in args: 1104 print '#----------------------------------------------------------------------' 1105 print "# GDB remote log file: '%s'" % file 1106 print '#----------------------------------------------------------------------' 1107 parse_gdb_log_file (file, options) 1108 if options.symbolicator: 1109 print '%s' % (options.symbolicator) 1110 1111else: 1112 import lldb 1113 if lldb.debugger: 1114 # This initializer is being run from LLDB in the embedded command interpreter 1115 # Add any commands contained in this module to LLDB 1116 lldb.debugger.HandleCommand('command script add -f gdbremote.start_gdb_log start_gdb_log') 1117 lldb.debugger.HandleCommand('command script add -f gdbremote.stop_gdb_log stop_gdb_log') 1118 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' 1119