adb.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. """The glue between the Python debugger interface and the Active Debugger interface
  2. """
  3. from win32com.axdebug.util import trace, _wrap, _wrap_remove
  4. from win32com.server.util import unwrap
  5. import win32com.client.connect
  6. import gateways
  7. import sys, bdb, traceback
  8. import axdebug, stackframe
  9. import win32api, pythoncom
  10. import thread, os
  11. def fnull(*args):
  12. pass
  13. try:
  14. os.environ["DEBUG_AXDEBUG"]
  15. debugging = 1
  16. except KeyError:
  17. debugging = 0
  18. traceenter = fnull # trace enter of functions
  19. tracev = fnull # verbose trace
  20. if debugging:
  21. traceenter = trace # trace enter of functions
  22. tracev = trace # verbose trace
  23. class OutputReflector:
  24. def __init__(self, file, writefunc):
  25. self.writefunc = writefunc
  26. self.file = file
  27. def __getattr__(self,name):
  28. return getattr(self.file, name)
  29. def write(self,message):
  30. self.writefunc(message)
  31. self.file.write(message)
  32. def _dumpf(frame):
  33. if frame is None:
  34. return "<None>"
  35. else:
  36. addn = "(with trace!)"
  37. if frame.f_trace is None:
  38. addn = " **No Trace Set **"
  39. return "Frame at %d, file %s, line: %d%s" % (id(frame), frame.f_code.co_filename, frame.f_lineno, addn)
  40. g_adb = None
  41. def OnSetBreakPoint(codeContext, breakPointState, lineNo):
  42. try:
  43. fileName = codeContext.codeContainer.GetFileName()
  44. # inject the code into linecache.
  45. import linecache
  46. linecache.cache[fileName] = 0, 0, codeContext.codeContainer.GetText(), fileName
  47. g_adb._OnSetBreakPoint(fileName, codeContext, breakPointState, lineNo+1)
  48. except:
  49. traceback.print_exc()
  50. class Adb(bdb.Bdb,gateways.RemoteDebugApplicationEvents):
  51. def __init__(self):
  52. self.debugApplication = None
  53. self.debuggingThread = None
  54. self.debuggingThreadStateHandle = None
  55. self.stackSnifferCookie = self.stackSniffer = None
  56. self.codeContainerProvider = None
  57. self.debuggingThread = None
  58. self.breakFlags = None
  59. self.breakReason = None
  60. self.appDebugger = None
  61. self.appEventConnection = None
  62. self.logicalbotframe = None # Anything at this level or below does not exist!
  63. self.currentframe = None # The frame we are currently in.
  64. self.recursiveData = [] # Data saved for each reentery on this thread.
  65. bdb.Bdb.__init__(self)
  66. self._threadprotectlock = thread.allocate_lock()
  67. self.reset()
  68. def canonic(self, fname):
  69. if fname[0]=='<':
  70. return fname
  71. return bdb.Bdb.canonic(self, fname)
  72. def reset(self):
  73. traceenter("adb.reset")
  74. bdb.Bdb.reset(self)
  75. def __xxxxx__set_break(self, filename, lineno, cond = None):
  76. # As per standard one, except no linecache checking!
  77. if filename not in self.breaks:
  78. self.breaks[filename] = []
  79. list = self.breaks[filename]
  80. if lineno in list:
  81. return 'There is already a breakpoint there!'
  82. list.append(lineno)
  83. if cond is not None: self.cbreaks[filename, lineno]=cond
  84. def stop_here(self, frame):
  85. traceenter("stop_here", _dumpf(frame), _dumpf(self.stopframe))
  86. # As per bdb.stop_here, except for logicalbotframe
  87. ## if self.stopframe is None:
  88. ## return 1
  89. if frame is self.stopframe:
  90. return 1
  91. tracev("stop_here said 'No'!")
  92. return 0
  93. def break_here(self, frame):
  94. traceenter("break_here", self.breakFlags, _dumpf(frame))
  95. self.breakReason = None
  96. if self.breakFlags==axdebug.APPBREAKFLAG_DEBUGGER_HALT:
  97. self.breakReason = axdebug.BREAKREASON_DEBUGGER_HALT
  98. elif self.breakFlags==axdebug.APPBREAKFLAG_DEBUGGER_BLOCK:
  99. self.breakReason = axdebug.BREAKREASON_DEBUGGER_BLOCK
  100. elif self.breakFlags==axdebug.APPBREAKFLAG_STEP:
  101. self.breakReason = axdebug.BREAKREASON_STEP
  102. else:
  103. print "Calling base 'break_here' with", self.breaks
  104. if bdb.Bdb.break_here(self, frame):
  105. self.breakReason = axdebug.BREAKREASON_BREAKPOINT
  106. return self.breakReason is not None
  107. def break_anywhere(self, frame):
  108. traceenter("break_anywhere", _dumpf(frame))
  109. if self.breakFlags==axdebug.APPBREAKFLAG_DEBUGGER_HALT:
  110. self.breakReason = axdebug.BREAKREASON_DEBUGGER_HALT
  111. return 1
  112. rc = bdb.Bdb.break_anywhere(self, frame)
  113. tracev("break_anywhere",_dumpf(frame),"returning",rc)
  114. return rc
  115. def dispatch_return(self, frame, arg):
  116. traceenter("dispatch_return", _dumpf(frame), arg)
  117. if self.logicalbotframe is frame:
  118. # We dont want to debug parent frames.
  119. tracev("dispatch_return resetting sys.trace")
  120. sys.settrace(None)
  121. return
  122. # self.bSetTrace = 0
  123. self.currentframe = frame.f_back
  124. return bdb.Bdb.dispatch_return(self, frame, arg)
  125. def dispatch_line(self, frame):
  126. traceenter("dispatch_line", _dumpf(frame), _dumpf(self.botframe))
  127. # trace("logbotframe is", _dumpf(self.logicalbotframe), "botframe is", self.botframe)
  128. if frame is self.logicalbotframe:
  129. trace("dispatch_line", _dumpf(frame), "for bottom frame returing tracer")
  130. # The next code executed in the frame above may be a builtin (eg, apply())
  131. # in which sys.trace needs to be set.
  132. sys.settrace(self.trace_dispatch)
  133. # And return the tracer incase we are about to execute Python code,
  134. # in which case sys tracer is ignored!
  135. return self.trace_dispatch
  136. if self.codeContainerProvider.FromFileName(frame.f_code.co_filename) is None:
  137. trace("dispatch_line has no document for", _dumpf(frame), "- skipping trace!")
  138. return None
  139. self.currentframe = frame # So the stack sniffer knows our most recent, debuggable code.
  140. return bdb.Bdb.dispatch_line(self, frame)
  141. def dispatch_call(self, frame, arg):
  142. traceenter("dispatch_call",_dumpf(frame))
  143. frame.f_locals['__axstack_address__'] = axdebug.GetStackAddress()
  144. if frame is self.botframe:
  145. trace("dispatch_call is self.botframe - returning tracer")
  146. return self.trace_dispatch
  147. # Not our bottom frame. If we have a document for it,
  148. # then trace it, otherwise run at full speed.
  149. if self.codeContainerProvider.FromFileName(frame.f_code.co_filename) is None:
  150. trace("dispatch_call has no document for", _dumpf(frame), "- skipping trace!")
  151. ## sys.settrace(None)
  152. return None
  153. return self.trace_dispatch
  154. # rc = bdb.Bdb.dispatch_call(self, frame, arg)
  155. # trace("dispatch_call", _dumpf(frame),"returned",rc)
  156. # return rc
  157. def trace_dispatch(self, frame, event, arg):
  158. traceenter("trace_dispatch", _dumpf(frame), event, arg)
  159. if self.debugApplication is None:
  160. trace("trace_dispatch has no application!")
  161. return # None
  162. return bdb.Bdb.trace_dispatch(self, frame, event, arg)
  163. #
  164. # The user functions do bugger all!
  165. #
  166. # def user_call(self, frame, argument_list):
  167. # traceenter("user_call",_dumpf(frame))
  168. def user_line(self, frame):
  169. traceenter("user_line",_dumpf(frame))
  170. # Traces at line zero
  171. if frame.f_lineno!=0:
  172. breakReason = self.breakReason
  173. if breakReason is None:
  174. breakReason = axdebug.BREAKREASON_STEP
  175. self._HandleBreakPoint(frame, None, breakReason)
  176. def user_return(self, frame, return_value):
  177. # traceenter("user_return",_dumpf(frame),return_value)
  178. bdb.Bdb.user_return(self, frame, return_value)
  179. def user_exception(self, frame, exc_info):
  180. # traceenter("user_exception")
  181. bdb.Bdb.user_exception(self, frame, exc_info)
  182. def _HandleBreakPoint(self, frame, tb, reason):
  183. traceenter("Calling HandleBreakPoint with reason", reason,"at frame", _dumpf(frame))
  184. traceenter(" Current frame is", _dumpf(self.currentframe))
  185. try:
  186. resumeAction = self.debugApplication.HandleBreakPoint(reason)
  187. tracev("HandleBreakPoint returned with ", resumeAction)
  188. except pythoncom.com_error, details:
  189. # Eeek - the debugger is dead, or something serious is happening.
  190. # Assume we should continue
  191. resumeAction = axdebug.BREAKRESUMEACTION_CONTINUE
  192. trace("HandleBreakPoint FAILED with", details)
  193. self.stack = []
  194. self.curindex = 0
  195. if resumeAction == axdebug.BREAKRESUMEACTION_ABORT:
  196. self.set_quit()
  197. elif resumeAction == axdebug.BREAKRESUMEACTION_CONTINUE:
  198. tracev("resume action is continue")
  199. self.set_continue()
  200. elif resumeAction == axdebug.BREAKRESUMEACTION_STEP_INTO:
  201. tracev("resume action is step")
  202. self.set_step()
  203. elif resumeAction == axdebug.BREAKRESUMEACTION_STEP_OVER:
  204. tracev("resume action is next")
  205. self.set_next(frame)
  206. elif resumeAction == axdebug.BREAKRESUMEACTION_STEP_OUT:
  207. tracev("resume action is stop out")
  208. self.set_return(frame)
  209. else:
  210. raise ValueError("unknown resume action flags")
  211. self.breakReason = None
  212. def set_trace(self):
  213. self.breakReason = axdebug.BREAKREASON_LANGUAGE_INITIATED
  214. bdb.Bdb.set_trace(self)
  215. def CloseApp(self):
  216. traceenter("ClosingApp")
  217. self.reset()
  218. self.logicalbotframe = None
  219. if self.stackSnifferCookie is not None:
  220. try:
  221. self.debugApplication.RemoveStackFrameSniffer(self.stackSnifferCookie)
  222. except pythoncom.com_error:
  223. trace("*** Could not RemoveStackFrameSniffer %d" % (self.stackSnifferCookie))
  224. if self.stackSniffer:
  225. _wrap_remove(self.stackSniffer)
  226. self.stackSnifferCookie = self.stackSniffer = None
  227. if self.appEventConnection is not None:
  228. self.appEventConnection.Disconnect()
  229. self.appEventConnection = None
  230. self.debugApplication = None
  231. self.appDebugger = None
  232. if self.codeContainerProvider is not None:
  233. self.codeContainerProvider.Close()
  234. self.codeContainerProvider = None
  235. def AttachApp(self, debugApplication, codeContainerProvider):
  236. # traceenter("AttachApp", debugApplication, codeContainerProvider)
  237. self.codeContainerProvider = codeContainerProvider
  238. self.debugApplication = debugApplication
  239. self.stackSniffer = _wrap(stackframe.DebugStackFrameSniffer(self), axdebug.IID_IDebugStackFrameSniffer)
  240. self.stackSnifferCookie = debugApplication.AddStackFrameSniffer(self.stackSniffer)
  241. # trace("StackFrameSniffer added (%d)" % self.stackSnifferCookie)
  242. # Connect to the application events.
  243. self.appEventConnection = win32com.client.connect.SimpleConnection(self.debugApplication, self, axdebug.IID_IRemoteDebugApplicationEvents)
  244. def ResetAXDebugging(self):
  245. traceenter("ResetAXDebugging", self, "with refcount", len(self.recursiveData))
  246. if win32api.GetCurrentThreadId()!=self.debuggingThread:
  247. trace("ResetAXDebugging called on other thread")
  248. return
  249. if len(self.recursiveData)==0:
  250. # print "ResetAXDebugging called for final time."
  251. self.logicalbotframe = None
  252. self.debuggingThread = None
  253. self.currentframe = None
  254. self.debuggingThreadStateHandle = None
  255. return
  256. self.logbotframe, self.stopframe, self.currentframe, self.debuggingThreadStateHandle = self.recursiveData[0]
  257. self.recursiveData = self.recursiveData[1:]
  258. def SetupAXDebugging(self, baseFrame = None, userFrame = None):
  259. """Get ready for potential debugging. Must be called on the thread
  260. that is being debugged.
  261. """
  262. # userFrame is for non AXScript debugging. This is the first frame of the
  263. # users code.
  264. if userFrame is None:
  265. userFrame = baseFrame
  266. else:
  267. # We have missed the "dispatch_call" function, so set this up now!
  268. userFrame.f_locals['__axstack_address__'] = axdebug.GetStackAddress()
  269. traceenter("SetupAXDebugging", self)
  270. self._threadprotectlock.acquire()
  271. try:
  272. thisThread = win32api.GetCurrentThreadId()
  273. if self.debuggingThread is None:
  274. self.debuggingThread = thisThread
  275. else:
  276. if self.debuggingThread!=thisThread:
  277. trace("SetupAXDebugging called on other thread - ignored!")
  278. return
  279. # push our context.
  280. self.recursiveData.insert(0, (self.logicalbotframe,self.stopframe, self.currentframe,self.debuggingThreadStateHandle))
  281. finally:
  282. self._threadprotectlock.release()
  283. trace("SetupAXDebugging has base frame as", _dumpf(baseFrame))
  284. self.botframe = baseFrame
  285. self.stopframe = userFrame
  286. self.logicalbotframe = baseFrame
  287. self.currentframe = None
  288. self.debuggingThreadStateHandle = axdebug.GetThreadStateHandle()
  289. self._BreakFlagsChanged()
  290. # RemoteDebugApplicationEvents
  291. def OnConnectDebugger(self, appDebugger):
  292. traceenter("OnConnectDebugger", appDebugger)
  293. self.appDebugger = appDebugger
  294. # Reflect output to appDebugger
  295. writefunc = lambda s: appDebugger.onDebugOutput(s)
  296. sys.stdout = OutputReflector(sys.stdout, writefunc)
  297. sys.stderr = OutputReflector(sys.stderr, writefunc)
  298. def OnDisconnectDebugger(self):
  299. traceenter("OnDisconnectDebugger")
  300. # Stop reflecting output
  301. if isinstance(sys.stdout, OutputReflector):
  302. sys.stdout = sys.stdout.file
  303. if isinstance(sys.stderr, OutputReflector):
  304. sys.stderr = sys.stderr.file
  305. self.appDebugger = None
  306. self.set_quit()
  307. def OnSetName(self, name):
  308. traceenter("OnSetName", name)
  309. def OnDebugOutput(self, string):
  310. traceenter("OnDebugOutput", string)
  311. def OnClose(self):
  312. traceenter("OnClose")
  313. def OnEnterBreakPoint(self, rdat):
  314. traceenter("OnEnterBreakPoint", rdat)
  315. def OnLeaveBreakPoint(self, rdat):
  316. traceenter("OnLeaveBreakPoint", rdat)
  317. def OnCreateThread(self, rdat):
  318. traceenter("OnCreateThread", rdat)
  319. def OnDestroyThread(self, rdat):
  320. traceenter("OnDestroyThread", rdat)
  321. def OnBreakFlagChange(self, abf, rdat):
  322. traceenter("Debugger OnBreakFlagChange", abf, rdat)
  323. self.breakFlags = abf
  324. self._BreakFlagsChanged()
  325. def _BreakFlagsChanged(self):
  326. traceenter("_BreakFlagsChanged to %s with our thread = %s, and debugging thread = %s" % (self.breakFlags, self.debuggingThread, win32api.GetCurrentThreadId()))
  327. trace("_BreakFlagsChanged has breaks", self.breaks)
  328. # If a request comes on our debugging thread, then do it now!
  329. # if self.debuggingThread!=win32api.GetCurrentThreadId():
  330. # return
  331. if len(self.breaks) or self.breakFlags:
  332. if self.logicalbotframe:
  333. trace("BreakFlagsChange with bot frame", _dumpf(self.logicalbotframe))
  334. # We have frames not to be debugged (eg, Scripting engine frames
  335. # (sys.settrace will be set when out logicalbotframe is hit -
  336. # this may not be the right thing to do, as it may not cause the
  337. # immediate break we desire.)
  338. self.logicalbotframe.f_trace = self.trace_dispatch
  339. else:
  340. trace("BreakFlagsChanged, but no bottom frame")
  341. if self.stopframe is not None:
  342. self.stopframe.f_trace = self.trace_dispatch
  343. # If we have the thread-state for the thread being debugged, then
  344. # we dynamically set its trace function - it is possible that the thread
  345. # being debugged is in a blocked call (eg, a message box) and we
  346. # want to hit the debugger the instant we return
  347. if self.debuggingThreadStateHandle is not None and \
  348. self.breakFlags and \
  349. self.debuggingThread != win32api.GetCurrentThreadId():
  350. axdebug.SetThreadStateTrace(self.debuggingThreadStateHandle, self.trace_dispatch)
  351. def _OnSetBreakPoint(self, key, codeContext, bps, lineNo):
  352. traceenter("_OnSetBreakPoint", self, key, codeContext, bps, lineNo)
  353. if bps==axdebug.BREAKPOINT_ENABLED:
  354. problem = self.set_break(key, lineNo)
  355. if problem:
  356. print "*** set_break failed -", problem
  357. trace("_OnSetBreakPoint just set BP and has breaks", self.breaks)
  358. else:
  359. self.clear_break(key, lineNo)
  360. self._BreakFlagsChanged()
  361. trace("_OnSetBreakPoint leaving with breaks", self.breaks)
  362. def Debugger():
  363. global g_adb
  364. if g_adb is None:
  365. g_adb = Adb()
  366. return g_adb