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