1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7"""Communication via the Windows COM interface.""" 8 9import inspect 10import time 11import sys 12 13# pylint: disable=import-error 14import win32com.client as com 15import win32api 16 17# pylint: enable=import-error 18 19from dex.utils.Exceptions import LoadDebuggerException 20 21_com_error = com.pywintypes.com_error # pylint: disable=no-member 22 23 24def get_file_version(file_): 25 try: 26 info = win32api.GetFileVersionInfo(file_, "\\") 27 ms = info["FileVersionMS"] 28 ls = info["FileVersionLS"] 29 return ".".join( 30 str(s) 31 for s in [ 32 win32api.HIWORD(ms), 33 win32api.LOWORD(ms), 34 win32api.HIWORD(ls), 35 win32api.LOWORD(ls), 36 ] 37 ) 38 except com.pywintypes.error: # pylint: disable=no-member 39 return "no versioninfo present" 40 41 42def _handle_com_error(e): 43 exc = sys.exc_info() 44 msg = win32api.FormatMessage(e.hresult) 45 try: 46 msg = msg.decode("CP1251") 47 except AttributeError: 48 pass 49 msg = msg.strip() 50 return msg, exc 51 52 53class ComObject(object): 54 """Wrap a raw Windows COM object in a class that implements auto-retry of 55 failed calls. 56 """ 57 58 def __init__(self, raw): 59 assert not isinstance(raw, ComObject), raw 60 self.__dict__["raw"] = raw 61 62 def __str__(self): 63 return self._call(self.raw.__str__) 64 65 def __getattr__(self, key): 66 if key in self.__dict__: 67 return self.__dict__[key] 68 return self._call(self.raw.__getattr__, key) 69 70 def __setattr__(self, key, val): 71 if key in self.__dict__: 72 self.__dict__[key] = val 73 self._call(self.raw.__setattr__, key, val) 74 75 def __getitem__(self, key): 76 return self._call(self.raw.__getitem__, key) 77 78 def __setitem__(self, key, val): 79 self._call(self.raw.__setitem__, key, val) 80 81 def __call__(self, *args): 82 return self._call(self.raw, *args) 83 84 @classmethod 85 def _call(cls, fn, *args): 86 """COM calls tend to randomly fail due to thread sync issues. 87 The Microsoft recommended solution is to set up a message filter object 88 to automatically retry failed calls, but this seems prohibitively hard 89 from python, so this is a custom solution to do the same thing. 90 All COM accesses should go through this function. 91 """ 92 ex = AssertionError("this should never be raised!") 93 94 assert ( 95 inspect.isfunction(fn) or inspect.ismethod(fn) or inspect.isbuiltin(fn) 96 ), (fn, type(fn)) 97 retries = ([0] * 50) + ([1] * 5) 98 for r in retries: 99 try: 100 try: 101 result = fn(*args) 102 if inspect.ismethod(result) or "win32com" in str(result.__class__): 103 result = ComObject(result) 104 return result 105 except _com_error as e: 106 msg, _ = _handle_com_error(e) 107 e = WindowsError(msg) # pylint: disable=undefined-variable 108 raise e 109 except (AttributeError, TypeError, OSError) as e: 110 ex = e 111 time.sleep(r) 112 raise ex 113 114 115class DTE(ComObject): 116 def __init__(self, class_string): 117 try: 118 super(DTE, self).__init__(com.DispatchEx(class_string)) 119 except _com_error as e: 120 msg, exc = _handle_com_error(e) 121 raise LoadDebuggerException( 122 "{} [{}]".format(msg, class_string), orig_exception=exc 123 ) 124