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