1#!/usr/bin/env python 2 3# Copyright JS Foundation and other contributors, http://js.foundation 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from __future__ import print_function 18import argparse 19import logging 20import re 21import select 22import struct 23import sys 24 25# Expected debugger protocol version. 26JERRY_DEBUGGER_VERSION = 9 27 28# Messages sent by the server to client. 29JERRY_DEBUGGER_CONFIGURATION = 1 30JERRY_DEBUGGER_PARSE_ERROR = 2 31JERRY_DEBUGGER_BYTE_CODE_CP = 3 32JERRY_DEBUGGER_PARSE_FUNCTION = 4 33JERRY_DEBUGGER_BREAKPOINT_LIST = 5 34JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST = 6 35JERRY_DEBUGGER_SOURCE_CODE = 7 36JERRY_DEBUGGER_SOURCE_CODE_END = 8 37JERRY_DEBUGGER_SOURCE_CODE_NAME = 9 38JERRY_DEBUGGER_SOURCE_CODE_NAME_END = 10 39JERRY_DEBUGGER_FUNCTION_NAME = 11 40JERRY_DEBUGGER_FUNCTION_NAME_END = 12 41JERRY_DEBUGGER_WAITING_AFTER_PARSE = 13 42JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP = 14 43JERRY_DEBUGGER_MEMSTATS_RECEIVE = 15 44JERRY_DEBUGGER_BREAKPOINT_HIT = 16 45JERRY_DEBUGGER_EXCEPTION_HIT = 17 46JERRY_DEBUGGER_EXCEPTION_STR = 18 47JERRY_DEBUGGER_EXCEPTION_STR_END = 19 48JERRY_DEBUGGER_BACKTRACE_TOTAL = 20 49JERRY_DEBUGGER_BACKTRACE = 21 50JERRY_DEBUGGER_BACKTRACE_END = 22 51JERRY_DEBUGGER_EVAL_RESULT = 23 52JERRY_DEBUGGER_EVAL_RESULT_END = 24 53JERRY_DEBUGGER_WAIT_FOR_SOURCE = 25 54JERRY_DEBUGGER_OUTPUT_RESULT = 26 55JERRY_DEBUGGER_OUTPUT_RESULT_END = 27 56JERRY_DEBUGGER_SCOPE_CHAIN = 28 57JERRY_DEBUGGER_SCOPE_CHAIN_END = 29 58JERRY_DEBUGGER_SCOPE_VARIABLES = 30 59JERRY_DEBUGGER_SCOPE_VARIABLES_END = 31 60JERRY_DEBUGGER_CLOSE_CONNECTION = 32 61 62# Debugger option flags 63JERRY_DEBUGGER_LITTLE_ENDIAN = 0x1 64 65# Subtypes of eval 66JERRY_DEBUGGER_EVAL_EVAL = "\0" 67JERRY_DEBUGGER_EVAL_THROW = "\1" 68JERRY_DEBUGGER_EVAL_ABORT = "\2" 69 70# Subtypes of eval result 71JERRY_DEBUGGER_EVAL_OK = 1 72JERRY_DEBUGGER_EVAL_ERROR = 2 73 74# Subtypes of output 75JERRY_DEBUGGER_OUTPUT_OK = 1 76JERRY_DEBUGGER_OUTPUT_ERROR = 2 77JERRY_DEBUGGER_OUTPUT_WARNING = 3 78JERRY_DEBUGGER_OUTPUT_DEBUG = 4 79JERRY_DEBUGGER_OUTPUT_TRACE = 5 80 81 82# Messages sent by the client to server. 83JERRY_DEBUGGER_FREE_BYTE_CODE_CP = 1 84JERRY_DEBUGGER_UPDATE_BREAKPOINT = 2 85JERRY_DEBUGGER_EXCEPTION_CONFIG = 3 86JERRY_DEBUGGER_PARSER_CONFIG = 4 87JERRY_DEBUGGER_MEMSTATS = 5 88JERRY_DEBUGGER_STOP = 6 89JERRY_DEBUGGER_PARSER_RESUME = 7 90JERRY_DEBUGGER_CLIENT_SOURCE = 8 91JERRY_DEBUGGER_CLIENT_SOURCE_PART = 9 92JERRY_DEBUGGER_NO_MORE_SOURCES = 10 93JERRY_DEBUGGER_CONTEXT_RESET = 11 94JERRY_DEBUGGER_CONTINUE = 12 95JERRY_DEBUGGER_STEP = 13 96JERRY_DEBUGGER_NEXT = 14 97JERRY_DEBUGGER_FINISH = 15 98JERRY_DEBUGGER_GET_BACKTRACE = 16 99JERRY_DEBUGGER_EVAL = 17 100JERRY_DEBUGGER_EVAL_PART = 18 101JERRY_DEBUGGER_GET_SCOPE_CHAIN = 19 102JERRY_DEBUGGER_GET_SCOPE_VARIABLES = 20 103 104JERRY_DEBUGGER_SCOPE_WITH = 1 105JERRY_DEBUGGER_SCOPE_LOCAL = 2 106JERRY_DEBUGGER_SCOPE_CLOSURE = 3 107JERRY_DEBUGGER_SCOPE_GLOBAL = 4 108JERRY_DEBUGGER_SCOPE_NON_CLOSURE = 5 109 110JERRY_DEBUGGER_VALUE_NONE = 1 111JERRY_DEBUGGER_VALUE_UNDEFINED = 2 112JERRY_DEBUGGER_VALUE_NULL = 3 113JERRY_DEBUGGER_VALUE_BOOLEAN = 4 114JERRY_DEBUGGER_VALUE_NUMBER = 5 115JERRY_DEBUGGER_VALUE_STRING = 6 116JERRY_DEBUGGER_VALUE_FUNCTION = 7 117JERRY_DEBUGGER_VALUE_ARRAY = 8 118JERRY_DEBUGGER_VALUE_OBJECT = 9 119 120def arguments_parse(): 121 parser = argparse.ArgumentParser(description="JerryScript debugger client") 122 123 parser.add_argument("address", action="store", nargs="?", default="localhost:5001", 124 help="specify a unique network address for tcp connection (default: %(default)s)") 125 parser.add_argument("-v", "--verbose", action="store_true", default=False, 126 help="increase verbosity (default: %(default)s)") 127 parser.add_argument("--non-interactive", action="store_true", default=False, 128 help="disable stop when newline is pressed (default: %(default)s)") 129 parser.add_argument("--color", action="store_true", default=False, 130 help="enable color highlighting on source commands (default: %(default)s)") 131 parser.add_argument("--display", action="store", default=None, type=int, 132 help="set display range") 133 parser.add_argument("--exception", action="store", default=None, type=int, choices=[0, 1], 134 help="set exception config, usage 1: [Enable] or 0: [Disable]") 135 parser.add_argument("--client-source", action="store", default=[], type=str, nargs="+", 136 help="specify a javascript source file to execute") 137 parser.add_argument("--channel", choices=["websocket", "rawpacket"], default="websocket", 138 help="specify the communication channel (default: %(default)s)") 139 parser.add_argument("--protocol", choices=["tcp", "serial"], default="tcp", 140 help="specify the transmission protocol over the communication channel (default: %(default)s)") 141 parser.add_argument("--serial-config", metavar="CONFIG_STRING", default="/dev/ttyUSB0,115200,8,N,1", 142 help="Configure parameters for serial port (default: %(default)s)") 143 args = parser.parse_args() 144 145 if args.verbose: 146 logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG) 147 logging.debug("Debug logging mode: ON") 148 149 return args 150 151 152class JerryBreakpoint(object): 153 154 def __init__(self, line, offset, function): 155 self.line = line 156 self.offset = offset 157 self.function = function 158 self.active_index = -1 159 160 def __str__(self): 161 result = self.function.source_name or "<unknown>" 162 result += ":%d" % (self.line) 163 164 if self.function.is_func: 165 result += " (in " 166 result += self.function.name or "function" 167 result += "() at line:%d, col:%d)" % (self.function.line, self.function.column) 168 return result 169 170 def __repr__(self): 171 return ("Breakpoint(line:%d, offset:%d, active_index:%d)" 172 % (self.line, self.offset, self.active_index)) 173 174class JerryPendingBreakpoint(object): 175 def __init__(self, line=None, source_name=None, function=None): 176 self.function = function 177 self.line = line 178 self.source_name = source_name 179 180 self.index = -1 181 182 def __str__(self): 183 result = self.source_name or "" 184 if self.line: 185 result += ":%d" % (self.line) 186 else: 187 result += "%s()" % (self.function) 188 return result 189 190 191class JerryFunction(object): 192 # pylint: disable=too-many-instance-attributes,too-many-arguments 193 def __init__(self, is_func, byte_code_cp, source, source_name, line, column, name, lines, offsets): 194 self.is_func = bool(is_func) 195 self.byte_code_cp = byte_code_cp 196 self.source = re.split("\r\n|[\r\n]", source) 197 self.source_name = source_name 198 self.name = name 199 self.lines = {} 200 self.offsets = {} 201 self.line = line 202 self.column = column 203 self.first_breakpoint_line = lines[0] 204 self.first_breakpoint_offset = offsets[0] 205 206 if len(self.source) > 1 and not self.source[-1]: 207 self.source.pop() 208 209 for i, _line in enumerate(lines): 210 offset = offsets[i] 211 breakpoint = JerryBreakpoint(_line, offset, self) 212 self.lines[_line] = breakpoint 213 self.offsets[offset] = breakpoint 214 215 def __repr__(self): 216 result = ("Function(byte_code_cp:0x%x, source_name:%r, name:%r, line:%d, column:%d { " 217 % (self.byte_code_cp, self.source_name, self.name, self.line, self.column)) 218 219 result += ','.join([str(breakpoint) for breakpoint in self.lines.values()]) 220 221 return result + " })" 222 223 224class Multimap(object): 225 226 def __init__(self): 227 self.map = {} 228 229 def get(self, key): 230 if key in self.map: 231 return self.map[key] 232 return [] 233 234 def insert(self, key, value): 235 if key in self.map: 236 self.map[key].append(value) 237 else: 238 self.map[key] = [value] 239 240 def delete(self, key, value): 241 items = self.map[key] 242 243 if len(items) == 1: 244 del self.map[key] 245 else: 246 del items[items.index(value)] 247 248 def __repr__(self): 249 return "Multimap(%r)" % (self.map) 250 251 252class DebuggerAction(object): 253 END = 0 254 WAIT = 1 255 TEXT = 2 256 PROMPT = 3 257 258 def __init__(self, action_type, action_text): 259 self.action_type = action_type 260 self.action_text = action_text 261 262 def get_type(self): 263 return self.action_type 264 265 def get_text(self): 266 return self.action_text 267 268 269class JerryDebugger(object): 270 # pylint: disable=too-many-instance-attributes,too-many-statements,too-many-public-methods,no-self-use 271 def __init__(self, channel): 272 self.prompt = False 273 self.function_list = {} 274 self.source = '' 275 self.source_name = '' 276 self.exception_string = '' 277 self.frame_index = 0 278 self.scope_vars = "" 279 self.scope_data = "" 280 self.client_sources = [] 281 self.last_breakpoint_hit = None 282 self.next_breakpoint_index = 0 283 self.active_breakpoint_list = {} 284 self.pending_breakpoint_list = {} 285 self.line_list = Multimap() 286 self.display = 0 287 self.green = '' 288 self.red = '' 289 self.yellow = '' 290 self.green_bg = '' 291 self.yellow_bg = '' 292 self.blue = '' 293 self.nocolor = '' 294 self.src_offset = 0 295 self.src_offset_diff = 0 296 self.non_interactive = False 297 self.current_out = b"" 298 self.current_log = b"" 299 self.channel = channel 300 301 config_size = 8 302 # The server will send the configuration message after connection established 303 # type [1] 304 # configuration [1] 305 # version [4] 306 # max_message_size [1] 307 # cpointer_size [1] 308 result = self.channel.connect(config_size) 309 310 if len(result) != config_size or ord(result[0]) != JERRY_DEBUGGER_CONFIGURATION: 311 raise Exception("Unexpected configuration") 312 313 self.little_endian = ord(result[1]) & JERRY_DEBUGGER_LITTLE_ENDIAN 314 self.max_message_size = ord(result[6]) 315 self.cp_size = ord(result[7]) 316 317 if self.little_endian: 318 self.byte_order = "<" 319 logging.debug("Little-endian machine") 320 else: 321 self.byte_order = ">" 322 logging.debug("Big-endian machine") 323 324 if self.cp_size == 2: 325 self.cp_format = "H" 326 else: 327 self.cp_format = "I" 328 329 self.idx_format = "I" 330 331 self.version = struct.unpack(self.byte_order + self.idx_format, result[2:6])[0] 332 if self.version != JERRY_DEBUGGER_VERSION: 333 raise Exception("Incorrect debugger version from target: %d expected: %d" % 334 (self.version, JERRY_DEBUGGER_VERSION)) 335 336 logging.debug("Compressed pointer size: %d", self.cp_size) 337 338 def __del__(self): 339 if self.channel is not None: 340 self.channel.close() 341 342 def _exec_command(self, command_id): 343 message = struct.pack(self.byte_order + "BB", 344 1, 345 command_id) 346 self.channel.send_message(self.byte_order, message) 347 348 def quit(self): 349 self.prompt = False 350 self._exec_command(JERRY_DEBUGGER_CONTINUE) 351 352 def set_colors(self): 353 self.nocolor = '\033[0m' 354 self.green = '\033[92m' 355 self.red = '\033[31m' 356 self.yellow = '\033[93m' 357 self.green_bg = '\033[42m\033[30m' 358 self.yellow_bg = '\033[43m\033[30m' 359 self.blue = '\033[94m' 360 361 def stop(self): 362 self._exec_command(JERRY_DEBUGGER_STOP) 363 364 def set_break(self, args): 365 if not args: 366 return "Error: Breakpoint index expected" 367 368 if ':' in args: 369 try: 370 if int(args.split(':', 1)[1]) <= 0: 371 return "Error: Positive breakpoint index expected" 372 373 return self._set_breakpoint(args, False) 374 375 except ValueError as val_errno: 376 return "Error: Positive breakpoint index expected: %s" % (val_errno) 377 378 return self._set_breakpoint(args, False) 379 380 def breakpoint_list(self): 381 result = '' 382 if self.active_breakpoint_list: 383 result += "=== %sActive breakpoints %s ===\n" % (self.green_bg, self.nocolor) 384 for breakpoint in self.active_breakpoint_list.values(): 385 result += " %d: %s\n" % (breakpoint.active_index, breakpoint) 386 if self.pending_breakpoint_list: 387 result += "=== %sPending breakpoints%s ===\n" % (self.yellow_bg, self.nocolor) 388 for breakpoint in self.pending_breakpoint_list.values(): 389 result += " %d: %s (pending)\n" % (breakpoint.index, breakpoint) 390 391 if not self.active_breakpoint_list and not self.pending_breakpoint_list: 392 result += "No breakpoints\n" 393 394 return result 395 396 def delete(self, args): 397 if not args: 398 return "Error: Breakpoint index expected\n" \ 399 "Delete the given breakpoint, use 'delete all|active|pending' " \ 400 "to clear all the given breakpoints\n " 401 elif args in ['all', 'pending', 'active']: 402 if args != "pending": 403 for i in self.active_breakpoint_list.values(): 404 breakpoint = self.active_breakpoint_list[i.active_index] 405 del self.active_breakpoint_list[i.active_index] 406 breakpoint.active_index = -1 407 self._send_breakpoint(breakpoint) 408 409 if args != "active": 410 if self.pending_breakpoint_list: 411 self.pending_breakpoint_list.clear() 412 self._send_parser_config(0) 413 return "" 414 415 try: 416 breakpoint_index = int(args) 417 except ValueError as val_errno: 418 return "Error: Integer number expected, %s\n" % (val_errno) 419 420 if breakpoint_index in self.active_breakpoint_list: 421 breakpoint = self.active_breakpoint_list[breakpoint_index] 422 del self.active_breakpoint_list[breakpoint_index] 423 breakpoint.active_index = -1 424 self._send_breakpoint(breakpoint) 425 return "Breakpoint %d deleted\n" % (breakpoint_index) 426 elif breakpoint_index in self.pending_breakpoint_list: 427 del self.pending_breakpoint_list[breakpoint_index] 428 if not self.pending_breakpoint_list: 429 self._send_parser_config(0) 430 return "Pending breakpoint %d deleted\n" % (breakpoint_index) 431 else: 432 return "Error: Breakpoint %d not found\n" % (breakpoint_index) 433 434 def next(self): 435 self.prompt = False 436 self._exec_command(JERRY_DEBUGGER_NEXT) 437 438 def step(self): 439 self.prompt = False 440 self._exec_command(JERRY_DEBUGGER_STEP) 441 442 def do_continue(self): 443 self.prompt = False 444 self._exec_command(JERRY_DEBUGGER_CONTINUE) 445 446 def finish(self): 447 self.prompt = False 448 self._exec_command(JERRY_DEBUGGER_FINISH) 449 450 def backtrace(self, args): 451 max_depth = 0 452 min_depth = 0 453 get_total = 0 454 455 if args: 456 args = args.split(" ") 457 try: 458 if "t" in args: 459 get_total = 1 460 args.remove("t") 461 462 if len(args) >= 2: 463 min_depth = int(args[0]) 464 max_depth = int(args[1]) 465 if max_depth <= 0 or min_depth < 0: 466 return "Error: Positive integer number expected\n" 467 if min_depth > max_depth: 468 return "Error: Start depth needs to be lower than or equal to max depth\n" 469 elif len(args) >= 1: 470 max_depth = int(args[0]) 471 if max_depth <= 0: 472 return "Error: Positive integer number expected\n" 473 474 except ValueError as val_errno: 475 return "Error: Positive integer number expected, %s\n" % (val_errno) 476 477 self.frame_index = min_depth 478 479 message = struct.pack(self.byte_order + "BB" + self.idx_format + self.idx_format + "B", 480 1 + 4 + 4 + 1, 481 JERRY_DEBUGGER_GET_BACKTRACE, 482 min_depth, 483 max_depth, 484 get_total) 485 486 self.channel.send_message(self.byte_order, message) 487 488 self.prompt = False 489 return "" 490 491 def eval(self, code): 492 self._send_string(JERRY_DEBUGGER_EVAL_EVAL + code, JERRY_DEBUGGER_EVAL) 493 self.prompt = False 494 495 def eval_at(self, code, index): 496 self._send_string(JERRY_DEBUGGER_EVAL_EVAL + code, JERRY_DEBUGGER_EVAL, index) 497 self.prompt = False 498 499 def throw(self, code): 500 self._send_string(JERRY_DEBUGGER_EVAL_THROW + code, JERRY_DEBUGGER_EVAL) 501 self.prompt = False 502 503 def abort(self, args): 504 self.delete("all") 505 self.exception("0") # disable the exception handler 506 self._send_string(JERRY_DEBUGGER_EVAL_ABORT + args, JERRY_DEBUGGER_EVAL) 507 self.prompt = False 508 509 def restart(self): 510 self._send_string(JERRY_DEBUGGER_EVAL_ABORT + "\"r353t\"", JERRY_DEBUGGER_EVAL) 511 self.prompt = False 512 513 def exception(self, args): 514 try: 515 enabled = int(args) 516 except (ValueError, TypeError): 517 enabled = -1 518 519 if enabled not in [0, 1]: 520 return "Error: Invalid input! Usage 1: [Enable] or 0: [Disable]\n" 521 522 if enabled: 523 logging.debug("Stop at exception enabled") 524 self._send_exception_config(enabled) 525 526 return "Stop at exception enabled\n" 527 528 logging.debug("Stop at exception disabled") 529 self._send_exception_config(enabled) 530 531 return "Stop at exception disabled\n" 532 533 def scope_chain(self): 534 self.prompt = False 535 self._exec_command(JERRY_DEBUGGER_GET_SCOPE_CHAIN) 536 537 def scope_variables(self, args): 538 index = 0 539 if args: 540 try: 541 index = int(args) 542 if index < 0: 543 print("Error: A non negative integer number expected") 544 return "" 545 546 except ValueError as val_errno: 547 return "Error: Non negative integer number expected, %s\n" % (val_errno) 548 549 message = struct.pack(self.byte_order + "BB" + self.idx_format, 550 1 + 4, 551 JERRY_DEBUGGER_GET_SCOPE_VARIABLES, 552 index) 553 554 self.channel.send_message(self.byte_order, message) 555 556 self.prompt = False 557 return "" 558 559 def memstats(self): 560 self.prompt = False 561 self._exec_command(JERRY_DEBUGGER_MEMSTATS) 562 563 def _send_string(self, args, message_type, index=0): 564 565 # 1: length of type byte 566 # 4: length of an uint32 value 567 message_header = 1 + 4 568 569 # Add scope chain index 570 if message_type == JERRY_DEBUGGER_EVAL: 571 args = struct.pack(self.byte_order + "I", index) + args 572 573 size = len(args) 574 575 max_fragment = min(self.max_message_size - message_header, size) 576 577 message = struct.pack(self.byte_order + "BBI", 578 max_fragment + message_header, 579 message_type, 580 size) 581 582 if size == max_fragment: 583 self.channel.send_message(self.byte_order, message + args) 584 return 585 586 self.channel.send_message(self.byte_order, message + args[0:max_fragment]) 587 offset = max_fragment 588 589 if message_type == JERRY_DEBUGGER_EVAL: 590 message_type = JERRY_DEBUGGER_EVAL_PART 591 else: 592 message_type = JERRY_DEBUGGER_CLIENT_SOURCE_PART 593 594 # 1: length of type byte 595 message_header = 1 596 597 max_fragment = self.max_message_size - message_header 598 while offset < size: 599 next_fragment = min(max_fragment, size - offset) 600 601 message = struct.pack(self.byte_order + "BB", 602 next_fragment + message_header, 603 message_type) 604 605 prev_offset = offset 606 offset += next_fragment 607 608 self.channel.send_message(self.byte_order, message + args[prev_offset:offset]) 609 610 def _breakpoint_pending_exists(self, breakpoint): 611 for existing_bp in self.pending_breakpoint_list.values(): 612 if (breakpoint.line and existing_bp.source_name == breakpoint.source_name and \ 613 existing_bp.line == breakpoint.line) \ 614 or (not breakpoint.line and existing_bp.function == breakpoint.function): 615 return True 616 617 return False 618 619 def _send_breakpoint(self, breakpoint): 620 message = struct.pack(self.byte_order + "BBB" + self.cp_format + self.idx_format, 621 1 + 1 + self.cp_size + 4, 622 JERRY_DEBUGGER_UPDATE_BREAKPOINT, 623 int(breakpoint.active_index >= 0), 624 breakpoint.function.byte_code_cp, 625 breakpoint.offset) 626 self.channel.send_message(self.byte_order, message) 627 628 def _send_bytecode_cp(self, byte_code_cp): 629 message = struct.pack(self.byte_order + "BB" + self.cp_format, 630 1 + self.cp_size, 631 JERRY_DEBUGGER_FREE_BYTE_CODE_CP, 632 byte_code_cp) 633 self.channel.send_message(self.byte_order, message) 634 635 def _send_exception_config(self, enable): 636 message = struct.pack(self.byte_order + "BBB", 637 1 + 1, 638 JERRY_DEBUGGER_EXCEPTION_CONFIG, 639 enable) 640 self.channel.send_message(self.byte_order, message) 641 642 def _send_parser_config(self, enable): 643 message = struct.pack(self.byte_order + "BBB", 644 1 + 1, 645 JERRY_DEBUGGER_PARSER_CONFIG, 646 enable) 647 self.channel.send_message(self.byte_order, message) 648 649 def store_client_sources(self, args): 650 self.client_sources = args 651 652 def send_client_source(self): 653 # Send no more source message if there is no source 654 if not self.client_sources: 655 self.send_no_more_source() 656 return 657 658 path = self.client_sources.pop(0) 659 if not path.lower().endswith('.js'): 660 sys.exit("Error: Javascript file expected!") 661 return 662 663 with open(path, 'r') as src_file: 664 content = path + "\0" + src_file.read() 665 self._send_string(content, JERRY_DEBUGGER_CLIENT_SOURCE) 666 667 def send_no_more_source(self): 668 self._exec_command(JERRY_DEBUGGER_NO_MORE_SOURCES) 669 670 # pylint: disable=too-many-branches,too-many-locals,too-many-statements,too-many-return-statements 671 def process_messages(self): 672 result = "" 673 while True: 674 data = self.channel.get_message(False) 675 if not self.non_interactive: 676 if sys.stdin in select.select([sys.stdin], [], [], 0)[0]: 677 sys.stdin.readline() 678 self.stop() 679 680 if data == b'': 681 action_type = DebuggerAction.PROMPT if self.prompt else DebuggerAction.WAIT 682 return DebuggerAction(action_type, "") 683 684 if not data: # Break the while loop if there is no more data. 685 return DebuggerAction(DebuggerAction.END, "") 686 687 buffer_type = ord(data[0]) 688 buffer_size = len(data) -1 689 690 logging.debug("Main buffer type: %d, message size: %d", buffer_type, buffer_size) 691 692 if buffer_type in [JERRY_DEBUGGER_PARSE_ERROR, 693 JERRY_DEBUGGER_BYTE_CODE_CP, 694 JERRY_DEBUGGER_PARSE_FUNCTION, 695 JERRY_DEBUGGER_BREAKPOINT_LIST, 696 JERRY_DEBUGGER_SOURCE_CODE, 697 JERRY_DEBUGGER_SOURCE_CODE_END, 698 JERRY_DEBUGGER_SOURCE_CODE_NAME, 699 JERRY_DEBUGGER_SOURCE_CODE_NAME_END, 700 JERRY_DEBUGGER_FUNCTION_NAME, 701 JERRY_DEBUGGER_FUNCTION_NAME_END]: 702 result = self._parse_source(data) 703 if result: 704 return DebuggerAction(DebuggerAction.TEXT, result) 705 706 elif buffer_type == JERRY_DEBUGGER_WAITING_AFTER_PARSE: 707 self._exec_command(JERRY_DEBUGGER_PARSER_RESUME) 708 709 elif buffer_type == JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP: 710 self._release_function(data) 711 712 elif buffer_type in [JERRY_DEBUGGER_BREAKPOINT_HIT, JERRY_DEBUGGER_EXCEPTION_HIT]: 713 breakpoint_data = struct.unpack(self.byte_order + self.cp_format + self.idx_format, data[1:]) 714 715 breakpoint = self._get_breakpoint(breakpoint_data) 716 self.last_breakpoint_hit = breakpoint[0] 717 718 if buffer_type == JERRY_DEBUGGER_EXCEPTION_HIT: 719 result += "Exception throw detected (to disable automatic stop type exception 0)\n" 720 if self.exception_string: 721 result += "Exception hint: %s\n" % (self.exception_string) 722 self.exception_string = "" 723 724 if breakpoint[1]: 725 breakpoint_info = "at" 726 else: 727 breakpoint_info = "around" 728 729 if breakpoint[0].active_index >= 0: 730 breakpoint_info += " breakpoint:%s%d%s" % (self.red, breakpoint[0].active_index, self.nocolor) 731 732 result += "Stopped %s %s\n" % (breakpoint_info, breakpoint[0]) 733 734 if self.display > 0: 735 result += self.print_source(self.display, self.src_offset) 736 737 self.prompt = True 738 return DebuggerAction(DebuggerAction.TEXT, result) 739 740 elif buffer_type == JERRY_DEBUGGER_EXCEPTION_STR: 741 self.exception_string += data[1:] 742 743 elif buffer_type == JERRY_DEBUGGER_EXCEPTION_STR_END: 744 self.exception_string += data[1:] 745 746 elif buffer_type == JERRY_DEBUGGER_BACKTRACE_TOTAL: 747 total = struct.unpack(self.byte_order + self.idx_format, data[1:])[0] 748 result += "Total number of frames: %d\n" % (total) 749 return DebuggerAction(DebuggerAction.TEXT, result) 750 751 elif buffer_type in [JERRY_DEBUGGER_BACKTRACE, JERRY_DEBUGGER_BACKTRACE_END]: 752 frame_index = self.frame_index 753 754 buffer_pos = 1 755 while buffer_size > 0: 756 breakpoint_data = struct.unpack(self.byte_order + self.cp_format + self.idx_format, 757 data[buffer_pos: buffer_pos + self.cp_size + 4]) 758 759 breakpoint = self._get_breakpoint(breakpoint_data) 760 761 result += "Frame %d: %s\n" % (frame_index, breakpoint[0]) 762 763 frame_index += 1 764 buffer_pos += 6 765 buffer_size -= 6 766 767 if buffer_type == JERRY_DEBUGGER_BACKTRACE_END: 768 self.prompt = True 769 else: 770 self.frame_index = frame_index 771 772 return DebuggerAction(DebuggerAction.TEXT, result) 773 774 elif buffer_type in [JERRY_DEBUGGER_EVAL_RESULT, 775 JERRY_DEBUGGER_EVAL_RESULT_END, 776 JERRY_DEBUGGER_OUTPUT_RESULT, 777 JERRY_DEBUGGER_OUTPUT_RESULT_END]: 778 779 result = self._process_incoming_text(buffer_type, data) 780 return DebuggerAction(DebuggerAction.TEXT, result) 781 782 elif buffer_type == JERRY_DEBUGGER_MEMSTATS_RECEIVE: 783 784 memory_stats = struct.unpack(self.byte_order + self.idx_format * 5, 785 data[1: 1 + 4 * 5]) 786 787 result += "Allocated bytes: %s\n" % memory_stats[0] 788 result += "Byte code bytes: %s\n" % memory_stats[1] 789 result += "String bytes: %s\n" % memory_stats[2] 790 result += "Object bytes: %s\n" % memory_stats[3] 791 result += "Property bytes: %s\n" % memory_stats[4] 792 793 self.prompt = True 794 return DebuggerAction(DebuggerAction.TEXT, result) 795 796 elif buffer_type == JERRY_DEBUGGER_WAIT_FOR_SOURCE: 797 self.send_client_source() 798 799 elif buffer_type in [JERRY_DEBUGGER_SCOPE_CHAIN, JERRY_DEBUGGER_SCOPE_CHAIN_END]: 800 self.scope_data = data[1:] 801 802 if buffer_type == JERRY_DEBUGGER_SCOPE_CHAIN_END: 803 result = self._process_scope() 804 self.scope_data = "" 805 806 self.prompt = True 807 808 return DebuggerAction(DebuggerAction.TEXT, result) 809 810 elif buffer_type in [JERRY_DEBUGGER_SCOPE_VARIABLES, JERRY_DEBUGGER_SCOPE_VARIABLES_END]: 811 self.scope_vars += "".join(data[1:]) 812 813 if buffer_type == JERRY_DEBUGGER_SCOPE_VARIABLES_END: 814 result = self._process_scope_variables() 815 self.scope_vars = "" 816 817 self.prompt = True 818 819 return DebuggerAction(DebuggerAction.TEXT, result) 820 821 elif JERRY_DEBUGGER_CLOSE_CONNECTION: 822 return DebuggerAction(DebuggerAction.END, "") 823 824 else: 825 raise Exception("Unknown message") 826 827 def print_source(self, line_num, offset): 828 msg = "" 829 last_bp = self.last_breakpoint_hit 830 831 if not last_bp: 832 return "" 833 834 lines = last_bp.function.source 835 if last_bp.function.source_name: 836 msg += "Source: %s\n" % (last_bp.function.source_name) 837 838 if line_num == 0: 839 start = 0 840 end = len(last_bp.function.source) 841 else: 842 start = max(last_bp.line - line_num, 0) 843 end = min(last_bp.line + line_num - 1, len(last_bp.function.source)) 844 if offset: 845 if start + offset < 0: 846 self.src_offset += self.src_offset_diff 847 offset += self.src_offset_diff 848 elif end + offset > len(last_bp.function.source): 849 self.src_offset -= self.src_offset_diff 850 offset -= self.src_offset_diff 851 852 start = max(start + offset, 0) 853 end = min(end + offset, len(last_bp.function.source)) 854 855 for i in range(start, end): 856 if i == last_bp.line - 1: 857 msg += "%s%4d%s %s>%s %s\n" % (self.green, i + 1, self.nocolor, self.red, \ 858 self.nocolor, lines[i]) 859 else: 860 msg += "%s%4d%s %s\n" % (self.green, i + 1, self.nocolor, lines[i]) 861 862 return msg 863 864 865 # pylint: disable=too-many-branches,too-many-locals,too-many-statements 866 def _parse_source(self, data): 867 source_code = "" 868 source_code_name = "" 869 function_name = "" 870 stack = [{"line": 1, 871 "column": 1, 872 "name": "", 873 "lines": [], 874 "offsets": []}] 875 new_function_list = {} 876 result = "" 877 878 while True: 879 if data is None: 880 return "Error: connection lost during source code receiving" 881 882 buffer_type = ord(data[0]) 883 buffer_size = len(data) - 1 884 885 logging.debug("Parser buffer type: %d, message size: %d", buffer_type, buffer_size) 886 887 if buffer_type == JERRY_DEBUGGER_PARSE_ERROR: 888 logging.error("Syntax error found") 889 return "" 890 891 elif buffer_type in [JERRY_DEBUGGER_SOURCE_CODE, JERRY_DEBUGGER_SOURCE_CODE_END]: 892 source_code += data[1:] 893 894 elif buffer_type in [JERRY_DEBUGGER_SOURCE_CODE_NAME, JERRY_DEBUGGER_SOURCE_CODE_NAME_END]: 895 source_code_name += data[1:] 896 897 elif buffer_type in [JERRY_DEBUGGER_FUNCTION_NAME, JERRY_DEBUGGER_FUNCTION_NAME_END]: 898 function_name += data[1:] 899 900 elif buffer_type == JERRY_DEBUGGER_PARSE_FUNCTION: 901 logging.debug("Source name: %s, function name: %s", source_code_name, function_name) 902 903 position = struct.unpack(self.byte_order + self.idx_format + self.idx_format, 904 data[1: 1 + 4 + 4]) 905 906 stack.append({"source": source_code, 907 "source_name": source_code_name, 908 "line": position[0], 909 "column": position[1], 910 "name": function_name, 911 "lines": [], 912 "offsets": []}) 913 function_name = "" 914 915 elif buffer_type in [JERRY_DEBUGGER_BREAKPOINT_LIST, JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST]: 916 name = "lines" 917 if buffer_type == JERRY_DEBUGGER_BREAKPOINT_OFFSET_LIST: 918 name = "offsets" 919 920 logging.debug("Breakpoint %s received", name) 921 922 buffer_pos = 1 923 while buffer_size > 0: 924 line = struct.unpack(self.byte_order + self.idx_format, 925 data[buffer_pos: buffer_pos + 4]) 926 stack[-1][name].append(line[0]) 927 buffer_pos += 4 928 buffer_size -= 4 929 930 elif buffer_type == JERRY_DEBUGGER_BYTE_CODE_CP: 931 byte_code_cp = struct.unpack(self.byte_order + self.cp_format, 932 data[1: 1 + self.cp_size])[0] 933 934 logging.debug("Byte code cptr received: {0x%x}", byte_code_cp) 935 936 func_desc = stack.pop() 937 938 # We know the last item in the list is the general byte code. 939 if not stack: 940 func_desc["source"] = source_code 941 func_desc["source_name"] = source_code_name 942 943 function = JerryFunction(stack, 944 byte_code_cp, 945 func_desc["source"], 946 func_desc["source_name"], 947 func_desc["line"], 948 func_desc["column"], 949 func_desc["name"], 950 func_desc["lines"], 951 func_desc["offsets"]) 952 953 new_function_list[byte_code_cp] = function 954 955 if not stack: 956 logging.debug("Parse completed.") 957 break 958 959 elif buffer_type == JERRY_DEBUGGER_RELEASE_BYTE_CODE_CP: 960 # Redefined functions are dropped during parsing. 961 byte_code_cp = struct.unpack(self.byte_order + self.cp_format, 962 data[1: 1 + self.cp_size])[0] 963 964 if byte_code_cp in new_function_list: 965 del new_function_list[byte_code_cp] 966 self._send_bytecode_cp(byte_code_cp) 967 else: 968 self._release_function(data) 969 970 elif buffer_type in [JERRY_DEBUGGER_OUTPUT_RESULT, 971 JERRY_DEBUGGER_OUTPUT_RESULT_END]: 972 result += self._process_incoming_text(buffer_type, data) 973 974 else: 975 logging.error("Parser error!") 976 raise Exception("Unexpected message") 977 978 data = self.channel.get_message(True) 979 980 # Copy the ready list to the global storage. 981 self.function_list.update(new_function_list) 982 983 for function in new_function_list.values(): 984 for line, breakpoint in function.lines.items(): 985 self.line_list.insert(line, breakpoint) 986 987 # Try to set the pending breakpoints 988 if self.pending_breakpoint_list: 989 logging.debug("Pending breakpoints available") 990 bp_list = self.pending_breakpoint_list 991 992 for breakpoint_index, breakpoint in bp_list.items(): 993 source_lines = 0 994 for src in new_function_list.values(): 995 if src.source_name == breakpoint.source_name: 996 source_lines = len(src.source) 997 break 998 999 if breakpoint.line: 1000 if breakpoint.line <= source_lines: 1001 command = breakpoint.source_name + ":" + str(breakpoint.line) 1002 set_result = self._set_breakpoint(command, True) 1003 1004 if set_result: 1005 result += set_result 1006 del bp_list[breakpoint_index] 1007 elif breakpoint.function: 1008 command = breakpoint.function 1009 set_result = self._set_breakpoint(command, True) 1010 1011 if set_result: 1012 result += set_result 1013 del bp_list[breakpoint_index] 1014 1015 if not bp_list: 1016 self._send_parser_config(0) 1017 return result 1018 1019 logging.debug("No pending breakpoints") 1020 return result 1021 1022 1023 def _release_function(self, data): 1024 byte_code_cp = struct.unpack(self.byte_order + self.cp_format, 1025 data[1: 1 + self.cp_size])[0] 1026 1027 function = self.function_list[byte_code_cp] 1028 1029 for line, breakpoint in function.lines.items(): 1030 self.line_list.delete(line, breakpoint) 1031 if breakpoint.active_index >= 0: 1032 del self.active_breakpoint_list[breakpoint.active_index] 1033 1034 del self.function_list[byte_code_cp] 1035 self._send_bytecode_cp(byte_code_cp) 1036 logging.debug("Function {0x%x} byte-code released", byte_code_cp) 1037 1038 1039 def _enable_breakpoint(self, breakpoint): 1040 if isinstance(breakpoint, JerryPendingBreakpoint): 1041 if self._breakpoint_pending_exists(breakpoint): 1042 return "%sPending breakpoint%s already exists\n" % (self.yellow, self.nocolor) 1043 1044 self.next_breakpoint_index += 1 1045 breakpoint.index = self.next_breakpoint_index 1046 self.pending_breakpoint_list[self.next_breakpoint_index] = breakpoint 1047 return ("%sPending breakpoint %d%s at %s\n" % (self.yellow, 1048 breakpoint.index, 1049 self.nocolor, 1050 breakpoint)) 1051 1052 if breakpoint.active_index < 0: 1053 self.next_breakpoint_index += 1 1054 self.active_breakpoint_list[self.next_breakpoint_index] = breakpoint 1055 breakpoint.active_index = self.next_breakpoint_index 1056 self._send_breakpoint(breakpoint) 1057 1058 return "%sBreakpoint %d%s at %s\n" % (self.green, 1059 breakpoint.active_index, 1060 self.nocolor, 1061 breakpoint) 1062 1063 1064 def _set_breakpoint(self, string, pending): 1065 line = re.match("(.*):(\\d+)$", string) 1066 result = "" 1067 1068 if line: 1069 source_name = line.group(1) 1070 new_line = int(line.group(2)) 1071 1072 for breakpoint in self.line_list.get(new_line): 1073 func_source = breakpoint.function.source_name 1074 if (source_name == func_source or 1075 func_source.endswith("/" + source_name) or 1076 func_source.endswith("\\" + source_name)): 1077 1078 result += self._enable_breakpoint(breakpoint) 1079 1080 else: 1081 functions_to_enable = [] 1082 for function in self.function_list.values(): 1083 if function.name == string: 1084 functions_to_enable.append(function) 1085 1086 functions_to_enable.sort(key=lambda x: x.line) 1087 1088 for function in functions_to_enable: 1089 result += self._enable_breakpoint(function.lines[function.first_breakpoint_line]) 1090 1091 if not result and not pending: 1092 print("No breakpoint found, do you want to add a %spending breakpoint%s? (y or [n])" % \ 1093 (self.yellow, self.nocolor)) 1094 1095 ans = sys.stdin.readline() 1096 if ans in ['yes\n', 'y\n']: 1097 if not self.pending_breakpoint_list: 1098 self._send_parser_config(1) 1099 1100 if line: 1101 breakpoint = JerryPendingBreakpoint(int(line.group(2)), line.group(1)) 1102 else: 1103 breakpoint = JerryPendingBreakpoint(function=string) 1104 result += self._enable_breakpoint(breakpoint) 1105 1106 return result 1107 1108 1109 def _get_breakpoint(self, breakpoint_data): 1110 function = self.function_list[breakpoint_data[0]] 1111 offset = breakpoint_data[1] 1112 1113 if offset in function.offsets: 1114 return (function.offsets[offset], True) 1115 1116 if offset < function.first_breakpoint_offset: 1117 return (function.offsets[function.first_breakpoint_offset], False) 1118 1119 nearest_offset = -1 1120 1121 for current_offset in function.offsets: 1122 if current_offset <= offset and current_offset > nearest_offset: 1123 nearest_offset = current_offset 1124 1125 return (function.offsets[nearest_offset], False) 1126 1127 def _process_incoming_text(self, buffer_type, data): 1128 message = b"" 1129 msg_type = buffer_type 1130 while True: 1131 if buffer_type in [JERRY_DEBUGGER_EVAL_RESULT_END, 1132 JERRY_DEBUGGER_OUTPUT_RESULT_END]: 1133 subtype = ord(data[-1]) 1134 message += data[1:-1] 1135 break 1136 else: 1137 message += data[1:] 1138 1139 data = self.channel.get_message(True) 1140 buffer_type = ord(data[0]) 1141 # Checks if the next frame would be an invalid data frame. 1142 # If it is not the message type, or the end type of it, an exception is thrown. 1143 if buffer_type not in [msg_type, msg_type + 1]: 1144 raise Exception("Invalid data caught") 1145 1146 # Subtypes of output 1147 if buffer_type == JERRY_DEBUGGER_OUTPUT_RESULT_END: 1148 if subtype == JERRY_DEBUGGER_OUTPUT_OK: 1149 log_type = "%sout:%s " % (self.blue, self.nocolor) 1150 1151 message = self.current_out + message 1152 lines = message.split("\n") 1153 self.current_out = lines.pop() 1154 1155 return "".join(["%s%s\n" % (log_type, line) for line in lines]) 1156 1157 if subtype == JERRY_DEBUGGER_OUTPUT_DEBUG: 1158 log_type = "%slog:%s " % (self.yellow, self.nocolor) 1159 1160 message = self.current_log + message 1161 lines = message.split("\n") 1162 self.current_log = lines.pop() 1163 1164 return "".join(["%s%s\n" % (log_type, line) for line in lines]) 1165 1166 if not message.endswith("\n"): 1167 message += "\n" 1168 1169 if subtype == JERRY_DEBUGGER_OUTPUT_WARNING: 1170 return "%swarning: %s%s" % (self.yellow, self.nocolor, message) 1171 elif subtype == JERRY_DEBUGGER_OUTPUT_ERROR: 1172 return "%serr: %s%s" % (self.red, self.nocolor, message) 1173 elif subtype == JERRY_DEBUGGER_OUTPUT_TRACE: 1174 return "%strace: %s%s" % (self.blue, self.nocolor, message) 1175 1176 # Subtypes of eval 1177 self.prompt = True 1178 1179 if not message.endswith("\n"): 1180 message += "\n" 1181 1182 if subtype == JERRY_DEBUGGER_EVAL_ERROR: 1183 return "Uncaught exception: %s" % (message) 1184 return message 1185 1186 def _process_scope_variables(self): 1187 buff_size = len(self.scope_vars) 1188 buff_pos = 0 1189 1190 table = [['name', 'type', 'value']] 1191 1192 while buff_pos != buff_size: 1193 # Process name 1194 name_length = ord(self.scope_vars[buff_pos:buff_pos + 1]) 1195 buff_pos += 1 1196 name = self.scope_vars[buff_pos:buff_pos + name_length] 1197 buff_pos += name_length 1198 1199 # Process type 1200 value_type = ord(self.scope_vars[buff_pos:buff_pos + 1]) 1201 1202 buff_pos += 1 1203 1204 value_length = ord(self.scope_vars[buff_pos:buff_pos + 1]) 1205 buff_pos += 1 1206 value = self.scope_vars[buff_pos: buff_pos + value_length] 1207 buff_pos += value_length 1208 1209 if value_type == JERRY_DEBUGGER_VALUE_UNDEFINED: 1210 table.append([name, 'undefined', value]) 1211 elif value_type == JERRY_DEBUGGER_VALUE_NULL: 1212 table.append([name, 'Null', value]) 1213 elif value_type == JERRY_DEBUGGER_VALUE_BOOLEAN: 1214 table.append([name, 'Boolean', value]) 1215 elif value_type == JERRY_DEBUGGER_VALUE_NUMBER: 1216 table.append([name, 'Number', value]) 1217 elif value_type == JERRY_DEBUGGER_VALUE_STRING: 1218 table.append([name, 'String', value]) 1219 elif value_type == JERRY_DEBUGGER_VALUE_FUNCTION: 1220 table.append([name, 'Function', value]) 1221 elif value_type == JERRY_DEBUGGER_VALUE_ARRAY: 1222 table.append([name, 'Array', '[' + value + ']']) 1223 elif value_type == JERRY_DEBUGGER_VALUE_OBJECT: 1224 table.append([name, 'Object', value]) 1225 1226 result = self._form_table(table) 1227 1228 return result 1229 1230 def _process_scope(self): 1231 result = "" 1232 table = [['level', 'type']] 1233 1234 for i, level in enumerate(self.scope_data): 1235 if ord(level) == JERRY_DEBUGGER_SCOPE_WITH: 1236 table.append([str(i), 'with']) 1237 elif ord(level) == JERRY_DEBUGGER_SCOPE_GLOBAL: 1238 table.append([str(i), 'global']) 1239 elif ord(level) == JERRY_DEBUGGER_SCOPE_NON_CLOSURE: 1240 # Currently it is only marks the catch closure. 1241 table.append([str(i), 'catch']) 1242 elif ord(level) == JERRY_DEBUGGER_SCOPE_LOCAL: 1243 table.append([str(i), 'local']) 1244 elif ord(level) == JERRY_DEBUGGER_SCOPE_CLOSURE: 1245 table.append([str(i), 'closure']) 1246 else: 1247 raise Exception("Unexpected scope chain element") 1248 1249 result = self._form_table(table) 1250 1251 return result 1252 1253 def _form_table(self, table): 1254 result = "" 1255 col_width = [max(len(x) for x in col) for col in zip(*table)] 1256 for line in table: 1257 result += " | ".join("{:{}}".format(x, col_width[i]) 1258 for i, x in enumerate(line)) + " \n" 1259 1260 return result 1261