diff --git a/test/example/fib.py b/test/example/fib.py index 5f7bfb61..39e8c0be 100644 --- a/test/example/fib.py +++ b/test/example/fib.py @@ -1,9 +1,7 @@ #!/usr/bin/env python - -def fib(x): - if x <= 1: - return 1 - return fib(x-1) + fib(x-2) - - -print("fib(2)= %d, fib(3) = %d, fib(4) = %d\n" % (fib(2), fib(3), fib(4))) +import sys +def fib(n): + if n <= 1: return 1 + return fib(n-1) + fib(n-2) +arg=int(sys.argv[1]) +print("fib({})={}".format(arg, fib(arg))) diff --git a/trepan/lib/deparse.py b/trepan/lib/deparse.py index 1a5c6c71..05ae8872 100644 --- a/trepan/lib/deparse.py +++ b/trepan/lib/deparse.py @@ -4,19 +4,27 @@ import sys, tempfile from StringIO import StringIO from hashlib import sha1 -from uncompyle6.semantics.linemap import deparse_code_with_map +from uncompyle6.semantics.linemap import code_deparse_with_map +from uncompyle6.semantics.fragments import ( + deparsed_find, code_deparse) import pyficache # FIXME remap filename to a short name. +deparse_cache = {} + def deparse_and_cache(co, errmsg_fn): # co = proc_obj.curframe.f_code out = StringIO() - try: - deparsed = deparse_code_with_map(co, out) - except: - errmsg_fn(str(sys.exc_info())) - errmsg_fn("error in deparsing code: %s" % co.co_filename) - return None, None + deparsed = deparse_cache.get(co, None) + if not deparsed: + try: + deparsed = code_deparse_with_map(co, out) + except: + errmsg_fn(str(sys.exc_info()[0])) + errmsg_fn("error in deparsing code: %s" % co.co_filename) + return None, None + + deparse_cache[co] = deparsed text = out.getvalue() linemap = [(line_no, deparsed.source_linemap[line_no]) @@ -38,6 +46,32 @@ def deparse_and_cache(co, errmsg_fn): linemap) return remapped_file, name_for_code +def deparse_offset(co, name, last_i, errmsg_fn): + deparsed = deparse_cache.get(co, None) + if not deparsed: + out = StringIO() + try: + # FIXME: cache co + deparsed = code_deparse(co, out) + from trepan.api import debug; debug() + except: + print(sys.exc_info()[1]) + if errmsg_fn: + errmsg_fn(sys.exc_info()[1]) + errmsg_fn("error in deparsing code") + deparse_cache[co] = deparsed + try: + nodeInfo = deparsed_find((name, last_i), deparsed, co) + except: + if errmsg_fn: + errmsg_fn(sys.exc_info()[1]) + errmsg_fn("error in deparsing code at offset %d" % last_i) + + if not nodeInfo: + nodeInfo = deparsed_find((name, last_i), deparsed, co) + return deparsed, nodeInfo + + # Demo it if __name__ == '__main__': import inspect @@ -50,6 +84,10 @@ def errmsg(msg_str): return curframe = inspect.currentframe() - # line_no = curframe.f_lineno + line_no = curframe.f_lineno mapped_name, name_for_code = deparse_and_cache(curframe.f_code, errmsg) print(pyficache.getline(mapped_name, 7)) + # mapped_name, name_for_code = deparse_offset(curframe.f_code, + # curframe.f_code.co_name, + # curframe.f_lasti, errmsg) + # print(pyficache.getline(mapped_name, 7)) diff --git a/trepan/lib/stack.py b/trepan/lib/stack.py index 6775bf1f..eb002603 100644 --- a/trepan/lib/stack.py +++ b/trepan/lib/stack.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2008-2010, 2013, 2015, 2017 Rocky Bernstein +# Copyright (C) 2008-2010, 2013, 2015, 2017-2018 Rocky Bernstein # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,13 +14,17 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ Functions for working with Python frames""" -import re, types + +import inspect, linecache, re from trepan.lib import bytecode as Mbytecode, printing as Mprint from trepan.lib import format as Mformat +from trepan.lib.deparse import deparse_offset +from trepan.lib import pp as Mpp from trepan.processor.cmdfns import deparse_fn format_token = Mformat.format_token +_with_local_varname = re.compile(r'_\[[0-9+]\]') def count_frames(frame, count_start=0): "Return a count of the number of frames" @@ -31,7 +35,6 @@ def count_frames(frame, count_start=0): return count import repr as Mrepr -import inspect _re_pseudo_file = re.compile(r'^<.+>') @@ -176,20 +179,58 @@ def get_call_function_name(frame): return None -def print_stack_entry(proc_obj, i_stack, color='plain'): +def print_stack_entry(proc_obj, i_stack, color='plain', opts={}): frame_lineno = proc_obj.stack[len(proc_obj.stack)-i_stack-1] frame, lineno = frame_lineno + intf = proc_obj.intf[-1] if frame is proc_obj.curframe: - proc_obj.intf[-1].msg_nocr(format_token(Mformat.Arrow, '->', - highlight=color)) + intf.msg_nocr(format_token(Mformat.Arrow, '->', + highlight=color)) else: - proc_obj.intf[-1].msg_nocr('##') - proc_obj.intf[-1].msg("%d %s" % + intf.msg_nocr('##') + intf.msg("%d %s" % (i_stack, format_stack_entry(proc_obj.debugger, frame_lineno, color=color))) + if opts.get('source', False): + filename = frame2file(proc_obj.core, frame) + line = linecache.getline(filename, lineno, frame.f_globals) + intf.msg(line) + pass + + if opts.get('deparse', False): + name = frame.f_code.co_name + deparsed, nodeInfo = deparse_offset(frame.f_code, name, frame.f_lasti, None) + if name == '': + name == 'module' + if nodeInfo: + extractInfo = deparsed.extract_node_info(nodeInfo) + intf.msg(extractInfo.selectedLine) + intf.msg(extractInfo.markerLine) + pass + if opts.get('full', False): + names = list(frame.f_locals.keys()) + for name in sorted(names): + if _with_local_varname.match(name): + val = frame.f_locals[name] + else: + val = proc_obj.getval(name, frame.f_locals) + pass + width = opts.get('width', 80) + Mpp.pp(val, width, intf.msg_nocr, intf.msg, + prefix='%s =' % name) + pass + + deparsed, nodeInfo = deparse_offset(frame.f_code, name, frame.f_lasti, None) + if name == '': + name == 'module' + if nodeInfo: + extractInfo = deparsed.extract_node_info(nodeInfo) + intf.msg(extractInfo.selectedLine) + intf.msg(extractInfo.markerLine) + pass -def print_stack_trace(proc_obj, count=None, color='plain'): +def print_stack_trace(proc_obj, count=None, color='plain', opts={}): "Print count entries of the stack trace" if count is None: n=len(proc_obj.stack) @@ -197,7 +238,7 @@ def print_stack_trace(proc_obj, count=None, color='plain'): n=min(len(proc_obj.stack), count) try: for i in range(n): - print_stack_entry(proc_obj, i, color=color) + print_stack_entry(proc_obj, i, color=color, opts=opts) except KeyboardInterrupt: pass return @@ -206,8 +247,7 @@ def print_stack_trace(proc_obj, count=None, color='plain'): def print_dict(s, obj, title): if hasattr(obj, "__dict__"): d=obj.__dict__ - if (isinstance(d, types.DictType) or - isinstance(d, types.DictProxyType)): + if isinstance(d, dict): keys = list(d.keys()) if len(keys) == 0: s += "\n No %s" % title diff --git a/trepan/processor/cmdbreak.py b/trepan/processor/cmdbreak.py index e3ae4173..7f446ed1 100644 --- a/trepan/processor/cmdbreak.py +++ b/trepan/processor/cmdbreak.py @@ -122,7 +122,8 @@ def parse_break_cmd(proc, args): # "break if True", # "break cmdproc.py:5", # "break set_break()", - "break cmdproc.setup()", + "break 4 if i == 5", + # "break cmdproc.setup()", ): args = cmd.split(' ') cmdproc.current_command = cmd diff --git a/trepan/processor/cmdproc.py b/trepan/processor/cmdproc.py index ba2e5941..010cbf8d 100644 --- a/trepan/processor/cmdproc.py +++ b/trepan/processor/cmdproc.py @@ -545,10 +545,11 @@ def get_int(self, arg, min_value=0, default=1, cmdname=None, pass return default - def getval(self, arg): + def getval(self, arg, locals=None): + if not locals: + locals = self.curframe.f_locals try: - return eval(arg, self.curframe.f_globals, - self.curframe.f_locals) + return eval(arg, self.curframe.f_globals, locals) except: t, v = sys.exc_info()[:2] if isinstance(t, str): diff --git a/trepan/processor/command/backtrace.py b/trepan/processor/command/backtrace.py index dc8ee593..cc94b766 100644 --- a/trepan/processor/command/backtrace.py +++ b/trepan/processor/command/backtrace.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2009, 2013, 2015, 2017 Rocky Bernstein +# Copyright (C) 2009, 2013, 2015, 2017-2018 Rocky Bernstein # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os +from getopt import getopt, GetoptError # Our local modules from trepan.processor.command import base_cmd as Mbase_cmd @@ -22,22 +23,36 @@ class BacktraceCommand(Mbase_cmd.DebuggerCommand): - """**backtrace** [*count*] + """**backtrace** [*opts*] [*count*] -Print a stack trace, with the most recent frame at the top. With a -positive number, print at most many entries. With a negative number -print the top entries minus that number. +Print backtrace of all stack frames, or innermost *count* frames. + +With a negative argument, print outermost -*count* frames. An arrow indicates the 'current frame'. The current frame determines the context used for many debugger commands such as expression evaluation or source-line listing. -Examples: +*opts* are: + + -d | --deparse - show deparsed call position + -s | --source - show source code line + -f | --full - locals of each frame + -h | --help - give this help + + backtrace # Print a full stack trace + backtrace 2 # Print only the top two entries + backtrace -1 # Print a stack trace except the initial (least recent) call. + backtrace -s # show source lines in listing + backtrace -d # show deparsed source lines in listing + backtrace -f # show with locals + backtrace -df # show with deparsed calls and locals + backtrace --deparse --full # same as above + +See also: --------- - backtrace # Print a full stack trace - backtrace 2 # Print only the top two entries - backtrace -1 # Print a stack trace except the initial (least recent) call. +`frame`, `locals`, `global`, `deparse`, `list`. """ aliases = ('bt', 'where') @@ -53,6 +68,30 @@ def complete(self, prefix): return Mframe.frame_complete(proc_obj, prefix, None) def run(self, args): + + try: + opts, args = getopt(args[1:], "hfds", + "help deparse full source".split()) + except GetoptError as err: + # print help information and exit: + print(str(err)) # will print something like "option -a not recognized" + return + + bt_opts = {'width': self.settings['width']} + for o, a in opts: + if o in ("-h", "--help"): + self.proc.commands['help'].run(['help', 'backtrace']) + return + elif o in ("-d", "--deparse"): + bt_opts['deparse'] = True + elif o in ("-f", "--full"): + bt_opts['full'] = True + elif o in ("-s", "--source"): + bt_opts['source'] = True + else: + self.errmsg("unhandled option '%s'" % o) + pass + if len(args) > 1: at_most = len(self.proc.stack) if at_most == 0: @@ -75,7 +114,8 @@ def run(self, args): self.errmsg("No stack.") return False Mstack.print_stack_trace(self.proc, count, - color=self.settings['highlight']) + color=self.settings['highlight'], + opts=bt_opts) return False pass diff --git a/trepan/processor/command/info_subcmd/locals.py b/trepan/processor/command/info_subcmd/locals.py index 604dbc88..7959a4e1 100644 --- a/trepan/processor/command/info_subcmd/locals.py +++ b/trepan/processor/command/info_subcmd/locals.py @@ -20,7 +20,8 @@ from trepan.lib import pp as Mpp from trepan.lib import complete as Mcomplete -# when the "with" statement is used we seem to get variables having names +# when the "with" statement is used, there +# can be get variables having names # _[1], _[2], etc. _with_local_varname = re.compile(r'_\[[0-9+]\]') diff --git a/trepan/processor/location.py b/trepan/processor/location.py index d444b463..02be6b67 100644 --- a/trepan/processor/location.py +++ b/trepan/processor/location.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2017 Rocky Bernstein +# Copyright (C) 2017-2018 Rocky Bernstein # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -20,7 +20,7 @@ from trepan.processor.parse.semantics import Location INVALID_LOCATION = None -def resolve_location(proc, location): +def resolve_location(proc, location, canonic=True): """Expand fields in Location namedtuple. If: '.': get fields from stack function/module: get fields from evaluation/introspection @@ -31,7 +31,7 @@ def resolve_location(proc, location): if not curframe: proc.errmsg("Don't have a stack to get location from") return INVALID_LOCATION - filename = Mstack.frame2file(proc.core, curframe, canonic=False) + filename = Mstack.frame2file(proc.core, curframe, canonic=canonic) lineno = inspect.getlineno(curframe) return Location(filename, lineno, False, None) @@ -106,13 +106,13 @@ def resolve_location(proc, location): % (lineno, filename, maxline)) return INVALID_LOCATION elif location.line_number: - filename = Mstack.frame2file(proc.core, curframe, canonic=False) + filename = Mstack.frame2file(proc.core, curframe, canonic=canonic) lineno = location.line_number is_address = location.is_address modfunc = None return Location(filename, lineno, is_address, modfunc) -def resolve_address_location(proc, location): +def resolve_address_location(proc, location, canonic=False): """Expand fields in Location namedtuple. If: '.': get fields from stack function/module: get fields from evaluation/introspection @@ -120,7 +120,7 @@ def resolve_address_location(proc, location): """ curframe = proc.curframe if location == '.': - filename = Mstack.frame2file(proc.core, curframe, canonic=False) + filename = Mstack.frame2file(proc.core, curframe, canonic=canonic) offset = curframe.f_lasti is_address = True return Location(filename, offset, False, None) diff --git a/trepan/processor/parse/parser.py b/trepan/processor/parse/parser.py index 7fcf5623..1625628a 100644 --- a/trepan/processor/parse/parser.py +++ b/trepan/processor/parse/parser.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Rocky Bernstein +# Copyright (c) 2017-2018 Rocky Bernstein """ Parsing for a trepan2/trepan3k debugger "breakpoint' or "list" command arguments @@ -54,14 +54,14 @@ def error(self, tokens, index): def nonterminal(self, nt, args): has_len = hasattr(args, '__len__') - collect = ('tokens',) - if nt in collect: - # - # Collect iterated thingies together. - # - rv = args[0] - for arg in args[1:]: - rv.append(arg) + # collect = ('tokens',) + # if nt in collect and len(args) > 1: + # # + # # Collect iterated thingies together. + # # + # rv = args[0] + # for arg in args[1:]: + # rv.append(arg) if (has_len and len(args) == 1 and hasattr(args[0], '__len__') and len(args[0]) == 1): @@ -161,6 +161,7 @@ def parse_location(start_symbol, text, out=sys.stdout, # 'errorstack': 'full', 'dups': False} parser = LocationParser(start_symbol, text, parser_debug) + # parser.check_grammar(frozenset(('bp_start', 'range_start', 'arange_start'))) return parser.parse(tokens) def parse_bp_location(*args, **kwargs): diff --git a/trepan/processor/parse/semantics.py b/trepan/processor/parse/semantics.py index 525f9c4e..634b1255 100644 --- a/trepan/processor/parse/semantics.py +++ b/trepan/processor/parse/semantics.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 by Rocky Bernstein +# Copyright (c) 2017-2018 by Rocky Bernstein from __future__ import print_function from trepan.processor.parse.parser import ( @@ -102,7 +102,7 @@ def n_location_if(self, node): else: assert False, 'location_if: Something is wrong; cannot find "if"' - condition = self.text[if_node.offset:] + condition = self.text[if_node.offset+len(if_node.value)+1:] # Pick out condition from string and location inside "IF" token self.result = BPLocation(location, condition) @@ -223,15 +223,15 @@ def default(self, node): IF FILENAME COLON COMMA SPACE DIRECTION""".split())): assert False, ("Something's wrong: you missed a rule for %s" % node.kind) - def traverse(self, node, ): + def traverse(self, node): return self.preorder(node) def build_bp_expr(string, show_tokens=False, show_ast=False, show_grammar=False): parser_debug = {'rules': False, 'transition': False, 'reduce': show_grammar, - 'errorstack': None, - 'context': True, 'dups': False + 'errorstack': None, 'dups': False + # 'context': True, 'dups': True } parsed = parse_bp_location(string, show_tokens=show_tokens, parser_debug=parser_debug) @@ -251,7 +251,7 @@ def build_range(string, show_tokens=False, show_ast=False, show_grammar=False): parser_debug = {'rules': False, 'transition': False, 'reduce': show_grammar, 'errorstack': None, - 'context': True, 'dups': False + 'context': False, 'dups': True } parsed = parse_range(string, show_tokens=show_tokens, parser_debug=parser_debug) @@ -268,7 +268,7 @@ def build_arange(string, show_tokens=False, show_ast=False, show_grammar=False): parser_debug = {'rules': False, 'transition': False, 'reduce': show_grammar, 'errorstack': None, - 'context': True, 'dups': False + 'context': True, 'dups': True } parsed = parse_arange(string, show_tokens=show_tokens, parser_debug=parser_debug)