Skip to content

Commit

Permalink
bt fixups: Sync with trepan3
Browse files Browse the repository at this point in the history
  • Loading branch information
rocky committed Apr 15, 2018
1 parent 9e91ea3 commit e7374d1
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 65 deletions.
14 changes: 6 additions & 8 deletions test/example/fib.py
Original file line number Diff line number Diff line change
@@ -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)))
54 changes: 46 additions & 8 deletions trepan/lib/deparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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
Expand All @@ -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))
64 changes: 52 additions & 12 deletions trepan/lib/stack.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2008-2010, 2013, 2015, 2017 Rocky Bernstein <rocky@gnu.org>
# Copyright (C) 2008-2010, 2013, 2015, 2017-2018 Rocky Bernstein <rocky@gnu.org>
#
# 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
Expand All @@ -14,13 +14,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" 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"
Expand All @@ -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'^<.+>')

Expand Down Expand Up @@ -176,28 +179,66 @@ 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 == '<module>':
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 == '<module>':
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)
else:
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
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion trepan/processor/cmdbreak.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions trepan/processor/cmdproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
60 changes: 50 additions & 10 deletions trepan/processor/command/backtrace.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from getopt import getopt, GetoptError

# Our local modules
from trepan.processor.command import base_cmd as Mbase_cmd
Expand All @@ -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')
Expand All @@ -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:
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion trepan/processor/command/info_subcmd/locals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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+]\]')

Expand Down

0 comments on commit e7374d1

Please sign in to comment.