NETHostWindows.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. #include <Atomic/IO/Log.h>
  2. #include <Atomic/IO/FileSystem.h>
  3. #include "NETHostWindows.h"
  4. // https://github.com/dotnet/coreclr/blob/master/Documentation/project-docs/clr-configuration-knobs.md
  5. // set COMPLUS_LogEnable=1
  6. // set COMPLUS_LogToConsole=1
  7. // set COMPLUS_LogLevel=9
  8. // set COMPLUS_ManagedLogFacility=0x00001000
  9. namespace Atomic
  10. {
  11. NETHostWindows::NETHostWindows(Context* context) :
  12. NETHost(context),
  13. clrRuntimeHost_(0),
  14. clrModule_(0),
  15. appDomainID_(0)
  16. {
  17. }
  18. NETHostWindows::~NETHostWindows()
  19. {
  20. }
  21. bool NETHostWindows::CreateDelegate(const String& assemblyName, const String& qualifiedClassName, const String& methodName, void** funcOut)
  22. {
  23. if (!clrRuntimeHost_)
  24. return false;
  25. HRESULT hr = clrRuntimeHost_->CreateDelegate(appDomainID_, WString(assemblyName).CString(), WString(qualifiedClassName).CString(), WString(methodName).CString(), (INT_PTR *)funcOut);
  26. if (FAILED(hr))
  27. {
  28. return false;
  29. }
  30. return true;
  31. }
  32. bool NETHostWindows::Initialize(const String& coreCLRFilesAbsPath, const String &assemblyLoadPaths)
  33. {
  34. // It is very important that this is the native path "\\" vs "/" as find files will return "/" or "\" depending
  35. // on what you give it, which will result in the domain failing to initialize as coreclr can't handle "/" on init
  36. coreCLRFilesAbsPath_ = GetNativePath(AddTrailingSlash(coreCLRFilesAbsPath));
  37. if (!LoadCLRDLL())
  38. return false;
  39. if (!InitCLRRuntimeHost())
  40. return false;
  41. if (!CreateAppDomain())
  42. return false;
  43. // MOVE THIS!
  44. typedef void (*StartupFunction)(const char* assemblyLoadPaths);
  45. StartupFunction startup;
  46. // The coreclr binding model will become locked upon loading the first assembly that is not on the TPA list, or
  47. // upon initializing the default context for the first time. For this test, test assemblies are located alongside
  48. // corerun, and hence will be on the TPA list. So, we should be able to set the default context once successfully,
  49. // and fail on the second try.
  50. // AssemblyLoadContext
  51. // https://github.com/dotnet/corefx/issues/3054
  52. // dnx loader
  53. // https://github.com/aspnet/dnx/tree/dev/src/Microsoft.Dnx.Loader
  54. bool result = CreateDelegate(
  55. "AtomicNETBootstrap",
  56. "Atomic.Bootstrap.AtomicLoadContext",
  57. "Startup",
  58. (void**) &startup);
  59. if (result)
  60. {
  61. startup(assemblyLoadPaths.CString());
  62. }
  63. // MOVE THIS!
  64. typedef void (*InitializeFunction)();
  65. InitializeFunction init;
  66. result = CreateDelegate(
  67. "AtomicNETEngine",
  68. "AtomicEngine.Atomic",
  69. "Initialize",
  70. (void**) &init);
  71. if (result)
  72. {
  73. init();
  74. }
  75. return true;
  76. }
  77. void NETHostWindows::WaitForDebuggerConnect()
  78. {
  79. while (!IsDebuggerPresent())
  80. {
  81. Sleep(100);
  82. }
  83. }
  84. bool NETHostWindows::LoadCLRDLL()
  85. {
  86. WString wcoreCLRDLLPath(coreCLRFilesAbsPath_ + "coreclr.dll");
  87. HMODULE result = ::LoadLibraryExW(wcoreCLRDLLPath.CString(), NULL, 0);
  88. if (!result)
  89. {
  90. LOGERRORF("Unable to load CoreCLR from %s", (coreCLRFilesAbsPath_ + "coreclr.dll").CString() );
  91. return false;
  92. }
  93. // Pin the module - CoreCLR.dll does not support being unloaded.
  94. HMODULE dummy_coreCLRModule;
  95. if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, wcoreCLRDLLPath.CString(), &dummy_coreCLRModule))
  96. {
  97. LOGERRORF("Unable to pin CoreCLR module: %s", (coreCLRFilesAbsPath_ + "coreclr.dll").CString() );
  98. return false;
  99. }
  100. clrModule_ = result;
  101. return true;
  102. }
  103. bool NETHostWindows::CreateAppDomain()
  104. {
  105. wchar_t appPath[MAX_LONGPATH] = W("C:\\Dev\\atomic\\AtomicGameEngine\\Artifacts\\AtomicNET\\");
  106. wchar_t appNiPath[MAX_LONGPATH * 2] = W("");
  107. //wcscpy_s(appPath, WString(coreCLRFilesAbsPath_).CString());
  108. wcscpy_s(appNiPath, appPath);
  109. wcscat_s(appNiPath, MAX_LONGPATH * 2, W(";"));
  110. wcscat_s(appNiPath, MAX_LONGPATH * 2, appPath);
  111. // Construct native search directory paths
  112. wchar_t nativeDllSearchDirs[MAX_LONGPATH * 3];
  113. wcscpy_s(nativeDllSearchDirs, appPath);
  114. wcscat_s(nativeDllSearchDirs, MAX_LONGPATH * 3, W(";"));
  115. wcscat_s(nativeDllSearchDirs, MAX_LONGPATH * 3, WString(coreCLRFilesAbsPath_).CString());
  116. //-------------------------------------------------------------
  117. // Create an AppDomain
  118. // Allowed property names:
  119. // APPBASE
  120. // - The base path of the application from which the exe and other assemblies will be loaded
  121. //
  122. // TRUSTED_PLATFORM_ASSEMBLIES
  123. // - The list of complete paths to each of the fully trusted assemblies
  124. //
  125. // APP_PATHS
  126. // - The list of paths which will be probed by the assembly loader
  127. //
  128. // APP_NI_PATHS
  129. // - The list of additional paths that the assembly loader will probe for ngen images
  130. //
  131. // NATIVE_DLL_SEARCH_DIRECTORIES
  132. // - The list of paths that will be probed for native DLLs called by PInvoke
  133. //
  134. // IMPORTANT: ALL PATHS MUST USE "\" and not "/"
  135. const wchar_t *property_keys[] = {
  136. W("TRUSTED_PLATFORM_ASSEMBLIES"),
  137. W("APP_PATHS"),
  138. W("APP_NI_PATHS"),
  139. W("NATIVE_DLL_SEARCH_DIRECTORIES"),
  140. W("AppDomainCompatSwitch")
  141. };
  142. const wchar_t *property_values[] = {
  143. // TRUSTED_PLATFORM_ASSEMBLIES
  144. tpaList_.CStr(),
  145. // APP_PATHS
  146. appPath,
  147. // APP_NI_PATHS
  148. appNiPath,
  149. // NATIVE_DLL_SEARCH_DIRECTORIES
  150. nativeDllSearchDirs,
  151. // AppDomainCompatSwitch
  152. W("UseLatestBehaviorWhenTFMNotSpecified")
  153. };
  154. HRESULT hr = clrRuntimeHost_->CreateAppDomainWithManager(
  155. W("AtomicNETDomain"), // The friendly name of the AppDomain
  156. // Flags:
  157. // APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS
  158. // - By default CoreCLR only allows platform neutral assembly to be run. To allow
  159. // assemblies marked as platform specific, include this flag
  160. //
  161. // APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP
  162. // - Allows sandboxed applications to make P/Invoke calls and use COM interop
  163. //
  164. // APPDOMAIN_SECURITY_SANDBOXED
  165. // - Enables sandboxing. If not set, the app is considered full trust
  166. //
  167. // APPDOMAIN_IGNORE_UNHANDLED_EXCEPTION
  168. // - Prevents the application from being torn down if a managed exception is unhandled
  169. //
  170. APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS |
  171. APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP |
  172. APPDOMAIN_DISABLE_TRANSPARENCY_ENFORCEMENT,
  173. NULL, // Name of the assembly that contains the AppDomainManager implementation
  174. NULL, // The AppDomainManager implementation type name
  175. sizeof(property_keys)/sizeof(wchar_t*), // The number of properties
  176. property_keys,
  177. property_values,
  178. &appDomainID_);
  179. if (FAILED(hr)) {
  180. LOGERRORF("Failed call to CreateAppDomainWithManager. ERRORCODE:%u ", hr);
  181. return false;
  182. }
  183. return true;
  184. }
  185. bool NETHostWindows::InitCLRRuntimeHost()
  186. {
  187. if (!clrModule_)
  188. return false;
  189. FnGetCLRRuntimeHost pfnGetCLRRuntimeHost =
  190. (FnGetCLRRuntimeHost)::GetProcAddress(clrModule_, "GetCLRRuntimeHost");
  191. if (!pfnGetCLRRuntimeHost)
  192. {
  193. LOGERRORF("Unable to get GetCLRRuntimeHost function from module: %s", (coreCLRFilesAbsPath_ + "coreclr.dll").CString() );
  194. return false;
  195. }
  196. HRESULT hr = pfnGetCLRRuntimeHost(IID_ICLRRuntimeHost2, (IUnknown**)&clrRuntimeHost_);
  197. if (FAILED(hr))
  198. {
  199. LOGERRORF("Failed to get ICLRRuntimeHost2 interface. ERRORCODE: %u", hr);
  200. return false;
  201. }
  202. // Set up the startup flags for the clr runtime
  203. STARTUP_FLAGS dwStartupFlags = (STARTUP_FLAGS)(
  204. STARTUP_FLAGS::STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN |
  205. STARTUP_FLAGS::STARTUP_SINGLE_APPDOMAIN /* |
  206. STARTUP_FLAGS::STARTUP_SERVER_GC*/
  207. );
  208. // Default startup flags
  209. hr = clrRuntimeHost_->SetStartupFlags(dwStartupFlags);
  210. if (FAILED(hr))
  211. {
  212. LOGERRORF("Failed to set startup flags. ERRORCODE: %u", hr);
  213. return false;
  214. }
  215. /*
  216. // Authenticate with either CORECLR_HOST_AUTHENTICATION_KEY or CORECLR_HOST_AUTHENTICATION_KEY_NONGEN
  217. hr = clrRuntimeHost_->Authenticate(CORECLR_HOST_AUTHENTICATION_KEY);
  218. if (FAILED(hr))
  219. {
  220. LOGERRORF("CoreCLR failed to authenticate. ERRORCODE: %u", hr);
  221. return false;
  222. }
  223. */
  224. hr = clrRuntimeHost_->Start();
  225. if (FAILED(hr))
  226. {
  227. LOGERRORF("Failed to start CoreCLR. ERRORCODE: %u", hr);
  228. return false;
  229. }
  230. if (!GenerateTPAList())
  231. {
  232. LOGERRORF("Failed to generate TPA List");
  233. return false;
  234. }
  235. return true;
  236. }
  237. bool NETHostWindows::TPAListContainsFile(wchar_t* fileNameWithoutExtension, wchar_t** rgTPAExtensions, int countExtensions)
  238. {
  239. if (!tpaList_.CStr()) return false;
  240. for (int iExtension = 0; iExtension < countExtensions; iExtension++)
  241. {
  242. wchar_t fileName[MAX_LONGPATH];
  243. wcscpy_s(fileName, MAX_LONGPATH, W("\\")); // So that we don't match other files that end with the current file name
  244. wcscat_s(fileName, MAX_LONGPATH, fileNameWithoutExtension);
  245. wcscat_s(fileName, MAX_LONGPATH, rgTPAExtensions[iExtension] + 1);
  246. wcscat_s(fileName, MAX_LONGPATH, W(";")); // So that we don't match other files that begin with the current file name
  247. if (wcsstr(tpaList_.CStr(), fileName))
  248. {
  249. return true;
  250. }
  251. }
  252. return false;
  253. }
  254. void NETHostWindows::RemoveExtensionAndNi(wchar_t* fileName)
  255. {
  256. // Remove extension, if it exists
  257. wchar_t* extension = wcsrchr(fileName, W('.'));
  258. if (extension != NULL)
  259. {
  260. extension[0] = W('\0');
  261. // Check for .ni
  262. size_t len = wcslen(fileName);
  263. if (len > 3 &&
  264. fileName[len - 1] == W('i') &&
  265. fileName[len - 2] == W('n') &&
  266. fileName[len - 3] == W('.') )
  267. {
  268. fileName[len - 3] = W('\0');
  269. }
  270. }
  271. }
  272. void NETHostWindows::AddFilesFromDirectoryToTPAList(const wchar_t* targetPath, wchar_t** rgTPAExtensions, int countExtensions)
  273. {
  274. wchar_t assemblyPath[MAX_LONGPATH];
  275. for (int iExtension = 0; iExtension < countExtensions; iExtension++)
  276. {
  277. wcscpy_s(assemblyPath, MAX_LONGPATH, targetPath);
  278. const size_t dirLength = wcslen(targetPath);
  279. wchar_t* const fileNameBuffer = assemblyPath + dirLength;
  280. const size_t fileNameBufferSize = MAX_LONGPATH - dirLength;
  281. wcscat_s(assemblyPath, rgTPAExtensions[iExtension]);
  282. WIN32_FIND_DATA data;
  283. HANDLE findHandle = FindFirstFile(assemblyPath, &data);
  284. if (findHandle != INVALID_HANDLE_VALUE)
  285. {
  286. do
  287. {
  288. if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  289. {
  290. // It seems that CoreCLR doesn't always use the first instance of an assembly on the TPA list (ni's may be preferred
  291. // over il, even if they appear later). So, only include the first instance of a simple assembly name to allow
  292. // users the opportunity to override Framework assemblies by placing dlls in %CORE_LIBRARIES%
  293. // ToLower for case-insensitive comparisons
  294. wchar_t* fileNameChar = data.cFileName;
  295. while (*fileNameChar)
  296. {
  297. *fileNameChar = towlower(*fileNameChar);
  298. fileNameChar++;
  299. }
  300. // Remove extension
  301. wchar_t fileNameWithoutExtension[MAX_LONGPATH];
  302. wcscpy_s(fileNameWithoutExtension, MAX_LONGPATH, data.cFileName);
  303. RemoveExtensionAndNi(fileNameWithoutExtension);
  304. // Add to the list if not already on it
  305. if (!TPAListContainsFile(fileNameWithoutExtension, rgTPAExtensions, countExtensions))
  306. {
  307. const size_t fileLength = wcslen(data.cFileName);
  308. const size_t assemblyPathLength = dirLength + fileLength;
  309. wcsncpy_s(fileNameBuffer, fileNameBufferSize, data.cFileName, fileLength);
  310. tpaList_.Append(assemblyPath, assemblyPathLength);
  311. tpaList_.Append(W(";"), 1);
  312. }
  313. else
  314. {
  315. LOGINFOF("NETHostWindows skipping assembly");
  316. }
  317. }
  318. } while (0 != FindNextFile(findHandle, &data));
  319. FindClose(findHandle);
  320. }
  321. }
  322. }
  323. bool NETHostWindows::GenerateTPAList()
  324. {
  325. wchar_t *rgTPAExtensions[] = {
  326. W("*.ni.dll"), // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir
  327. W("*.dll"),
  328. W("*.ni.exe"),
  329. W("*.exe"),
  330. };
  331. AddFilesFromDirectoryToTPAList(WString(coreCLRFilesAbsPath_).CString(), rgTPAExtensions, _countof(rgTPAExtensions));
  332. #ifdef ATOMIC_DEV_BUILD
  333. WString tpaAbsPath(GetNativePath(ToString("%s/Submodules/CoreCLR/AnyCPU/TPA/", ATOMIC_ROOT_SOURCE_DIR)));
  334. WString atomicTPAAbsPath(GetNativePath(ToString("%s/Artifacts/AtomicNET/TPA/", ATOMIC_ROOT_SOURCE_DIR)));
  335. #else
  336. assert(0);
  337. #endif
  338. AddFilesFromDirectoryToTPAList(tpaAbsPath.CString(), rgTPAExtensions, _countof(rgTPAExtensions));
  339. AddFilesFromDirectoryToTPAList(atomicTPAAbsPath.CString(), rgTPAExtensions, _countof(rgTPAExtensions));
  340. return true;
  341. }
  342. }
  343. /*
  344. RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogEnable, W("LogEnable"), "Turns on the traditional CLR log.")
  345. RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogFacility, W("LogFacility"), "Specifies a facility mask for CLR log. (See 'loglf.h'; VM interprets string value as hex number.) Also used by stresslog.")
  346. RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogFacility2, W("LogFacility2"), "Specifies a facility mask for CLR log. (See 'loglf.h'; VM interprets string value as hex number.) Also used by stresslog.")
  347. RETAIL_CONFIG_DWORD_INFO(EXTERNAL_logFatalError, W("logFatalError"), 1, "Specifies whether EventReporter logs fatal errors in the Windows event log.")
  348. CONFIG_STRING_INFO_EX(INTERNAL_LogFile, W("LogFile"), "Specifies a file name for the CLR log.", CLRConfig::REGUTIL_default)
  349. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogFileAppend, W("LogFileAppend"), "Specifies whether to append to or replace the CLR log file.")
  350. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogFlushFile, W("LogFlushFile"), "Specifies whether to flush the CLR log file file on each write.")
  351. RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(EXTERNAL_LogLevel, W("LogLevel"), "4=10 msgs, 9=1000000, 10=everything")
  352. RETAIL_CONFIG_STRING_INFO_DIRECT_ACCESS(INTERNAL_LogPath, W("LogPath"), "?Fusion debug log path.")
  353. RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogToConsole, W("LogToConsole"), "Writes the CLR log to console.")
  354. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogToDebugger, W("LogToDebugger"), "Writes the CLR log to debugger (OutputDebugStringA).")
  355. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogToFile, W("LogToFile"), "Writes the CLR log to a file.")
  356. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogWithPid, W("LogWithPid"), "Appends pid to filename for the CLR log.")
  357. RETAIL_CONFIG_DWORD_INFO_EX(EXTERNAL_FusionLogFileNamesIncludePid, W("FusionLogFileNamesIncludePid"), 0, "Fusion logging will append process id to log filenames.", CLRConfig::REGUTIL_default)
  358. */