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