escargot/tools/debugger/debugger.py
Ádám László Kulcsár 475149426f Implement escargot debugger restart support
Implement restart in escargot and python debugger.
Also add debugger test.

Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-05-07 16:12:31 +09:00

337 lines
9.8 KiB
Python
Executable file

#!/usr/bin/env python3
# Copyright 2015-present Samsung Electronics Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
from cmd import Cmd
from pprint import pprint
import math
import socket
import sys
import logging
import time
import debugger_core
from debugger_websocket import WebSocket
from debugger_tcp import TcpSocket
def write(string):
print(string, end='')
class DebuggerPrompt(Cmd):
# pylint: disable=too-many-instance-attributes,too-many-arguments
def __init__(self, debugger):
Cmd.__init__(self)
self.debugger = debugger
self.stop = False
self.quit = False
def precmd(self, line):
self.stop = False
if self.debugger.non_interactive:
print("%s" % line)
return line
def postcmd(self, stop, line):
return self.stop
def do_quit(self, _):
""" Exit debugger """
self.debugger.quit()
self.quit = True
self.stop = True
def do_EOF(self, _):
""" Exit Escargot debugger """
print("Unexpected end of input. Connection closed.")
self.debugger.quit()
self.quit = True
self.stop = True
def do_display(self, args):
""" Toggle source code display after breakpoints """
if args:
line_num = src_check_args(args)
if line_num >= 0:
self.debugger.display = line_num
else:
print("Non-negative integer number expected, 0 turns off this function")
def do_watch(self, args):
""" Add value to watched values display after breakpoints """
if not args:
write("Error: Argument expected\n")
else:
write(self.debugger.set_watch(args))
do_w = do_watch
def do_print_watches(self, _):
self.debugger.print_watches()
do_pw = do_print_watches
def do_watches(self, _):
self.debugger.list_watches()
do_list_watches = do_watches
do_lw = do_watches
def do_break(self, args):
""" Insert breakpoints on the given lines or functions """
if not args:
write("Error: Argument expected\n")
else:
write(self.debugger.set_break(args))
do_b = do_break
def do_list(self, _):
""" Lists the available breakpoints """
write(self.debugger.breakpoint_list())
def do_delete(self, args):
""" Delete the given breakpoint, use 'delete all|active|pending' to clear all the given breakpoints """
write(self.debugger.delete(args))
def do_delete_watch(self, args):
""" Delete the given watch """
write(self.debugger.delete_watch(args))
def do_continue(self, _):
""" Continue execution """
self.debugger.do_continue()
self.stop = True
if not self.debugger.non_interactive:
print("Press enter to stop JavaScript execution.")
do_c = do_continue
def do_step(self, _):
""" Next breakpoint, step into functions """
self.debugger.step()
self.stop = True
do_s = do_step
def do_next(self, args):
""" Next breakpoint in the same context """
self.stop = True
if not args:
args = 0
self.debugger.next()
return
try:
args = int(args)
if args <= 0:
raise ValueError(args)
while args > 0:
self.debugger.next()
time.sleep(0.1)
while True:
result = self.debugger.process_messages()
res_type = result.get_type()
if res_type == result.END:
self.quit = True
return
elif res_type == result.TEXT:
write(result.get_text())
elif res_type == result.PROMPT:
break
args -= 1
except ValueError as val_errno:
print("Error: expected a positive integer: %s" % val_errno)
do_n = do_next
def do_finish(self, _):
""" Continue running until the current function returns """
self.debugger.finish()
self.stop = True
do_f = do_finish
def do_src(self, args):
""" Get current source code """
if args:
line_num = src_check_args(args)
if line_num >= 0:
write(self.debugger.print_source(line_num, 0))
else:
write(self.debugger.print_source(0, 0))
do_source = do_src
def do_scroll(self, _):
""" Scroll the source up or down """
while True:
key = sys.stdin.readline()
if key == 'w\n':
_scroll_direction(self.debugger, "up")
elif key == 's\n':
_scroll_direction(self.debugger, "down")
elif key == 'q\n':
break
else:
print("Invalid key")
def do_eval(self, args):
""" Evaluate JavaScript source code """
self.debugger.eval(args)
self.stop = True
do_e = do_eval
do_p = do_eval
do_print = do_eval
def do_backtrace(self, args):
""" Get backtrace data from debugger """
write(self.debugger.backtrace(args))
self.stop = True
do_bt = do_backtrace
def do_take_snapshot(self, args):
""" Save heap snapshot to a .snapshot file in the escargot binary's folder """
file_name = self.debugger._send_take_snapshot()
print("Took heap snapshot as \'" + file_name.decode('utf-8')[1:] + "\'!")
self.stop = True
def do_scope(self, args):
""" Get lexical environment chain """
self.debugger.scope_chain(args)
self.stop = True
def do_variables(self, args):
""" Get scope variables """
write(self.debugger.scope_variables(args))
self.stop = True
def do_object(self, args):
""" Get object by index """
if not args:
write("Error: Argument expected")
else:
write(self.debugger.object(args))
self.stop = True
def do_dump(self, args):
""" Dump all of the debugger data """
if args:
print("Error: No argument expected")
else:
pprint(self.debugger.function_list)
def do_restart(self, args):
""" Restart current debugging from the start of the file """
self.debugger.send_restart()
self.debugger.step()
self.stop = True
do_re = do_restart
def _scroll_direction(debugger, direction):
""" Helper function for do_scroll """
debugger.src_offset_diff = int(max(math.floor(debugger.display / 3), 1))
if direction == "up":
debugger.src_offset -= debugger.src_offset_diff
else:
debugger.src_offset += debugger.src_offset_diff
print(debugger.print_source(debugger.display, debugger.src_offset)['value'])
def src_check_args(args):
try:
line_num = int(args)
if line_num < 0:
print("Error: Non-negative integer number expected")
return -1
return line_num
except ValueError as val_errno:
print("Error: Non-negative integer number expected: %s" % (val_errno))
return -1
# pylint: disable=too-many-branches,too-many-locals,too-many-statements,redefined-variable-type
def main():
args = debugger_core.arguments_parse()
tranport = TcpSocket(args.address)
protocol = WebSocket(tranport)
debugger = debugger_core.Debugger(protocol)
debugger.non_interactive = args.non_interactive
logging.debug("Connected to Escargot")
prompt = DebuggerPrompt(debugger)
prompt.prompt = "(escargot-debugger) "
if args.color:
debugger.set_colors()
if args.display:
debugger.display = args.display
prompt.do_display(args.display)
else:
prompt.stop = False
if args.client_source:
debugger.store_client_sources(args.client_source)
if args.wait_before_exit:
debugger.set_wait_exit(args.wait_before_exit)
if args.command:
commands = args.command[0].split(";")
i = 0
while i < len(commands):
result = debugger.process_messages()
res_type = result.get_type()
if res_type == result.END:
return
elif res_type == result.PROMPT:
write(commands[i] + "\n")
prompt.onecmd(commands[i] + "\n")
i += 1
elif res_type == result.TEXT:
write(result.get_text())
continue
while True:
if prompt.quit:
break
result = debugger.process_messages()
res_type = result.get_type()
if res_type == result.END:
break
elif res_type == result.PROMPT:
prompt.cmdloop()
elif res_type == result.TEXT:
write(result.get_text())
continue
if __name__ == "__main__":
try:
main()
print("Connection closed.")
sys.exit(0)
except socket.error as error_msg:
ERRNO = error_msg.errno
MSG = str(error_msg)
if ERRNO == 111:
sys.exit("Failed to connect to the Escargot debugger.")
elif ERRNO == 32 or ERRNO == 104:
sys.exit("Connection closed.")
else:
sys.exit("Failed to connect to the Escargot debugger.\nError: %s" % (MSG))