pdb: Initial micropython support.

Andrew Leech 2022-06-21 09:11:37 +10:00
rodzic 109711d911
commit c3870d36fb
1 zmienionych plików z 62 dodań i 46 usunięć

Wyświetl plik

@ -66,25 +66,28 @@ Debugger commands
""" """
# NOTE: the actual command documentation is collected from docstrings of the # NOTE: the actual command documentation is collected from docstrings of the
# commands and is appended to __doc__ after the class has been defined. # commands and is appended to __doc__ after the class has been defined.
import builtins as __builtins__
import os import os
import io import io
import re import re
import sys import sys
import cmd import cmd
import bdb import bdb
import dis # import dis # MPY: dis not currently available
import code import code
import glob import glob
import pprint import pprint
import signal # import signal # MPY: signal not currently available
import inspect # import inspect # MPY: inspect not currently available
import tokenize import tokenize
import functools # import functools
import traceback import traceback
import linecache import linecache
from typing import Union try:
from typing import Union
except ImportError:
pass
class Restart(Exception): class Restart(Exception):
@ -104,7 +107,9 @@ def find_function(funcname, filename):
with fp: with fp:
for lineno, line in enumerate(fp, start=1): for lineno, line in enumerate(fp, start=1):
if cre.match(line): if cre.match(line):
return funcname, filename, lineno ## MPY: increment line number by 1 as we want to break on the
# first line of the function, not the function def line itself
return funcname, filename, lineno + 1
return None return None
def getsourcelines(obj): def getsourcelines(obj):
@ -117,11 +122,12 @@ def getsourcelines(obj):
return inspect.getblock(lines[lineno:]), lineno+1 return inspect.getblock(lines[lineno:]), lineno+1
def lasti2lineno(code, lasti): def lasti2lineno(code, lasti):
linestarts = list(dis.findlinestarts(code)) ## MPY: dis not currently available
linestarts.reverse() # linestarts = list(dis.findlinestarts(code))
for i, lineno in linestarts: # linestarts.reverse()
if lasti >= i: # for i, lineno in linestarts:
return lineno # if lasti >= i:
# return lineno
return 0 return 0
@ -131,40 +137,39 @@ class _rstr(str):
return self return self
class ScriptTarget(str): class ScriptTarget:
def __new__(cls, val): def __init__(self, val):
# Mutate self to be the "real path". # Mutate self to be the "real path".
res = super().__new__(cls, os.path.realpath(val)) self.path = os.path.realpath(val)
# Store the original path for error reporting. # Store the original path for error reporting.
res.orig = val self.orig = val
return res
def check(self): def check(self):
if not os.path.exists(self): if not os.path.exists(self.path):
print('Error:', self.orig, 'does not exist') print('Error:', self.orig, 'does not exist')
sys.exit(1) sys.exit(1)
# Replace pdb's dir with script's dir in front of module search path. # Replace pdb's dir with script's dir in front of module search path.
sys.path[0] = os.path.dirname(self) sys.path[0] = os.path.dirname(self.path)
@property @property
def filename(self): def filename(self):
return self return self.path
@property @property
def namespace(self): def namespace(self):
return dict( return dict(
__name__='__main__', __name__='__main__',
__file__=self, __file__=self.path,
__builtins__=__builtins__, __builtins__=__builtins__,
) )
@property @property
def code(self): def code(self):
with io.open(self) as fp: with io.open(self.path) as fp:
return f"exec(compile({fp.read()!r}, {self!r}, 'exec'))" ## MPY: f-string !r syntax not supported
return f"exec(compile({repr(fp.read())}, {repr(self.path)}, 'exec'))"
class ModuleTarget(str): class ModuleTarget(str):
@ -175,7 +180,7 @@ class ModuleTarget(str):
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)
@functools.cached_property # @functools.cached_property
def _details(self): def _details(self):
import runpy import runpy
return runpy._get_module_details(self) return runpy._get_module_details(self)
@ -219,9 +224,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
nosigint=False, readrc=True): nosigint=False, readrc=True):
bdb.Bdb.__init__(self, skip=skip) bdb.Bdb.__init__(self, skip)
cmd.Cmd.__init__(self, completekey, stdin, stdout) cmd.Cmd.__init__(self, completekey, stdin, stdout)
sys.audit("pdb.Pdb") # sys.audit("pdb.Pdb")
if stdout: if stdout:
self.use_rawinput = 0 self.use_rawinput = 0
self.prompt = '(Pdb) ' self.prompt = '(Pdb) '
@ -422,7 +427,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
if Pdb._previous_sigint_handler: if Pdb._previous_sigint_handler:
try: try:
signal.signal(signal.SIGINT, Pdb._previous_sigint_handler) signal.signal(signal.SIGINT, Pdb._previous_sigint_handler)
except ValueError: # ValueError: signal only works in main thread except (ValueError, NameError): # ValueError: signal only works in main thread
pass pass
else: else:
Pdb._previous_sigint_handler = None Pdb._previous_sigint_handler = None
@ -573,7 +578,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# Collect globals and locals. It is usually not really sensible to also # Collect globals and locals. It is usually not really sensible to also
# complete builtins, and they clutter the namespace quite heavily, so we # complete builtins, and they clutter the namespace quite heavily, so we
# leave them out. # leave them out.
ns = {**self.curframe.f_globals, **self.curframe_locals} ns = {}
ns.update(self.curframe.f_globals)
ns.update(self.curframe_locals)
if '.' in text: if '.' in text:
# Walk an attribute chain up to the last part, similar to what # Walk an attribute chain up to the last part, similar to what
# rlcompleter does. This will bail if any of the parts are not # rlcompleter does. This will bail if any of the parts are not
@ -1137,7 +1144,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
try: try:
Pdb._previous_sigint_handler = \ Pdb._previous_sigint_handler = \
signal.signal(signal.SIGINT, self.sigint_handler) signal.signal(signal.SIGINT, self.sigint_handler)
except ValueError: except (ValueError, NameError):
# ValueError happens when do_continue() is invoked from # ValueError happens when do_continue() is invoked from
# a non-main thread in which case we just continue without # a non-main thread in which case we just continue without
# SIGINT set. Would printing a message here (once) make # SIGINT set. Would printing a message here (once) make
@ -1475,7 +1482,9 @@ class Pdb(bdb.Bdb, cmd.Cmd):
Start an interactive interpreter whose global namespace Start an interactive interpreter whose global namespace
contains all the (global and local) names found in the current scope. contains all the (global and local) names found in the current scope.
""" """
ns = {**self.curframe.f_globals, **self.curframe_locals} ns = {}
ns.update(self.curframe.f_globals)
ns.update(self.curframe_locals)
code.interact("*interactive*", local=ns) code.interact("*interactive*", local=ns)
def do_alias(self, arg): def do_alias(self, arg):
@ -1640,29 +1649,34 @@ class Pdb(bdb.Bdb, cmd.Cmd):
# __main__ will break). Clear __main__ and replace with # __main__ will break). Clear __main__ and replace with
# the target namespace. # the target namespace.
import __main__ import __main__
try:
__main__.__dict__
except AttributeError:
__main__.__dict__ = dict()
__main__.__dict__.clear() __main__.__dict__.clear()
__main__.__dict__.update(target.namespace) __main__.__dict__.update(target.namespace)
self.run(target.code) self.run(target.code)
# Collect all command help into docstring, if not run with -OO # Collect all command help into docstring, if not run with -OO
## MPY: NameError: name '__doc__' isn't defined
# if __doc__ is not None:
# # unfortunately we can't guess this order from the class definition
# _help_order = [
# 'help', 'where', 'down', 'up', 'break', 'tbreak', 'clear', 'disable',
# 'enable', 'ignore', 'condition', 'commands', 'step', 'next', 'until',
# 'jump', 'return', 'retval', 'run', 'continue', 'list', 'longlist',
# 'args', 'p', 'pp', 'whatis', 'source', 'display', 'undisplay',
# 'interact', 'alias', 'unalias', 'debug', 'quit',
# ]
if __doc__ is not None: # for _command in _help_order:
# unfortunately we can't guess this order from the class definition # __doc__ += getattr(Pdb, 'do_' + _command).__doc__.strip() + '\n\n'
_help_order = [ # __doc__ += Pdb.help_exec.__doc__
'help', 'where', 'down', 'up', 'break', 'tbreak', 'clear', 'disable',
'enable', 'ignore', 'condition', 'commands', 'step', 'next', 'until',
'jump', 'return', 'retval', 'run', 'continue', 'list', 'longlist',
'args', 'p', 'pp', 'whatis', 'source', 'display', 'undisplay',
'interact', 'alias', 'unalias', 'debug', 'quit',
]
for _command in _help_order: # del _help_order, _command
__doc__ += getattr(Pdb, 'do_' + _command).__doc__.strip() + '\n\n'
__doc__ += Pdb.help_exec.__doc__
del _help_order, _command
# Simplified interface # Simplified interface
@ -1781,9 +1795,11 @@ def main():
sys.exit(1) sys.exit(1)
except: except:
traceback.print_exc() traceback.print_exc()
t = sys.exc_info()[2]
if t is None:
break
print("Uncaught exception. Entering post mortem debugging") print("Uncaught exception. Entering post mortem debugging")
print("Running 'cont' or 'step' will restart the program") print("Running 'cont' or 'step' will restart the program")
t = sys.exc_info()[2]
pdb.interaction(None, t) pdb.interaction(None, t)
print("Post mortem debugger finished. The " + target + print("Post mortem debugger finished. The " + target +
" will be restarted") " will be restarted")