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