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 subprocess 21import json 22import math 23import optparse 24import os 25import re 26import shlex 27import string 28import sys 29import tempfile 30import xml.etree.ElementTree as ET 31 32#---------------------------------------------------------------------- 33# Global variables 34#---------------------------------------------------------------------- 35g_log_file = '' 36g_byte_order = 'little' 37g_number_regex = re.compile('^(0x[0-9a-fA-F]+|[0-9]+)') 38g_thread_id_regex = re.compile('^(-1|[0-9a-fA-F]+|0)') 39 40 41class TerminalColors: 42 '''Simple terminal colors class''' 43 44 def __init__(self, enabled=True): 45 # TODO: discover terminal type from "file" and disable if 46 # it can't handle the color codes 47 self.enabled = enabled 48 49 def reset(self): 50 '''Reset all terminal colors and formatting.''' 51 if self.enabled: 52 return "\x1b[0m" 53 return '' 54 55 def bold(self, on=True): 56 '''Enable or disable bold depending on the "on" parameter.''' 57 if self.enabled: 58 if on: 59 return "\x1b[1m" 60 else: 61 return "\x1b[22m" 62 return '' 63 64 def italics(self, on=True): 65 '''Enable or disable italics depending on the "on" parameter.''' 66 if self.enabled: 67 if on: 68 return "\x1b[3m" 69 else: 70 return "\x1b[23m" 71 return '' 72 73 def underline(self, on=True): 74 '''Enable or disable underline depending on the "on" parameter.''' 75 if self.enabled: 76 if on: 77 return "\x1b[4m" 78 else: 79 return "\x1b[24m" 80 return '' 81 82 def inverse(self, on=True): 83 '''Enable or disable inverse depending on the "on" parameter.''' 84 if self.enabled: 85 if on: 86 return "\x1b[7m" 87 else: 88 return "\x1b[27m" 89 return '' 90 91 def strike(self, on=True): 92 '''Enable or disable strike through depending on the "on" parameter.''' 93 if self.enabled: 94 if on: 95 return "\x1b[9m" 96 else: 97 return "\x1b[29m" 98 return '' 99 100 def black(self, fg=True): 101 '''Set the foreground or background color to black. 102 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 103 if self.enabled: 104 if fg: 105 return "\x1b[30m" 106 else: 107 return "\x1b[40m" 108 return '' 109 110 def red(self, fg=True): 111 '''Set the foreground or background color to red. 112 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 113 if self.enabled: 114 if fg: 115 return "\x1b[31m" 116 else: 117 return "\x1b[41m" 118 return '' 119 120 def green(self, fg=True): 121 '''Set the foreground or background color to green. 122 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 123 if self.enabled: 124 if fg: 125 return "\x1b[32m" 126 else: 127 return "\x1b[42m" 128 return '' 129 130 def yellow(self, fg=True): 131 '''Set the foreground or background color to yellow. 132 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 133 if self.enabled: 134 if fg: 135 return "\x1b[33m" 136 else: 137 return "\x1b[43m" 138 return '' 139 140 def blue(self, fg=True): 141 '''Set the foreground or background color to blue. 142 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 143 if self.enabled: 144 if fg: 145 return "\x1b[34m" 146 else: 147 return "\x1b[44m" 148 return '' 149 150 def magenta(self, fg=True): 151 '''Set the foreground or background color to magenta. 152 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 153 if self.enabled: 154 if fg: 155 return "\x1b[35m" 156 else: 157 return "\x1b[45m" 158 return '' 159 160 def cyan(self, fg=True): 161 '''Set the foreground or background color to cyan. 162 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 163 if self.enabled: 164 if fg: 165 return "\x1b[36m" 166 else: 167 return "\x1b[46m" 168 return '' 169 170 def white(self, fg=True): 171 '''Set the foreground or background color to white. 172 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 173 if self.enabled: 174 if fg: 175 return "\x1b[37m" 176 else: 177 return "\x1b[47m" 178 return '' 179 180 def default(self, fg=True): 181 '''Set the foreground or background color to the default. 182 The foreground color will be set if "fg" tests True. The background color will be set if "fg" tests False.''' 183 if self.enabled: 184 if fg: 185 return "\x1b[39m" 186 else: 187 return "\x1b[49m" 188 return '' 189 190 191def start_gdb_log(debugger, command, result, dict): 192 '''Start logging GDB remote packets by enabling logging with timestamps and 193 thread safe logging. Follow a call to this function with a call to "stop_gdb_log" 194 in order to dump out the commands.''' 195 global g_log_file 196 command_args = shlex.split(command) 197 usage = "usage: start_gdb_log [options] [<LOGFILEPATH>]" 198 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 199 be aggregated and displayed.''' 200 parser = optparse.OptionParser( 201 description=description, 202 prog='start_gdb_log', 203 usage=usage) 204 parser.add_option( 205 '-v', 206 '--verbose', 207 action='store_true', 208 dest='verbose', 209 help='display verbose debug info', 210 default=False) 211 try: 212 (options, args) = parser.parse_args(command_args) 213 except: 214 return 215 216 if g_log_file: 217 result.PutCString( 218 'error: logging is already in progress with file "%s"' % 219 g_log_file) 220 else: 221 args_len = len(args) 222 if args_len == 0: 223 g_log_file = tempfile.mktemp() 224 elif len(args) == 1: 225 g_log_file = args[0] 226 227 if g_log_file: 228 debugger.HandleCommand( 229 'log enable --threadsafe --timestamp --file "%s" gdb-remote packets' % 230 g_log_file) 231 result.PutCString( 232 "GDB packet logging enable with log file '%s'\nUse the 'stop_gdb_log' command to stop logging and show packet statistics." % 233 g_log_file) 234 return 235 236 result.PutCString('error: invalid log file path') 237 result.PutCString(usage) 238 239 240def stop_gdb_log(debugger, command, result, dict): 241 '''Stop logging GDB remote packets to the file that was specified in a call 242 to "start_gdb_log" and normalize the timestamps to be relative to the first 243 timestamp in the log file. Also print out statistics for how long each 244 command took to allow performance bottlenecks to be determined.''' 245 global g_log_file 246 # Any commands whose names might be followed by more valid C identifier 247 # characters must be listed here 248 command_args = shlex.split(command) 249 usage = "usage: stop_gdb_log [options]" 250 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.''' 251 parser = optparse.OptionParser( 252 description=description, 253 prog='stop_gdb_log', 254 usage=usage) 255 parser.add_option( 256 '-v', 257 '--verbose', 258 action='store_true', 259 dest='verbose', 260 help='display verbose debug info', 261 default=False) 262 parser.add_option( 263 '-q', 264 '--quiet', 265 action='store_true', 266 dest='quiet', 267 help='display verbose debug info', 268 default=False) 269 parser.add_option( 270 '-C', 271 '--color', 272 action='store_true', 273 dest='color', 274 help='add terminal colors', 275 default=False) 276 parser.add_option( 277 '-c', 278 '--sort-by-count', 279 action='store_true', 280 dest='sort_count', 281 help='display verbose debug info', 282 default=False) 283 parser.add_option( 284 '-s', 285 '--symbolicate', 286 action='store_true', 287 dest='symbolicate', 288 help='symbolicate addresses in log using current "lldb.target"', 289 default=False) 290 try: 291 (options, args) = parser.parse_args(command_args) 292 except: 293 return 294 options.colors = TerminalColors(options.color) 295 options.symbolicator = None 296 if options.symbolicate: 297 if lldb.target: 298 import lldb.utils.symbolication 299 options.symbolicator = lldb.utils.symbolication.Symbolicator() 300 options.symbolicator.target = lldb.target 301 else: 302 print("error: can't symbolicate without a target") 303 304 if not g_log_file: 305 result.PutCString( 306 'error: logging must have been previously enabled with a call to "stop_gdb_log"') 307 elif os.path.exists(g_log_file): 308 if len(args) == 0: 309 debugger.HandleCommand('log disable gdb-remote packets') 310 result.PutCString( 311 "GDB packet logging disabled. Logged packets are in '%s'" % 312 g_log_file) 313 parse_gdb_log_file(g_log_file, options) 314 else: 315 result.PutCString(usage) 316 else: 317 print('error: the GDB packet log file "%s" does not exist' % g_log_file) 318 319 320def is_hex_byte(str): 321 if len(str) == 2: 322 return str[0] in string.hexdigits and str[1] in string.hexdigits 323 return False 324 325def get_hex_string_if_all_printable(str): 326 try: 327 s = binascii.unhexlify(str) 328 if all(c in string.printable for c in s): 329 return s 330 except TypeError: 331 pass 332 return None 333 334# global register info list 335g_register_infos = list() 336g_max_register_info_name_len = 0 337 338 339class RegisterInfo: 340 """Class that represents register information""" 341 342 def __init__(self, kvp): 343 self.info = dict() 344 for kv in kvp: 345 key = kv[0] 346 value = kv[1] 347 self.info[key] = value 348 349 def name(self): 350 '''Get the name of the register.''' 351 if self.info and 'name' in self.info: 352 return self.info['name'] 353 return None 354 355 def bit_size(self): 356 '''Get the size in bits of the register.''' 357 if self.info and 'bitsize' in self.info: 358 return int(self.info['bitsize']) 359 return 0 360 361 def byte_size(self): 362 '''Get the size in bytes of the register.''' 363 return self.bit_size() / 8 364 365 def get_value_from_hex_string(self, hex_str): 366 '''Dump the register value given a native byte order encoded hex ASCII byte string.''' 367 encoding = self.info['encoding'] 368 bit_size = self.bit_size() 369 packet = Packet(hex_str) 370 if encoding == 'uint': 371 uval = packet.get_hex_uint(g_byte_order) 372 if bit_size == 8: 373 return '0x%2.2x' % (uval) 374 elif bit_size == 16: 375 return '0x%4.4x' % (uval) 376 elif bit_size == 32: 377 return '0x%8.8x' % (uval) 378 elif bit_size == 64: 379 return '0x%16.16x' % (uval) 380 bytes = list() 381 uval = packet.get_hex_uint8() 382 while uval is not None: 383 bytes.append(uval) 384 uval = packet.get_hex_uint8() 385 value_str = '0x' 386 if g_byte_order == 'little': 387 bytes.reverse() 388 for byte in bytes: 389 value_str += '%2.2x' % byte 390 return '%s' % (value_str) 391 392 def __str__(self): 393 '''Dump the register info key/value pairs''' 394 s = '' 395 for key in self.info.keys(): 396 if s: 397 s += ', ' 398 s += "%s=%s " % (key, self.info[key]) 399 return s 400 401 402class Packet: 403 """Class that represents a packet that contains string data""" 404 405 def __init__(self, packet_str): 406 self.str = packet_str 407 408 def peek_char(self): 409 ch = 0 410 if self.str: 411 ch = self.str[0] 412 return ch 413 414 def get_char(self): 415 ch = 0 416 if self.str: 417 ch = self.str[0] 418 self.str = self.str[1:] 419 return ch 420 421 def skip_exact_string(self, s): 422 if self.str and self.str.startswith(s): 423 self.str = self.str[len(s):] 424 return True 425 else: 426 return False 427 428 def get_thread_id(self, fail_value=-1): 429 match = g_number_regex.match(self.str) 430 if match: 431 number_str = match.group(1) 432 self.str = self.str[len(number_str):] 433 return int(number_str, 0) 434 else: 435 return fail_value 436 437 def get_hex_uint8(self): 438 if self.str and len(self.str) >= 2 and self.str[ 439 0] in string.hexdigits and self.str[1] in string.hexdigits: 440 uval = int(self.str[0:2], 16) 441 self.str = self.str[2:] 442 return uval 443 return None 444 445 def get_hex_uint16(self, byte_order): 446 uval = 0 447 if byte_order == 'big': 448 uval |= self.get_hex_uint8() << 8 449 uval |= self.get_hex_uint8() 450 else: 451 uval |= self.get_hex_uint8() 452 uval |= self.get_hex_uint8() << 8 453 return uval 454 455 def get_hex_uint32(self, byte_order): 456 uval = 0 457 if byte_order == 'big': 458 uval |= self.get_hex_uint8() << 24 459 uval |= self.get_hex_uint8() << 16 460 uval |= self.get_hex_uint8() << 8 461 uval |= self.get_hex_uint8() 462 else: 463 uval |= self.get_hex_uint8() 464 uval |= self.get_hex_uint8() << 8 465 uval |= self.get_hex_uint8() << 16 466 uval |= self.get_hex_uint8() << 24 467 return uval 468 469 def get_hex_uint64(self, byte_order): 470 uval = 0 471 if byte_order == 'big': 472 uval |= self.get_hex_uint8() << 56 473 uval |= self.get_hex_uint8() << 48 474 uval |= self.get_hex_uint8() << 40 475 uval |= self.get_hex_uint8() << 32 476 uval |= self.get_hex_uint8() << 24 477 uval |= self.get_hex_uint8() << 16 478 uval |= self.get_hex_uint8() << 8 479 uval |= self.get_hex_uint8() 480 else: 481 uval |= self.get_hex_uint8() 482 uval |= self.get_hex_uint8() << 8 483 uval |= self.get_hex_uint8() << 16 484 uval |= self.get_hex_uint8() << 24 485 uval |= self.get_hex_uint8() << 32 486 uval |= self.get_hex_uint8() << 40 487 uval |= self.get_hex_uint8() << 48 488 uval |= self.get_hex_uint8() << 56 489 return uval 490 491 def get_number(self, fail_value=-1): 492 '''Get a number from the packet. The number must be in big endian format and should be parsed 493 according to its prefix (starts with "0x" means hex, starts with "0" means octal, starts with 494 [1-9] means decimal, etc)''' 495 match = g_number_regex.match(self.str) 496 if match: 497 number_str = match.group(1) 498 self.str = self.str[len(number_str):] 499 return int(number_str, 0) 500 else: 501 return fail_value 502 503 def get_hex_ascii_str(self, n=0): 504 hex_chars = self.get_hex_chars(n) 505 if hex_chars: 506 return binascii.unhexlify(hex_chars) 507 else: 508 return None 509 510 def get_hex_chars(self, n=0): 511 str_len = len(self.str) 512 if n == 0: 513 # n was zero, so we need to determine all hex chars and 514 # stop when we hit the end of the string of a non-hex character 515 while n < str_len and self.str[n] in string.hexdigits: 516 n = n + 1 517 else: 518 if n > str_len: 519 return None # Not enough chars 520 # Verify all chars are hex if a length was specified 521 for i in range(n): 522 if self.str[i] not in string.hexdigits: 523 return None # Not all hex digits 524 if n == 0: 525 return None 526 hex_str = self.str[0:n] 527 self.str = self.str[n:] 528 return hex_str 529 530 def get_hex_uint(self, byte_order, n=0): 531 if byte_order == 'big': 532 hex_str = self.get_hex_chars(n) 533 if hex_str is None: 534 return None 535 return int(hex_str, 16) 536 else: 537 uval = self.get_hex_uint8() 538 if uval is None: 539 return None 540 uval_result = 0 541 shift = 0 542 while uval is not None: 543 uval_result |= (uval << shift) 544 shift += 8 545 uval = self.get_hex_uint8() 546 return uval_result 547 548 def get_key_value_pairs(self): 549 kvp = list() 550 if ';' in self.str: 551 key_value_pairs = string.split(self.str, ';') 552 for key_value_pair in key_value_pairs: 553 if len(key_value_pair): 554 kvp.append(string.split(key_value_pair, ':')) 555 return kvp 556 557 def split(self, ch): 558 return string.split(self.str, ch) 559 560 def split_hex(self, ch, byte_order): 561 hex_values = list() 562 strings = string.split(self.str, ch) 563 for str in strings: 564 hex_values.append(Packet(str).get_hex_uint(byte_order)) 565 return hex_values 566 567 def __str__(self): 568 return self.str 569 570 def __len__(self): 571 return len(self.str) 572 573g_thread_suffix_regex = re.compile(';thread:([0-9a-fA-F]+);') 574 575 576def get_thread_from_thread_suffix(str): 577 if str: 578 match = g_thread_suffix_regex.match(str) 579 if match: 580 return int(match.group(1), 16) 581 return None 582 583 584def cmd_qThreadStopInfo(options, cmd, args): 585 packet = Packet(args) 586 tid = packet.get_hex_uint('big') 587 print("get_thread_stop_info (tid = 0x%x)" % (tid)) 588 589 590def cmd_stop_reply(options, cmd, args): 591 print("get_last_stop_info()") 592 return False 593 594 595def rsp_stop_reply(options, cmd, cmd_args, rsp): 596 global g_byte_order 597 packet = Packet(rsp) 598 stop_type = packet.get_char() 599 if stop_type == 'T' or stop_type == 'S': 600 signo = packet.get_hex_uint8() 601 key_value_pairs = packet.get_key_value_pairs() 602 for key_value_pair in key_value_pairs: 603 key = key_value_pair[0] 604 if is_hex_byte(key): 605 reg_num = Packet(key).get_hex_uint8() 606 if reg_num < len(g_register_infos): 607 reg_info = g_register_infos[reg_num] 608 key_value_pair[0] = reg_info.name() 609 key_value_pair[1] = reg_info.get_value_from_hex_string( 610 key_value_pair[1]) 611 elif key == 'jthreads' or key == 'jstopinfo': 612 key_value_pair[1] = binascii.unhexlify(key_value_pair[1]) 613 key_value_pairs.insert(0, ['signal', signo]) 614 print('stop_reply():') 615 dump_key_value_pairs(key_value_pairs) 616 elif stop_type == 'W': 617 exit_status = packet.get_hex_uint8() 618 print('stop_reply(): exit (status=%i)' % exit_status) 619 elif stop_type == 'O': 620 print('stop_reply(): stdout = "%s"' % packet.str) 621 622 623def cmd_unknown_packet(options, cmd, args): 624 if args: 625 print("cmd: %s, args: %s", cmd, args) 626 else: 627 print("cmd: %s", cmd) 628 return False 629 630 631def cmd_qSymbol(options, cmd, args): 632 if args == ':': 633 print('ready to serve symbols') 634 else: 635 packet = Packet(args) 636 symbol_addr = packet.get_hex_uint('big') 637 if symbol_addr is None: 638 if packet.skip_exact_string(':'): 639 symbol_name = packet.get_hex_ascii_str() 640 print('lookup_symbol("%s") -> symbol not available yet' % (symbol_name)) 641 else: 642 print('error: bad command format') 643 else: 644 if packet.skip_exact_string(':'): 645 symbol_name = packet.get_hex_ascii_str() 646 print('lookup_symbol("%s") -> 0x%x' % (symbol_name, symbol_addr)) 647 else: 648 print('error: bad command format') 649 650def cmd_QSetWithHexString(options, cmd, args): 651 print('%s("%s")' % (cmd[:-1], binascii.unhexlify(args))) 652 653def cmd_QSetWithString(options, cmd, args): 654 print('%s("%s")' % (cmd[:-1], args)) 655 656def cmd_QSetWithUnsigned(options, cmd, args): 657 print('%s(%i)' % (cmd[:-1], int(args))) 658 659def rsp_qSymbol(options, cmd, cmd_args, rsp): 660 if len(rsp) == 0: 661 print("Unsupported") 662 else: 663 if rsp == "OK": 664 print("No more symbols to lookup") 665 else: 666 packet = Packet(rsp) 667 if packet.skip_exact_string("qSymbol:"): 668 symbol_name = packet.get_hex_ascii_str() 669 print('lookup_symbol("%s")' % (symbol_name)) 670 else: 671 print('error: response string should start with "qSymbol:": respnse is "%s"' % (rsp)) 672 673 674def cmd_qXfer(options, cmd, args): 675 # $qXfer:features:read:target.xml:0,1ffff#14 676 print("read target special data %s" % (args)) 677 return True 678 679 680def rsp_qXfer(options, cmd, cmd_args, rsp): 681 data = string.split(cmd_args, ':') 682 if data[0] == 'features': 683 if data[1] == 'read': 684 filename, extension = os.path.splitext(data[2]) 685 if extension == '.xml': 686 response = Packet(rsp) 687 xml_string = response.get_hex_ascii_str() 688 if xml_string: 689 ch = xml_string[0] 690 if ch == 'l': 691 xml_string = xml_string[1:] 692 xml_root = ET.fromstring(xml_string) 693 for reg_element in xml_root.findall("./feature/reg"): 694 if not 'value_regnums' in reg_element.attrib: 695 reg_info = RegisterInfo([]) 696 if 'name' in reg_element.attrib: 697 reg_info.info[ 698 'name'] = reg_element.attrib['name'] 699 else: 700 reg_info.info['name'] = 'unspecified' 701 if 'encoding' in reg_element.attrib: 702 reg_info.info['encoding'] = reg_element.attrib[ 703 'encoding'] 704 else: 705 reg_info.info['encoding'] = 'uint' 706 if 'offset' in reg_element.attrib: 707 reg_info.info[ 708 'offset'] = reg_element.attrib['offset'] 709 if 'bitsize' in reg_element.attrib: 710 reg_info.info[ 711 'bitsize'] = reg_element.attrib['bitsize'] 712 g_register_infos.append(reg_info) 713 print('XML for "%s":' % (data[2])) 714 ET.dump(xml_root) 715 716 717def cmd_A(options, cmd, args): 718 print('launch process:') 719 packet = Packet(args) 720 while True: 721 arg_len = packet.get_number() 722 if arg_len == -1: 723 break 724 if not packet.skip_exact_string(','): 725 break 726 arg_idx = packet.get_number() 727 if arg_idx == -1: 728 break 729 if not packet.skip_exact_string(','): 730 break 731 arg_value = packet.get_hex_ascii_str(arg_len) 732 print('argv[%u] = "%s"' % (arg_idx, arg_value)) 733 734 735def cmd_qC(options, cmd, args): 736 print("query_current_thread_id()") 737 738 739def rsp_qC(options, cmd, cmd_args, rsp): 740 packet = Packet(rsp) 741 if packet.skip_exact_string("QC"): 742 tid = packet.get_thread_id() 743 print("current_thread_id = %#x" % (tid)) 744 else: 745 print("current_thread_id = old thread ID") 746 747 748def cmd_query_packet(options, cmd, args): 749 if args: 750 print("%s%s" % (cmd, args)) 751 else: 752 print("%s" % (cmd)) 753 return False 754 755 756def rsp_ok_error(rsp): 757 print("rsp: ", rsp) 758 759 760def rsp_ok_means_supported(options, cmd, cmd_args, rsp): 761 if rsp == 'OK': 762 print("%s%s is supported" % (cmd, cmd_args)) 763 elif rsp == '': 764 print("%s%s is not supported" % (cmd, cmd_args)) 765 else: 766 print("%s%s -> %s" % (cmd, cmd_args, rsp)) 767 768 769def rsp_ok_means_success(options, cmd, cmd_args, rsp): 770 if rsp == 'OK': 771 print("success") 772 elif rsp == '': 773 print("%s%s is not supported" % (cmd, cmd_args)) 774 else: 775 print("%s%s -> %s" % (cmd, cmd_args, rsp)) 776 777 778def dump_key_value_pairs(key_value_pairs): 779 max_key_len = 0 780 for key_value_pair in key_value_pairs: 781 key_len = len(key_value_pair[0]) 782 if max_key_len < key_len: 783 max_key_len = key_len 784 for key_value_pair in key_value_pairs: 785 key = key_value_pair[0] 786 value = key_value_pair[1] 787 unhex_value = get_hex_string_if_all_printable(value) 788 if unhex_value: 789 print("%*s = %s (%s)" % (max_key_len, key, value, unhex_value)) 790 else: 791 print("%*s = %s" % (max_key_len, key, value)) 792 793 794def rsp_dump_key_value_pairs(options, cmd, cmd_args, rsp): 795 if rsp: 796 print('%s response:' % (cmd)) 797 packet = Packet(rsp) 798 key_value_pairs = packet.get_key_value_pairs() 799 dump_key_value_pairs(key_value_pairs) 800 else: 801 print("not supported") 802 803 804def cmd_c(options, cmd, args): 805 print("continue()") 806 return False 807 808 809def cmd_s(options, cmd, args): 810 print("step()") 811 return False 812 813 814def cmd_qSpeedTest(options, cmd, args): 815 print(("qSpeedTest: cmd='%s', args='%s'" % (cmd, args))) 816 817 818def rsp_qSpeedTest(options, cmd, cmd_args, rsp): 819 print(("qSpeedTest: rsp='%s' cmd='%s', args='%s'" % (rsp, cmd, args))) 820 821 822def cmd_vCont(options, cmd, args): 823 if args == '?': 824 print("%s: get supported extended continue modes" % (cmd)) 825 else: 826 got_other_threads = 0 827 s = '' 828 for thread_action in string.split(args[1:], ';'): 829 (short_action, thread) = string.split(thread_action, ':') 830 tid = int(thread, 16) 831 if short_action == 'c': 832 action = 'continue' 833 elif short_action == 's': 834 action = 'step' 835 elif short_action[0] == 'C': 836 action = 'continue with signal 0x%s' % (short_action[1:]) 837 elif short_action == 'S': 838 action = 'step with signal 0x%s' % (short_action[1:]) 839 else: 840 action = short_action 841 if s: 842 s += ', ' 843 if tid == -1: 844 got_other_threads = 1 845 s += 'other-threads:' 846 else: 847 s += 'thread 0x%4.4x: %s' % (tid, action) 848 if got_other_threads: 849 print("extended_continue (%s)" % (s)) 850 else: 851 print("extended_continue (%s, other-threads: suspend)" % (s)) 852 return False 853 854 855def rsp_vCont(options, cmd, cmd_args, rsp): 856 if cmd_args == '?': 857 # Skip the leading 'vCont;' 858 rsp = rsp[6:] 859 modes = string.split(rsp, ';') 860 s = "%s: supported extended continue modes include: " % (cmd) 861 862 for i, mode in enumerate(modes): 863 if i: 864 s += ', ' 865 if mode == 'c': 866 s += 'continue' 867 elif mode == 'C': 868 s += 'continue with signal' 869 elif mode == 's': 870 s += 'step' 871 elif mode == 'S': 872 s += 'step with signal' 873 elif mode == 't': 874 s += 'stop' 875 # else: 876 # s += 'unrecognized vCont mode: ', str(mode) 877 print(s) 878 elif rsp: 879 if rsp[0] == 'T' or rsp[0] == 'S' or rsp[0] == 'W' or rsp[0] == 'X': 880 rsp_stop_reply(options, cmd, cmd_args, rsp) 881 return 882 if rsp[0] == 'O': 883 print("stdout: %s" % (rsp)) 884 return 885 else: 886 print("not supported (cmd = '%s', args = '%s', rsp = '%s')" % (cmd, cmd_args, rsp)) 887 888 889def cmd_vAttach(options, cmd, args): 890 (extra_command, args) = string.split(args, ';') 891 if extra_command: 892 print("%s%s(%s)" % (cmd, extra_command, args)) 893 else: 894 print("attach(pid = %u)" % int(args, 16)) 895 return False 896 897 898def cmd_qRegisterInfo(options, cmd, args): 899 print('query_register_info(reg_num=%i)' % (int(args, 16))) 900 return False 901 902 903def rsp_qRegisterInfo(options, cmd, cmd_args, rsp): 904 global g_max_register_info_name_len 905 print('query_register_info(reg_num=%i):' % (int(cmd_args, 16)), end=' ') 906 if len(rsp) == 3 and rsp[0] == 'E': 907 g_max_register_info_name_len = 0 908 for reg_info in g_register_infos: 909 name_len = len(reg_info.name()) 910 if g_max_register_info_name_len < name_len: 911 g_max_register_info_name_len = name_len 912 print(' DONE') 913 else: 914 packet = Packet(rsp) 915 reg_info = RegisterInfo(packet.get_key_value_pairs()) 916 g_register_infos.append(reg_info) 917 print(reg_info) 918 return False 919 920 921def cmd_qThreadInfo(options, cmd, args): 922 if cmd == 'qfThreadInfo': 923 query_type = 'first' 924 else: 925 query_type = 'subsequent' 926 print('get_current_thread_list(type=%s)' % (query_type)) 927 return False 928 929 930def rsp_qThreadInfo(options, cmd, cmd_args, rsp): 931 packet = Packet(rsp) 932 response_type = packet.get_char() 933 if response_type == 'm': 934 tids = packet.split_hex(';', 'big') 935 for i, tid in enumerate(tids): 936 if i: 937 print(',', end=' ') 938 print('0x%x' % (tid), end=' ') 939 print() 940 elif response_type == 'l': 941 print('END') 942 943 944def rsp_hex_big_endian(options, cmd, cmd_args, rsp): 945 if rsp == '': 946 print("%s%s is not supported" % (cmd, cmd_args)) 947 else: 948 packet = Packet(rsp) 949 uval = packet.get_hex_uint('big') 950 print('%s: 0x%x' % (cmd, uval)) 951 952 953def cmd_read_mem_bin(options, cmd, args): 954 # x0x7fff5fc39200,0x200 955 packet = Packet(args) 956 addr = packet.get_hex_uint('big') 957 comma = packet.get_char() 958 size = packet.get_hex_uint('big') 959 print('binary_read_memory (addr = 0x%16.16x, size = %u)' % (addr, size)) 960 return False 961 962 963def rsp_mem_bin_bytes(options, cmd, cmd_args, rsp): 964 packet = Packet(cmd_args) 965 addr = packet.get_hex_uint('big') 966 comma = packet.get_char() 967 size = packet.get_hex_uint('big') 968 print('memory:') 969 if size > 0: 970 dump_hex_memory_buffer(addr, rsp) 971 972 973def cmd_read_memory(options, cmd, args): 974 packet = Packet(args) 975 addr = packet.get_hex_uint('big') 976 comma = packet.get_char() 977 size = packet.get_hex_uint('big') 978 print('read_memory (addr = 0x%16.16x, size = %u)' % (addr, size)) 979 return False 980 981 982def dump_hex_memory_buffer(addr, hex_byte_str): 983 packet = Packet(hex_byte_str) 984 idx = 0 985 ascii = '' 986 uval = packet.get_hex_uint8() 987 while uval is not None: 988 if ((idx % 16) == 0): 989 if ascii: 990 print(' ', ascii) 991 ascii = '' 992 print('0x%x:' % (addr + idx), end=' ') 993 print('%2.2x' % (uval), end=' ') 994 if 0x20 <= uval and uval < 0x7f: 995 ascii += '%c' % uval 996 else: 997 ascii += '.' 998 uval = packet.get_hex_uint8() 999 idx = idx + 1 1000 if ascii: 1001 print(' ', ascii) 1002 ascii = '' 1003 1004 1005def cmd_write_memory(options, cmd, args): 1006 packet = Packet(args) 1007 addr = packet.get_hex_uint('big') 1008 if packet.get_char() != ',': 1009 print('error: invalid write memory command (missing comma after address)') 1010 return 1011 size = packet.get_hex_uint('big') 1012 if packet.get_char() != ':': 1013 print('error: invalid write memory command (missing colon after size)') 1014 return 1015 print('write_memory (addr = 0x%16.16x, size = %u, data:' % (addr, size)) 1016 dump_hex_memory_buffer(addr, packet.str) 1017 return False 1018 1019 1020def cmd_alloc_memory(options, cmd, args): 1021 packet = Packet(args) 1022 byte_size = packet.get_hex_uint('big') 1023 if packet.get_char() != ',': 1024 print('error: invalid allocate memory command (missing comma after address)') 1025 return 1026 print('allocate_memory (byte-size = %u (0x%x), permissions = %s)' % (byte_size, byte_size, packet.str)) 1027 return False 1028 1029 1030def rsp_alloc_memory(options, cmd, cmd_args, rsp): 1031 packet = Packet(rsp) 1032 addr = packet.get_hex_uint('big') 1033 print('addr = 0x%x' % addr) 1034 1035 1036def cmd_dealloc_memory(options, cmd, args): 1037 packet = Packet(args) 1038 addr = packet.get_hex_uint('big') 1039 if packet.get_char() != ',': 1040 print('error: invalid allocate memory command (missing comma after address)') 1041 else: 1042 print('deallocate_memory (addr = 0x%x, permissions = %s)' % (addr, packet.str)) 1043 return False 1044 1045 1046def rsp_memory_bytes(options, cmd, cmd_args, rsp): 1047 addr = Packet(cmd_args).get_hex_uint('big') 1048 dump_hex_memory_buffer(addr, rsp) 1049 1050 1051def get_register_name_equal_value(options, reg_num, hex_value_str): 1052 if reg_num < len(g_register_infos): 1053 reg_info = g_register_infos[reg_num] 1054 value_str = reg_info.get_value_from_hex_string(hex_value_str) 1055 s = reg_info.name() + ' = ' 1056 if options.symbolicator: 1057 symbolicated_addresses = options.symbolicator.symbolicate( 1058 int(value_str, 0)) 1059 if symbolicated_addresses: 1060 s += options.colors.magenta() 1061 s += '%s' % symbolicated_addresses[0] 1062 s += options.colors.reset() 1063 return s 1064 s += value_str 1065 return s 1066 else: 1067 reg_value = Packet(hex_value_str).get_hex_uint(g_byte_order) 1068 return 'reg(%u) = 0x%x' % (reg_num, reg_value) 1069 1070 1071def cmd_read_one_reg(options, cmd, args): 1072 packet = Packet(args) 1073 reg_num = packet.get_hex_uint('big') 1074 tid = get_thread_from_thread_suffix(packet.str) 1075 name = None 1076 if reg_num < len(g_register_infos): 1077 name = g_register_infos[reg_num].name() 1078 if packet.str: 1079 packet.get_char() # skip ; 1080 thread_info = packet.get_key_value_pairs() 1081 tid = int(thread_info[0][1], 16) 1082 s = 'read_register (reg_num=%u' % reg_num 1083 if name: 1084 s += ' (%s)' % (name) 1085 if tid is not None: 1086 s += ', tid = 0x%4.4x' % (tid) 1087 s += ')' 1088 print(s) 1089 return False 1090 1091 1092def rsp_read_one_reg(options, cmd, cmd_args, rsp): 1093 packet = Packet(cmd_args) 1094 reg_num = packet.get_hex_uint('big') 1095 print(get_register_name_equal_value(options, reg_num, rsp)) 1096 1097 1098def cmd_write_one_reg(options, cmd, args): 1099 packet = Packet(args) 1100 reg_num = packet.get_hex_uint('big') 1101 if packet.get_char() != '=': 1102 print('error: invalid register write packet') 1103 else: 1104 name = None 1105 hex_value_str = packet.get_hex_chars() 1106 tid = get_thread_from_thread_suffix(packet.str) 1107 s = 'write_register (reg_num=%u' % reg_num 1108 if name: 1109 s += ' (%s)' % (name) 1110 s += ', value = ' 1111 s += get_register_name_equal_value(options, reg_num, hex_value_str) 1112 if tid is not None: 1113 s += ', tid = 0x%4.4x' % (tid) 1114 s += ')' 1115 print(s) 1116 return False 1117 1118 1119def dump_all_regs(packet): 1120 for reg_info in g_register_infos: 1121 nibble_size = reg_info.bit_size() / 4 1122 hex_value_str = packet.get_hex_chars(nibble_size) 1123 if hex_value_str is not None: 1124 value = reg_info.get_value_from_hex_string(hex_value_str) 1125 print('%*s = %s' % (g_max_register_info_name_len, reg_info.name(), value)) 1126 else: 1127 return 1128 1129 1130def cmd_read_all_regs(cmd, cmd_args): 1131 packet = Packet(cmd_args) 1132 packet.get_char() # toss the 'g' command character 1133 tid = get_thread_from_thread_suffix(packet.str) 1134 if tid is not None: 1135 print('read_all_register(thread = 0x%4.4x)' % tid) 1136 else: 1137 print('read_all_register()') 1138 return False 1139 1140 1141def rsp_read_all_regs(options, cmd, cmd_args, rsp): 1142 packet = Packet(rsp) 1143 dump_all_regs(packet) 1144 1145 1146def cmd_write_all_regs(options, cmd, args): 1147 packet = Packet(args) 1148 print('write_all_registers()') 1149 dump_all_regs(packet) 1150 return False 1151 1152g_bp_types = ["software_bp", "hardware_bp", "write_wp", "read_wp", "access_wp"] 1153 1154 1155def cmd_bp(options, cmd, args): 1156 if cmd == 'Z': 1157 s = 'set_' 1158 else: 1159 s = 'clear_' 1160 packet = Packet(args) 1161 bp_type = packet.get_hex_uint('big') 1162 packet.get_char() # Skip , 1163 bp_addr = packet.get_hex_uint('big') 1164 packet.get_char() # Skip , 1165 bp_size = packet.get_hex_uint('big') 1166 s += g_bp_types[bp_type] 1167 s += " (addr = 0x%x, size = %u)" % (bp_addr, bp_size) 1168 print(s) 1169 return False 1170 1171 1172def cmd_mem_rgn_info(options, cmd, args): 1173 packet = Packet(args) 1174 packet.get_char() # skip ':' character 1175 addr = packet.get_hex_uint('big') 1176 print('get_memory_region_info (addr=0x%x)' % (addr)) 1177 return False 1178 1179 1180def cmd_kill(options, cmd, args): 1181 print('kill_process()') 1182 return False 1183 1184 1185def cmd_jThreadsInfo(options, cmd, args): 1186 print('jThreadsInfo()') 1187 return False 1188 1189 1190def cmd_jGetLoadedDynamicLibrariesInfos(options, cmd, args): 1191 print('jGetLoadedDynamicLibrariesInfos()') 1192 return False 1193 1194 1195def decode_packet(s, start_index=0): 1196 # print '\ndecode_packet("%s")' % (s[start_index:]) 1197 index = s.find('}', start_index) 1198 have_escapes = index != -1 1199 if have_escapes: 1200 normal_s = s[start_index:index] 1201 else: 1202 normal_s = s[start_index:] 1203 # print 'normal_s = "%s"' % (normal_s) 1204 if have_escapes: 1205 escape_char = '%c' % (ord(s[index + 1]) ^ 0x20) 1206 # print 'escape_char for "%s" = %c' % (s[index:index+2], escape_char) 1207 return normal_s + escape_char + decode_packet(s, index + 2) 1208 else: 1209 return normal_s 1210 1211 1212def rsp_json(options, cmd, cmd_args, rsp): 1213 print('%s() reply:' % (cmd)) 1214 json_tree = json.loads(rsp) 1215 print(json.dumps(json_tree, indent=4, separators=(',', ': '))) 1216 1217 1218def rsp_jGetLoadedDynamicLibrariesInfos(options, cmd, cmd_args, rsp): 1219 if cmd_args: 1220 rsp_json(options, cmd, cmd_args, rsp) 1221 else: 1222 rsp_ok_means_supported(options, cmd, cmd_args, rsp) 1223 1224gdb_remote_commands = { 1225 '\\?': {'cmd': cmd_stop_reply, 'rsp': rsp_stop_reply, 'name': "stop reply pacpket"}, 1226 'qThreadStopInfo': {'cmd': cmd_qThreadStopInfo, 'rsp': rsp_stop_reply, 'name': "stop reply pacpket"}, 1227 'QStartNoAckMode': {'cmd': cmd_query_packet, 'rsp': rsp_ok_means_supported, 'name': "query if no ack mode is supported"}, 1228 'QThreadSuffixSupported': {'cmd': cmd_query_packet, 'rsp': rsp_ok_means_supported, 'name': "query if thread suffix is supported"}, 1229 'QListThreadsInStopReply': {'cmd': cmd_query_packet, 'rsp': rsp_ok_means_supported, 'name': "query if threads in stop reply packets are supported"}, 1230 'QSetDetachOnError:': {'cmd': cmd_QSetWithUnsigned, 'rsp': rsp_ok_means_success, 'name': "set if we should detach on error"}, 1231 'QSetDisableASLR:': {'cmd': cmd_QSetWithUnsigned, 'rsp': rsp_ok_means_success, 'name': "set if we should disable ASLR"}, 1232 'qLaunchSuccess': {'cmd': cmd_query_packet, 'rsp': rsp_ok_means_success, 'name': "check on launch success for the A packet"}, 1233 'A': {'cmd': cmd_A, 'rsp': rsp_ok_means_success, 'name': "launch process"}, 1234 'QLaunchArch:': {'cmd': cmd_QSetWithString, 'rsp': rsp_ok_means_supported, 'name': "set the arch to launch in case the file contains multiple architectures"}, 1235 'qVAttachOrWaitSupported': {'cmd': cmd_query_packet, 'rsp': rsp_ok_means_supported, 'name': "set the launch architecture"}, 1236 'qHostInfo': {'cmd': cmd_query_packet, 'rsp': rsp_dump_key_value_pairs, 'name': "get host information"}, 1237 'qC': {'cmd': cmd_qC, 'rsp': rsp_qC, 'name': "return the current thread ID"}, 1238 'vCont': {'cmd': cmd_vCont, 'rsp': rsp_vCont, 'name': "extended continue command"}, 1239 'qSpeedTest': {'cmd':cmd_qSpeedTest, 'rsp': rsp_qSpeedTest, 'name': 'speed test packdet'}, 1240 'vAttach': {'cmd': cmd_vAttach, 'rsp': rsp_stop_reply, 'name': "attach to process"}, 1241 'c': {'cmd': cmd_c, 'rsp': rsp_stop_reply, 'name': "continue"}, 1242 's': {'cmd': cmd_s, 'rsp': rsp_stop_reply, 'name': "step"}, 1243 'qRegisterInfo': {'cmd': cmd_qRegisterInfo, 'rsp': rsp_qRegisterInfo, 'name': "query register info"}, 1244 'qfThreadInfo': {'cmd': cmd_qThreadInfo, 'rsp': rsp_qThreadInfo, 'name': "get current thread list"}, 1245 'qsThreadInfo': {'cmd': cmd_qThreadInfo, 'rsp': rsp_qThreadInfo, 'name': "get current thread list"}, 1246 'qShlibInfoAddr': {'cmd': cmd_query_packet, 'rsp': rsp_hex_big_endian, 'name': "get shared library info address"}, 1247 'qMemoryRegionInfo': {'cmd': cmd_mem_rgn_info, 'rsp': rsp_dump_key_value_pairs, 'name': "get memory region information"}, 1248 'qProcessInfo': {'cmd': cmd_query_packet, 'rsp': rsp_dump_key_value_pairs, 'name': "get process info"}, 1249 'qSupported': {'cmd': cmd_query_packet, 'rsp': rsp_ok_means_supported, 'name': "query supported"}, 1250 'qXfer:': {'cmd': cmd_qXfer, 'rsp': rsp_qXfer, 'name': "qXfer"}, 1251 'qSymbol:': {'cmd': cmd_qSymbol, 'rsp': rsp_qSymbol, 'name': "qSymbol"}, 1252 'QSetSTDIN:' : {'cmd' : cmd_QSetWithHexString, 'rsp' : rsp_ok_means_success, 'name': "set STDIN prior to launching with A packet"}, 1253 'QSetSTDOUT:' : {'cmd' : cmd_QSetWithHexString, 'rsp' : rsp_ok_means_success, 'name': "set STDOUT prior to launching with A packet"}, 1254 'QSetSTDERR:' : {'cmd' : cmd_QSetWithHexString, 'rsp' : rsp_ok_means_success, 'name': "set STDERR prior to launching with A packet"}, 1255 'QEnvironment:' : {'cmd' : cmd_QSetWithString, 'rsp' : rsp_ok_means_success, 'name': "set an environment variable prior to launching with A packet"}, 1256 'QEnvironmentHexEncoded:' : {'cmd' : cmd_QSetWithHexString, 'rsp' : rsp_ok_means_success, 'name': "set an environment variable prior to launching with A packet"}, 1257 'x': {'cmd': cmd_read_mem_bin, 'rsp': rsp_mem_bin_bytes, 'name': "read memory binary"}, 1258 'X': {'cmd': cmd_write_memory, 'rsp': rsp_ok_means_success, 'name': "write memory binary"}, 1259 'm': {'cmd': cmd_read_memory, 'rsp': rsp_memory_bytes, 'name': "read memory"}, 1260 'M': {'cmd': cmd_write_memory, 'rsp': rsp_ok_means_success, 'name': "write memory"}, 1261 '_M': {'cmd': cmd_alloc_memory, 'rsp': rsp_alloc_memory, 'name': "allocate memory"}, 1262 '_m': {'cmd': cmd_dealloc_memory, 'rsp': rsp_ok_means_success, 'name': "deallocate memory"}, 1263 'p': {'cmd': cmd_read_one_reg, 'rsp': rsp_read_one_reg, 'name': "read single register"}, 1264 'P': {'cmd': cmd_write_one_reg, 'rsp': rsp_ok_means_success, 'name': "write single register"}, 1265 'g': {'cmd': cmd_read_all_regs, 'rsp': rsp_read_all_regs, 'name': "read all registers"}, 1266 'G': {'cmd': cmd_write_all_regs, 'rsp': rsp_ok_means_success, 'name': "write all registers"}, 1267 'z': {'cmd': cmd_bp, 'rsp': rsp_ok_means_success, 'name': "clear breakpoint or watchpoint"}, 1268 'Z': {'cmd': cmd_bp, 'rsp': rsp_ok_means_success, 'name': "set breakpoint or watchpoint"}, 1269 'k': {'cmd': cmd_kill, 'rsp': rsp_stop_reply, 'name': "kill process"}, 1270 'jThreadsInfo': {'cmd': cmd_jThreadsInfo, 'rsp': rsp_json, 'name': "JSON get all threads info"}, 1271 'jGetLoadedDynamicLibrariesInfos:': {'cmd': cmd_jGetLoadedDynamicLibrariesInfos, 'rsp': rsp_jGetLoadedDynamicLibrariesInfos, 'name': 'JSON get loaded dynamic libraries'}, 1272} 1273 1274 1275def calculate_mean_and_standard_deviation(floats): 1276 sum = 0.0 1277 count = len(floats) 1278 if count == 0: 1279 return (0.0, 0.0) 1280 for f in floats: 1281 sum += f 1282 mean = sum / count 1283 accum = 0.0 1284 for f in floats: 1285 delta = f - mean 1286 accum += delta * delta 1287 1288 std_dev = math.sqrt(accum / (count - 1)) 1289 return (mean, std_dev) 1290 1291 1292def parse_gdb_log_file(path, options): 1293 f = open(path) 1294 parse_gdb_log(f, options) 1295 f.close() 1296 1297 1298def round_up(n, incr): 1299 return float(((int(n) + incr) / incr) * incr) 1300 1301 1302def plot_latencies(sec_times): 1303 # import numpy as np 1304 import matplotlib.pyplot as plt 1305 1306 for (i, name) in enumerate(sec_times.keys()): 1307 times = sec_times[name] 1308 if len(times) <= 1: 1309 continue 1310 plt.subplot(2, 1, 1) 1311 plt.title('Packet "%s" Times' % (name)) 1312 plt.xlabel('Packet') 1313 units = 'ms' 1314 adj_times = [] 1315 max_time = 0.0 1316 for time in times: 1317 time = time * 1000.0 1318 adj_times.append(time) 1319 if time > max_time: 1320 max_time = time 1321 if max_time < 1.0: 1322 units = 'us' 1323 max_time = 0.0 1324 for i in range(len(adj_times)): 1325 adj_times[i] *= 1000.0 1326 if adj_times[i] > max_time: 1327 max_time = adj_times[i] 1328 plt.ylabel('Time (%s)' % (units)) 1329 max_y = None 1330 for i in [5.0, 10.0, 25.0, 50.0]: 1331 if max_time < i: 1332 max_y = round_up(max_time, i) 1333 break 1334 if max_y is None: 1335 max_y = round_up(max_time, 100.0) 1336 plt.ylim(0.0, max_y) 1337 plt.plot(adj_times, 'o-') 1338 plt.show() 1339 1340 1341def parse_gdb_log(file, options): 1342 '''Parse a GDB log file that was generated by enabling logging with: 1343 (lldb) log enable --threadsafe --timestamp --file <FILE> gdb-remote packets 1344 This log file will contain timestamps and this function will then normalize 1345 those packets to be relative to the first value timestamp that is found and 1346 show delta times between log lines and also keep track of how long it takes 1347 for GDB remote commands to make a send/receive round trip. This can be 1348 handy when trying to figure out why some operation in the debugger is taking 1349 a long time during a preset set of debugger commands.''' 1350 1351 tricky_commands = ['qRegisterInfo'] 1352 timestamp_regex = re.compile('(\s*)([1-9][0-9]+\.[0-9]+)([^0-9].*)$') 1353 packet_name_regex = re.compile('([A-Za-z_]+)[^a-z]') 1354 packet_transmit_name_regex = re.compile( 1355 '(?P<direction>send|read) packet: (?P<packet>.*)') 1356 packet_contents_name_regex = re.compile('\$([^#]*)#[0-9a-fA-F]{2}') 1357 packet_checksum_regex = re.compile('.*#[0-9a-fA-F]{2}$') 1358 packet_names_regex_str = '(' + \ 1359 '|'.join(gdb_remote_commands.keys()) + ')(.*)' 1360 packet_names_regex = re.compile(packet_names_regex_str) 1361 1362 base_time = 0.0 1363 last_time = 0.0 1364 min_time = 100000000.0 1365 packet_total_times = {} 1366 all_packet_times = [] 1367 packet_times = {} 1368 packet_counts = {} 1369 lines = file.read().splitlines() 1370 last_command = None 1371 last_command_args = None 1372 last_command_packet = None 1373 hide_next_response = False 1374 num_lines = len(lines) 1375 skip_count = 0 1376 for (line_index, line) in enumerate(lines): 1377 # See if we need to skip any lines 1378 if skip_count > 0: 1379 skip_count -= 1 1380 continue 1381 m = packet_transmit_name_regex.search(line) 1382 is_command = False 1383 direction = None 1384 if m: 1385 direction = m.group('direction') 1386 is_command = direction == 'send' 1387 packet = m.group('packet') 1388 sys.stdout.write(options.colors.green()) 1389 if not options.quiet and not hide_next_response: 1390 print('# ', line) 1391 sys.stdout.write(options.colors.reset()) 1392 1393 # print 'direction = "%s", packet = "%s"' % (direction, packet) 1394 1395 if packet[0] == '+': 1396 if is_command: 1397 print('-->', end=' ') 1398 else: 1399 print('<--', end=' ') 1400 if not options.quiet: 1401 print('ACK') 1402 continue 1403 elif packet[0] == '-': 1404 if is_command: 1405 print('-->', end=' ') 1406 else: 1407 print('<--', end=' ') 1408 if not options.quiet: 1409 print('NACK') 1410 continue 1411 elif packet[0] == '$': 1412 m = packet_contents_name_regex.match(packet) 1413 if not m and packet[0] == '$': 1414 multiline_packet = packet 1415 idx = line_index + 1 1416 while idx < num_lines: 1417 if not options.quiet and not hide_next_response: 1418 print('# ', lines[idx]) 1419 multiline_packet += lines[idx] 1420 m = packet_contents_name_regex.match(multiline_packet) 1421 if m: 1422 packet = multiline_packet 1423 skip_count = idx - line_index 1424 break 1425 else: 1426 idx += 1 1427 if m: 1428 if is_command: 1429 print('-->', end=' ') 1430 else: 1431 print('<--', end=' ') 1432 contents = decode_packet(m.group(1)) 1433 if is_command: 1434 hide_next_response = False 1435 m = packet_names_regex.match(contents) 1436 if m: 1437 last_command = m.group(1) 1438 if last_command == '?': 1439 last_command = '\\?' 1440 packet_name = last_command 1441 last_command_args = m.group(2) 1442 last_command_packet = contents 1443 hide_next_response = gdb_remote_commands[last_command][ 1444 'cmd'](options, last_command, last_command_args) 1445 else: 1446 packet_match = packet_name_regex.match(contents) 1447 if packet_match: 1448 packet_name = packet_match.group(1) 1449 for tricky_cmd in tricky_commands: 1450 if packet_name.find(tricky_cmd) == 0: 1451 packet_name = tricky_cmd 1452 else: 1453 packet_name = contents 1454 last_command = None 1455 last_command_args = None 1456 last_command_packet = None 1457 elif last_command: 1458 gdb_remote_commands[last_command]['rsp']( 1459 options, last_command, last_command_args, contents) 1460 else: 1461 print('error: invalid packet: "', packet, '"') 1462 else: 1463 print('???') 1464 else: 1465 print('## ', line) 1466 match = timestamp_regex.match(line) 1467 if match: 1468 curr_time = float(match.group(2)) 1469 if last_time and not is_command: 1470 delta = curr_time - last_time 1471 all_packet_times.append(delta) 1472 delta = 0.0 1473 if base_time: 1474 delta = curr_time - last_time 1475 else: 1476 base_time = curr_time 1477 1478 if not is_command: 1479 if line.find('read packet: $') >= 0 and packet_name: 1480 if packet_name in packet_total_times: 1481 packet_total_times[packet_name] += delta 1482 packet_counts[packet_name] += 1 1483 else: 1484 packet_total_times[packet_name] = delta 1485 packet_counts[packet_name] = 1 1486 if packet_name not in packet_times: 1487 packet_times[packet_name] = [] 1488 packet_times[packet_name].append(delta) 1489 packet_name = None 1490 if min_time > delta: 1491 min_time = delta 1492 1493 if not options or not options.quiet: 1494 print('%s%.6f %+.6f%s' % (match.group(1), 1495 curr_time - base_time, 1496 delta, 1497 match.group(3))) 1498 last_time = curr_time 1499 # else: 1500 # print line 1501 (average, std_dev) = calculate_mean_and_standard_deviation(all_packet_times) 1502 if average and std_dev: 1503 print('%u packets with average packet time of %f and standard deviation of %f' % (len(all_packet_times), average, std_dev)) 1504 if packet_total_times: 1505 total_packet_time = 0.0 1506 total_packet_count = 0 1507 for key, vvv in packet_total_times.items(): 1508 # print ' key = (%s) "%s"' % (type(key), key) 1509 # print 'value = (%s) %s' % (type(vvv), vvv) 1510 # if type(vvv) == 'float': 1511 total_packet_time += vvv 1512 for key, vvv in packet_counts.items(): 1513 total_packet_count += vvv 1514 1515 print('#------------------------------------------------------------') 1516 print('# Packet timing summary:') 1517 print('# Totals: time = %6f, count = %6d' % (total_packet_time, 1518 total_packet_count)) 1519 print('# Min packet time: time = %6f' % (min_time)) 1520 print('#------------------------------------------------------------') 1521 print('# Packet Time (sec) Percent Count Latency') 1522 print('#------------------------- ----------- ------- ------ -------') 1523 if options and options.sort_count: 1524 res = sorted( 1525 packet_counts, 1526 key=packet_counts.__getitem__, 1527 reverse=True) 1528 else: 1529 res = sorted( 1530 packet_total_times, 1531 key=packet_total_times.__getitem__, 1532 reverse=True) 1533 1534 if last_time > 0.0: 1535 for item in res: 1536 packet_total_time = packet_total_times[item] 1537 packet_percent = ( 1538 packet_total_time / total_packet_time) * 100.0 1539 packet_count = packet_counts[item] 1540 print(" %24s %11.6f %5.2f%% %6d %9.6f" % ( 1541 item, packet_total_time, packet_percent, packet_count, 1542 float(packet_total_time) / float(packet_count))) 1543 if options.plot: 1544 plot_latencies(packet_times) 1545 1546if __name__ == '__main__': 1547 usage = "usage: gdbremote [options]" 1548 description = '''The command disassembles a GDB remote packet log.''' 1549 parser = optparse.OptionParser( 1550 description=description, 1551 prog='gdbremote', 1552 usage=usage) 1553 parser.add_option( 1554 '-v', 1555 '--verbose', 1556 action='store_true', 1557 dest='verbose', 1558 help='display verbose debug info', 1559 default=False) 1560 parser.add_option( 1561 '--plot', 1562 action='store_true', 1563 dest='plot', 1564 help='plot packet latencies by packet type', 1565 default=False) 1566 parser.add_option( 1567 '-q', 1568 '--quiet', 1569 action='store_true', 1570 dest='quiet', 1571 help='display verbose debug info', 1572 default=False) 1573 parser.add_option( 1574 '-C', 1575 '--color', 1576 action='store_true', 1577 dest='color', 1578 help='add terminal colors', 1579 default=False) 1580 parser.add_option( 1581 '-c', 1582 '--sort-by-count', 1583 action='store_true', 1584 dest='sort_count', 1585 help='display verbose debug info', 1586 default=False) 1587 parser.add_option( 1588 '--crashlog', 1589 type='string', 1590 dest='crashlog', 1591 help='symbolicate using a darwin crash log file', 1592 default=False) 1593 try: 1594 (options, args) = parser.parse_args(sys.argv[1:]) 1595 except: 1596 print('error: argument error') 1597 sys.exit(1) 1598 1599 options.colors = TerminalColors(options.color) 1600 options.symbolicator = None 1601 if options.crashlog: 1602 import lldb 1603 lldb.debugger = lldb.SBDebugger.Create() 1604 import lldb.macosx.crashlog 1605 options.symbolicator = lldb.macosx.crashlog.CrashLog(options.crashlog) 1606 print('%s' % (options.symbolicator)) 1607 1608 # This script is being run from the command line, create a debugger in case we are 1609 # going to use any debugger functions in our function. 1610 if len(args): 1611 for file in args: 1612 print('#----------------------------------------------------------------------') 1613 print("# GDB remote log file: '%s'" % file) 1614 print('#----------------------------------------------------------------------') 1615 parse_gdb_log_file(file, options) 1616 if options.symbolicator: 1617 print('%s' % (options.symbolicator)) 1618 else: 1619 parse_gdb_log(sys.stdin, options) 1620 1621else: 1622 import lldb 1623 if lldb.debugger: 1624 # This initializer is being run from LLDB in the embedded command interpreter 1625 # Add any commands contained in this module to LLDB 1626 lldb.debugger.HandleCommand( 1627 'command script add -f gdbremote.start_gdb_log start_gdb_log') 1628 lldb.debugger.HandleCommand( 1629 'command script add -f gdbremote.stop_gdb_log stop_gdb_log') 1630 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') 1631