1106 lines
37 KiB
Python
1106 lines
37 KiB
Python
# debugger.py
|
|
|
|
# A debugger for Pythonwin. Built from pdb.
|
|
|
|
# Mark Hammond (MHammond@skippinet.com.au) - Dec 94.
|
|
|
|
# usage:
|
|
# >>> import pywin.debugger
|
|
# >>> pywin.debugger.GetDebugger().run("command")
|
|
|
|
import bdb
|
|
import os
|
|
import pdb
|
|
import string
|
|
import sys
|
|
import traceback
|
|
import types
|
|
|
|
import commctrl
|
|
import pywin.docking.DockingBar
|
|
import win32api
|
|
import win32con
|
|
import win32ui
|
|
from pywin.framework import app, editor, interact, scriptutils
|
|
from pywin.framework.editor.color.coloreditor import MARKER_BREAKPOINT, MARKER_CURRENT
|
|
from pywin.mfc import afxres, dialog, object, window
|
|
from pywin.tools import browser, hierlist
|
|
|
|
# import win32traceutil
|
|
if win32ui.UNICODE:
|
|
LVN_ENDLABELEDIT = commctrl.LVN_ENDLABELEDITW
|
|
else:
|
|
LVN_ENDLABELEDIT = commctrl.LVN_ENDLABELEDITA
|
|
|
|
from .dbgcon import *
|
|
|
|
error = "pywin.debugger.error"
|
|
|
|
|
|
def SetInteractiveContext(globs, locs):
|
|
if interact.edit is not None and interact.edit.currentView is not None:
|
|
interact.edit.currentView.SetContext(globs, locs)
|
|
|
|
|
|
def _LineStateToMarker(ls):
|
|
if ls == LINESTATE_CURRENT:
|
|
return MARKER_CURRENT
|
|
# elif ls == LINESTATE_CALLSTACK:
|
|
# return MARKER_CALLSTACK
|
|
return MARKER_BREAKPOINT
|
|
|
|
|
|
class HierListItem(browser.HLIPythonObject):
|
|
pass
|
|
|
|
|
|
class HierFrameItem(HierListItem):
|
|
def __init__(self, frame, debugger):
|
|
HierListItem.__init__(self, frame, repr(frame))
|
|
self.debugger = debugger
|
|
|
|
def GetText(self):
|
|
name = self.myobject.f_code.co_name
|
|
if not name or name == "?":
|
|
# See if locals has a '__name__' (ie, a module)
|
|
if "__name__" in self.myobject.f_locals:
|
|
name = str(self.myobject.f_locals["__name__"]) + " module"
|
|
else:
|
|
name = "<Debugger Context>"
|
|
|
|
return "%s (%s:%d)" % (
|
|
name,
|
|
os.path.split(self.myobject.f_code.co_filename)[1],
|
|
self.myobject.f_lineno,
|
|
)
|
|
|
|
def GetBitmapColumn(self):
|
|
if self.debugger.curframe is self.myobject:
|
|
return 7
|
|
else:
|
|
return 8
|
|
|
|
def GetSubList(self):
|
|
ret = []
|
|
ret.append(HierFrameDict(self.myobject.f_locals, "Locals", 2))
|
|
ret.append(HierFrameDict(self.myobject.f_globals, "Globals", 1))
|
|
return ret
|
|
|
|
def IsExpandable(self):
|
|
return 1
|
|
|
|
def TakeDefaultAction(self):
|
|
# Set the default frame to be this frame.
|
|
self.debugger.set_cur_frame(self.myobject)
|
|
return 1
|
|
|
|
|
|
class HierFrameDict(browser.HLIDict):
|
|
def __init__(self, dict, name, bitmapColumn):
|
|
self.bitmapColumn = bitmapColumn
|
|
browser.HLIDict.__init__(self, dict, name)
|
|
|
|
def GetBitmapColumn(self):
|
|
return self.bitmapColumn
|
|
|
|
|
|
class NoStackAvailableItem(HierListItem):
|
|
def __init__(self, why):
|
|
HierListItem.__init__(self, None, why)
|
|
|
|
def IsExpandable(self):
|
|
return 0
|
|
|
|
def GetText(self):
|
|
return self.name
|
|
|
|
def GetBitmapColumn(self):
|
|
return 8
|
|
|
|
|
|
class HierStackRoot(HierListItem):
|
|
def __init__(self, debugger):
|
|
HierListItem.__init__(self, debugger, None)
|
|
self.last_stack = []
|
|
|
|
## def __del__(self):
|
|
## print "HierStackRoot dieing"
|
|
def GetSubList(self):
|
|
debugger = self.myobject
|
|
# print self.debugger.stack, self.debugger.curframe
|
|
ret = []
|
|
if debugger.debuggerState == DBGSTATE_BREAK:
|
|
stackUse = debugger.stack[:]
|
|
stackUse.reverse()
|
|
self.last_stack = []
|
|
for frame, lineno in stackUse:
|
|
self.last_stack.append((frame, lineno))
|
|
if (
|
|
frame is debugger.userbotframe
|
|
): # Dont bother showing frames below our bottom frame.
|
|
break
|
|
for frame, lineno in self.last_stack:
|
|
ret.append(HierFrameItem(frame, debugger))
|
|
## elif debugger.debuggerState==DBGSTATE_NOT_DEBUGGING:
|
|
## ret.append(NoStackAvailableItem('<nothing is being debugged>'))
|
|
## else:
|
|
## ret.append(NoStackAvailableItem('<stack not available while running>'))
|
|
return ret
|
|
|
|
def GetText(self):
|
|
return "root item"
|
|
|
|
def IsExpandable(self):
|
|
return 1
|
|
|
|
|
|
class HierListDebugger(hierlist.HierListWithItems):
|
|
"""Hier List of stack frames, breakpoints, whatever"""
|
|
|
|
def __init__(self):
|
|
hierlist.HierListWithItems.__init__(
|
|
self, None, win32ui.IDB_DEBUGGER_HIER, None, win32api.RGB(255, 0, 0)
|
|
)
|
|
|
|
def Setup(self, debugger):
|
|
root = HierStackRoot(debugger)
|
|
self.AcceptRoot(root)
|
|
|
|
|
|
# def Refresh(self):
|
|
# self.Setup()
|
|
|
|
|
|
class DebuggerWindow(window.Wnd):
|
|
def __init__(self, ob):
|
|
window.Wnd.__init__(self, ob)
|
|
self.debugger = None
|
|
|
|
def Init(self, debugger):
|
|
self.debugger = debugger
|
|
|
|
def GetDefRect(self):
|
|
defRect = app.LoadWindowSize("Debugger Windows\\" + self.title)
|
|
if defRect[2] - defRect[0] == 0:
|
|
defRect = 0, 0, 150, 150
|
|
return defRect
|
|
|
|
def OnDestroy(self, msg):
|
|
newSize = self.GetWindowPlacement()[4]
|
|
pywin.framework.app.SaveWindowSize("Debugger Windows\\" + self.title, newSize)
|
|
return window.Wnd.OnDestroy(self, msg)
|
|
|
|
def OnKeyDown(self, msg):
|
|
key = msg[2]
|
|
if key in (13, 27, 32):
|
|
return 1
|
|
if key in (46, 8): # delete/BS key
|
|
self.DeleteSelected()
|
|
return 0
|
|
view = scriptutils.GetActiveView()
|
|
try:
|
|
firer = view.bindings.fire_key_event
|
|
except AttributeError:
|
|
firer = None
|
|
if firer is not None:
|
|
return firer(msg)
|
|
else:
|
|
return 1
|
|
|
|
def DeleteSelected(self):
|
|
win32api.MessageBeep()
|
|
|
|
def EditSelected(self):
|
|
win32api.MessageBeep()
|
|
|
|
|
|
class DebuggerStackWindow(DebuggerWindow):
|
|
title = "Stack"
|
|
|
|
def __init__(self):
|
|
DebuggerWindow.__init__(self, win32ui.CreateTreeCtrl())
|
|
self.list = HierListDebugger()
|
|
self.listOK = 0
|
|
|
|
def SaveState(self):
|
|
self.list.DeleteAllItems()
|
|
self.listOK = 0
|
|
win32ui.WriteProfileVal(
|
|
"Debugger Windows\\" + self.title, "Visible", self.IsWindowVisible()
|
|
)
|
|
|
|
def CreateWindow(self, parent):
|
|
style = (
|
|
win32con.WS_CHILD
|
|
| win32con.WS_VISIBLE
|
|
| win32con.WS_BORDER
|
|
| commctrl.TVS_HASLINES
|
|
| commctrl.TVS_LINESATROOT
|
|
| commctrl.TVS_HASBUTTONS
|
|
)
|
|
self._obj_.CreateWindow(style, self.GetDefRect(), parent, win32ui.IDC_LIST1)
|
|
self.HookMessage(self.OnKeyDown, win32con.WM_KEYDOWN)
|
|
self.HookMessage(self.OnKeyDown, win32con.WM_SYSKEYDOWN)
|
|
self.list.HierInit(parent, self)
|
|
self.listOK = 0 # delayed setup
|
|
# self.list.Setup()
|
|
|
|
def RespondDebuggerState(self, state):
|
|
assert self.debugger is not None, "Init not called"
|
|
if not self.listOK:
|
|
self.listOK = 1
|
|
self.list.Setup(self.debugger)
|
|
else:
|
|
self.list.Refresh()
|
|
|
|
def RespondDebuggerData(self):
|
|
try:
|
|
handle = self.GetChildItem(0)
|
|
except win32ui.error:
|
|
return # No items
|
|
while 1:
|
|
item = self.list.ItemFromHandle(handle)
|
|
col = self.list.GetBitmapColumn(item)
|
|
selCol = self.list.GetSelectedBitmapColumn(item)
|
|
if selCol is None:
|
|
selCol = col
|
|
if self.list.GetItemImage(handle) != (col, selCol):
|
|
self.list.SetItemImage(handle, col, selCol)
|
|
try:
|
|
handle = self.GetNextSiblingItem(handle)
|
|
except win32ui.error:
|
|
break
|
|
|
|
|
|
class DebuggerListViewWindow(DebuggerWindow):
|
|
def __init__(self):
|
|
DebuggerWindow.__init__(self, win32ui.CreateListCtrl())
|
|
|
|
def CreateWindow(self, parent):
|
|
list = self
|
|
style = (
|
|
win32con.WS_CHILD
|
|
| win32con.WS_VISIBLE
|
|
| win32con.WS_BORDER
|
|
| commctrl.LVS_EDITLABELS
|
|
| commctrl.LVS_REPORT
|
|
)
|
|
self._obj_.CreateWindow(style, self.GetDefRect(), parent, win32ui.IDC_LIST1)
|
|
self.HookMessage(self.OnKeyDown, win32con.WM_KEYDOWN)
|
|
self.HookMessage(self.OnKeyDown, win32con.WM_SYSKEYDOWN)
|
|
list = self
|
|
title, width = self.columns[0]
|
|
itemDetails = (commctrl.LVCFMT_LEFT, width, title, 0)
|
|
list.InsertColumn(0, itemDetails)
|
|
col = 1
|
|
for title, width in self.columns[1:]:
|
|
col = col + 1
|
|
itemDetails = (commctrl.LVCFMT_LEFT, width, title, 0)
|
|
list.InsertColumn(col, itemDetails)
|
|
parent.HookNotify(self.OnListEndLabelEdit, LVN_ENDLABELEDIT)
|
|
parent.HookNotify(self.OnItemRightClick, commctrl.NM_RCLICK)
|
|
parent.HookNotify(self.OnItemDoubleClick, commctrl.NM_DBLCLK)
|
|
|
|
def RespondDebuggerData(self):
|
|
pass
|
|
|
|
def RespondDebuggerState(self, state):
|
|
pass
|
|
|
|
def EditSelected(self):
|
|
try:
|
|
sel = self.GetNextItem(-1, commctrl.LVNI_SELECTED)
|
|
except win32ui.error:
|
|
return
|
|
self.EditLabel(sel)
|
|
|
|
def OnKeyDown(self, msg):
|
|
key = msg[2]
|
|
# If someone starts typing, they probably are trying to edit the text!
|
|
if chr(key) in string.ascii_uppercase:
|
|
self.EditSelected()
|
|
return 0
|
|
return DebuggerWindow.OnKeyDown(self, msg)
|
|
|
|
def OnItemDoubleClick(self, notify_data, extra):
|
|
self.EditSelected()
|
|
|
|
def OnItemRightClick(self, notify_data, extra):
|
|
# First select the item we right-clicked on.
|
|
pt = self.ScreenToClient(win32api.GetCursorPos())
|
|
flags, hItem, subitem = self.HitTest(pt)
|
|
if hItem == -1 or commctrl.TVHT_ONITEM & flags == 0:
|
|
return None
|
|
self.SetItemState(hItem, commctrl.LVIS_SELECTED, commctrl.LVIS_SELECTED)
|
|
|
|
menu = win32ui.CreatePopupMenu()
|
|
menu.AppendMenu(win32con.MF_STRING | win32con.MF_ENABLED, 1000, "Edit item")
|
|
menu.AppendMenu(win32con.MF_STRING | win32con.MF_ENABLED, 1001, "Delete item")
|
|
dockbar = self.GetParent()
|
|
if dockbar.IsFloating():
|
|
hook_parent = win32ui.GetMainFrame()
|
|
else:
|
|
hook_parent = self.GetParentFrame()
|
|
hook_parent.HookCommand(self.OnEditItem, 1000)
|
|
hook_parent.HookCommand(self.OnDeleteItem, 1001)
|
|
menu.TrackPopupMenu(win32api.GetCursorPos()) # track at mouse position.
|
|
return None
|
|
|
|
def OnDeleteItem(self, command, code):
|
|
self.DeleteSelected()
|
|
|
|
def OnEditItem(self, command, code):
|
|
self.EditSelected()
|
|
|
|
|
|
class DebuggerBreakpointsWindow(DebuggerListViewWindow):
|
|
title = "Breakpoints"
|
|
columns = [("Condition", 70), ("Location", 1024)]
|
|
|
|
def SaveState(self):
|
|
items = []
|
|
for i in range(self.GetItemCount()):
|
|
items.append(self.GetItemText(i, 0))
|
|
items.append(self.GetItemText(i, 1))
|
|
win32ui.WriteProfileVal(
|
|
"Debugger Windows\\" + self.title, "BreakpointList", "\t".join(items)
|
|
)
|
|
win32ui.WriteProfileVal(
|
|
"Debugger Windows\\" + self.title, "Visible", self.IsWindowVisible()
|
|
)
|
|
return 1
|
|
|
|
def OnListEndLabelEdit(self, std, extra):
|
|
item = extra[0]
|
|
text = item[4]
|
|
if text is None:
|
|
return
|
|
|
|
item_id = self.GetItem(item[0])[6]
|
|
|
|
from bdb import Breakpoint
|
|
|
|
for bplist in Breakpoint.bplist.values():
|
|
for bp in bplist:
|
|
if id(bp) == item_id:
|
|
if text.strip().lower() == "none":
|
|
text = None
|
|
bp.cond = text
|
|
break
|
|
self.RespondDebuggerData()
|
|
|
|
def DeleteSelected(self):
|
|
try:
|
|
num = self.GetNextItem(-1, commctrl.LVNI_SELECTED)
|
|
item_id = self.GetItem(num)[6]
|
|
from bdb import Breakpoint
|
|
|
|
for bplist in list(Breakpoint.bplist.values()):
|
|
for bp in bplist:
|
|
if id(bp) == item_id:
|
|
self.debugger.clear_break(bp.file, bp.line)
|
|
break
|
|
except win32ui.error:
|
|
win32api.MessageBeep()
|
|
self.RespondDebuggerData()
|
|
|
|
def RespondDebuggerData(self):
|
|
l = self
|
|
l.DeleteAllItems()
|
|
index = -1
|
|
from bdb import Breakpoint
|
|
|
|
for bplist in Breakpoint.bplist.values():
|
|
for bp in bplist:
|
|
baseName = os.path.split(bp.file)[1]
|
|
cond = bp.cond
|
|
item = index + 1, 0, 0, 0, str(cond), 0, id(bp)
|
|
index = l.InsertItem(item)
|
|
l.SetItemText(index, 1, "%s: %s" % (baseName, bp.line))
|
|
|
|
|
|
class DebuggerWatchWindow(DebuggerListViewWindow):
|
|
title = "Watch"
|
|
columns = [("Expression", 70), ("Value", 1024)]
|
|
|
|
def CreateWindow(self, parent):
|
|
DebuggerListViewWindow.CreateWindow(self, parent)
|
|
items = win32ui.GetProfileVal(
|
|
"Debugger Windows\\" + self.title, "Items", ""
|
|
).split("\t")
|
|
index = -1
|
|
for item in items:
|
|
if item:
|
|
index = self.InsertItem(index + 1, item)
|
|
self.InsertItem(index + 1, "<New Item>")
|
|
|
|
def SaveState(self):
|
|
items = []
|
|
for i in range(self.GetItemCount() - 1):
|
|
items.append(self.GetItemText(i, 0))
|
|
win32ui.WriteProfileVal(
|
|
"Debugger Windows\\" + self.title, "Items", "\t".join(items)
|
|
)
|
|
win32ui.WriteProfileVal(
|
|
"Debugger Windows\\" + self.title, "Visible", self.IsWindowVisible()
|
|
)
|
|
return 1
|
|
|
|
def OnListEndLabelEdit(self, std, extra):
|
|
item = extra[0]
|
|
itemno = item[0]
|
|
text = item[4]
|
|
if text is None:
|
|
return
|
|
self.SetItemText(itemno, 0, text)
|
|
if itemno == self.GetItemCount() - 1:
|
|
self.InsertItem(itemno + 1, "<New Item>")
|
|
self.RespondDebuggerState(self.debugger.debuggerState)
|
|
|
|
def DeleteSelected(self):
|
|
try:
|
|
num = self.GetNextItem(-1, commctrl.LVNI_SELECTED)
|
|
if num < self.GetItemCount() - 1: # We cant delete the last
|
|
self.DeleteItem(num)
|
|
except win32ui.error:
|
|
win32api.MessageBeep()
|
|
|
|
def RespondDebuggerState(self, state):
|
|
globs = locs = None
|
|
if state == DBGSTATE_BREAK:
|
|
if self.debugger.curframe:
|
|
globs = self.debugger.curframe.f_globals
|
|
locs = self.debugger.curframe.f_locals
|
|
elif state == DBGSTATE_NOT_DEBUGGING:
|
|
import __main__
|
|
|
|
globs = locs = __main__.__dict__
|
|
for i in range(self.GetItemCount() - 1):
|
|
text = self.GetItemText(i, 0)
|
|
if globs is None:
|
|
val = ""
|
|
else:
|
|
try:
|
|
val = repr(eval(text, globs, locs))
|
|
except SyntaxError:
|
|
val = "Syntax Error"
|
|
except:
|
|
t, v, tb = sys.exc_info()
|
|
val = traceback.format_exception_only(t, v)[0].strip()
|
|
tb = None # prevent a cycle.
|
|
self.SetItemText(i, 1, val)
|
|
|
|
|
|
def CreateDebuggerDialog(parent, klass):
|
|
control = klass()
|
|
control.CreateWindow(parent)
|
|
return control
|
|
|
|
|
|
DebuggerDialogInfos = (
|
|
(0xE810, DebuggerStackWindow, None),
|
|
(0xE811, DebuggerBreakpointsWindow, (10, 10)),
|
|
(0xE812, DebuggerWatchWindow, None),
|
|
)
|
|
|
|
|
|
# Prepare all the "control bars" for this package.
|
|
# If control bars are not all loaded when the toolbar-state functions are
|
|
# called, things go horribly wrong.
|
|
def PrepareControlBars(frame):
|
|
style = (
|
|
win32con.WS_CHILD
|
|
| afxres.CBRS_SIZE_DYNAMIC
|
|
| afxres.CBRS_TOP
|
|
| afxres.CBRS_TOOLTIPS
|
|
| afxres.CBRS_FLYBY
|
|
)
|
|
tbd = win32ui.CreateToolBar(frame, style, win32ui.ID_VIEW_TOOLBAR_DBG)
|
|
tbd.ModifyStyle(0, commctrl.TBSTYLE_FLAT)
|
|
tbd.LoadToolBar(win32ui.IDR_DEBUGGER)
|
|
tbd.EnableDocking(afxres.CBRS_ALIGN_ANY)
|
|
tbd.SetWindowText("Debugger")
|
|
frame.DockControlBar(tbd)
|
|
|
|
# and the other windows.
|
|
for id, klass, float in DebuggerDialogInfos:
|
|
try:
|
|
frame.GetControlBar(id)
|
|
exists = 1
|
|
except win32ui.error:
|
|
exists = 0
|
|
if exists:
|
|
continue
|
|
bar = pywin.docking.DockingBar.DockingBar()
|
|
style = win32con.WS_CHILD | afxres.CBRS_LEFT # don't create visible.
|
|
bar.CreateWindow(
|
|
frame,
|
|
CreateDebuggerDialog,
|
|
klass.title,
|
|
id,
|
|
style,
|
|
childCreatorArgs=(klass,),
|
|
)
|
|
bar.SetBarStyle(
|
|
bar.GetBarStyle()
|
|
| afxres.CBRS_TOOLTIPS
|
|
| afxres.CBRS_FLYBY
|
|
| afxres.CBRS_SIZE_DYNAMIC
|
|
)
|
|
bar.EnableDocking(afxres.CBRS_ALIGN_ANY)
|
|
if float is None:
|
|
frame.DockControlBar(bar)
|
|
else:
|
|
frame.FloatControlBar(bar, float, afxres.CBRS_ALIGN_ANY)
|
|
|
|
## frame.ShowControlBar(bar, 0, 1)
|
|
|
|
|
|
SKIP_NONE = 0
|
|
SKIP_STEP = 1
|
|
SKIP_RUN = 2
|
|
|
|
debugger_parent = pdb.Pdb
|
|
|
|
|
|
class Debugger(debugger_parent):
|
|
def __init__(self):
|
|
self.inited = 0
|
|
self.skipBotFrame = SKIP_NONE
|
|
self.userbotframe = None
|
|
self.frameShutdown = 0
|
|
self.pumping = 0
|
|
self.debuggerState = DBGSTATE_NOT_DEBUGGING # Assume so, anyway.
|
|
self.shownLineCurrent = None # The last filename I highlighted.
|
|
self.shownLineCallstack = None # The last filename I highlighted.
|
|
self.last_cmd_debugged = ""
|
|
self.abortClosed = 0
|
|
self.isInitialBreakpoint = 0
|
|
debugger_parent.__init__(self)
|
|
|
|
# See if any break-points have been set in the editor
|
|
for doc in editor.editorTemplate.GetDocumentList():
|
|
lineNo = -1
|
|
while 1:
|
|
lineNo = doc.MarkerGetNext(lineNo + 1, MARKER_BREAKPOINT)
|
|
if lineNo <= 0:
|
|
break
|
|
self.set_break(doc.GetPathName(), lineNo)
|
|
|
|
self.reset()
|
|
self.inForcedGUI = win32ui.GetApp().IsInproc()
|
|
self.options = LoadDebuggerOptions()
|
|
self.bAtException = self.bAtPostMortem = 0
|
|
|
|
def __del__(self):
|
|
self.close()
|
|
|
|
def close(self, frameShutdown=0):
|
|
# abortClose indicates if we have total shutdown
|
|
# (ie, main window is dieing)
|
|
if self.pumping:
|
|
# Can stop pump here, as it only posts a message, and
|
|
# returns immediately.
|
|
if not self.StopDebuggerPump(): # User cancelled close.
|
|
return 0
|
|
# NOTE - from this point on the close can not be
|
|
# stopped - the WM_QUIT message is already in the queue.
|
|
self.frameShutdown = frameShutdown
|
|
if not self.inited:
|
|
return 1
|
|
self.inited = 0
|
|
|
|
SetInteractiveContext(None, None)
|
|
|
|
frame = win32ui.GetMainFrame()
|
|
# Hide the debuger toolbars (as they wont normally form part of the main toolbar state.
|
|
for id, klass, float in DebuggerDialogInfos:
|
|
try:
|
|
tb = frame.GetControlBar(id)
|
|
if tb.dialog is not None: # We may never have actually been shown.
|
|
tb.dialog.SaveState()
|
|
frame.ShowControlBar(tb, 0, 1)
|
|
except win32ui.error:
|
|
pass
|
|
|
|
self._UnshowCurrentLine()
|
|
self.set_quit()
|
|
return 1
|
|
|
|
def StopDebuggerPump(self):
|
|
assert self.pumping, "Can't stop the debugger pump if Im not pumping!"
|
|
# After stopping a pump, I may never return.
|
|
if self.GUIAboutToFinishInteract():
|
|
self.pumping = 0
|
|
win32ui.StopDebuggerPump() # Posts a message, so we do return.
|
|
return 1
|
|
return 0
|
|
|
|
def get_option(self, option):
|
|
"""Public interface into debugger options"""
|
|
try:
|
|
return self.options[option]
|
|
except KeyError:
|
|
raise error("Option %s is not a valid option" % option)
|
|
|
|
def prep_run(self, cmd):
|
|
pass
|
|
|
|
def done_run(self, cmd=None):
|
|
self.RespondDebuggerState(DBGSTATE_NOT_DEBUGGING)
|
|
self.close()
|
|
|
|
def canonic(self, fname):
|
|
return os.path.abspath(fname).lower()
|
|
|
|
def reset(self):
|
|
debugger_parent.reset(self)
|
|
self.userbotframe = None
|
|
self.UpdateAllLineStates()
|
|
self._UnshowCurrentLine()
|
|
|
|
def setup(self, f, t):
|
|
debugger_parent.setup(self, f, t)
|
|
self.bAtException = t is not None
|
|
|
|
def set_break(self, filename, lineno, temporary=0, cond=None):
|
|
filename = self.canonic(filename)
|
|
self.SetLineState(filename, lineno, LINESTATE_BREAKPOINT)
|
|
return debugger_parent.set_break(self, filename, lineno, temporary, cond)
|
|
|
|
def clear_break(self, filename, lineno):
|
|
filename = self.canonic(filename)
|
|
self.ResetLineState(filename, lineno, LINESTATE_BREAKPOINT)
|
|
return debugger_parent.clear_break(self, filename, lineno)
|
|
|
|
def cmdloop(self):
|
|
if self.frameShutdown:
|
|
return # App in the process of closing - never break in!
|
|
self.GUIAboutToBreak()
|
|
|
|
def print_stack_entry(self, frame):
|
|
# We dont want a stack printed - our GUI is better :-)
|
|
pass
|
|
|
|
def user_return(self, frame, return_value):
|
|
# Same as parent, just no "print"
|
|
# This function is called when a return trap is set here
|
|
frame.f_locals["__return__"] = return_value
|
|
self.interaction(frame, None)
|
|
|
|
def user_call(self, frame, args):
|
|
# base class has an annoying 'print' that adds no value to us...
|
|
if self.stop_here(frame):
|
|
self.interaction(frame, None)
|
|
|
|
def user_exception(self, frame, exc_info):
|
|
# This function is called if an exception occurs,
|
|
# but only if we are to stop at or just below this level
|
|
(exc_type, exc_value, exc_traceback) = exc_info
|
|
if self.get_option(OPT_STOP_EXCEPTIONS):
|
|
frame.f_locals["__exception__"] = exc_type, exc_value
|
|
print("Unhandled exception while debugging...")
|
|
# on both py2k and py3k, we may be called with exc_value
|
|
# being the args to the exception, or it may already be
|
|
# instantiated (IOW, PyErr_Normalize() hasn't been
|
|
# called on the args). In py2k this is fine, but in
|
|
# py3k, traceback.print_exception fails. So on py3k
|
|
# we instantiate an exception instance to print.
|
|
if sys.version_info > (3,) and not isinstance(exc_value, BaseException):
|
|
# they are args - may be a single item or already a tuple
|
|
if not isinstance(exc_value, tuple):
|
|
exc_value = (exc_value,)
|
|
exc_value = exc_type(*exc_value)
|
|
|
|
traceback.print_exception(exc_type, exc_value, exc_traceback)
|
|
self.interaction(frame, exc_traceback)
|
|
|
|
def user_line(self, frame):
|
|
if frame.f_lineno == 0:
|
|
return
|
|
debugger_parent.user_line(self, frame)
|
|
|
|
def stop_here(self, frame):
|
|
if self.isInitialBreakpoint:
|
|
self.isInitialBreakpoint = 0
|
|
self.set_continue()
|
|
return 0
|
|
if frame is self.botframe and self.skipBotFrame == SKIP_RUN:
|
|
self.set_continue()
|
|
return 0
|
|
if frame is self.botframe and self.skipBotFrame == SKIP_STEP:
|
|
self.set_step()
|
|
return 0
|
|
return debugger_parent.stop_here(self, frame)
|
|
|
|
def run(self, cmd, globals=None, locals=None, start_stepping=1):
|
|
if not isinstance(cmd, (str, types.CodeType)):
|
|
raise TypeError("Only strings can be run")
|
|
self.last_cmd_debugged = cmd
|
|
if start_stepping:
|
|
self.isInitialBreakpoint = 0
|
|
else:
|
|
self.isInitialBreakpoint = 1
|
|
try:
|
|
if globals is None:
|
|
import __main__
|
|
|
|
globals = __main__.__dict__
|
|
if locals is None:
|
|
locals = globals
|
|
self.reset()
|
|
self.prep_run(cmd)
|
|
sys.settrace(self.trace_dispatch)
|
|
if type(cmd) != types.CodeType:
|
|
cmd = cmd + "\n"
|
|
try:
|
|
try:
|
|
if start_stepping:
|
|
self.skipBotFrame = SKIP_STEP
|
|
else:
|
|
self.skipBotFrame = SKIP_RUN
|
|
exec(cmd, globals, locals)
|
|
except bdb.BdbQuit:
|
|
pass
|
|
finally:
|
|
self.skipBotFrame = SKIP_NONE
|
|
self.quitting = 1
|
|
sys.settrace(None)
|
|
|
|
finally:
|
|
self.done_run(cmd)
|
|
|
|
def runeval(self, expr, globals=None, locals=None):
|
|
self.prep_run(expr)
|
|
try:
|
|
debugger_parent.runeval(self, expr, globals, locals)
|
|
finally:
|
|
self.done_run(expr)
|
|
|
|
def runexec(self, what, globs=None, locs=None):
|
|
self.reset()
|
|
sys.settrace(self.trace_dispatch)
|
|
try:
|
|
try:
|
|
exec(what, globs, locs)
|
|
except bdb.BdbQuit:
|
|
pass
|
|
finally:
|
|
self.quitting = 1
|
|
sys.settrace(None)
|
|
|
|
def do_set_step(self):
|
|
if self.GUIAboutToRun():
|
|
self.set_step()
|
|
|
|
def do_set_next(self):
|
|
if self.GUIAboutToRun():
|
|
self.set_next(self.curframe)
|
|
|
|
def do_set_return(self):
|
|
if self.GUIAboutToRun():
|
|
self.set_return(self.curframe)
|
|
|
|
def do_set_continue(self):
|
|
if self.GUIAboutToRun():
|
|
self.set_continue()
|
|
|
|
def set_quit(self):
|
|
ok = 1
|
|
if self.pumping:
|
|
ok = self.StopDebuggerPump()
|
|
if ok:
|
|
debugger_parent.set_quit(self)
|
|
|
|
def _dump_frame_(self, frame, name=None):
|
|
if name is None:
|
|
name = ""
|
|
if frame:
|
|
if frame.f_code and frame.f_code.co_filename:
|
|
fname = os.path.split(frame.f_code.co_filename)[1]
|
|
else:
|
|
fname = "??"
|
|
print(repr(name), fname, frame.f_lineno, frame)
|
|
else:
|
|
print(repr(name), "None")
|
|
|
|
def set_trace(self):
|
|
# Start debugging from _2_ levels up!
|
|
try:
|
|
1 + ""
|
|
except:
|
|
frame = sys.exc_info()[2].tb_frame.f_back.f_back
|
|
self.reset()
|
|
self.userbotframe = None
|
|
while frame:
|
|
# scriptutils.py creates a local variable with name
|
|
# '_debugger_stop_frame_', and we dont go past it
|
|
# (everything above this is Pythonwin framework code)
|
|
if "_debugger_stop_frame_" in frame.f_locals:
|
|
self.userbotframe = frame
|
|
break
|
|
|
|
frame.f_trace = self.trace_dispatch
|
|
self.botframe = frame
|
|
frame = frame.f_back
|
|
self.set_step()
|
|
sys.settrace(self.trace_dispatch)
|
|
|
|
def set_cur_frame(self, frame):
|
|
# Sets the "current" frame - ie, the frame with focus. This is the
|
|
# frame on which "step out" etc actions are taken.
|
|
# This may or may not be the top of the stack.
|
|
assert frame is not None, "You must pass a valid frame"
|
|
self.curframe = frame
|
|
for f, index in self.stack:
|
|
if f is frame:
|
|
self.curindex = index
|
|
break
|
|
else:
|
|
assert 0, "Can't find the frame in the stack."
|
|
SetInteractiveContext(frame.f_globals, frame.f_locals)
|
|
self.GUIRespondDebuggerData()
|
|
self.ShowCurrentLine()
|
|
|
|
def IsBreak(self):
|
|
return self.debuggerState == DBGSTATE_BREAK
|
|
|
|
def IsDebugging(self):
|
|
return self.debuggerState != DBGSTATE_NOT_DEBUGGING
|
|
|
|
def RespondDebuggerState(self, state):
|
|
if state == self.debuggerState:
|
|
return
|
|
if state == DBGSTATE_NOT_DEBUGGING: # Debugger exists, but not doing anything
|
|
title = ""
|
|
elif state == DBGSTATE_RUNNING: # Code is running under the debugger.
|
|
title = " - running"
|
|
elif state == DBGSTATE_BREAK: # We are at a breakpoint or stepping or whatever.
|
|
if self.bAtException:
|
|
if self.bAtPostMortem:
|
|
title = " - post mortem exception"
|
|
else:
|
|
title = " - exception"
|
|
else:
|
|
title = " - break"
|
|
else:
|
|
raise error("Invalid debugger state passed!")
|
|
win32ui.GetMainFrame().SetWindowText(
|
|
win32ui.LoadString(win32ui.IDR_MAINFRAME) + title
|
|
)
|
|
if self.debuggerState == DBGSTATE_QUITTING and state != DBGSTATE_NOT_DEBUGGING:
|
|
print("Ignoring state change cos Im trying to stop!", state)
|
|
return
|
|
self.debuggerState = state
|
|
try:
|
|
frame = win32ui.GetMainFrame()
|
|
except win32ui.error:
|
|
frame = None
|
|
if frame is not None:
|
|
for id, klass, float in DebuggerDialogInfos:
|
|
cb = win32ui.GetMainFrame().GetControlBar(id).dialog
|
|
cb.RespondDebuggerState(state)
|
|
# Tell each open editor window about the state transition
|
|
for doc in editor.editorTemplate.GetDocumentList():
|
|
doc.OnDebuggerStateChange(state)
|
|
self.ShowCurrentLine()
|
|
|
|
#
|
|
# GUI debugger interface.
|
|
#
|
|
def GUICheckInit(self):
|
|
if self.inited:
|
|
return
|
|
self.inited = 1
|
|
frame = win32ui.GetMainFrame()
|
|
|
|
# Ensure the debugger windows are attached to the debugger.
|
|
for id, klass, float in DebuggerDialogInfos:
|
|
w = frame.GetControlBar(id)
|
|
w.dialog.Init(self)
|
|
# Show toolbar if it was visible during last debug session
|
|
# This would be better done using a CDockState, but that class is not wrapped yet
|
|
if win32ui.GetProfileVal(
|
|
"Debugger Windows\\" + w.dialog.title, "Visible", 0
|
|
):
|
|
frame.ShowControlBar(w, 1, 1)
|
|
|
|
# ALWAYS show debugging toolbar, regardless of saved state
|
|
tb = frame.GetControlBar(win32ui.ID_VIEW_TOOLBAR_DBG)
|
|
frame.ShowControlBar(tb, 1, 1)
|
|
self.GUIRespondDebuggerData()
|
|
|
|
# frame.RecalcLayout()
|
|
|
|
def GetDebuggerBar(self, barName):
|
|
frame = win32ui.GetMainFrame()
|
|
for id, klass, float in DebuggerDialogInfos:
|
|
if klass.title == barName:
|
|
return frame.GetControlBar(id)
|
|
assert 0, "Can't find a bar of that name!"
|
|
|
|
def GUIRespondDebuggerData(self):
|
|
if not self.inited: # GUI not inited - no toolbars etc.
|
|
return
|
|
|
|
for id, klass, float in DebuggerDialogInfos:
|
|
cb = win32ui.GetMainFrame().GetControlBar(id).dialog
|
|
cb.RespondDebuggerData()
|
|
|
|
def GUIAboutToRun(self):
|
|
if not self.StopDebuggerPump():
|
|
return 0
|
|
self._UnshowCurrentLine()
|
|
self.RespondDebuggerState(DBGSTATE_RUNNING)
|
|
SetInteractiveContext(None, None)
|
|
return 1
|
|
|
|
def GUIAboutToBreak(self):
|
|
"Called as the GUI debugger is about to get context, and take control of the running program."
|
|
self.GUICheckInit()
|
|
self.RespondDebuggerState(DBGSTATE_BREAK)
|
|
self.GUIAboutToInteract()
|
|
if self.pumping:
|
|
print("!!! Already pumping - outa here")
|
|
return
|
|
self.pumping = 1
|
|
win32ui.StartDebuggerPump() # NOTE - This will NOT return until the user is finished interacting
|
|
assert not self.pumping, "Should not be pumping once the pump has finished"
|
|
if self.frameShutdown: # User shut down app while debugging
|
|
win32ui.GetMainFrame().PostMessage(win32con.WM_CLOSE)
|
|
|
|
def GUIAboutToInteract(self):
|
|
"Called as the GUI is about to perform any interaction with the user"
|
|
frame = win32ui.GetMainFrame()
|
|
# Remember the enabled state of our main frame
|
|
# may be disabled primarily if a modal dialog is displayed.
|
|
# Only get at enabled via GetWindowLong.
|
|
self.bFrameEnabled = frame.IsWindowEnabled()
|
|
self.oldForeground = None
|
|
fw = win32ui.GetForegroundWindow()
|
|
if fw is not frame:
|
|
self.oldForeground = fw
|
|
# fw.EnableWindow(0) Leave enabled for now?
|
|
self.oldFrameEnableState = frame.IsWindowEnabled()
|
|
frame.EnableWindow(1)
|
|
if self.inForcedGUI and not frame.IsWindowVisible():
|
|
frame.ShowWindow(win32con.SW_SHOW)
|
|
frame.UpdateWindow()
|
|
if self.curframe:
|
|
SetInteractiveContext(self.curframe.f_globals, self.curframe.f_locals)
|
|
else:
|
|
SetInteractiveContext(None, None)
|
|
self.GUIRespondDebuggerData()
|
|
|
|
def GUIAboutToFinishInteract(self):
|
|
"""Called as the GUI is about to finish any interaction with the user
|
|
Returns non zero if we are allowed to stop interacting"""
|
|
if self.oldForeground is not None:
|
|
try:
|
|
win32ui.GetMainFrame().EnableWindow(self.oldFrameEnableState)
|
|
self.oldForeground.EnableWindow(1)
|
|
except win32ui.error:
|
|
# old window may be dead.
|
|
pass
|
|
# self.oldForeground.SetForegroundWindow() - fails??
|
|
if not self.inForcedGUI:
|
|
return 1 # Never a problem, and nothing else to do.
|
|
# If we are running a forced GUI, we may never get an opportunity
|
|
# to interact again. Therefore we perform a "SaveAll", to makesure that
|
|
# any documents are saved before leaving.
|
|
for template in win32ui.GetApp().GetDocTemplateList():
|
|
for doc in template.GetDocumentList():
|
|
if not doc.SaveModified():
|
|
return 0
|
|
# All documents saved - now hide the app and debugger.
|
|
if self.get_option(OPT_HIDE):
|
|
frame = win32ui.GetMainFrame()
|
|
frame.ShowWindow(win32con.SW_HIDE)
|
|
return 1
|
|
|
|
#
|
|
# Pythonwin interface - all stuff to do with showing source files,
|
|
# changing line states etc.
|
|
#
|
|
def ShowLineState(self, fileName, lineNo, lineState):
|
|
# Set the state of a line, open if not already
|
|
self.ShowLineNo(fileName, lineNo)
|
|
self.SetLineState(fileName, lineNo, lineState)
|
|
|
|
def SetLineState(self, fileName, lineNo, lineState):
|
|
# Set the state of a line if the document is open.
|
|
doc = editor.editorTemplate.FindOpenDocument(fileName)
|
|
if doc is not None:
|
|
marker = _LineStateToMarker(lineState)
|
|
if not doc.MarkerCheck(lineNo, marker):
|
|
doc.MarkerAdd(lineNo, marker)
|
|
|
|
def ResetLineState(self, fileName, lineNo, lineState):
|
|
# Set the state of a line if the document is open.
|
|
doc = editor.editorTemplate.FindOpenDocument(fileName)
|
|
if doc is not None:
|
|
marker = _LineStateToMarker(lineState)
|
|
doc.MarkerDelete(lineNo, marker)
|
|
|
|
def UpdateDocumentLineStates(self, doc):
|
|
# Show all lines in their special status color. If the doc is open
|
|
# all line states are reset.
|
|
doc.MarkerDeleteAll(MARKER_BREAKPOINT)
|
|
doc.MarkerDeleteAll(MARKER_CURRENT)
|
|
fname = self.canonic(doc.GetPathName())
|
|
# Now loop over all break-points
|
|
for line in self.breaks.get(fname, []):
|
|
doc.MarkerAdd(line, MARKER_BREAKPOINT)
|
|
# And the current line if in this document.
|
|
if self.shownLineCurrent and fname == self.shownLineCurrent[0]:
|
|
lineNo = self.shownLineCurrent[1]
|
|
if not doc.MarkerCheck(lineNo, MARKER_CURRENT):
|
|
doc.MarkerAdd(lineNo, MARKER_CURRENT)
|
|
|
|
# if self.shownLineCallstack and fname == self.shownLineCallstack[0]:
|
|
# doc.MarkerAdd(self.shownLineCallstack[1], MARKER_CURRENT)
|
|
|
|
def UpdateAllLineStates(self):
|
|
for doc in editor.editorTemplate.GetDocumentList():
|
|
self.UpdateDocumentLineStates(doc)
|
|
|
|
def ShowCurrentLine(self):
|
|
# Show the current line. Only ever 1 current line - undoes last current
|
|
# The "Current Line" is self.curframe.
|
|
# The "Callstack Line" is the top of the stack.
|
|
# If current == callstack, only show as current.
|
|
self._UnshowCurrentLine() # un-highlight the old one.
|
|
if self.curframe:
|
|
fileName = self.canonic(self.curframe.f_code.co_filename)
|
|
lineNo = self.curframe.f_lineno
|
|
self.shownLineCurrent = fileName, lineNo
|
|
self.ShowLineState(fileName, lineNo, LINESTATE_CURRENT)
|
|
|
|
def _UnshowCurrentLine(self):
|
|
"Unshow the current line, and forget it"
|
|
if self.shownLineCurrent is not None:
|
|
fname, lineno = self.shownLineCurrent
|
|
self.ResetLineState(fname, lineno, LINESTATE_CURRENT)
|
|
self.shownLineCurrent = None
|
|
|
|
def ShowLineNo(self, filename, lineno):
|
|
wasOpen = editor.editorTemplate.FindOpenDocument(filename) is not None
|
|
if os.path.isfile(filename) and scriptutils.JumpToDocument(filename, lineno):
|
|
if not wasOpen:
|
|
doc = editor.editorTemplate.FindOpenDocument(filename)
|
|
if doc is not None:
|
|
self.UpdateDocumentLineStates(doc)
|
|
return 1
|
|
return 0
|
|
return 1
|
|
else:
|
|
# Can't find the source file - linecache may have it?
|
|
import linecache
|
|
|
|
line = linecache.getline(filename, lineno)
|
|
print(
|
|
"%s(%d): %s"
|
|
% (os.path.basename(filename), lineno, line[:-1].expandtabs(4))
|
|
)
|
|
return 0
|