NETHostWindows.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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. {
  16. }
  17. NETHostWindows::~NETHostWindows()
  18. {
  19. }
  20. bool NETHostWindows::Initialize(const String& coreCLRFilesAbsPath)
  21. {
  22. // It is very important that this is the native path "\\" vs "/" as find files will return "/" or "\" depending
  23. // on what you give it, which will result in the domain failing to initialize as coreclr can't handle "/" on init
  24. coreCLRFilesAbsPath_ = GetNativePath(AddTrailingSlash(coreCLRFilesAbsPath));
  25. if (!LoadCLRDLL())
  26. return false;
  27. if (!InitCLRRuntimeHost())
  28. return false;
  29. if (!CreateAppDomain())
  30. return false;
  31. return true;
  32. }
  33. bool NETHostWindows::LoadCLRDLL()
  34. {
  35. WString wcoreCLRDLLPath(coreCLRFilesAbsPath_ + "coreclr.dll");
  36. HMODULE result = ::LoadLibraryExW(wcoreCLRDLLPath.CString(), NULL, 0);
  37. if (!result)
  38. {
  39. LOGERRORF("Unable to load CoreCLR from %s", (coreCLRFilesAbsPath_ + "coreclr.dll").CString() );
  40. return false;
  41. }
  42. // Pin the module - CoreCLR.dll does not support being unloaded.
  43. HMODULE dummy_coreCLRModule;
  44. if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, wcoreCLRDLLPath.CString(), &dummy_coreCLRModule))
  45. {
  46. LOGERRORF("Unable to pin CoreCLR module: %s", (coreCLRFilesAbsPath_ + "coreclr.dll").CString() );
  47. return false;
  48. }
  49. clrModule_ = result;
  50. return true;
  51. }
  52. bool NETHostWindows::CreateAppDomain()
  53. {
  54. wchar_t appPath[MAX_LONGPATH] = W("C:\\Dev\\coreclr\\x64\\");
  55. wchar_t appNiPath[MAX_LONGPATH * 2] = W("");
  56. wcscpy_s(appNiPath, appPath);
  57. wcscat_s(appNiPath, MAX_LONGPATH * 2, W(";"));
  58. wcscat_s(appNiPath, MAX_LONGPATH * 2, appPath);
  59. // Construct native search directory paths
  60. wchar_t nativeDllSearchDirs[MAX_LONGPATH * 3];
  61. wcscpy_s(nativeDllSearchDirs, appPath);
  62. wcscat_s(nativeDllSearchDirs, MAX_LONGPATH * 3, W(";"));
  63. wcscat_s(nativeDllSearchDirs, MAX_LONGPATH * 3, WString(coreCLRFilesAbsPath_).CString());
  64. //-------------------------------------------------------------
  65. // Create an AppDomain
  66. // Allowed property names:
  67. // APPBASE
  68. // - The base path of the application from which the exe and other assemblies will be loaded
  69. //
  70. // TRUSTED_PLATFORM_ASSEMBLIES
  71. // - The list of complete paths to each of the fully trusted assemblies
  72. //
  73. // APP_PATHS
  74. // - The list of paths which will be probed by the assembly loader
  75. //
  76. // APP_NI_PATHS
  77. // - The list of additional paths that the assembly loader will probe for ngen images
  78. //
  79. // NATIVE_DLL_SEARCH_DIRECTORIES
  80. // - The list of paths that will be probed for native DLLs called by PInvoke
  81. //
  82. // IMPORTANT: ALL PATHS MUST USE "\" and not "/"
  83. const wchar_t *property_keys[] = {
  84. W("TRUSTED_PLATFORM_ASSEMBLIES"),
  85. W("APP_PATHS"),
  86. W("APP_NI_PATHS"),
  87. W("NATIVE_DLL_SEARCH_DIRECTORIES"),
  88. W("AppDomainCompatSwitch")
  89. };
  90. const wchar_t *property_values[] = {
  91. // TRUSTED_PLATFORM_ASSEMBLIES
  92. tpaList_.CStr(),
  93. // APP_PATHS
  94. appPath,
  95. // APP_NI_PATHS
  96. appNiPath,
  97. // NATIVE_DLL_SEARCH_DIRECTORIES
  98. nativeDllSearchDirs,
  99. // AppDomainCompatSwitch
  100. W("UseLatestBehaviorWhenTFMNotSpecified")
  101. };
  102. DWORD domainId;
  103. HRESULT hr = clrRuntimeHost_->CreateAppDomainWithManager(
  104. W("HelloWorld.exe"), // The friendly name of the AppDomain
  105. // Flags:
  106. // APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS
  107. // - By default CoreCLR only allows platform neutral assembly to be run. To allow
  108. // assemblies marked as platform specific, include this flag
  109. //
  110. // APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP
  111. // - Allows sandboxed applications to make P/Invoke calls and use COM interop
  112. //
  113. // APPDOMAIN_SECURITY_SANDBOXED
  114. // - Enables sandboxing. If not set, the app is considered full trust
  115. //
  116. // APPDOMAIN_IGNORE_UNHANDLED_EXCEPTION
  117. // - Prevents the application from being torn down if a managed exception is unhandled
  118. //
  119. APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS |
  120. APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP |
  121. APPDOMAIN_DISABLE_TRANSPARENCY_ENFORCEMENT,
  122. NULL, // Name of the assembly that contains the AppDomainManager implementation
  123. NULL, // The AppDomainManager implementation type name
  124. sizeof(property_keys)/sizeof(wchar_t*), // The number of properties
  125. property_keys,
  126. property_values,
  127. &domainId);
  128. if (FAILED(hr)) {
  129. LOGERRORF("Failed call to CreateAppDomainWithManager. ERRORCODE:%u ", hr);
  130. return false;
  131. }
  132. return true;
  133. }
  134. bool NETHostWindows::InitCLRRuntimeHost()
  135. {
  136. if (!clrModule_)
  137. return false;
  138. FnGetCLRRuntimeHost pfnGetCLRRuntimeHost =
  139. (FnGetCLRRuntimeHost)::GetProcAddress(clrModule_, "GetCLRRuntimeHost");
  140. if (!pfnGetCLRRuntimeHost)
  141. {
  142. LOGERRORF("Unable to get GetCLRRuntimeHost function from module: %s", (coreCLRFilesAbsPath_ + "coreclr.dll").CString() );
  143. return false;
  144. }
  145. HRESULT hr = pfnGetCLRRuntimeHost(IID_ICLRRuntimeHost2, (IUnknown**)&clrRuntimeHost_);
  146. if (FAILED(hr))
  147. {
  148. LOGERRORF("Failed to get ICLRRuntimeHost2 interface. ERRORCODE: %u", hr);
  149. return false;
  150. }
  151. // Set up the startup flags for the clr runtime
  152. STARTUP_FLAGS dwStartupFlags = (STARTUP_FLAGS)(
  153. STARTUP_FLAGS::STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN |
  154. STARTUP_FLAGS::STARTUP_SINGLE_APPDOMAIN /* |
  155. STARTUP_FLAGS::STARTUP_SERVER_GC*/
  156. );
  157. // Default startup flags
  158. hr = clrRuntimeHost_->SetStartupFlags(dwStartupFlags);
  159. if (FAILED(hr))
  160. {
  161. LOGERRORF("Failed to set startup flags. ERRORCODE: %u", hr);
  162. return false;
  163. }
  164. /*
  165. // Authenticate with either CORECLR_HOST_AUTHENTICATION_KEY or CORECLR_HOST_AUTHENTICATION_KEY_NONGEN
  166. hr = clrRuntimeHost_->Authenticate(CORECLR_HOST_AUTHENTICATION_KEY);
  167. if (FAILED(hr))
  168. {
  169. LOGERRORF("CoreCLR failed to authenticate. ERRORCODE: %u", hr);
  170. return false;
  171. }
  172. */
  173. hr = clrRuntimeHost_->Start();
  174. if (FAILED(hr))
  175. {
  176. LOGERRORF("Failed to start CoreCLR. ERRORCODE: %u", hr);
  177. return false;
  178. }
  179. if (!GenerateTPAList())
  180. {
  181. LOGERRORF("Failed to generate TPA List");
  182. return false;
  183. }
  184. return true;
  185. }
  186. bool NETHostWindows::TPAListContainsFile(wchar_t* fileNameWithoutExtension, wchar_t** rgTPAExtensions, int countExtensions)
  187. {
  188. if (!tpaList_.CStr()) return false;
  189. for (int iExtension = 0; iExtension < countExtensions; iExtension++)
  190. {
  191. wchar_t fileName[MAX_LONGPATH];
  192. wcscpy_s(fileName, MAX_LONGPATH, W("\\")); // So that we don't match other files that end with the current file name
  193. wcscat_s(fileName, MAX_LONGPATH, fileNameWithoutExtension);
  194. wcscat_s(fileName, MAX_LONGPATH, rgTPAExtensions[iExtension] + 1);
  195. wcscat_s(fileName, MAX_LONGPATH, W(";")); // So that we don't match other files that begin with the current file name
  196. if (wcsstr(tpaList_.CStr(), fileName))
  197. {
  198. return true;
  199. }
  200. }
  201. return false;
  202. }
  203. void NETHostWindows::RemoveExtensionAndNi(wchar_t* fileName)
  204. {
  205. // Remove extension, if it exists
  206. wchar_t* extension = wcsrchr(fileName, W('.'));
  207. if (extension != NULL)
  208. {
  209. extension[0] = W('\0');
  210. // Check for .ni
  211. size_t len = wcslen(fileName);
  212. if (len > 3 &&
  213. fileName[len - 1] == W('i') &&
  214. fileName[len - 2] == W('n') &&
  215. fileName[len - 3] == W('.') )
  216. {
  217. fileName[len - 3] = W('\0');
  218. }
  219. }
  220. }
  221. void NETHostWindows::AddFilesFromDirectoryToTPAList(const wchar_t* targetPath, wchar_t** rgTPAExtensions, int countExtensions)
  222. {
  223. wchar_t assemblyPath[MAX_LONGPATH];
  224. for (int iExtension = 0; iExtension < countExtensions; iExtension++)
  225. {
  226. wcscpy_s(assemblyPath, MAX_LONGPATH, targetPath);
  227. const size_t dirLength = wcslen(targetPath);
  228. wchar_t* const fileNameBuffer = assemblyPath + dirLength;
  229. const size_t fileNameBufferSize = MAX_LONGPATH - dirLength;
  230. wcscat_s(assemblyPath, rgTPAExtensions[iExtension]);
  231. WIN32_FIND_DATA data;
  232. HANDLE findHandle = FindFirstFile(assemblyPath, &data);
  233. if (findHandle != INVALID_HANDLE_VALUE)
  234. {
  235. do
  236. {
  237. if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  238. {
  239. // It seems that CoreCLR doesn't always use the first instance of an assembly on the TPA list (ni's may be preferred
  240. // over il, even if they appear later). So, only include the first instance of a simple assembly name to allow
  241. // users the opportunity to override Framework assemblies by placing dlls in %CORE_LIBRARIES%
  242. // ToLower for case-insensitive comparisons
  243. wchar_t* fileNameChar = data.cFileName;
  244. while (*fileNameChar)
  245. {
  246. *fileNameChar = towlower(*fileNameChar);
  247. fileNameChar++;
  248. }
  249. // Remove extension
  250. wchar_t fileNameWithoutExtension[MAX_LONGPATH];
  251. wcscpy_s(fileNameWithoutExtension, MAX_LONGPATH, data.cFileName);
  252. RemoveExtensionAndNi(fileNameWithoutExtension);
  253. // Add to the list if not already on it
  254. if (!TPAListContainsFile(fileNameWithoutExtension, rgTPAExtensions, countExtensions))
  255. {
  256. const size_t fileLength = wcslen(data.cFileName);
  257. const size_t assemblyPathLength = dirLength + fileLength;
  258. wcsncpy_s(fileNameBuffer, fileNameBufferSize, data.cFileName, fileLength);
  259. tpaList_.Append(assemblyPath, assemblyPathLength);
  260. tpaList_.Append(W(";"), 1);
  261. }
  262. else
  263. {
  264. LOGINFOF("NETHostWindows skipping assembly");
  265. }
  266. }
  267. } while (0 != FindNextFile(findHandle, &data));
  268. FindClose(findHandle);
  269. }
  270. }
  271. }
  272. bool NETHostWindows::GenerateTPAList()
  273. {
  274. wchar_t *rgTPAExtensions[] = {
  275. W("*.ni.dll"), // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir
  276. W("*.dll"),
  277. W("*.ni.exe"),
  278. W("*.exe"),
  279. };
  280. AddFilesFromDirectoryToTPAList(WString(coreCLRFilesAbsPath_).CString(), rgTPAExtensions, _countof(rgTPAExtensions));
  281. return true;
  282. }
  283. }
  284. /*
  285. RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogEnable, W("LogEnable"), "Turns on the traditional CLR log.")
  286. 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.")
  287. 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.")
  288. RETAIL_CONFIG_DWORD_INFO(EXTERNAL_logFatalError, W("logFatalError"), 1, "Specifies whether EventReporter logs fatal errors in the Windows event log.")
  289. CONFIG_STRING_INFO_EX(INTERNAL_LogFile, W("LogFile"), "Specifies a file name for the CLR log.", CLRConfig::REGUTIL_default)
  290. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogFileAppend, W("LogFileAppend"), "Specifies whether to append to or replace the CLR log file.")
  291. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogFlushFile, W("LogFlushFile"), "Specifies whether to flush the CLR log file file on each write.")
  292. RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(EXTERNAL_LogLevel, W("LogLevel"), "4=10 msgs, 9=1000000, 10=everything")
  293. RETAIL_CONFIG_STRING_INFO_DIRECT_ACCESS(INTERNAL_LogPath, W("LogPath"), "?Fusion debug log path.")
  294. RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogToConsole, W("LogToConsole"), "Writes the CLR log to console.")
  295. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogToDebugger, W("LogToDebugger"), "Writes the CLR log to debugger (OutputDebugStringA).")
  296. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogToFile, W("LogToFile"), "Writes the CLR log to a file.")
  297. CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_LogWithPid, W("LogWithPid"), "Appends pid to filename for the CLR log.")
  298. RETAIL_CONFIG_DWORD_INFO_EX(EXTERNAL_FusionLogFileNamesIncludePid, W("FusionLogFileNamesIncludePid"), 0, "Fusion logging will append process id to log filenames.", CLRConfig::REGUTIL_default)
  299. */