Skip to content

Commit

Permalink
bdb: Add initial micropython support.
Browse files Browse the repository at this point in the history
Requires micropython to be compiled with MICROPY_PY_SYS_SETTRACE.
Also requires micropython/micropython#8767
  • Loading branch information
pi-anl committed Jun 22, 2022
1 parent ab4929d commit 6f5221d
Showing 1 changed file with 31 additions and 13 deletions.
44 changes: 31 additions & 13 deletions python-stdlib/bdb/bdb.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
"""Debugger basics"""

# This is originally from cpython 3.10: https://raw.githubusercontent.com/python/cpython/3.10/Lib/bdb.py
# Patches for micropython have been commented as such.

import fnmatch
import sys
import os
from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR

## MPY: no inspect module avaialble
# from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR

__all__ = ["BdbQuit", "Bdb", "Breakpoint"]

GENERATOR_AND_COROUTINE_FLAGS = CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR
## MPY: These flags currently don't exist
# GENERATOR_AND_COROUTINE_FLAGS = CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR


class BdbQuit(Exception):
Expand Down Expand Up @@ -46,11 +52,12 @@ def canonic(self, filename):
"""
if filename == "<" + filename[1:-1] + ">":
return filename

canonic = self.fncache.get(filename)
if not canonic:
canonic = os.path.abspath(filename)
canonic = os.path.normcase(canonic)
self.fncache[filename] = canonic
self.fncache[filename] = canonic
return canonic

def reset(self):
Expand Down Expand Up @@ -115,6 +122,12 @@ def dispatch_line(self, frame):
if self.quitting: raise BdbQuit
return self.trace_dispatch

def is_coroutine(self, frame):
## MPY: co_flags attrib not available, compatible method of detecting coroutine TBD
# return frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
return False


def dispatch_call(self, frame, arg):
"""Invoke user function and return trace function for call event.
Expand All @@ -131,7 +144,7 @@ def dispatch_call(self, frame, arg):
# No need to trace this function
return # None
# Ignore call events in generator except when stepping.
if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
if self.stopframe and self.is_coroutine(frame):
return self.trace_dispatch
self.user_call(frame, arg)
if self.quitting: raise BdbQuit
Expand All @@ -146,7 +159,7 @@ def dispatch_return(self, frame, arg):
"""
if self.stop_here(frame) or frame == self.returnframe:
# Ignore return events in generator except when stepping.
if self.stopframe and frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
if self.stopframe and self.is_coroutine(frame):
return self.trace_dispatch
try:
self.frame_returning = frame
Expand All @@ -170,7 +183,7 @@ def dispatch_exception(self, frame, arg):
# When stepping with next/until/return in a generator frame, skip
# the internal StopIteration exception (with no traceback)
# triggered by a subiterator run with the 'yield from' statement.
if not (frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
if not (self.is_coroutine(frame)
and arg[0] is StopIteration and arg[2] is None):
self.user_exception(frame, arg)
if self.quitting: raise BdbQuit
Expand All @@ -179,7 +192,7 @@ def dispatch_exception(self, frame, arg):
# next/until command at the last statement in the generator before the
# exception.
elif (self.stopframe and frame is not self.stopframe
and self.stopframe.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS
and self.is_coroutine(self.stopframe)
and arg[0] in (StopIteration, GeneratorExit)):
self.user_exception(frame, arg)
if self.quitting: raise BdbQuit
Expand Down Expand Up @@ -315,7 +328,7 @@ def set_next(self, frame):

def set_return(self, frame):
"""Stop when returning from the given frame."""
if frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS:
if self.is_coroutine(frame):
self._set_stopinfo(frame, None, -1)
else:
self._set_stopinfo(frame.f_back, frame)
Expand All @@ -326,7 +339,7 @@ def set_trace(self, frame=None):
If frame is not specified, debugging starts from caller's frame.
"""
if frame is None:
frame = sys._getframe().f_back
frame = sys._getframe(1)
self.reset()
while frame:
frame.f_trace = self.trace_dispatch
Expand All @@ -345,7 +358,8 @@ def set_continue(self):
if not self.breaks:
# no breakpoints; run without debugger overhead
sys.settrace(None)
frame = sys._getframe().f_back
## MPY: was sys._getframe().f_back but f_back missing when inside trace dispatch functions
frame = sys._getframe(1)
while frame and frame is not self.botframe:
del frame.f_trace
frame = frame.f_back
Expand Down Expand Up @@ -557,7 +571,11 @@ def format_stack_entry(self, frame_lineno, lprefix=': '):
line of code (if it exists).
"""
import linecache, reprlib
# frame, lineno = frame_lineno
# return repr(frame.f_code)
## MPY: linecache, reprlib, f_locals not yet available
# import linecache, reprlib
import linecache
frame, lineno = frame_lineno
filename = self.canonic(frame.f_code.co_filename)
s = '%s(%r)' % (filename, lineno)
Expand All @@ -569,7 +587,7 @@ def format_stack_entry(self, frame_lineno, lprefix=': '):
if '__return__' in frame.f_locals:
rv = frame.f_locals['__return__']
s += '->'
s += reprlib.repr(rv)
s += repr(rv)
line = linecache.getline(filename, lineno, frame.f_globals)
if line:
s += lprefix + line.strip()
Expand Down Expand Up @@ -628,7 +646,7 @@ def runctx(self, cmd, globals, locals):

# This method is more useful to debug a single function call.

def runcall(self, func, /, *args, **kwds):
def runcall(self, func, *args, **kwds):
"""Debug a single function call.
Return the result of the function call.
Expand Down

0 comments on commit 6f5221d

Please sign in to comment.