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