From c31348069341e26a17d3bfcfd40f4a28f116730d Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 7 Nov 2023 14:21:52 -0800 Subject: [PATCH 1/7] Support opcode events in bdb --- Lib/bdb.py | 69 ++++++++++++++++++++++++++++++++++++++------ Lib/test/test_pdb.py | 1 - 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 1acf7957f0d669..58bb368b2ae5a1 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -33,7 +33,10 @@ def __init__(self, skip=None): self.breaks = {} self.fncache = {} self.frame_trace_lines = {} + self.frame_trace_opcodes = {} self.frame_returning = None + self.trace_opcodes = False + self.__curframe = None self._load_breaks() @@ -85,6 +88,9 @@ def trace_dispatch(self, frame, event, arg): The arg parameter depends on the previous event. """ + + self.__curframe = frame + if self.quitting: return # None if event == 'line': @@ -101,6 +107,8 @@ def trace_dispatch(self, frame, event, arg): return self.trace_dispatch if event == 'c_return': return self.trace_dispatch + if event == 'opcode': + return self.dispatch_opcode(frame, arg) print('bdb.Bdb.dispatch: unknown debugging event:', repr(event)) return self.trace_dispatch @@ -187,6 +195,17 @@ def dispatch_exception(self, frame, arg): return self.trace_dispatch + def dispatch_opcode(self, frame, arg): + """Invoke user function and return trace function for opcode event. + If the debugger stops on the current opcode, invoke + self.user_opcode(). Raise BdbQuit if self.quitting is set. + Return self.trace_dispatch to continue tracing in this scope. + """ + if self.stop_here(frame) or self.break_here(frame): + self.user_opcode(frame) + if self.quitting: raise BdbQuit + return self.trace_dispatch + # Normally derived classes don't override the following # methods, but they may if they want to redefine the # definition of stopping and breakpoints. @@ -273,7 +292,21 @@ def user_exception(self, frame, exc_info): """Called when we stop on an exception.""" pass - def _set_stopinfo(self, stopframe, returnframe, stoplineno=0): + def user_opcode(self, frame): + """Called when we are about to execute an opcode.""" + pass + + def _set_trace_opcodes(self, trace_opcodes): + if trace_opcodes != self.trace_opcodes: + self.trace_opcodes = trace_opcodes + frame = self.__curframe + while frame is not None: + frame.f_trace_opcodes = trace_opcodes + if frame is self.botframe: + break + frame = frame.f_back + + def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False): """Set the attributes for stopping. If stoplineno is greater than or equal to 0, then stop at line @@ -286,6 +319,21 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0): # stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno -1 means: don't stop at all self.stoplineno = stoplineno + if opcode: + # We are stopping at opcode level + self._set_trace_opcodes(True) + else: + self._set_trace_opcodes(False) + + def _set_caller_tracefunc(self): + # Issue #13183: pdb skips frames after hitting a breakpoint and running + # step commands. + # Restore the trace function in the caller (that may not have been set + # for performance reasons) when returning from the current frame. + if self.frame_returning: + caller_frame = self.frame_returning.f_back + if caller_frame and not caller_frame.f_trace: + caller_frame.f_trace = self.trace_dispatch # Derived classes and clients can call the following methods # to affect the stepping state. @@ -300,16 +348,14 @@ def set_until(self, frame, lineno=None): def set_step(self): """Stop after one line of code.""" - # Issue #13183: pdb skips frames after hitting a breakpoint and running - # step commands. - # Restore the trace function in the caller (that may not have been set - # for performance reasons) when returning from the current frame. - if self.frame_returning: - caller_frame = self.frame_returning.f_back - if caller_frame and not caller_frame.f_trace: - caller_frame.f_trace = self.trace_dispatch + self._set_caller_tracefunc() self._set_stopinfo(None, None) + def set_stepinstr(self): + """Stop before the next instruction.""" + self._set_caller_tracefunc() + self._set_stopinfo(None, None, opcode=True) + def set_next(self, frame): """Stop on the next line in or below the given frame.""" self._set_stopinfo(frame, None) @@ -329,11 +375,13 @@ def set_trace(self, frame=None): if frame is None: frame = sys._getframe().f_back self.reset() + self.__curframe = frame while frame: frame.f_trace = self.trace_dispatch self.botframe = frame # We need f_trace_liens == True for the debugger to work self.frame_trace_lines[frame] = frame.f_trace_lines + self.frame_trace_opcodes[frame] = frame.f_trace_opcodes frame.f_trace_lines = True frame = frame.f_back self.set_step() @@ -355,7 +403,10 @@ def set_continue(self): frame = frame.f_back for frame, prev_trace_lines in self.frame_trace_lines.items(): frame.f_trace_lines = prev_trace_lines + for frame, prev_trace_opcodes in self.frame_trace_opcodes.items(): + frame.f_trace_opcodes = prev_trace_opcodes self.frame_trace_lines = {} + self.frame_trace_opcodes = {} def set_quit(self): """Set quitting attribute to True. diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 5508f7bff37994..b9bec0a99e9ade 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2343,7 +2343,6 @@ def test_pdb_issue_gh_108976(): ... 'continue' ... ]): ... test_function() - bdb.Bdb.dispatch: unknown debugging event: 'opcode' > (5)test_function() -> a = 1 (Pdb) continue From 260658027ff59079ce4d03674c601fc99672155f Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 7 Nov 2023 14:40:07 -0800 Subject: [PATCH 2/7] Add basic test for stepinstr --- Lib/test/test_bdb.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py index 568c88e326c087..ed1a63daea1186 100644 --- a/Lib/test/test_bdb.py +++ b/Lib/test/test_bdb.py @@ -228,6 +228,10 @@ def user_exception(self, frame, exc_info): self.process_event('exception', frame) self.next_set_method() + def user_opcode(self, frame): + self.process_event('opcode', frame) + self.next_set_method() + def do_clear(self, arg): # The temporary breakpoints are deleted in user_line(). bp_list = [self.currentbp] @@ -366,7 +370,7 @@ def next_set_method(self): set_method = getattr(self, 'set_' + set_type) # The following set methods give back control to the tracer. - if set_type in ('step', 'continue', 'quit'): + if set_type in ('step', 'stepinstr', 'continue', 'quit'): set_method() return elif set_type in ('next', 'return'): @@ -610,6 +614,15 @@ def test_step_next_on_last_statement(self): with TracerRun(self) as tracer: tracer.runcall(tfunc_main) + def test_stepinstr(self): + self.expect_set = [ + ('line', 2, 'tfunc_main'), ('stepinstr', ), + ('opcode', 2, 'tfunc_main'), ('next', ), + ('line', 3, 'tfunc_main'), ('quit', ), + ] + with TracerRun(self) as tracer: + tracer.runcall(tfunc_main) + def test_next(self): self.expect_set = [ ('line', 2, 'tfunc_main'), ('step', ), From 99ec1e1a312e450b5adbca6b8e8b305890e51b46 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 22:41:44 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst diff --git a/Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst b/Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst new file mode 100644 index 00000000000000..ed856e7667a372 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-07-22-41-42.gh-issue-111744.TbLxF0.rst @@ -0,0 +1 @@ +Support opcode events in :mod:`bdb` From a91fe16b0da34c1cddb09f3b0562a02ba61f730e Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 3 May 2024 18:23:20 -0700 Subject: [PATCH 4/7] Update Lib/bdb.py Co-authored-by: Brandt Bucher --- Lib/bdb.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 58bb368b2ae5a1..3a7294cd820a73 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -319,11 +319,7 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, opcode=False): # stoplineno >= 0 means: stop at line >= the stoplineno # stoplineno -1 means: don't stop at all self.stoplineno = stoplineno - if opcode: - # We are stopping at opcode level - self._set_trace_opcodes(True) - else: - self._set_trace_opcodes(False) + self._set_trace_opcodes(opcode) def _set_caller_tracefunc(self): # Issue #13183: pdb skips frames after hitting a breakpoint and running From 10ba29546ca3e0267393187c3449d32d45189f43 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 3 May 2024 18:32:55 -0700 Subject: [PATCH 5/7] Combine the two dicts into one --- Lib/bdb.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 3a7294cd820a73..33777624cb81ca 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -32,8 +32,7 @@ def __init__(self, skip=None): self.skip = set(skip) if skip else None self.breaks = {} self.fncache = {} - self.frame_trace_lines = {} - self.frame_trace_opcodes = {} + self.frame_trace_lines_opcodes = {} self.frame_returning = None self.trace_opcodes = False self.__curframe = None @@ -375,9 +374,8 @@ def set_trace(self, frame=None): while frame: frame.f_trace = self.trace_dispatch self.botframe = frame - # We need f_trace_liens == True for the debugger to work - self.frame_trace_lines[frame] = frame.f_trace_lines - self.frame_trace_opcodes[frame] = frame.f_trace_opcodes + self.frame_trace_lines_opcodes[frame] = (frame.f_trace_lines, frame.f_trace_opcodes) + # We need f_trace_lines == True for the debugger to work frame.f_trace_lines = True frame = frame.f_back self.set_step() @@ -397,10 +395,8 @@ def set_continue(self): while frame and frame is not self.botframe: del frame.f_trace frame = frame.f_back - for frame, prev_trace_lines in self.frame_trace_lines.items(): - frame.f_trace_lines = prev_trace_lines - for frame, prev_trace_opcodes in self.frame_trace_opcodes.items(): - frame.f_trace_opcodes = prev_trace_opcodes + for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items(): + frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes self.frame_trace_lines = {} self.frame_trace_opcodes = {} From 65a3a76a47880648e4d0c6b8c4626ba11bf2f0f8 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 3 May 2024 22:18:00 -0700 Subject: [PATCH 6/7] Rename __curframe to enterframe --- Lib/bdb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 33777624cb81ca..f8f5bd671989d6 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -35,7 +35,7 @@ def __init__(self, skip=None): self.frame_trace_lines_opcodes = {} self.frame_returning = None self.trace_opcodes = False - self.__curframe = None + self.enterframe = None self._load_breaks() @@ -88,7 +88,7 @@ def trace_dispatch(self, frame, event, arg): The arg parameter depends on the previous event. """ - self.__curframe = frame + self.enterframe = frame if self.quitting: return # None @@ -298,7 +298,7 @@ def user_opcode(self, frame): def _set_trace_opcodes(self, trace_opcodes): if trace_opcodes != self.trace_opcodes: self.trace_opcodes = trace_opcodes - frame = self.__curframe + frame = self.enterframe while frame is not None: frame.f_trace_opcodes = trace_opcodes if frame is self.botframe: @@ -370,7 +370,7 @@ def set_trace(self, frame=None): if frame is None: frame = sys._getframe().f_back self.reset() - self.__curframe = frame + self.enterframe = frame while frame: frame.f_trace = self.trace_dispatch self.botframe = frame From 05072f49f79188c6c9cd5adae73d556f314831c2 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 3 May 2024 23:34:27 -0700 Subject: [PATCH 7/7] Fix a missing piece --- Lib/bdb.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index f8f5bd671989d6..675c8ae51df4c3 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -397,8 +397,7 @@ def set_continue(self): frame = frame.f_back for frame, (trace_lines, trace_opcodes) in self.frame_trace_lines_opcodes.items(): frame.f_trace_lines, frame.f_trace_opcodes = trace_lines, trace_opcodes - self.frame_trace_lines = {} - self.frame_trace_opcodes = {} + self.frame_trace_lines_opcodes = {} def set_quit(self): """Set quitting attribute to True.