From 5e4538ff95ce272b6435ec2cb6d3443c41185b16 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 26 Mar 2023 18:05:03 -0700 Subject: [PATCH 01/10] Support assembly display on pdb --- Lib/pdb.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 3a06cd00ad2bf1..925410d4961a3f 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -230,6 +230,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, pass self.allow_kbdint = False self.nosigint = nosigint + self.assem_mode = False # Read ~/.pdbrc and ./.pdbrc self.rcLines = [] @@ -1287,6 +1288,19 @@ def do_pp(self, arg): complete_p = _complete_expression complete_pp = _complete_expression + def do_assem(self, arg): + """assem [on | off] + Toggle/Set assembly mode + """ + if not arg: + self.assem_mode = not self.assem_mode + elif arg == "on": + self.assem_mode = True + elif arg == "off": + self.assem_mode = False + else: + self.message("usage: assem [on | off]") + def do_list(self, arg): """l(ist) [first [,last] | .] @@ -1336,7 +1350,8 @@ def do_list(self, arg): try: lines = linecache.getlines(filename, self.curframe.f_globals) self._print_lines(lines[first-1:last], first, breaklist, - self.curframe) + self.curframe, + dis.get_instructions(self.curframe.f_code)) self.lineno = min(last, len(lines)) if len(lines) < last: self.message('[EOF]') @@ -1355,7 +1370,8 @@ def do_longlist(self, arg): except OSError as err: self.error(err) return - self._print_lines(lines, lineno, breaklist, self.curframe) + self._print_lines(lines, lineno, breaklist, self.curframe, + dis.get_instructions(self.curframe.f_code)) do_ll = do_longlist def do_source(self, arg): @@ -1375,13 +1391,15 @@ def do_source(self, arg): complete_source = _complete_expression - def _print_lines(self, lines, start, breaks=(), frame=None): + def _print_lines(self, lines, start, breaks=(), frame=None, instructions=None): """Print a range of lines.""" if frame: current_lineno = frame.f_lineno exc_lineno = self.tb_lineno.get(frame, -1) else: current_lineno = exc_lineno = -1 + if self.assem_mode and instructions: + inst = next(instructions) for lineno, line in enumerate(lines, start): s = str(lineno).rjust(3) if len(s) < 4: @@ -1395,6 +1413,20 @@ def _print_lines(self, lines, start, breaks=(), frame=None): elif lineno == exc_lineno: s += '>>' self.message(s + '\t' + line.rstrip()) + if self.assem_mode and instructions: + while True: + if inst.positions.lineno == lineno: + current_inst = frame and frame.f_lasti == inst.offset + disassem = inst._disassemble(lineno_width=None, + mark_as_current=current_inst) + self.message(f" {disassem}") + elif inst.positions.lineno is not None and inst.positions.lineno > lineno: + break + try: + inst = next(instructions) + except StopIteration: + break + def do_whatis(self, arg): """whatis arg From fc50e5190f45ac0dad5692de549a2f7b440bf369 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Fri, 31 Mar 2023 15:38:41 -0700 Subject: [PATCH 02/10] Implement ni and si, do li and lli instead of assem --- Lib/bdb.py | 72 ++++++++++++++++++++++++-- Lib/pdb.py | 147 ++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 174 insertions(+), 45 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 7f9b09514ffd00..ab35ef64c8edff 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -32,6 +32,9 @@ def __init__(self, skip=None): self.skip = set(skip) if skip else None self.breaks = {} self.fncache = {} + self._curframe = None + self.lasti = -1 + self.trace_opcodes = False self.frame_returning = None self._load_breaks() @@ -84,6 +87,8 @@ 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': @@ -94,6 +99,8 @@ def trace_dispatch(self, frame, event, arg): return self.dispatch_return(frame, arg) if event == 'exception': return self.dispatch_exception(frame, arg) + if event == 'opcode': + return self.dispatch_opcode(frame) if event == 'c_call': return self.trace_dispatch if event == 'c_exception': @@ -115,6 +122,18 @@ def dispatch_line(self, frame): if self.quitting: raise BdbQuit return self.trace_dispatch + def dispatch_opcode(self, frame): + """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 + def dispatch_call(self, frame, arg): """Invoke user function and return trace function for call event. @@ -122,6 +141,11 @@ def dispatch_call(self, frame, arg): self.user_call(). Raise BdbQuit if self.quitting is set. Return self.trace_dispatch to continue tracing in this scope. """ + if self.trace_opcodes: + frame.f_trace_opcodes = True + else: + frame.f_trace_opcodes = False + # XXX 'arg' is no longer used if self.botframe is None: # First call of dispatch since reset() @@ -209,9 +233,15 @@ def stop_here(self, frame): if frame is self.stopframe: if self.stoplineno == -1: return False - return frame.f_lineno >= self.stoplineno + if self.trace_opcodes: + return self.lasti != frame.f_lasti + else: + return frame.f_lineno >= self.stoplineno if not self.stopframe: - return True + if self.trace_opcodes: + return self.lasti != frame.f_lasti + else: + return True return False def break_here(self, frame): @@ -272,7 +302,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 stop or break at a 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, lasti=None): """Set the attributes for stopping. If stoplineno is greater than or equal to 0, then stop at line @@ -285,6 +329,12 @@ 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 lasti: + # We are stopping at opcode level + self._set_trace_opcodes(True) + self.lasti = lasti + else: + self._set_trace_opcodes(False) # Derived classes and clients can call the following methods # to affect the stepping state. @@ -309,10 +359,26 @@ def set_step(self): caller_frame.f_trace = self.trace_dispatch self._set_stopinfo(None, None) + def set_stepinst(self, frame): + """Stop after one opcode.""" + # 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_stopinfo(None, None, lasti=frame.f_lasti) + def set_next(self, frame): """Stop on the next line in or below the given frame.""" self._set_stopinfo(frame, None) + def set_nextinst(self, frame): + """Stop on the next line in or below the given frame.""" + self._set_stopinfo(frame, None, lasti=frame.f_lasti) + def set_return(self, frame): """Stop when returning from the given frame.""" if frame.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS: diff --git a/Lib/pdb.py b/Lib/pdb.py index 925410d4961a3f..be2ac1d0c46bfb 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -203,6 +203,7 @@ def namespace(self): # command "pdb.line_prefix = '\n% '". # line_prefix = ': ' # Use this to get the old situation back line_prefix = '\n-> ' # Probably a better default +inst_prefix = '\n--> ' # Probably a better default class Pdb(bdb.Bdb, cmd.Cmd): @@ -230,7 +231,6 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, pass self.allow_kbdint = False self.nosigint = nosigint - self.assem_mode = False # Read ~/.pdbrc and ./.pdbrc self.rcLines = [] @@ -331,6 +331,14 @@ def user_line(self, frame): if self.bp_commands(frame): self.interaction(frame, None) + def user_opcode(self, frame): + if self._wait_for_mainpyfile: + if (self.mainpyfile != self.canonic(frame.f_code.co_filename) + or frame.f_lineno <= 0): + return + self._wait_for_mainpyfile = False + self.interaction(frame, None) + def bp_commands(self, frame): """Call every command that was set for the current active breakpoint (if there is one). @@ -423,6 +431,8 @@ def interaction(self, frame, traceback): self.forget() return self.print_stack_entry(self.stack[self.curindex]) + if self.trace_opcodes: + self.print_current_inst(frame) self._cmdloop() self.forget() @@ -1088,6 +1098,16 @@ def do_step(self, arg): return 1 do_s = do_step + def do_stepinst(self, arg): + """s(tep) + Execute the current line, stop at the first possible occasion + (either in a function that is called or in the current + function). + """ + self.set_stepinst(self.curframe) + return 1 + do_si = do_stepinst + def do_next(self, arg): """n(ext) Continue execution until the next line in the current function @@ -1097,6 +1117,15 @@ def do_next(self, arg): return 1 do_n = do_next + def do_nextinst(self, arg): + """n(ext) + Continue execution until the next line in the current function + is reached or it returns. + """ + self.set_nextinst(self.curframe) + return 1 + do_ni = do_nextinst + def do_run(self, arg): """run [args...] Restart the debugged python program. If a string is supplied @@ -1288,34 +1317,7 @@ def do_pp(self, arg): complete_p = _complete_expression complete_pp = _complete_expression - def do_assem(self, arg): - """assem [on | off] - Toggle/Set assembly mode - """ - if not arg: - self.assem_mode = not self.assem_mode - elif arg == "on": - self.assem_mode = True - elif arg == "off": - self.assem_mode = False - else: - self.message("usage: assem [on | off]") - - def do_list(self, arg): - """l(ist) [first [,last] | .] - - List source code for the current file. Without arguments, - list 11 lines around the current line or continue the previous - listing. With . as argument, list 11 lines around the current - line. With one argument, list 11 lines starting at that line. - With two arguments, list the given range; if the second - argument is less than the first, it is a count. - - The current line in the current frame is indicated by "->". - If an exception is being debugged, the line where the - exception was originally raised or propagated is indicated by - ">>", if it differs from the current line. - """ + def _do_list(self, arg, show_instructions=False): self.lastcmd = 'list' last = None if arg and arg != '.': @@ -1349,20 +1351,57 @@ def do_list(self, arg): breaklist = self.get_file_breaks(filename) try: lines = linecache.getlines(filename, self.curframe.f_globals) + instructions = dis.get_instructions(self.curframe.f_code) \ + if show_instructions else None self._print_lines(lines[first-1:last], first, breaklist, self.curframe, - dis.get_instructions(self.curframe.f_code)) + instructions) self.lineno = min(last, len(lines)) if len(lines) < last: self.message('[EOF]') except KeyboardInterrupt: pass + + def do_list(self, arg): + """l(ist) [first [,last] | .] + + List source code for the current file. Without arguments, + list 11 lines around the current line or continue the previous + listing. With . as argument, list 11 lines around the current + line. With one argument, list 11 lines starting at that line. + With two arguments, list the given range; if the second + argument is less than the first, it is a count. + + The current line in the current frame is indicated by "->". + If an exception is being debugged, the line where the + exception was originally raised or propagated is indicated by + ">>", if it differs from the current line. + """ + self._do_list(arg, False) do_l = do_list - def do_longlist(self, arg): - """longlist | ll - List the whole source code for the current function or frame. + def do_listinst(self, arg): + """listinst | li [first[, last] | .] + + List source code for the current file with instructions. + + Without arguments, list 11 lines around the current line or + continue the previous listing. With . as argument, list 11 + lines around the current line. With one argument, list 11 + lines starting at that line. With two arguments, list the + given range; if the second argument is less than the first, + it is a count. + + The current line in the current frame is indicated by "->". + The current instruction is indicated by "-->" + If an exception is being debugged, the line where the + exception was originally raised or propagated is indicated by + ">>", if it differs from the current line. """ + self._do_list(arg, True) + do_li = do_listinst + + def _do_longlist(self, arg, show_instructions=False): filename = self.curframe.f_code.co_filename breaklist = self.get_file_breaks(filename) try: @@ -1370,10 +1409,27 @@ def do_longlist(self, arg): except OSError as err: self.error(err) return + instructions = dis.get_instructions(self.curframe.f_code) \ + if show_instructions else None self._print_lines(lines, lineno, breaklist, self.curframe, - dis.get_instructions(self.curframe.f_code)) + instructions) + + def do_longlist(self, arg): + """longlist | ll + List the whole source code for the current function or frame. + """ + self._do_longlist(arg, False) do_ll = do_longlist + def do_longlistinst(self, arg): + """longlistinst | lli + + List the whole source code with instructions for the current + function or frame. + """ + self._do_longlist(arg, True) + do_lli = do_longlistinst + def do_source(self, arg): """source expression Try to get source code for the given object and display it. @@ -1398,7 +1454,7 @@ def _print_lines(self, lines, start, breaks=(), frame=None, instructions=None): exc_lineno = self.tb_lineno.get(frame, -1) else: current_lineno = exc_lineno = -1 - if self.assem_mode and instructions: + if instructions: inst = next(instructions) for lineno, line in enumerate(lines, start): s = str(lineno).rjust(3) @@ -1413,21 +1469,21 @@ def _print_lines(self, lines, start, breaks=(), frame=None, instructions=None): elif lineno == exc_lineno: s += '>>' self.message(s + '\t' + line.rstrip()) - if self.assem_mode and instructions: + if instructions: while True: if inst.positions.lineno == lineno: current_inst = frame and frame.f_lasti == inst.offset disassem = inst._disassemble(lineno_width=None, mark_as_current=current_inst) self.message(f" {disassem}") - elif inst.positions.lineno is not None and inst.positions.lineno > lineno: + elif inst.positions.lineno is not None and \ + inst.positions.lineno > lineno: break try: inst = next(instructions) except StopIteration: break - def do_whatis(self, arg): """whatis arg Print the type of the argument. @@ -1591,6 +1647,13 @@ def print_stack_entry(self, frame_lineno, prompt_prefix=line_prefix): self.message(prefix + self.format_stack_entry(frame_lineno, prompt_prefix)) + def print_current_inst(self, frame): + for inst in dis.get_instructions(frame.f_code): + if inst.offset == frame.f_lasti: + self.message(inst._disassemble(lineno_width=None, + mark_as_current=True)) + return + # Provide help def do_help(self, arg): @@ -1700,10 +1763,10 @@ def _compile_error_message(self, expr): # 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', + 'enable', 'ignore', 'condition', 'commands', 'step', 'stepinst', + 'next', 'nextinst', '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: From c3af63c198b46606e8853a27a38e5ac72343ac9d Mon Sep 17 00:00:00 2001 From: gaogaotiantian Date: Tue, 4 Apr 2023 20:51:41 -0700 Subject: [PATCH 03/10] Fixed a typo and use adaptive for instructions Co-authored-by: Artem Mukhin --- Lib/bdb.py | 2 +- Lib/pdb.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index ab35ef64c8edff..6aa1e2b7ea3e20 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -303,7 +303,7 @@ def user_exception(self, frame, exc_info): pass def user_opcode(self, frame): - """Called when we stop or break at a opcode.""" + """Called when we stop or break at an opcode.""" pass def _set_trace_opcodes(self, trace_opcodes): diff --git a/Lib/pdb.py b/Lib/pdb.py index be2ac1d0c46bfb..39a2eccbf48bee 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1351,7 +1351,7 @@ def _do_list(self, arg, show_instructions=False): breaklist = self.get_file_breaks(filename) try: lines = linecache.getlines(filename, self.curframe.f_globals) - instructions = dis.get_instructions(self.curframe.f_code) \ + instructions = dis.get_instructions(self.curframe.f_code, adaptive=True) \ if show_instructions else None self._print_lines(lines[first-1:last], first, breaklist, self.curframe, @@ -1409,7 +1409,7 @@ def _do_longlist(self, arg, show_instructions=False): except OSError as err: self.error(err) return - instructions = dis.get_instructions(self.curframe.f_code) \ + instructions = dis.get_instructions(self.curframe.f_code, adaptive=True) \ if show_instructions else None self._print_lines(lines, lineno, breaklist, self.curframe, instructions) From 4016fd44f09e4615e892e8bb7fc7dafd165c6ecf Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 4 Apr 2023 21:10:54 -0700 Subject: [PATCH 04/10] Fix docstrings and commands Used stepi instead of stepinst(other commands too) Abstracted a function to restore caller trace function --- Lib/bdb.py | 35 ++++++++++++++++------------------- Lib/pdb.py | 48 ++++++++++++++++++++++++++---------------------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/Lib/bdb.py b/Lib/bdb.py index 6aa1e2b7ea3e20..429771ded4c281 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -78,6 +78,7 @@ def trace_dispatch(self, frame, event, arg): is entered. return: A function or other code block is about to return. exception: An exception has occurred. + opcode: An opcode is going to be executed. c_call: A C function is about to be called. c_return: A C function has returned. c_exception: A C function has raised an exception. @@ -303,7 +304,7 @@ def user_exception(self, frame, exc_info): pass def user_opcode(self, frame): - """Called when we stop or break at an opcode.""" + """Called when we are about to execute an opcode.""" pass def _set_trace_opcodes(self, trace_opcodes): @@ -336,6 +337,16 @@ def _set_stopinfo(self, stopframe, returnframe, stoplineno=0, lasti=None): 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. @@ -349,33 +360,19 @@ 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_stepinst(self, frame): + def set_stepi(self, frame): """Stop after one opcode.""" - # 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, lasti=frame.f_lasti) def set_next(self, frame): """Stop on the next line in or below the given frame.""" self._set_stopinfo(frame, None) - def set_nextinst(self, frame): + def set_nexti(self, frame): """Stop on the next line in or below the given frame.""" self._set_stopinfo(frame, None, lasti=frame.f_lasti) diff --git a/Lib/pdb.py b/Lib/pdb.py index 39a2eccbf48bee..0017219e7243e0 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -332,6 +332,7 @@ def user_line(self, frame): self.interaction(frame, None) def user_opcode(self, frame): + """This function is called when we are about to execute an opcode.""" if self._wait_for_mainpyfile: if (self.mainpyfile != self.canonic(frame.f_code.co_filename) or frame.f_lineno <= 0): @@ -1098,15 +1099,15 @@ def do_step(self, arg): return 1 do_s = do_step - def do_stepinst(self, arg): + def do_stepi(self, arg): """s(tep) Execute the current line, stop at the first possible occasion (either in a function that is called or in the current function). """ - self.set_stepinst(self.curframe) + self.set_stepi(self.curframe) return 1 - do_si = do_stepinst + do_si = do_stepi def do_next(self, arg): """n(ext) @@ -1117,14 +1118,14 @@ def do_next(self, arg): return 1 do_n = do_next - def do_nextinst(self, arg): + def do_nexti(self, arg): """n(ext) Continue execution until the next line in the current function is reached or it returns. """ - self.set_nextinst(self.curframe) + self.set_nexti(self.curframe) return 1 - do_ni = do_nextinst + do_ni = do_nexti def do_run(self, arg): """run [args...] @@ -1380,17 +1381,19 @@ def do_list(self, arg): self._do_list(arg, False) do_l = do_list - def do_listinst(self, arg): - """listinst | li [first[, last] | .] + def do_listi(self, arg): + """listi | li [first[, last] | .] List source code for the current file with instructions. - Without arguments, list 11 lines around the current line or - continue the previous listing. With . as argument, list 11 - lines around the current line. With one argument, list 11 - lines starting at that line. With two arguments, list the - given range; if the second argument is less than the first, - it is a count. + Without arguments, list 11 lines with their corresponding + instructions around the current line or continue the + previous listing. With . as argument, list 11 lines with + their corresponding instructions around the current line. + With one argument, list 11 lines with their corresponding + instructions starting at that line. With two arguments, + list the given range; if the second argument is less than + the first, it is a count. The current line in the current frame is indicated by "->". The current instruction is indicated by "-->" @@ -1399,7 +1402,7 @@ def do_listinst(self, arg): ">>", if it differs from the current line. """ self._do_list(arg, True) - do_li = do_listinst + do_li = do_listi def _do_longlist(self, arg, show_instructions=False): filename = self.curframe.f_code.co_filename @@ -1421,14 +1424,14 @@ def do_longlist(self, arg): self._do_longlist(arg, False) do_ll = do_longlist - def do_longlistinst(self, arg): - """longlistinst | lli + def do_longlisti(self, arg): + """longlisti | lli List the whole source code with instructions for the current function or frame. """ self._do_longlist(arg, True) - do_lli = do_longlistinst + do_lli = do_longlisti def do_source(self, arg): """source expression @@ -1763,10 +1766,11 @@ def _compile_error_message(self, expr): # 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', 'stepinst', - 'next', 'nextinst', 'until', 'jump', 'return', 'retval', 'run', - 'continue', 'list', 'longlist', 'args', 'p', 'pp', 'whatis', 'source', - 'display', 'undisplay', 'interact', 'alias', 'unalias', 'debug', 'quit', + 'enable', 'ignore', 'condition', 'commands', 'step', 'stepi', + 'next', 'nexti', 'until', 'jump', 'return', 'retval', 'run', + 'continue', 'list', 'listi', 'longlist', 'longlisti', 'args', 'p', + 'pp', 'whatis', 'source', 'display', 'undisplay', 'interact', 'alias', + 'unalias', 'debug', 'quit', ] for _command in _help_order: From 11d13958dda05f45238f767c82b9be5a0361814f Mon Sep 17 00:00:00 2001 From: gaogaotiantian Date: Wed, 5 Apr 2023 16:30:51 -0700 Subject: [PATCH 05/10] Fix docstring for stepi Co-authored-by: Artem Mukhin --- Lib/pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 0017219e7243e0..d48a415ffa7f39 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1101,7 +1101,7 @@ def do_step(self, arg): def do_stepi(self, arg): """s(tep) - Execute the current line, stop at the first possible occasion + Execute the current instruction, stop at the first possible occasion (either in a function that is called or in the current function). """ From a42a420e8f8c2c9541e4735ed9459f684cd94810 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 8 Apr 2023 16:02:47 -0700 Subject: [PATCH 06/10] Add tests for instruction level commands --- Lib/test/test_pdb.py | 96 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index de2bab46495729..458cef02ac3ca2 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -1,5 +1,6 @@ # A test suite for pdb; not very comprehensive at the moment. +import dis import doctest import os import pdb @@ -2351,6 +2352,101 @@ def _create_fake_frozen_module(): # verify that pdb found the source of the "frozen" function self.assertIn('x = "Sentinel string for gh-93696"', stdout, "Sentinel statement not found") + def get_func_opnames(self, func_def, func_name): + extract_code = f""" + import dis + for inst in dis.get_instructions({func_name}): + print(inst.opname) + """ + + with redirect_stdout(StringIO()) as s: + exec(textwrap.dedent(func_def) + textwrap.dedent(extract_code)) + + return s.getvalue().splitlines() + + def test_list_instruction(self): + func_def = """ + def f(): + a = [1, 2, 3] + return a[0] + """ + func_exec = """ + f() + """ + script = func_def + func_exec + + commands_li = """ + break f + c + li + """ + + commands_lli = """ + break f + c + lli + """ + + # Make sure all the opcodes are listed + stdout, stderr = self.run_pdb_module(script, commands_li) + for opname in self.get_func_opnames(func_def, "f"): + self.assertIn(opname, stdout) + + stdout, stderr = self.run_pdb_module(script, commands_lli) + for opname in self.get_func_opnames(func_def, "f"): + self.assertIn(opname, stdout) + + def test_instruction_level_control(self): + func_def = """ + def f(): + a = [1, 2, 3] + return a[0] + """ + func_exec = """ + f() + """ + script = func_def + func_exec + + commands = """ + ni + li + """ + + # Check that after ni, current instruction is displayed + stdout, stderr = self.run_pdb_module(script, commands) + lines = [line.strip() for line in stdout.splitlines()] + for idx, line in enumerate(lines): + if "-->" in line: + # Found the current instruction indicator after ni + # Make sure that is listed in li + self.assertIn(line, lines[idx+1:]) + break + + commands = """ + ni + ni + ni + c + """ + + stdout, stderr = self.run_pdb_module(script, commands) + curr_instr_lines = [line.strip() for line in stdout.splitlines() if "-->" in line] + self.assertEqual(len(curr_instr_lines), 3) + for line in curr_instr_lines: + # Make sure ni is moving forward, not stopping at the same instrunction + self.assertEqual(curr_instr_lines.count(line), 1) + + # this test is under the assumption that within 10 instructions the function + # f should be called + commands = "si\n" * 10 + "c\n" + + stdout, stderr = self.run_pdb_module(script, commands) + curr_instr_lines = [line.strip() for line in stdout.splitlines()] + # Make sure si stepped into the function so the users can see the source + # code of the function + self.assertTrue(any("-> a = [1, 2, 3]" in line for line in curr_instr_lines)) + + class ChecklineTests(unittest.TestCase): def setUp(self): linecache.clearcache() # Pdb.checkline() uses linecache.getline() From 116ec5d4ea3665b3f524fc871766ebfa3f7b32d1 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 8 Apr 2023 16:18:15 -0700 Subject: [PATCH 07/10] Add docs for instruction commands --- Doc/library/pdb.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index d80c5eebbf27a7..ea2e5b614bf631 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -379,6 +379,13 @@ can be overridden by the local file. Execute the current line, stop at the first possible occasion (either in a function that is called or on the next line in the current function). +.. pdbcommand:: si | stepi + + Execute the current instruction, stop at the first possible occasion (either in a + function that is called or on the next instruction in the current function). + + .. versionadded:: 3.12 + .. pdbcommand:: n(ext) Continue execution until the next line in the current function is reached or @@ -387,6 +394,13 @@ can be overridden by the local file. executes called functions at (nearly) full speed, only stopping at the next line in the current function.) +.. pdbcommand:: ni | nexti + + Continue execution until the next instruction in the current function is reached or + it returns. + + .. versionadded:: 3.12 + .. pdbcommand:: unt(il) [lineno] Without argument, continue execution until the line with a number greater @@ -433,6 +447,12 @@ can be overridden by the local file. .. versionadded:: 3.2 The ``>>`` marker. +.. pdbcommand:: li | listi [first[, last]] + + Similar to :pdbcmd:`list`, but also display instructions with source code + + .. versionadded:: 3.12 + .. pdbcommand:: ll | longlist List all source code for the current function or frame. Interesting lines @@ -440,6 +460,12 @@ can be overridden by the local file. .. versionadded:: 3.2 +.. pdbcommand:: lli | longlisti + + Similar to :pdbcmd:`ll`, but also display instructions with source code + + .. versionadded:: 3.12 + .. pdbcommand:: a(rgs) Print the argument list of the current function. From 89b6899ec63bca01187c86750163543f435771ba Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 8 Apr 2023 23:20:35 +0000 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2023-04-08-23-20-34.gh-issue-103049.PY_2cD.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2023-04-08-23-20-34.gh-issue-103049.PY_2cD.rst diff --git a/Misc/NEWS.d/next/Library/2023-04-08-23-20-34.gh-issue-103049.PY_2cD.rst b/Misc/NEWS.d/next/Library/2023-04-08-23-20-34.gh-issue-103049.PY_2cD.rst new file mode 100644 index 00000000000000..f20de87713ba6f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-08-23-20-34.gh-issue-103049.PY_2cD.rst @@ -0,0 +1 @@ +Add instruction commands support for :mod:`pdb` From faabf32511df5f6f29e5d168023dea1ad51beb8d Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 9 Apr 2023 11:06:05 -0700 Subject: [PATCH 09/10] Fixed a typo in comment Co-authored-by: Artem Mukhin --- Lib/test/test_pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 458cef02ac3ca2..735092f6d7ab22 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -2433,7 +2433,7 @@ def f(): curr_instr_lines = [line.strip() for line in stdout.splitlines() if "-->" in line] self.assertEqual(len(curr_instr_lines), 3) for line in curr_instr_lines: - # Make sure ni is moving forward, not stopping at the same instrunction + # Make sure ni is moving forward, not stopping at the same instruction self.assertEqual(curr_instr_lines.count(line), 1) # this test is under the assumption that within 10 instructions the function From e258bf525448d8ba072dfc99c8b347427277174a Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 9 Apr 2023 11:20:55 -0700 Subject: [PATCH 10/10] Update some docs and comments --- Doc/library/pdb.rst | 15 +++++++++------ Lib/pdb.py | 23 ++++++++++++++--------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index ea2e5b614bf631..00caa97d212a2c 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -381,8 +381,9 @@ can be overridden by the local file. .. pdbcommand:: si | stepi - Execute the current instruction, stop at the first possible occasion (either in a - function that is called or on the next instruction in the current function). + Execute the current bytecode instruction, stop at the first possible occasion + (either in a function that is called or on the next instruction in the + current function). .. versionadded:: 3.12 @@ -396,8 +397,8 @@ can be overridden by the local file. .. pdbcommand:: ni | nexti - Continue execution until the next instruction in the current function is reached or - it returns. + Continue execution until the next bytecode instruction in the current function + is reached or it returns. .. versionadded:: 3.12 @@ -449,7 +450,8 @@ can be overridden by the local file. .. pdbcommand:: li | listi [first[, last]] - Similar to :pdbcmd:`list`, but also display instructions with source code + Similar to :pdbcmd:`list`, but also display bytecode instructions with + the source code .. versionadded:: 3.12 @@ -462,7 +464,8 @@ can be overridden by the local file. .. pdbcommand:: lli | longlisti - Similar to :pdbcmd:`ll`, but also display instructions with source code + Similar to :pdbcmd:`ll`, but also display bytecode instructions with + the source code .. versionadded:: 3.12 diff --git a/Lib/pdb.py b/Lib/pdb.py index d48a415ffa7f39..3ff9eb1dc9adb7 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1101,9 +1101,9 @@ def do_step(self, arg): def do_stepi(self, arg): """s(tep) - Execute the current instruction, stop at the first possible occasion - (either in a function that is called or in the current - function). + Execute the current bytecode instruction, stop at the first + possible occasion (either in a function that is called or in + the current function). """ self.set_stepi(self.curframe) return 1 @@ -1112,7 +1112,7 @@ def do_stepi(self, arg): def do_next(self, arg): """n(ext) Continue execution until the next line in the current function - is reached or it returns. + is reached or the current function returns. """ self.set_next(self.curframe) return 1 @@ -1120,8 +1120,8 @@ def do_next(self, arg): def do_nexti(self, arg): """n(ext) - Continue execution until the next line in the current function - is reached or it returns. + Continue execution until the next bytecode instruction in the + current function is reached or the current function returns. """ self.set_nexti(self.curframe) return 1 @@ -1384,7 +1384,8 @@ def do_list(self, arg): def do_listi(self, arg): """listi | li [first[, last] | .] - List source code for the current file with instructions. + List source code for the current file with bytecode + instructions. Without arguments, list 11 lines with their corresponding instructions around the current line or continue the @@ -1427,8 +1428,8 @@ def do_longlist(self, arg): def do_longlisti(self, arg): """longlisti | lli - List the whole source code with instructions for the current - function or frame. + List the whole source code with bytecode instructions for + the current function or frame. """ self._do_longlist(arg, True) do_lli = do_longlisti @@ -1473,6 +1474,10 @@ def _print_lines(self, lines, start, breaks=(), frame=None, instructions=None): s += '>>' self.message(s + '\t' + line.rstrip()) if instructions: + # For the current line of the source code, get all the + # instructions belong to it. We keep a single iterator + # `instructions` for all the instructions compiled from + # the source and try to only go through the iterator once while True: if inst.positions.lineno == lineno: current_inst = frame and frame.f_lasti == inst.offset pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy