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. from . 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 as 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