diff options
Diffstat (limited to 'src/coreclr/hosts')
33 files changed, 3132 insertions, 0 deletions
diff --git a/src/coreclr/hosts/.gitmirror b/src/coreclr/hosts/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/coreclr/hosts/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/coreclr/hosts/CMakeLists.txt b/src/coreclr/hosts/CMakeLists.txt new file mode 100644 index 0000000000..bb425b908a --- /dev/null +++ b/src/coreclr/hosts/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(inc) + +if(WIN32) + add_subdirectory(corerun) + add_subdirectory(coreconsole) +else(WIN32) + add_subdirectory(unixcoreruncommon) + add_subdirectory(unixcorerun) + add_subdirectory(unixcoreconsole) + if(CLR_CMAKE_PLATFORM_DARWIN) + add_subdirectory(osxbundlerun) + endif(CLR_CMAKE_PLATFORM_DARWIN) +endif(WIN32) diff --git a/src/coreclr/hosts/coreconsole/.gitmirror b/src/coreclr/hosts/coreconsole/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/coreclr/hosts/coreconsole/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/coreclr/hosts/coreconsole/CMakeLists.txt b/src/coreclr/hosts/coreconsole/CMakeLists.txt new file mode 100644 index 0000000000..a8eae3209b --- /dev/null +++ b/src/coreclr/hosts/coreconsole/CMakeLists.txt @@ -0,0 +1,33 @@ +project(CoreConsole) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CoreConsole_SOURCES coreconsole.cpp logger.cpp) +set(CoreConsole_RESOURCES native.rc) + +add_definitions(-DFX_VER_INTERNALNAME_STR=CoreConsole.exe) + +if(CLR_CMAKE_PLATFORM_UNIX) + # This does not compile on Linux yet + if(CAN_BE_COMPILED_ON_LINUX) + _add_executable(CoreConsole + ${CoreConsole_SOURCES} + ${CoreConsole_RESOURCES} + ) + endif(CAN_BE_COMPILED_ON_LINUX) + +else() + _add_executable(CoreConsole + ${CoreConsole_SOURCES} + ${CoreConsole_RESOURCES} + ) + + target_link_libraries(CoreConsole + ${STATIC_MT_CRT_LIB} + ${STATIC_MT_VCRT_LIB} + ) + + # Can't compile on linux yet so only add for windows + install_clr(CoreConsole) + +endif(CLR_CMAKE_PLATFORM_UNIX)
\ No newline at end of file diff --git a/src/coreclr/hosts/coreconsole/CoreConsole.nativeproj b/src/coreclr/hosts/coreconsole/CoreConsole.nativeproj new file mode 100644 index 0000000000..bdb190fbbb --- /dev/null +++ b/src/coreclr/hosts/coreconsole/CoreConsole.nativeproj @@ -0,0 +1,32 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood"> + <!--Import the settings--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + + <!--Leaf project Properties--> + <PropertyGroup> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + <OutputName>CoreConsole</OutputName> + <TargetType>PROGRAM</TargetType> + <LinkSubsystem>console</LinkSubsystem> + <EntryPoint>wmain</EntryPoint> + <ClAdditionalOptions>$(ClAdditionalOptions) -DUNICODE -D_UNICODE</ClAdditionalOptions> + <IsTestNetHost>true</IsTestNetHost> + </PropertyGroup> + + <ItemGroup> + <TargetLib Include="$(CoreSystemCrt)" /> + <TargetLib Condition="'$(BuildForWindows7)'=='true'" Include="$(SdkLibPath)\mincore_fw.lib" /> + <TargetLib Condition="'$(BuildForWindows7)'!='true'" Include="$(SdkLibPath)\mincore.lib" /> + </ItemGroup> + + <ItemGroup> + <RCResourceFile Include="native.rc" /> + </ItemGroup> + <ItemGroup> + <CppCompile Include="coreconsole.cpp" /> + <CppCompile Include="logger.cpp" /> + </ItemGroup> + <!--Import the targets--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.targets" /> +</Project> diff --git a/src/coreclr/hosts/coreconsole/coreconsole.cpp b/src/coreclr/hosts/coreconsole/coreconsole.cpp new file mode 100644 index 0000000000..1533dff88e --- /dev/null +++ b/src/coreclr/hosts/coreconsole/coreconsole.cpp @@ -0,0 +1,669 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// A simple CoreCLR host that runs a managed binary with the same name as this executable but with *.dll extension +// The dll binary must contain main entry point. +// + +#include "windows.h" +#include <stdio.h> +#include "mscoree.h" +#include <Logger.h> +#include "palclr.h" + +// The name of the CoreCLR native runtime DLL. +static const wchar_t *coreCLRDll = W("CoreCLR.dll"); + +// Dynamically expanding string buffer to hold TPA list +class StringBuffer { + static const int m_defaultSize = 4096; + wchar_t* m_buffer; + size_t m_capacity; + size_t m_length; + + StringBuffer(const StringBuffer&); + StringBuffer& operator =(const StringBuffer&); + +public: + StringBuffer() : m_capacity(0), m_buffer(nullptr), m_length(0) { + } + + ~StringBuffer() { + delete[] m_buffer; + } + + const wchar_t* CStr() const { + return m_buffer; + } + + void Append(const wchar_t* str, size_t strLen) { + if (!m_buffer) { + m_buffer = new wchar_t[m_defaultSize]; + m_capacity = m_defaultSize; + } + if (m_length + strLen + 1 > m_capacity) { + size_t newCapacity = m_capacity * 2; + wchar_t* newBuffer = new wchar_t[newCapacity]; + wcsncpy_s(newBuffer, newCapacity, m_buffer, m_length); + delete[] m_buffer; + m_buffer = newBuffer; + m_capacity = newCapacity; + } + wcsncpy_s(m_buffer + m_length, m_capacity - m_length, str, strLen); + m_length += strLen; + } +}; + +// Encapsulates the environment that CoreCLR will run in, including the TPALIST +class HostEnvironment +{ + // The path to this module + wchar_t m_hostPath[MAX_LONGPATH]; + + // The path to the directory containing this module + wchar_t m_hostDirectoryPath[MAX_LONGPATH]; + + // The name of this module, without the path + wchar_t *m_hostExeName; + + // The list of paths to the assemblies that will be trusted by CoreCLR + StringBuffer m_tpaList; + + ICLRRuntimeHost2* m_CLRRuntimeHost; + + HMODULE m_coreCLRModule; + + Logger *m_log; + + + // Attempts to load CoreCLR.dll from the given directory. + // On success pins the dll, sets m_coreCLRDirectoryPath and returns the HMODULE. + // On failure returns nullptr. + HMODULE TryLoadCoreCLR(const wchar_t* directoryPath) { + + wchar_t coreCLRPath[MAX_LONGPATH]; + wcscpy_s(coreCLRPath, directoryPath); + wcscat_s(coreCLRPath, coreCLRDll); + + *m_log << W("Attempting to load: ") << coreCLRPath << Logger::endl; + + HMODULE result = ::LoadLibraryExW(coreCLRPath, NULL, 0); + if (!result) { + *m_log << W("Failed to load: ") << coreCLRPath << Logger::endl; + *m_log << W("Error code: ") << GetLastError() << Logger::endl; + return nullptr; + } + + // Pin the module - CoreCLR.dll does not support being unloaded. + HMODULE dummy_coreCLRModule; + if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, coreCLRPath, &dummy_coreCLRModule)) { + *m_log << W("Failed to pin: ") << coreCLRPath << Logger::endl; + return nullptr; + } + + wchar_t coreCLRLoadedPath[MAX_LONGPATH]; + ::GetModuleFileNameW(result, coreCLRLoadedPath, MAX_LONGPATH); + + *m_log << W("Loaded: ") << coreCLRLoadedPath << Logger::endl; + + return result; + } + +public: + // The path to the directory that CoreCLR is in + wchar_t m_coreCLRDirectoryPath[MAX_LONGPATH]; + + HostEnvironment(Logger *logger) + : m_log(logger), m_CLRRuntimeHost(nullptr) { + + // Discover the path to this exe's module. All other files are expected to be in the same directory. + DWORD thisModuleLength = ::GetModuleFileNameW(::GetModuleHandleW(nullptr), m_hostPath, MAX_LONGPATH); + + // Search for the last backslash in the host path. + int lastBackslashIndex; + for (lastBackslashIndex = thisModuleLength-1; lastBackslashIndex >= 0; lastBackslashIndex--) { + if (m_hostPath[lastBackslashIndex] == W('\\')) { + break; + } + } + + // Copy the directory path + ::wcsncpy_s(m_hostDirectoryPath, m_hostPath, lastBackslashIndex + 1); + + // Save the exe name + m_hostExeName = m_hostPath + lastBackslashIndex + 1; + + *m_log << W("Host directory: ") << m_hostDirectoryPath << Logger::endl; + + // Check for %CORE_ROOT% and try to load CoreCLR.dll from it if it is set + wchar_t coreRoot[MAX_LONGPATH]; + size_t outSize; + m_coreCLRModule = NULL; // Initialize this here since we don't call TryLoadCoreCLR if CORE_ROOT is unset. + if (_wgetenv_s(&outSize, coreRoot, MAX_LONGPATH, W("CORE_ROOT")) == 0 && outSize > 0) + { + wcscat_s(coreRoot, MAX_LONGPATH, W("\\")); + m_coreCLRModule = TryLoadCoreCLR(coreRoot); + } + else + { + *m_log << W("CORE_ROOT not set; skipping") << Logger::endl; + *m_log << W("You can set the environment variable CORE_ROOT to point to the path") << Logger::endl; + *m_log << W("where CoreCLR.dll lives to help this executable find it.") << Logger::endl; + } + + // Try to load CoreCLR from the directory that this exexutable is in + if (!m_coreCLRModule) { + m_coreCLRModule = TryLoadCoreCLR(m_hostDirectoryPath); + } + + if (m_coreCLRModule) { + + // Save the directory that CoreCLR was found in + DWORD modulePathLength = ::GetModuleFileNameW(m_coreCLRModule, m_coreCLRDirectoryPath, MAX_LONGPATH); + + // Search for the last backslash and terminate it there to keep just the directory path with trailing slash + for (lastBackslashIndex = modulePathLength-1; lastBackslashIndex >= 0; lastBackslashIndex--) { + if (m_coreCLRDirectoryPath[lastBackslashIndex] == W('\\')) { + m_coreCLRDirectoryPath[lastBackslashIndex + 1] = W('\0'); + break; + } + } + + } else { + *m_log << W("Unable to load ") << coreCLRDll << Logger::endl; + } + } + + ~HostEnvironment() { + if(m_coreCLRModule) { + // Free the module. This is done for completeness, but in fact CoreCLR.dll + // was pinned earlier so this call won't actually free it. The pinning is + // done because CoreCLR does not support unloading. + ::FreeLibrary(m_coreCLRModule); + } + } + + bool TPAListContainsFile(_In_z_ wchar_t* fileNameWithoutExtension, _In_reads_(countExtensions) wchar_t** rgTPAExtensions, int countExtensions) + { + if (!m_tpaList.CStr()) return false; + + for (int iExtension = 0; iExtension < countExtensions; iExtension++) + { + wchar_t fileName[MAX_LONGPATH]; + wcscpy_s(fileName, MAX_LONGPATH, W("\\")); // So that we don't match other files that end with the current file name + wcscat_s(fileName, MAX_LONGPATH, fileNameWithoutExtension); + wcscat_s(fileName, MAX_LONGPATH, rgTPAExtensions[iExtension] + 1); + wcscat_s(fileName, MAX_LONGPATH, W(";")); // So that we don't match other files that begin with the current file name + + if (wcsstr(m_tpaList.CStr(), fileName)) + { + return true; + } + } + return false; + } + + void RemoveExtensionAndNi(_In_z_ wchar_t* fileName) + { + // Remove extension, if it exists + wchar_t* extension = wcsrchr(fileName, W('.')); + if (extension != NULL) + { + extension[0] = W('\0'); + + // Check for .ni + size_t len = wcslen(fileName); + if (len > 3 && + fileName[len - 1] == W('i') && + fileName[len - 2] == W('n') && + fileName[len - 3] == W('.') ) + { + fileName[len - 3] = W('\0'); + } + } + } + + void AddFilesFromDirectoryToTPAList(_In_z_ wchar_t* targetPath, _In_reads_(countExtensions) wchar_t** rgTPAExtensions, int countExtensions) + { + *m_log << W("Adding assemblies from ") << targetPath << W(" to the TPA list") << Logger::endl; + wchar_t assemblyPath[MAX_LONGPATH]; + + for (int iExtension = 0; iExtension < countExtensions; iExtension++) + { + wcscpy_s(assemblyPath, MAX_LONGPATH, targetPath); + + const size_t dirLength = wcslen(targetPath); + wchar_t* const fileNameBuffer = assemblyPath + dirLength; + const size_t fileNameBufferSize = MAX_LONGPATH - dirLength; + + wcscat_s(assemblyPath, rgTPAExtensions[iExtension]); + WIN32_FIND_DATA data; + HANDLE findHandle = FindFirstFile(assemblyPath, &data); + + if (findHandle != INVALID_HANDLE_VALUE) { + do { + if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + // It seems that CoreCLR doesn't always use the first instance of an assembly on the TPA list (ni's may be preferred + // over il, even if they appear later). So, only include the first instance of a simple assembly name to allow + // users the opportunity to override Framework assemblies by placing dlls in %CORE_LIBRARIES% + + // ToLower for case-insensitive comparisons + wchar_t* fileNameChar = data.cFileName; + while (*fileNameChar) + { + *fileNameChar = towlower(*fileNameChar); + fileNameChar++; + } + + // Remove extension + wchar_t fileNameWithoutExtension[MAX_LONGPATH]; + wcscpy_s(fileNameWithoutExtension, MAX_LONGPATH, data.cFileName); + + RemoveExtensionAndNi(fileNameWithoutExtension); + + // Add to the list if not already on it + if (!TPAListContainsFile(fileNameWithoutExtension, rgTPAExtensions, countExtensions)) + { + const size_t fileLength = wcslen(data.cFileName); + const size_t assemblyPathLength = dirLength + fileLength; + wcsncpy_s(fileNameBuffer, fileNameBufferSize, data.cFileName, fileLength); + m_tpaList.Append(assemblyPath, assemblyPathLength); + m_tpaList.Append(W(";"), 1); + } + else + { + *m_log << W("Not adding ") << targetPath << data.cFileName << W(" to the TPA list because another file with the same name is already present on the list") << Logger::endl; + } + } + } while (0 != FindNextFile(findHandle, &data)); + + FindClose(findHandle); + } + } + } + + // Returns the semicolon-separated list of paths to runtime dlls that are considered trusted. + // On first call, scans the coreclr directory for dlls and adds them all to the list. + const wchar_t * GetTpaList() { + if (!m_tpaList.CStr()) { + wchar_t *rgTPAExtensions[] = { + W("*.ni.dll"), // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir + W("*.dll"), + W("*.ni.exe"), + W("*.exe"), + }; + + // Add files from %CORE_LIBRARIES% if specified + wchar_t coreLibraries[MAX_LONGPATH]; + size_t outSize; + if (_wgetenv_s(&outSize, coreLibraries, MAX_LONGPATH, W("CORE_LIBRARIES")) == 0 && outSize > 0) + { + wcscat_s(coreLibraries, MAX_LONGPATH, W("\\")); + AddFilesFromDirectoryToTPAList(coreLibraries, rgTPAExtensions, _countof(rgTPAExtensions)); + } + else + { + *m_log << W("CORE_LIBRARIES not set; skipping") << Logger::endl; + *m_log << W("You can set the environment variable CORE_LIBRARIES to point to a") << Logger::endl; + *m_log << W("path containing additional platform assemblies,") << Logger::endl; + } + + AddFilesFromDirectoryToTPAList(m_coreCLRDirectoryPath, rgTPAExtensions, _countof(rgTPAExtensions)); + } + + return m_tpaList.CStr(); + } + + // Returns the path to the host module + const wchar_t * GetHostPath() const { + return m_hostPath; + } + + // Returns the path to the host module + const wchar_t * GetHostExeName() const { + return m_hostExeName; + } + + // Returns the ICLRRuntimeHost2 instance, loading it from CoreCLR.dll if necessary, or nullptr on failure. + ICLRRuntimeHost2* GetCLRRuntimeHost() { + if (!m_CLRRuntimeHost) { + + if (!m_coreCLRModule) { + *m_log << W("Unable to load ") << coreCLRDll << Logger::endl; + return nullptr; + } + + *m_log << W("Finding GetCLRRuntimeHost(...)") << Logger::endl; + + FnGetCLRRuntimeHost pfnGetCLRRuntimeHost = + (FnGetCLRRuntimeHost)::GetProcAddress(m_coreCLRModule, "GetCLRRuntimeHost"); + + if (!pfnGetCLRRuntimeHost) { + *m_log << W("Failed to find function GetCLRRuntimeHost in ") << coreCLRDll << Logger::endl; + return nullptr; + } + + *m_log << W("Calling GetCLRRuntimeHost(...)") << Logger::endl; + + HRESULT hr = pfnGetCLRRuntimeHost(IID_ICLRRuntimeHost2, (IUnknown**)&m_CLRRuntimeHost); + if (FAILED(hr)) { + *m_log << W("Failed to get ICLRRuntimeHost2 interface. ERRORCODE: ") << hr << Logger::endl; + return nullptr; + } + } + + return m_CLRRuntimeHost; + } + + +}; + +bool TryRun(const int argc, const wchar_t* argv[], Logger &log, const bool verbose, const bool waitForDebugger, DWORD &exitCode, _In_z_ wchar_t* programPath) +{ + // Assume failure + exitCode = -1; + + HostEnvironment hostEnvironment(&log); + + wchar_t appPath[MAX_LONGPATH] = W(""); + wchar_t appNiPath[MAX_LONGPATH * 2] = W(""); + wchar_t managedAssemblyFullName[MAX_LONGPATH] = W(""); + + wchar_t* filePart = NULL; + + if (!::GetFullPathName(programPath, MAX_LONGPATH, appPath, &filePart)) { + log << W("Failed to get full path: ") << programPath << Logger::endl; + log << W("Error code: ") << GetLastError() << Logger::endl; + return false; + } + + wcscpy_s(managedAssemblyFullName, appPath); + + *(filePart) = W('\0'); + + log << W("Loading: ") << managedAssemblyFullName << Logger::endl; + + wcscpy_s(appNiPath, appPath); + wcscat_s(appNiPath, MAX_LONGPATH * 2, W(";")); + wcscat_s(appNiPath, MAX_LONGPATH * 2, appPath); + + // Construct native search directory paths + wchar_t nativeDllSearchDirs[MAX_LONGPATH * 3]; + + wcscpy_s(nativeDllSearchDirs, appPath); + wchar_t coreLibraries[MAX_LONGPATH]; + size_t outSize; + if (_wgetenv_s(&outSize, coreLibraries, MAX_LONGPATH, W("CORE_LIBRARIES")) == 0 && outSize > 0) + { + wcscat_s(nativeDllSearchDirs, MAX_LONGPATH * 3, W(";")); + wcscat_s(nativeDllSearchDirs, MAX_LONGPATH * 3, coreLibraries); + } + wcscat_s(nativeDllSearchDirs, MAX_LONGPATH * 3, W(";")); + wcscat_s(nativeDllSearchDirs, MAX_LONGPATH * 3, hostEnvironment.m_coreCLRDirectoryPath); + + // Start the CoreCLR + + ICLRRuntimeHost2 *host = hostEnvironment.GetCLRRuntimeHost(); + if (!host) { + return false; + } + + HRESULT hr; + + log << W("Setting ICLRRuntimeHost2 startup flags") << Logger::endl; + + // Default startup flags + hr = host->SetStartupFlags((STARTUP_FLAGS) + (STARTUP_FLAGS::STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN | + STARTUP_FLAGS::STARTUP_SINGLE_APPDOMAIN | + STARTUP_FLAGS::STARTUP_CONCURRENT_GC)); + if (FAILED(hr)) { + log << W("Failed to set startup flags. ERRORCODE: ") << hr << Logger::endl; + return false; + } + + log << W("Starting ICLRRuntimeHost2") << Logger::endl; + + hr = host->Start(); + if (FAILED(hr)) { + log << W("Failed to start CoreCLR. ERRORCODE: ") << hr << Logger:: endl; + return false; + } + + //------------------------------------------------------------- + + // Create an AppDomain + + // Allowed property names: + // APPBASE + // - The base path of the application from which the exe and other assemblies will be loaded + // + // TRUSTED_PLATFORM_ASSEMBLIES + // - The list of complete paths to each of the fully trusted assemblies + // + // APP_PATHS + // - The list of paths which will be probed by the assembly loader + // + // APP_NI_PATHS + // - The list of additional paths that the assembly loader will probe for ngen images + // + // NATIVE_DLL_SEARCH_DIRECTORIES + // - The list of paths that will be probed for native DLLs called by PInvoke + // + const wchar_t *property_keys[] = { + W("TRUSTED_PLATFORM_ASSEMBLIES"), + W("APP_PATHS"), + W("APP_NI_PATHS"), + W("NATIVE_DLL_SEARCH_DIRECTORIES"), + W("AppDomainCompatSwitch") + }; + const wchar_t *property_values[] = { + // TRUSTED_PLATFORM_ASSEMBLIES + hostEnvironment.GetTpaList(), + // APP_PATHS + appPath, + // APP_NI_PATHS + appNiPath, + // NATIVE_DLL_SEARCH_DIRECTORIES + nativeDllSearchDirs, + // AppDomainCompatSwitch + W("UseLatestBehaviorWhenTFMNotSpecified") + }; + + + log << W("Creating an AppDomain") << Logger::endl; + log << W("TRUSTED_PLATFORM_ASSEMBLIES=") << property_values[0] << Logger::endl; + log << W("APP_PATHS=") << property_values[1] << Logger::endl; + log << W("APP_NI_PATHS=") << property_values[2] << Logger::endl; + log << W("NATIVE_DLL_SEARCH_DIRECTORIES=") << property_values[3] << Logger::endl; + + DWORD domainId; + + hr = host->CreateAppDomainWithManager( + hostEnvironment.GetHostExeName(), // The friendly name of the AppDomain + // Flags: + // APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS + // - By default CoreCLR only allows platform neutral assembly to be run. To allow + // assemblies marked as platform specific, include this flag + // + // APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP + // - Allows sandboxed applications to make P/Invoke calls and use COM interop + // + // APPDOMAIN_SECURITY_SANDBOXED + // - Enables sandboxing. If not set, the app is considered full trust + // + // APPDOMAIN_IGNORE_UNHANDLED_EXCEPTION + // - Prevents the application from being torn down if a managed exception is unhandled + // + APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS | + APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP | + APPDOMAIN_DISABLE_TRANSPARENCY_ENFORCEMENT, + NULL, // Name of the assembly that contains the AppDomainManager implementation + NULL, // The AppDomainManager implementation type name + sizeof(property_keys)/sizeof(wchar_t*), // The number of properties + property_keys, + property_values, + &domainId); + + if (FAILED(hr)) { + log << W("Failed call to CreateAppDomainWithManager. ERRORCODE: ") << hr << Logger::endl; + return false; + } + + if(waitForDebugger) + { + if(!IsDebuggerPresent()) + { + log << W("Waiting for the debugger to attach. Press any key to continue ...") << Logger::endl; + getchar(); + if (IsDebuggerPresent()) + { + log << "Debugger is attached." << Logger::endl; + } + else + { + log << "Debugger failed to attach." << Logger::endl; + } + } + } + + hr = host->ExecuteAssembly(domainId, managedAssemblyFullName, argc, (argc)?&(argv[0]):NULL, &exitCode); + if (FAILED(hr)) { + log << W("Failed call to ExecuteAssembly. ERRORCODE: ") << hr << Logger::endl; + return false; + } + + log << W("App exit value = ") << exitCode << Logger::endl; + + + //------------------------------------------------------------- + + // Unload the AppDomain + + log << W("Unloading the AppDomain") << Logger::endl; + + hr = host->UnloadAppDomain( + domainId, + true); // Wait until done + + if (FAILED(hr)) { + log << W("Failed to unload the AppDomain. ERRORCODE: ") << hr << Logger::endl; + return false; + } + + + //------------------------------------------------------------- + + // Stop the host + + log << W("Stopping the host") << Logger::endl; + + hr = host->Stop(); + + if (FAILED(hr)) { + log << W("Failed to stop the host. ERRORCODE: ") << hr << Logger::endl; + return false; + } + + //------------------------------------------------------------- + + // Release the reference to the host + + log << W("Releasing ICLRRuntimeHost2") << Logger::endl; + + host->Release(); + + return true; + +} + +void showHelp() { + ::wprintf( + W("Runs executables on CoreCLR\r\n") + W("\r\n") + W("USAGE: <program>.exe [/_d] [/_v]\r\n") + W("\r\n") + W(" Runs <program>.dll managed program on CoreCLR.\r\n") + W(" /_v causes verbose output to be written to the console.\r\n") + W(" /_d causes the process to wait for a debugger to attach before starting.\r\n") + W("\r\n") + W(" CoreCLR is searched for in %%core_root%%, then in the directory that this executable is in.\r\n") + W(" The program dll needs to be in the same directory as this executable.\r\n") + W(" The program dll needs to have main entry point.\r\n") + ); +} + +static wchar_t programPath[MAX_LONGPATH]; + +int __cdecl wmain(const int argc, const wchar_t* argv[]) +{ + DWORD dwModuleFileName = GetModuleFileName(NULL, programPath, MAX_LONGPATH); + if (dwModuleFileName == 0 || dwModuleFileName >= MAX_LONGPATH) { + ::wprintf(W("Failed to get the path to the current executable")); + return -1; + } + auto extension = wcsrchr(programPath, '.'); + if (extension == NULL || (wcscmp(extension, L".exe") != 0)) { + ::wprintf(W("This executable needs to have 'exe' extension")); + return -1; + } + + // Change the extension from ".exe" to ".dll" + extension[1] = 'd'; + extension[2] = 'l'; + extension[3] = 'l'; + + // Parse the options from the command line + bool verbose = false; + bool waitForDebugger = false; + bool helpRequested = false; + int newArgc = argc - 1; + const wchar_t **newArgv = argv + 1; + + auto stringsEqual = [](const wchar_t * const a, const wchar_t * const b) -> bool { + return ::_wcsicmp(a, b) == 0; + }; + + auto tryParseOption = [&](const wchar_t* arg) -> bool { + if ( stringsEqual(arg, W("/_v")) || stringsEqual(arg, W("-_v")) ) { + verbose = true; + return true; + } else if ( stringsEqual(arg, W("/_d")) || stringsEqual(arg, W("-_d")) ) { + waitForDebugger = true; + return true; + } else if ( stringsEqual(arg, W("/_h")) || stringsEqual(arg, W("-_h")) ) { + helpRequested = true; + return true; + } else { + return false; + } + }; + + while (newArgc > 0 && tryParseOption(newArgv[0])) { + newArgc--; + newArgv++; + } + + if (helpRequested) { + showHelp(); + return -1; + } + else { + Logger log; + if (verbose) { + log.Enable(); + } + else { + log.Disable(); + } + + DWORD exitCode; + auto success = TryRun(newArgc, newArgv, log, verbose, waitForDebugger, exitCode, programPath); + + log << W("Execution ") << (success ? W("succeeded") : W("failed")) << Logger::endl; + + return exitCode; + } +} diff --git a/src/coreclr/hosts/coreconsole/logger.cpp b/src/coreclr/hosts/coreconsole/logger.cpp new file mode 100644 index 0000000000..7c13c9b547 --- /dev/null +++ b/src/coreclr/hosts/coreconsole/logger.cpp @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include <stdio.h> +#include <windows.h> +#include <Logger.h> +#include "palclr.h" + +void Logger::Enable() { + m_isEnabled = true; +} + +void Logger::Disable() { + m_isEnabled = false; +} + +void print(const wchar_t *val) { + // If val is longer than 2048 characters, wprintf will refuse to print it. + // So write it in chunks. + + const size_t chunkSize = 1024; + + wchar_t chunk[chunkSize]; + + auto valLength = ::wcslen(val); + + for (size_t i = 0 ; i < valLength ; i += chunkSize) { + + ::wcsncpy_s(chunk, chunkSize, val + i, _TRUNCATE); + + ::wprintf(W("%s"), chunk); + } +} + +Logger& Logger::operator<< (bool val) { + if (m_isEnabled) { + if (val) { + EnsurePrefixIsPrinted(); + print(W("true")); + } else { + EnsurePrefixIsPrinted(); + print(W("false")); + } + } + return *this; +} + +Logger& Logger::operator<< (int val) { + + if (m_isEnabled) { + EnsurePrefixIsPrinted(); + ::wprintf(W("%d"), val); + } + + return *this; +} + +#ifdef _MSC_VER +Logger& Logger::operator<< (long val) { + if (m_isEnabled) { + EnsurePrefixIsPrinted(); + ::wprintf(W("%d"), val); + } + return *this; +} + +Logger& Logger::operator<< (unsigned long val) { + if (m_isEnabled) { + EnsurePrefixIsPrinted(); + ::wprintf(W("%d"), val); + } + return *this; +} +#endif + +Logger& Logger::operator<< (const wchar_t *val) { + if (m_isEnabled) { + EnsurePrefixIsPrinted(); + print(val); + } + return *this; +} + +Logger& Logger::operator<< (Logger& ( *pf )(Logger&)) { + if (m_isEnabled) { + return pf(*this); + } else { + return *this; + } +} + +void Logger::EnsurePrefixIsPrinted() { + if (this->m_isEnabled && this->m_prefixRequired) { + print(W(" HOSTLOG: ")); + m_prefixRequired = false; + } +} + +// Manipulators + +// Newline +Logger& Logger::endl (Logger& log) { + if (log.m_isEnabled) { + log.EnsurePrefixIsPrinted(); + print(W("\r\n")); + log.m_prefixRequired = true; + log.m_formatHRESULT = false; + } + return log; +} + diff --git a/src/coreclr/hosts/coreconsole/logger.h b/src/coreclr/hosts/coreconsole/logger.h new file mode 100644 index 0000000000..af54205396 --- /dev/null +++ b/src/coreclr/hosts/coreconsole/logger.h @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// Logger for the CoreCLR host. +// Relies on the SYSCRT and therefore cannot use C++ libraries. +// + +class Logger { + bool m_isEnabled; + bool m_prefixRequired; + bool m_formatHRESULT; + +public: + Logger() : + m_isEnabled(true), + m_prefixRequired(true), + m_formatHRESULT(false) { } + + ~Logger() { } + + // Enables output from the logger + void Enable(); + + // Disables output from the logger + void Disable(); + + + Logger& operator<< (bool val); + Logger& operator<< (short val); + Logger& operator<< (unsigned short val); + Logger& operator<< (int val); + Logger& operator<< (unsigned int val); +#ifdef _MSC_VER + Logger& operator<< (long val); + Logger& operator<< (unsigned long val); +#endif + Logger& operator<< (float val); + Logger& operator<< (double val); + Logger& operator<< (long double val); + Logger& operator<< (const wchar_t* val); + Logger& operator<< (Logger& ( *pf )(Logger&)); + static Logger& endl ( Logger& log ); + static Logger& hresult ( Logger& log); + +private: + void EnsurePrefixIsPrinted(); +}; + + + + + diff --git a/src/coreclr/hosts/coreconsole/native.rc b/src/coreclr/hosts/coreconsole/native.rc new file mode 100644 index 0000000000..66900223e0 --- /dev/null +++ b/src/coreclr/hosts/coreconsole/native.rc @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#define FX_VER_FILEDESCRIPTION_STR "Microsoft CoreCLR Program launcher\0" + +#include <fxver.h> +#include <fxver.rc> diff --git a/src/coreclr/hosts/corerun/.gitmirror b/src/coreclr/hosts/corerun/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/coreclr/hosts/corerun/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/coreclr/hosts/corerun/CMakeLists.txt b/src/coreclr/hosts/corerun/CMakeLists.txt new file mode 100644 index 0000000000..6868bb12c3 --- /dev/null +++ b/src/coreclr/hosts/corerun/CMakeLists.txt @@ -0,0 +1,38 @@ +project(CoreRun) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CoreRun_SOURCES corerun.cpp logger.cpp) +set(CoreRun_RESOURCES native.rc) + +add_definitions(-DFX_VER_INTERNALNAME_STR=CoreRun.exe) + +if(CLR_CMAKE_PLATFORM_UNIX) + # This does not compile on Linux yet + if(CAN_BE_COMPILED_ON_LINUX) + _add_executable(CoreRun + ${CoreRun_SOURCES} + ${CoreRun_RESOURCES} + ) + endif(CAN_BE_COMPILED_ON_LINUX) + +else() + _add_executable(CoreRun + ${CoreRun_SOURCES} + ${CoreRun_RESOURCES} + ) + + target_link_libraries(CoreRun + utilcodestaticnohost + advapi32.lib + oleaut32.lib + uuid.lib + user32.lib + ${STATIC_MT_CRT_LIB} + ${STATIC_MT_VCRT_LIB} + ) + + # Can't compile on linux yet so only add for windows + install_clr(CoreRun) + +endif(CLR_CMAKE_PLATFORM_UNIX)
\ No newline at end of file diff --git a/src/coreclr/hosts/corerun/coreRun.nativeproj b/src/coreclr/hosts/corerun/coreRun.nativeproj new file mode 100644 index 0000000000..87b64a5408 --- /dev/null +++ b/src/coreclr/hosts/corerun/coreRun.nativeproj @@ -0,0 +1,45 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="dogfood"> + <!--*****************************************************--> + <!--This MSBuild project file was automatically generated--> + <!--from the original SOURCES/DIRS file by the KBC tool.--> + <!--*****************************************************--> + <!--Import the settings--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + + <!--Leaf project Properties--> + <PropertyGroup> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + <OutputName>CoreRun</OutputName> + <TargetType>PROGRAM</TargetType> + <LinkSubsystem>console</LinkSubsystem> + <EntryPoint>wmain</EntryPoint> + <ClAdditionalOptions>$(ClAdditionalOptions) -DUNICODE -D_UNICODE</ClAdditionalOptions> + <IsTestNetHost>true</IsTestNetHost> + </PropertyGroup> + + <ItemGroup> + <LinkPreCrtLibs Include="$(ClrLibPath)\utilcodestaticnohost.lib" /> + <ProjectReference Include="$(ClrSrcDirectory)utilcode\staticnohost\staticnohost.nativeproj" /> + </ItemGroup> + <ItemGroup> + <TargetLib Include="$(CoreSystemCrt)" /> + <TargetLib Condition="'$(BuildForWindows7)'=='true'" Include="$(SdkLibPath)\mincore_fw.lib" /> + <TargetLib Condition="'$(BuildForWindows7)'=='true'" Include="$(SdkLibPath)\oleaut32.lib" /> + <TargetLib Condition="'$(BuildForWindows7)'!='true'" Include="$(SdkLibPath)\mincore.lib" /> + <TargetLib Condition="'$(BuildForWindows7)'!='true'" Include="$(SdkLibPath)\mincore_legacy.lib" /> + <TargetLib Condition="'$(BuildForWindows7)'!='true'" Include="$(SdkLibPath)\mincore_private.lib" /> + <TargetLib Condition="'$(BuildForWindows7)'!='true'" Include="$(SdkLibPath)\mincore_obsolete.lib" /> + <TargetLib Include="$(SdkLibPath)\uuid.lib" /> + </ItemGroup> + + <ItemGroup> + <RCResourceFile Include="native.rc" /> + </ItemGroup> + <ItemGroup> + <CppCompile Include="coreRun.cpp" /> + <CppCompile Include="logger.cpp" /> + </ItemGroup> + <!--Import the targets--> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.targets" /> +</Project> diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp new file mode 100644 index 0000000000..e7ddab21e9 --- /dev/null +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -0,0 +1,691 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +// +// .A simple CoreCLR host that runs on CoreSystem. +// + +#include "windows.h" +#include <stdio.h> +#include "mscoree.h" +#include <Logger.h> +#include "palclr.h" +#include "sstring.h" + +// Utility macro for testing whether or not a flag is set. +#define HAS_FLAG(value, flag) (((value) & (flag)) == (flag)) + +// Environment variable for setting whether or not to use Server GC. +// Off by default. +static const wchar_t *serverGcVar = W("CORECLR_SERVER_GC"); + +// Environment variable for setting whether or not to use Concurrent GC. +// On by default. +static const wchar_t *concurrentGcVar = W("CORECLR_CONCURRENT_GC"); + +// The name of the CoreCLR native runtime DLL. +static const wchar_t *coreCLRDll = W("CoreCLR.dll"); + +// The location where CoreCLR is expected to be installed. If CoreCLR.dll isn't +// found in the same directory as the host, it will be looked for here. +static const wchar_t *coreCLRInstallDirectory = W("%windir%\\system32\\"); + +// Encapsulates the environment that CoreCLR will run in, including the TPALIST +class HostEnvironment +{ + // The path to this module + PathString m_hostPath; + + // The path to the directory containing this module + PathString m_hostDirectoryPath; + + // The name of this module, without the path + const wchar_t *m_hostExeName; + + // The list of paths to the assemblies that will be trusted by CoreCLR + SString m_tpaList; + + ICLRRuntimeHost2* m_CLRRuntimeHost; + + HMODULE m_coreCLRModule; + + Logger *m_log; + + + // Attempts to load CoreCLR.dll from the given directory. + // On success pins the dll, sets m_coreCLRDirectoryPath and returns the HMODULE. + // On failure returns nullptr. + HMODULE TryLoadCoreCLR(const wchar_t* directoryPath) { + + StackSString coreCLRPath(directoryPath); + coreCLRPath.Append(coreCLRDll); + + *m_log << W("Attempting to load: ") << coreCLRPath.GetUnicode() << Logger::endl; + + HMODULE result = WszLoadLibraryEx(coreCLRPath, NULL, 0); + if (!result) { + *m_log << W("Failed to load: ") << coreCLRPath.GetUnicode() << Logger::endl; + *m_log << W("Error code: ") << GetLastError() << Logger::endl; + return nullptr; + } + + // Pin the module - CoreCLR.dll does not support being unloaded. + HMODULE dummy_coreCLRModule; + if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, coreCLRPath, &dummy_coreCLRModule)) { + *m_log << W("Failed to pin: ") << coreCLRPath.GetUnicode() << Logger::endl; + return nullptr; + } + + StackSString coreCLRLoadedPath; + WszGetModuleFileName(result, coreCLRLoadedPath); + + *m_log << W("Loaded: ") << coreCLRLoadedPath.GetUnicode() << Logger::endl; + + return result; + } + +public: + // The path to the directory that CoreCLR is in + PathString m_coreCLRDirectoryPath; + + HostEnvironment(Logger *logger) + : m_log(logger), m_CLRRuntimeHost(nullptr) { + + // Discover the path to this exe's module. All other files are expected to be in the same directory. + WszGetModuleFileName(::GetModuleHandleW(nullptr), m_hostPath); + + // Search for the last backslash in the host path. + SString::CIterator lastBackslash = m_hostPath.End(); + m_hostPath.FindBack(lastBackslash, W('\\')); + + // Copy the directory path + m_hostDirectoryPath.Set(m_hostPath, m_hostPath.Begin(), lastBackslash + 1); + + // Save the exe name + m_hostExeName = m_hostPath.GetUnicode(lastBackslash + 1); + + *m_log << W("Host directory: ") << m_hostDirectoryPath.GetUnicode() << Logger::endl; + + // Check for %CORE_ROOT% and try to load CoreCLR.dll from it if it is set + StackSString coreRoot; + m_coreCLRModule = NULL; // Initialize this here since we don't call TryLoadCoreCLR if CORE_ROOT is unset. + if (WszGetEnvironmentVariable(W("CORE_ROOT"), coreRoot) > 0 && coreRoot.GetCount() > 0) + { + coreRoot.Append(W('\\')); + m_coreCLRModule = TryLoadCoreCLR(coreRoot); + } + else + { + *m_log << W("CORE_ROOT not set; skipping") << Logger::endl; + *m_log << W("You can set the environment variable CORE_ROOT to point to the path") << Logger::endl; + *m_log << W("where CoreCLR.dll lives to help CoreRun.exe find it.") << Logger::endl; + } + + // Try to load CoreCLR from the directory that coreRun is in + if (!m_coreCLRModule) { + m_coreCLRModule = TryLoadCoreCLR(m_hostDirectoryPath); + } + + if (!m_coreCLRModule) { + + // Failed to load. Try to load from the well-known location. + wchar_t coreCLRInstallPath[MAX_LONGPATH]; + ::ExpandEnvironmentStringsW(coreCLRInstallDirectory, coreCLRInstallPath, MAX_LONGPATH); + m_coreCLRModule = TryLoadCoreCLR(coreCLRInstallPath); + + } + + if (m_coreCLRModule) { + + // Save the directory that CoreCLR was found in + DWORD modulePathLength = WszGetModuleFileName(m_coreCLRModule, m_coreCLRDirectoryPath); + + // Search for the last backslash and terminate it there to keep just the directory path with trailing slash + SString::Iterator lastBackslash = m_coreCLRDirectoryPath.End(); + m_coreCLRDirectoryPath.FindBack(lastBackslash, W('\\')); + m_coreCLRDirectoryPath.Truncate(lastBackslash + 1); + + } else { + *m_log << W("Unable to load ") << coreCLRDll << Logger::endl; + } + } + + ~HostEnvironment() { + if(m_coreCLRModule) { + // Free the module. This is done for completeness, but in fact CoreCLR.dll + // was pinned earlier so this call won't actually free it. The pinning is + // done because CoreCLR does not support unloading. + ::FreeLibrary(m_coreCLRModule); + } + } + + bool TPAListContainsFile(_In_z_ wchar_t* fileNameWithoutExtension, _In_reads_(countExtensions) wchar_t** rgTPAExtensions, int countExtensions) + { + if (m_tpaList.IsEmpty()) return false; + + for (int iExtension = 0; iExtension < countExtensions; iExtension++) + { + StackSString fileName; + fileName.Append(W("\\")); // So that we don't match other files that end with the current file name + fileName.Append(fileNameWithoutExtension); + fileName.Append(rgTPAExtensions[iExtension] + 1); + fileName.Append(W(";")); // So that we don't match other files that begin with the current file name + + if (m_tpaList.Find(m_tpaList.Begin(), fileName)) + { + return true; + } + } + return false; + } + + void RemoveExtensionAndNi(_In_z_ wchar_t* fileName) + { + // Remove extension, if it exists + wchar_t* extension = wcsrchr(fileName, W('.')); + if (extension != NULL) + { + extension[0] = W('\0'); + + // Check for .ni + size_t len = wcslen(fileName); + if (len > 3 && + fileName[len - 1] == W('i') && + fileName[len - 2] == W('n') && + fileName[len - 3] == W('.') ) + { + fileName[len - 3] = W('\0'); + } + } + } + + void AddFilesFromDirectoryToTPAList(_In_z_ const wchar_t* targetPath, _In_reads_(countExtensions) wchar_t** rgTPAExtensions, int countExtensions) + { + *m_log << W("Adding assemblies from ") << targetPath << W(" to the TPA list") << Logger::endl; + StackSString assemblyPath; + const size_t dirLength = wcslen(targetPath); + + for (int iExtension = 0; iExtension < countExtensions; iExtension++) + { + assemblyPath.Set(targetPath, (DWORD)dirLength); + assemblyPath.Append(rgTPAExtensions[iExtension]); + WIN32_FIND_DATA data; + HANDLE findHandle = WszFindFirstFile(assemblyPath, &data); + + if (findHandle != INVALID_HANDLE_VALUE) { + do { + if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + // It seems that CoreCLR doesn't always use the first instance of an assembly on the TPA list (ni's may be preferred + // over il, even if they appear later). So, only include the first instance of a simple assembly name to allow + // users the opportunity to override Framework assemblies by placing dlls in %CORE_LIBRARIES% + + // ToLower for case-insensitive comparisons + wchar_t* fileNameChar = data.cFileName; + while (*fileNameChar) + { + *fileNameChar = towlower(*fileNameChar); + fileNameChar++; + } + + // Remove extension + wchar_t fileNameWithoutExtension[MAX_PATH_FNAME]; + wcscpy_s(fileNameWithoutExtension, MAX_PATH_FNAME, data.cFileName); + + RemoveExtensionAndNi(fileNameWithoutExtension); + + // Add to the list if not already on it + if (!TPAListContainsFile(fileNameWithoutExtension, rgTPAExtensions, countExtensions)) + { + assemblyPath.Truncate(assemblyPath.Begin() + (DWORD)dirLength); + assemblyPath.Append(data.cFileName); + m_tpaList.Append(assemblyPath); + m_tpaList.Append(W(';')); + } + else + { + *m_log << W("Not adding ") << targetPath << data.cFileName << W(" to the TPA list because another file with the same name is already present on the list") << Logger::endl; + } + } + } while (0 != WszFindNextFile(findHandle, &data)); + + FindClose(findHandle); + } + } + } + + // Returns the semicolon-separated list of paths to runtime dlls that are considered trusted. + // On first call, scans the coreclr directory for dlls and adds them all to the list. + const wchar_t * GetTpaList() { + if (m_tpaList.IsEmpty()) { + wchar_t *rgTPAExtensions[] = { + W("*.ni.dll"), // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir + W("*.dll"), + W("*.ni.exe"), + W("*.exe"), + W("*.ni.winmd") + W("*.winmd") + }; + + // Add files from %CORE_LIBRARIES% if specified + StackSString coreLibraries; + if (WszGetEnvironmentVariable(W("CORE_LIBRARIES"), coreLibraries) > 0 && coreLibraries.GetCount() > 0) + { + coreLibraries.Append(W('\\')); + AddFilesFromDirectoryToTPAList(coreLibraries, rgTPAExtensions, _countof(rgTPAExtensions)); + } + else + { + *m_log << W("CORE_LIBRARIES not set; skipping") << Logger::endl; + *m_log << W("You can set the environment variable CORE_LIBRARIES to point to a") << Logger::endl; + *m_log << W("path containing additional platform assemblies,") << Logger::endl; + } + + AddFilesFromDirectoryToTPAList(m_coreCLRDirectoryPath, rgTPAExtensions, _countof(rgTPAExtensions)); + } + + return m_tpaList; + } + + // Returns the path to the host module + const wchar_t * GetHostPath() { + return m_hostPath; + } + + // Returns the path to the host module + const wchar_t * GetHostExeName() { + return m_hostExeName; + } + + // Returns the ICLRRuntimeHost2 instance, loading it from CoreCLR.dll if necessary, or nullptr on failure. + ICLRRuntimeHost2* GetCLRRuntimeHost() { + if (!m_CLRRuntimeHost) { + + if (!m_coreCLRModule) { + *m_log << W("Unable to load ") << coreCLRDll << Logger::endl; + return nullptr; + } + + *m_log << W("Finding GetCLRRuntimeHost(...)") << Logger::endl; + + FnGetCLRRuntimeHost pfnGetCLRRuntimeHost = + (FnGetCLRRuntimeHost)::GetProcAddress(m_coreCLRModule, "GetCLRRuntimeHost"); + + if (!pfnGetCLRRuntimeHost) { + *m_log << W("Failed to find function GetCLRRuntimeHost in ") << coreCLRDll << Logger::endl; + return nullptr; + } + + *m_log << W("Calling GetCLRRuntimeHost(...)") << Logger::endl; + + HRESULT hr = pfnGetCLRRuntimeHost(IID_ICLRRuntimeHost2, (IUnknown**)&m_CLRRuntimeHost); + if (FAILED(hr)) { + *m_log << W("Failed to get ICLRRuntimeHost2 interface. ERRORCODE: ") << Logger::hresult << hr << Logger::endl; + return nullptr; + } + } + + return m_CLRRuntimeHost; + } + + +}; + +// Creates the startup flags for the runtime, starting with the default startup +// flags and adding or removing from them based on environment variables. Only +// two environment variables are respected right now: serverGcVar, controlling +// Server GC, and concurrentGcVar, controlling Concurrent GC. +STARTUP_FLAGS CreateStartupFlags() { + auto initialFlags = + static_cast<STARTUP_FLAGS>( + STARTUP_FLAGS::STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN | + STARTUP_FLAGS::STARTUP_SINGLE_APPDOMAIN | + STARTUP_FLAGS::STARTUP_CONCURRENT_GC); + + // server GC is off by default, concurrent GC is on by default. + auto checkVariable = [&](STARTUP_FLAGS flag, const wchar_t *var) { + wchar_t result[25]; + size_t outsize; + if (_wgetenv_s(&outsize, result, 25, var) == 0 && outsize > 0) { + // set the flag if the var is present and set to 1, + // clear the flag if the var isp resent and set to 0. + // Otherwise, ignore it. + if (_wcsicmp(result, W("1")) == 0) { + initialFlags = static_cast<STARTUP_FLAGS>(initialFlags | flag); + } else if (_wcsicmp(result, W("0")) == 0) { + initialFlags = static_cast<STARTUP_FLAGS>(initialFlags & ~flag); + } + } + }; + + checkVariable(STARTUP_FLAGS::STARTUP_SERVER_GC, serverGcVar); + checkVariable(STARTUP_FLAGS::STARTUP_CONCURRENT_GC, concurrentGcVar); + + return initialFlags; +} + +bool TryRun(const int argc, const wchar_t* argv[], Logger &log, const bool verbose, const bool waitForDebugger, DWORD &exitCode) +{ + + // Assume failure + exitCode = -1; + + HostEnvironment hostEnvironment(&log); + + //------------------------------------------------------------- + + // Find the specified exe. This is done using LoadLibrary so that + // the OS library search semantics are used to find it. + + const wchar_t* exeName = argc > 0 ? argv[0] : nullptr; + if(exeName == nullptr) + { + log << W("No exename specified.") << Logger::endl; + return false; + } + + StackSString appPath; + StackSString appNiPath; + StackSString managedAssemblyFullName; + StackSString appLocalWinmetadata; + + wchar_t* filePart = NULL; + + COUNT_T size = MAX_LONGPATH; + wchar_t* appPathPtr = appPath.OpenUnicodeBuffer(size - 1); + DWORD length = WszGetFullPathName(exeName, size, appPathPtr, &filePart); + if (length >= size) + { + appPath.CloseBuffer(); + size = length; + appPathPtr = appPath.OpenUnicodeBuffer(size - 1); + length = WszGetFullPathName(exeName, size, appPathPtr, &filePart); + } + if (length == 0 || length >= size) { + log << W("Failed to get full path: ") << exeName << Logger::endl; + log << W("Error code: ") << GetLastError() << Logger::endl; + return false; + } + + managedAssemblyFullName.Set(appPathPtr); + + *(filePart) = W('\0'); + appPath.CloseBuffer(DWORD(filePart - appPathPtr)); + + log << W("Loading: ") << managedAssemblyFullName.GetUnicode() << Logger::endl; + + appLocalWinmetadata.Set(appPath); + appLocalWinmetadata.Append(W("\\WinMetadata")); + + DWORD dwAttrib = WszGetFileAttributes(appLocalWinmetadata); + bool appLocalWinMDexists = dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); + + if (!appLocalWinMDexists) { + appLocalWinmetadata.Clear(); + } + appNiPath.Set(appPath); + appNiPath.Append(W("NI")); + appNiPath.Append(W(";")); + appNiPath.Append(appPath); + + // Construct native search directory paths + StackSString nativeDllSearchDirs(appPath); + StackSString coreLibraries; + if (WszGetEnvironmentVariable(W("CORE_LIBRARIES"), coreLibraries) > 0 && coreLibraries.GetCount() > 0) + { + nativeDllSearchDirs.Append(W(";")); + nativeDllSearchDirs.Append(coreLibraries); + } + nativeDllSearchDirs.Append(W(";")); + nativeDllSearchDirs.Append(hostEnvironment.m_coreCLRDirectoryPath); + + // Start the CoreCLR + + ICLRRuntimeHost2 *host = hostEnvironment.GetCLRRuntimeHost(); + if (!host) { + return false; + } + + HRESULT hr; + + + STARTUP_FLAGS flags = CreateStartupFlags(); + log << W("Setting ICLRRuntimeHost2 startup flags") << Logger::endl; + log << W("Server GC enabled: ") << HAS_FLAG(flags, STARTUP_FLAGS::STARTUP_SERVER_GC) << Logger::endl; + log << W("Concurrent GC enabled: ") << HAS_FLAG(flags, STARTUP_FLAGS::STARTUP_CONCURRENT_GC) << Logger::endl; + + // Default startup flags + hr = host->SetStartupFlags(flags); + if (FAILED(hr)) { + log << W("Failed to set startup flags. ERRORCODE: ") << Logger::hresult << hr << Logger::endl; + return false; + } + + log << W("Starting ICLRRuntimeHost2") << Logger::endl; + + hr = host->Start(); + if (FAILED(hr)) { + log << W("Failed to start CoreCLR. ERRORCODE: ") << Logger::hresult << hr << Logger:: endl; + return false; + } + + //------------------------------------------------------------- + + // Create an AppDomain + + // Allowed property names: + // APPBASE + // - The base path of the application from which the exe and other assemblies will be loaded + // + // TRUSTED_PLATFORM_ASSEMBLIES + // - The list of complete paths to each of the fully trusted assemblies + // + // APP_PATHS + // - The list of paths which will be probed by the assembly loader + // + // APP_NI_PATHS + // - The list of additional paths that the assembly loader will probe for ngen images + // + // NATIVE_DLL_SEARCH_DIRECTORIES + // - The list of paths that will be probed for native DLLs called by PInvoke + // + const wchar_t *property_keys[] = { + W("TRUSTED_PLATFORM_ASSEMBLIES"), + W("APP_PATHS"), + W("APP_NI_PATHS"), + W("NATIVE_DLL_SEARCH_DIRECTORIES"), + W("AppDomainCompatSwitch"), + W("APP_LOCAL_WINMETADATA") + }; + const wchar_t *property_values[] = { + // TRUSTED_PLATFORM_ASSEMBLIES + hostEnvironment.GetTpaList(), + // APP_PATHS + appPath, + // APP_NI_PATHS + appNiPath, + // NATIVE_DLL_SEARCH_DIRECTORIES + nativeDllSearchDirs, + // AppDomainCompatSwitch + W("UseLatestBehaviorWhenTFMNotSpecified"), + // APP_LOCAL_WINMETADATA + appLocalWinmetadata + }; + + log << W("Creating an AppDomain") << Logger::endl; + for (int idx = 0; idx < sizeof(property_keys) / sizeof(wchar_t*); idx++) + { + log << property_keys[idx] << W("=") << property_values[idx] << Logger::endl; + } + + DWORD domainId; + + hr = host->CreateAppDomainWithManager( + hostEnvironment.GetHostExeName(), // The friendly name of the AppDomain + // Flags: + // APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS + // - By default CoreCLR only allows platform neutral assembly to be run. To allow + // assemblies marked as platform specific, include this flag + // + // APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP + // - Allows sandboxed applications to make P/Invoke calls and use COM interop + // + // APPDOMAIN_SECURITY_SANDBOXED + // - Enables sandboxing. If not set, the app is considered full trust + // + // APPDOMAIN_IGNORE_UNHANDLED_EXCEPTION + // - Prevents the application from being torn down if a managed exception is unhandled + // + APPDOMAIN_ENABLE_PLATFORM_SPECIFIC_APPS | + APPDOMAIN_ENABLE_PINVOKE_AND_CLASSIC_COMINTEROP | + APPDOMAIN_DISABLE_TRANSPARENCY_ENFORCEMENT, + NULL, // Name of the assembly that contains the AppDomainManager implementation + NULL, // The AppDomainManager implementation type name + sizeof(property_keys)/sizeof(wchar_t*), // The number of properties + property_keys, + property_values, + &domainId); + + if (FAILED(hr)) { + log << W("Failed call to CreateAppDomainWithManager. ERRORCODE: ") << Logger::hresult << hr << Logger::endl; + return false; + } + + if(waitForDebugger) + { + if(!IsDebuggerPresent()) + { + log << W("Waiting for the debugger to attach. Press any key to continue ...") << Logger::endl; + getchar(); + if (IsDebuggerPresent()) + { + log << "Debugger is attached." << Logger::endl; + } + else + { + log << "Debugger failed to attach." << Logger::endl; + } + } + } + + hr = host->ExecuteAssembly(domainId, managedAssemblyFullName, argc-1, (argc-1)?&(argv[1]):NULL, &exitCode); + if (FAILED(hr)) { + log << W("Failed call to ExecuteAssembly. ERRORCODE: ") << Logger::hresult << hr << Logger::endl; + return false; + } + + log << W("App exit value = ") << exitCode << Logger::endl; + + + //------------------------------------------------------------- + + // Unload the AppDomain + + log << W("Unloading the AppDomain") << Logger::endl; + + hr = host->UnloadAppDomain( + domainId, + true); // Wait until done + + if (FAILED(hr)) { + log << W("Failed to unload the AppDomain. ERRORCODE: ") << Logger::hresult << hr << Logger::endl; + return false; + } + + + //------------------------------------------------------------- + + // Stop the host + + log << W("Stopping the host") << Logger::endl; + + hr = host->Stop(); + + if (FAILED(hr)) { + log << W("Failed to stop the host. ERRORCODE: ") << Logger::hresult << hr << Logger::endl; + return false; + } + + //------------------------------------------------------------- + + // Release the reference to the host + + log << W("Releasing ICLRRuntimeHost2") << Logger::endl; + + host->Release(); + + return true; + +} + +void showHelp() { + ::wprintf( + W("Runs executables on CoreCLR\r\n") + W("\r\n") + W("USAGE: coreRun [/d] [/v] Managed.exe\r\n") + W("\r\n") + W(" where Managed.exe is a managed executable built for CoreCLR\r\n") + W(" /v causes verbose output to be written to the console\r\n") + W(" /d causes coreRun to wait for a debugger to attach before\r\n") + W(" launching Managed.exe\r\n") + W("\r\n") + W(" CoreCLR is searched for in %%core_root%%, then in the directory\r\n") + W(" that coreRun.exe is in, then finally in %s.\r\n"), + coreCLRInstallDirectory + ); +} + +int __cdecl wmain(const int argc, const wchar_t* argv[]) +{ + // Parse the options from the command line + + bool verbose = false; + bool waitForDebugger = false; + bool helpRequested = false; + int newArgc = argc - 1; + const wchar_t **newArgv = argv + 1; + + auto stringsEqual = [](const wchar_t * const a, const wchar_t * const b) -> bool { + return ::_wcsicmp(a, b) == 0; + }; + + auto tryParseOption = [&](const wchar_t* arg) -> bool { + if ( stringsEqual(arg, W("/v")) || stringsEqual(arg, W("-v")) ) { + verbose = true; + return true; + } else if ( stringsEqual(arg, W("/d")) || stringsEqual(arg, W("-d")) ) { + waitForDebugger = true; + return true; + } else if ( stringsEqual(arg, W("/?")) || stringsEqual(arg, W("-?")) || stringsEqual(arg, W("-h")) || stringsEqual(arg, W("--help")) ) { + helpRequested = true; + return true; + } else { + return false; + } + }; + + while (newArgc > 0 && tryParseOption(newArgv[0])) { + newArgc--; + newArgv++; + } + + if (argc < 2 || helpRequested || newArgc==0) { + showHelp(); + return -1; + } else { + Logger log; + if (verbose) { + log.Enable(); + } else { + log.Disable(); + } + + DWORD exitCode; + auto success = TryRun(newArgc, newArgv, log, verbose, waitForDebugger, exitCode); + + log << W("Execution ") << (success ? W("succeeded") : W("failed")) << Logger::endl; + + return exitCode; + } +} diff --git a/src/coreclr/hosts/corerun/logger.cpp b/src/coreclr/hosts/corerun/logger.cpp new file mode 100644 index 0000000000..95eb7c9561 --- /dev/null +++ b/src/coreclr/hosts/corerun/logger.cpp @@ -0,0 +1,269 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + + +#include <stdio.h> +#include <windows.h> +#include <Logger.h> +#include "palclr.h" + +void Logger::Enable() { + m_isEnabled = true; +} + +void Logger::Disable() { + m_isEnabled = false; +} + +void print(const wchar_t *val) { + // If val is longer than 2048 characters, wprintf will refuse to print it. + // So write it in chunks. + + const size_t chunkSize = 1024; + + wchar_t chunk[chunkSize]; + + auto valLength = ::wcslen(val); + + for (size_t i = 0 ; i < valLength ; i += chunkSize) { + + ::wcsncpy_s(chunk, chunkSize, val + i, _TRUNCATE); + + ::wprintf(W("%s"), chunk); + } +} + +Logger& Logger::operator<< (bool val) { + if (m_isEnabled) { + if (val) { + EnsurePrefixIsPrinted(); + print(W("true")); + } else { + EnsurePrefixIsPrinted(); + print(W("false")); + } + } + return *this; +} +void PrintAsHResult(int val) { + const wchar_t * str = nullptr; + + switch (val) { + case 0x00000000: str = W("S_OK"); break; + case 0x00000001: str = W("S_FALSE"); break; + case 0x8000000B: str = W("E_BOUNDS"); break; + case 0x8000000C: str = W("E_CHANGED_STATE"); break; + case 0x80000013: str = W("RO_E_CLOSED"); break; + case 0x8000211D: str = W("COR_E_AMBIGUOUSMATCH"); break; + case 0x80004001: str = W("E_NOTIMPL"); break; + case 0x80004002: str = W("COR_E_INVALIDCAST"); break; + //case 0x80004002: str = W("E_NOINTERFACE"); break; + case 0x80004003: str = W("COR_E_NULLREFERENCE"); break; + //case 0x80004003: str = W("E_POINTER"); break; + case 0x80004004: str = W("E_ABORT"); break; + case 0x80004005: str = W("E_FAIL"); break; + case 0x8000FFFF: str = W("E_UNEXPECTED"); break; + case 0x8002000a: str = W("DISP_E_OVERFLOW"); break; + case 0x8002000e: str = W("COR_E_TARGETPARAMCOUNT"); break; + case 0x80020012: str = W("COR_E_DIVIDEBYZERO"); break; + case 0x80028ca0: str = W("TYPE_E_TYPEMISMATCH"); break; + case 0x80070005: str = W("COR_E_UNAUTHORIZEDACCESS"); break; + //case 0x80070005: str = W("E_ACCESSDENIED"); break; + case 0x80070006: str = W("E_HANDLE"); break; + case 0x8007000B: str = W("COR_E_BADIMAGEFORMAT"); break; + case 0x8007000E: str = W("COR_E_OUTOFMEMORY"); break; + //case 0x8007000E: str = W("E_OUTOFMEMORY"); break; + case 0x80070057: str = W("COR_E_ARGUMENT"); break; + //case 0x80070057: str = W("E_INVALIDARG"); break; + case 0x80070216: str = W("COR_E_ARITHMETIC"); break; + case 0x800703E9: str = W("COR_E_STACKOVERFLOW"); break; + case 0x80090020: str = W("NTE_FAIL"); break; + case 0x80131013: str = W("COR_E_TYPEUNLOADED"); break; + case 0x80131014: str = W("COR_E_APPDOMAINUNLOADED"); break; + case 0x80131015: str = W("COR_E_CANNOTUNLOADAPPDOMAIN"); break; + case 0x80131040: str = W("FUSION_E_REF_DEF_MISMATCH"); break; + case 0x80131047: str = W("FUSION_E_INVALID_NAME"); break; + case 0x80131416: str = W("CORSEC_E_POLICY_EXCEPTION"); break; + case 0x80131417: str = W("CORSEC_E_MIN_GRANT_FAIL"); break; + case 0x80131418: str = W("CORSEC_E_NO_EXEC_PERM"); break; + //case 0x80131418: str = W("CORSEC_E_XMLSYNTAX"); break; + case 0x80131430: str = W("CORSEC_E_CRYPTO"); break; + case 0x80131431: str = W("CORSEC_E_CRYPTO_UNEX_OPER"); break; + case 0x80131500: str = W("COR_E_EXCEPTION"); break; + case 0x80131501: str = W("COR_E_SYSTEM"); break; + case 0x80131502: str = W("COR_E_ARGUMENTOUTOFRANGE"); break; + case 0x80131503: str = W("COR_E_ARRAYTYPEMISMATCH"); break; + case 0x80131504: str = W("COR_E_CONTEXTMARSHAL"); break; + case 0x80131505: str = W("COR_E_TIMEOUT"); break; + case 0x80131506: str = W("COR_E_EXECUTIONENGINE"); break; + case 0x80131507: str = W("COR_E_FIELDACCESS"); break; + case 0x80131508: str = W("COR_E_INDEXOUTOFRANGE"); break; + case 0x80131509: str = W("COR_E_INVALIDOPERATION"); break; + case 0x8013150A: str = W("COR_E_SECURITY"); break; + case 0x8013150C: str = W("COR_E_SERIALIZATION"); break; + case 0x8013150D: str = W("COR_E_VERIFICATION"); break; + case 0x80131510: str = W("COR_E_METHODACCESS"); break; + case 0x80131511: str = W("COR_E_MISSINGFIELD"); break; + case 0x80131512: str = W("COR_E_MISSINGMEMBER"); break; + case 0x80131513: str = W("COR_E_MISSINGMETHOD"); break; + case 0x80131514: str = W("COR_E_MULTICASTNOTSUPPORTED"); break; + case 0x80131515: str = W("COR_E_NOTSUPPORTED"); break; + case 0x80131516: str = W("COR_E_OVERFLOW"); break; + case 0x80131517: str = W("COR_E_RANK"); break; + case 0x80131518: str = W("COR_E_SYNCHRONIZATIONLOCK"); break; + case 0x80131519: str = W("COR_E_THREADINTERRUPTED"); break; + case 0x8013151A: str = W("COR_E_MEMBERACCESS"); break; + case 0x80131520: str = W("COR_E_THREADSTATE"); break; + case 0x80131521: str = W("COR_E_THREADSTOP"); break; + case 0x80131522: str = W("COR_E_TYPELOAD"); break; + case 0x80131523: str = W("COR_E_ENTRYPOINTNOTFOUND"); break; + //case 0x80131523: str = W("COR_E_UNSUPPORTEDFORMAT"); break; + case 0x80131524: str = W("COR_E_DLLNOTFOUND"); break; + case 0x80131525: str = W("COR_E_THREADSTART"); break; + case 0x80131527: str = W("COR_E_INVALIDCOMOBJECT"); break; + case 0x80131528: str = W("COR_E_NOTFINITENUMBER"); break; + case 0x80131529: str = W("COR_E_DUPLICATEWAITOBJECT"); break; + case 0x8013152B: str = W("COR_E_SEMAPHOREFULL"); break; + case 0x8013152C: str = W("COR_E_WAITHANDLECANNOTBEOPENED"); break; + case 0x8013152D: str = W("COR_E_ABANDONEDMUTEX"); break; + case 0x80131530: str = W("COR_E_THREADABORTED"); break; + case 0x80131531: str = W("COR_E_INVALIDOLEVARIANTTYPE"); break; + case 0x80131532: str = W("COR_E_MISSINGMANIFESTRESOURCE"); break; + case 0x80131533: str = W("COR_E_SAFEARRAYTYPEMISMATCH"); break; + case 0x80131534: str = W("COR_E_TYPEINITIALIZATION"); break; + case 0x80131535: str = W("COR_E_COMEMULATE"); break; + //case 0x80131535: str = W("COR_E_MARSHALDIRECTIVE"); break; + case 0x80131536: str = W("COR_E_MISSINGSATELLITEASSEMBLY"); break; + case 0x80131537: str = W("COR_E_FORMAT"); break; + case 0x80131538: str = W("COR_E_SAFEARRAYRANKMISMATCH"); break; + case 0x80131539: str = W("COR_E_PLATFORMNOTSUPPORTED"); break; + case 0x8013153A: str = W("COR_E_INVALIDPROGRAM"); break; + case 0x8013153B: str = W("COR_E_OPERATIONCANCELED"); break; + case 0x8013153D: str = W("COR_E_INSUFFICIENTMEMORY"); break; + case 0x8013153E: str = W("COR_E_RUNTIMEWRAPPED"); break; + case 0x80131541: str = W("COR_E_DATAMISALIGNED"); break; + case 0x80131543: str = W("COR_E_TYPEACCESS"); break; + case 0x80131577: str = W("COR_E_KEYNOTFOUND"); break; + case 0x80131578: str = W("COR_E_INSUFFICIENTEXECUTIONSTACK"); break; + case 0x80131600: str = W("COR_E_APPLICATION"); break; + case 0x80131601: str = W("COR_E_INVALIDFILTERCRITERIA"); break; + case 0x80131602: str = W("COR_E_REFLECTIONTYPELOAD "); break; + case 0x80131603: str = W("COR_E_TARGET"); break; + case 0x80131604: str = W("COR_E_TARGETINVOCATION"); break; + case 0x80131605: str = W("COR_E_CUSTOMATTRIBUTEFORMAT"); break; + case 0x80131622: str = W("COR_E_OBJECTDISPOSED"); break; + case 0x80131623: str = W("COR_E_SAFEHANDLEMISSINGATTRIBUTE"); break; + case 0x80131640: str = W("COR_E_HOSTPROTECTION"); break; + } + + ::wprintf(W("0x%x"), val); + + if (str != nullptr) { + ::wprintf(W("/%0s"), str); + } + +} + +Logger& Logger::operator<< (int val) { + + if (m_isEnabled) { + + EnsurePrefixIsPrinted(); + + if (m_formatHRESULT) { + + PrintAsHResult(val); + m_formatHRESULT = false; + + } else { + + ::wprintf(W("%d"), val); + + } + } + + return *this; +} + +#ifdef _MSC_VER +Logger& Logger::operator<< (long val) { + if (m_isEnabled) { + EnsurePrefixIsPrinted(); + + if (m_formatHRESULT) { + + PrintAsHResult(val); + m_formatHRESULT = false; + + } else { + + ::wprintf(W("%d"), val); + + } + } + return *this; +} + +Logger& Logger::operator<< (unsigned long val) { + if (m_isEnabled) { + EnsurePrefixIsPrinted(); + + if (m_formatHRESULT) { + + PrintAsHResult(val); + m_formatHRESULT = false; + + } else { + + ::wprintf(W("%d"), val); + + } + } + return *this; +} +#endif + +Logger& Logger::operator<< (const wchar_t *val) { + if (m_isEnabled) { + EnsurePrefixIsPrinted(); + print(val); + } + return *this; +} + +Logger& Logger::operator<< (Logger& ( *pf )(Logger&)) { + if (m_isEnabled) { + return pf(*this); + } else { + return *this; + } +} + +void Logger::EnsurePrefixIsPrinted() { + if (this->m_isEnabled && this->m_prefixRequired) { + print(W(" HOSTLOG: ")); + m_prefixRequired = false; + } +} + +// Manipulators + +// Newline +Logger& Logger::endl (Logger& log) { + if (log.m_isEnabled) { + log.EnsurePrefixIsPrinted(); + print(W("\r\n")); + log.m_prefixRequired = true; + log.m_formatHRESULT = false; + } + return log; +} + +// Format the next integer value as an HResult +Logger& Logger::hresult (Logger& log) { + log.m_formatHRESULT = true; + return log; +} + diff --git a/src/coreclr/hosts/corerun/logger.h b/src/coreclr/hosts/corerun/logger.h new file mode 100644 index 0000000000..510f3bc97b --- /dev/null +++ b/src/coreclr/hosts/corerun/logger.h @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + + +// +// Logger for the CoreCLR host ccrun. +// Relies on the SYSCRT and therefore cannot use C++ libraries. +// + + +class Logger { + bool m_isEnabled; + bool m_prefixRequired; + bool m_formatHRESULT; + +public: + Logger() : + m_isEnabled(true), + m_prefixRequired(true), + m_formatHRESULT(false) { } + + ~Logger() { } + + // Enables output from the logger + void Enable(); + + // Disables output from the logger + void Disable(); + + + Logger& operator<< (bool val); + Logger& operator<< (short val); + Logger& operator<< (unsigned short val); + Logger& operator<< (int val); + Logger& operator<< (unsigned int val); +#ifdef _MSC_VER + Logger& operator<< (long val); + Logger& operator<< (unsigned long val); +#endif + Logger& operator<< (float val); + Logger& operator<< (double val); + Logger& operator<< (long double val); + Logger& operator<< (const wchar_t* val); + Logger& operator<< (Logger& ( *pf )(Logger&)); + static Logger& endl ( Logger& log ); + static Logger& hresult ( Logger& log); + +private: + void EnsurePrefixIsPrinted(); +}; + + + + + diff --git a/src/coreclr/hosts/corerun/native.rc b/src/coreclr/hosts/corerun/native.rc new file mode 100644 index 0000000000..5ad4f9893a --- /dev/null +++ b/src/coreclr/hosts/corerun/native.rc @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#define FX_VER_FILEDESCRIPTION_STR "Microsoft CoreCLR EXE launcher\0" + +#include <fxver.h> +#include <fxver.rc> diff --git a/src/coreclr/hosts/corerun/test.txt b/src/coreclr/hosts/corerun/test.txt new file mode 100644 index 0000000000..037873ba55 --- /dev/null +++ b/src/coreclr/hosts/corerun/test.txt @@ -0,0 +1 @@ +time 2 diff --git a/src/coreclr/hosts/dirs.proj b/src/coreclr/hosts/dirs.proj new file mode 100644 index 0000000000..11382958cb --- /dev/null +++ b/src/coreclr/hosts/dirs.proj @@ -0,0 +1,19 @@ +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(_NTDRIVE)$(_NTROOT)\ndp\clr\clr.props" /> + + <PropertyGroup> + <BuildInPhase1>true</BuildInPhase1> + <BuildInPhaseDefault>false</BuildInPhaseDefault> + <BuildCoreBinaries>true</BuildCoreBinaries> + <BuildSysBinaries>true</BuildSysBinaries> + </PropertyGroup> + + <!--The following projects will build during PHASE 1--> + <ItemGroup Condition="'$(BuildExePhase)' == '1' and '$(FeatureCoreClr)' == 'true'"> + <ProjectFile Include="coreRun\coreRun.nativeproj" /> + <ProjectFile Include="fxprun\fxprun.nativeproj" /> + <ProjectFile Include="coreconsole\CoreConsole.nativeproj" /> + </ItemGroup> + + <Import Project="$(_NTDRIVE)$(_NTROOT)\tools\Microsoft.DevDiv.Traversal.targets" /> +</Project> diff --git a/src/coreclr/hosts/inc/.gitmirrorall b/src/coreclr/hosts/inc/.gitmirrorall new file mode 100644 index 0000000000..9ee5c57b99 --- /dev/null +++ b/src/coreclr/hosts/inc/.gitmirrorall @@ -0,0 +1 @@ +This folder will be mirrored by the Git-TFS Mirror recursively.
\ No newline at end of file diff --git a/src/coreclr/hosts/inc/coreclrhost.h b/src/coreclr/hosts/inc/coreclrhost.h new file mode 100644 index 0000000000..f0d7952aa6 --- /dev/null +++ b/src/coreclr/hosts/inc/coreclrhost.h @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// APIs for hosting CoreCLR +// + +#ifndef __CORECLR_HOST_H__ +#define __CORECLR_HOST_H__ + +// For each hosting API, we define a function prototype and a function pointer +// The prototype is useful for implicit linking against the dynamic coreclr +// library and the pointer for explicit dynamic loading (dlopen, LoadLibrary) +#define CORECLR_HOSTING_API(function, ...) \ + extern "C" int function(__VA_ARGS__); \ + typedef int (*function##_ptr)(__VA_ARGS__) + +CORECLR_HOSTING_API(coreclr_initialize, + const char* exePath, + const char* appDomainFriendlyName, + int propertyCount, + const char** propertyKeys, + const char** propertyValues, + void** hostHandle, + unsigned int* domainId); + +CORECLR_HOSTING_API(coreclr_shutdown, + void* hostHandle, + unsigned int domainId); + +CORECLR_HOSTING_API(coreclr_create_delegate, + void* hostHandle, + unsigned int domainId, + const char* entryPointAssemblyName, + const char* entryPointTypeName, + const char* entryPointMethodName, + void** delegate); + +CORECLR_HOSTING_API(coreclr_execute_assembly, + void* hostHandle, + unsigned int domainId, + int argc, + const char** argv, + const char* managedAssemblyPath, + unsigned int* exitCode); + +#undef CORECLR_HOSTING_API + +#endif // __CORECLR_HOST_H__ diff --git a/src/coreclr/hosts/osxbundlerun/.gitmirrorall b/src/coreclr/hosts/osxbundlerun/.gitmirrorall new file mode 100644 index 0000000000..9ee5c57b99 --- /dev/null +++ b/src/coreclr/hosts/osxbundlerun/.gitmirrorall @@ -0,0 +1 @@ +This folder will be mirrored by the Git-TFS Mirror recursively.
\ No newline at end of file diff --git a/src/coreclr/hosts/osxbundlerun/CMakeLists.txt b/src/coreclr/hosts/osxbundlerun/CMakeLists.txt new file mode 100644 index 0000000000..49e2248ee7 --- /dev/null +++ b/src/coreclr/hosts/osxbundlerun/CMakeLists.txt @@ -0,0 +1,24 @@ +project(osxbundlerun) + +include_directories(../unixcoreruncommon) + +add_compile_options(-fPIE) + +set(CORERUN_SOURCES + ../unixcoreruncommon/coreruncommon.cpp + osxbundlerun.cpp +) + +_add_executable(osxbundlerun + ${CORERUN_SOURCES} +) + +target_link_libraries(osxbundlerun + dl +) + +add_dependencies(osxbundlerun + coreclr +) + +install_clr(osxbundlerun) diff --git a/src/coreclr/hosts/osxbundlerun/osxbundlerun.cpp b/src/coreclr/hosts/osxbundlerun/osxbundlerun.cpp new file mode 100644 index 0000000000..d4de5c764e --- /dev/null +++ b/src/coreclr/hosts/osxbundlerun/osxbundlerun.cpp @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// CoreCLR boot loader for OSX app packages. +// +// Assumes the following app package structure +// +// /Contents/MacOS/yourAppName (osxbundlerun renamed to your app name) +// /Contents/CoreClrBundle/ The CoreCLR runtime, or a symlink to it if external +// /Contents/ManagedBundle/ Your managed assemblies, including yourAppName.exe +// +// Of course you can also include whatever else you might need in the app package +// +// Symlinking the CoreClrBundle is handy for dev/debug builds. eg: +// +// Contents> ln -s ~/dotnet/runtime/ CoreClrBundle +// +// All command line arguments are passed directly to the managed assembly's Main(args) +// Note that args[0] will be /Contents/MacOS/yourAppName, not /Contents/ManagedBundle/yourAppName.exe + + +#include <coreruncommon.h> +#include <string> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> + +int corerun(const int argc, const char* argv[]) +{ + // Make sure we have a full path for argv[0]. + std::string argv0AbsolutePath; + if (!GetAbsolutePath(argv[0], argv0AbsolutePath)) + { + perror("Could not get full path to current executable"); + return -1; + } + + // Get name of self and containing folder (typically the MacOS folder) + int lastSlashPos = argv0AbsolutePath.rfind('/'); + std::string appName = argv0AbsolutePath.substr(lastSlashPos+1); + std::string appFolder = argv0AbsolutePath.substr(0, lastSlashPos); + + // Strip off "MacOS" to get to the "Contents" folder + std::string contentsFolder; + if (!GetDirectory(appFolder.c_str(), contentsFolder)) + { + perror("Could not get Contents folder"); + return -1; + } + + // Append standard locations + std::string clrFilesAbsolutePath = contentsFolder + "/CoreClrBundle"; + std::string managedFolderAbsolutePath = contentsFolder + "/ManagedBundle/"; + std::string managedAssemblyAbsolutePath = managedFolderAbsolutePath + appName + ".exe"; + + // Pass all command line arguments to managed executable + const char** managedAssemblyArgv = argv; + int managedAssemblyArgc = argc; + + // Check if the specified managed assembly file exists + struct stat sb; + if (stat(managedAssemblyAbsolutePath.c_str(), &sb) == -1) + { + perror(managedAssemblyAbsolutePath.c_str()); + return -1; + } + + // Verify that the managed assembly path points to a file + if (!S_ISREG(sb.st_mode)) + { + fprintf(stderr, "The specified managed assembly is not a file\n"); + return -1; + } + + // And go... + int exitCode = ExecuteManagedAssembly( + argv0AbsolutePath.c_str(), + clrFilesAbsolutePath.c_str(), + managedAssemblyAbsolutePath.c_str(), + managedAssemblyArgc, + managedAssemblyArgv); + + return exitCode; +} + +int main(const int argc, const char* argv[]) +{ + return corerun(argc, argv); +} diff --git a/src/coreclr/hosts/unixcoreconsole/.gitmirrorall b/src/coreclr/hosts/unixcoreconsole/.gitmirrorall new file mode 100644 index 0000000000..9ee5c57b99 --- /dev/null +++ b/src/coreclr/hosts/unixcoreconsole/.gitmirrorall @@ -0,0 +1 @@ +This folder will be mirrored by the Git-TFS Mirror recursively.
\ No newline at end of file diff --git a/src/coreclr/hosts/unixcoreconsole/CMakeLists.txt b/src/coreclr/hosts/unixcoreconsole/CMakeLists.txt new file mode 100644 index 0000000000..8988e60dcf --- /dev/null +++ b/src/coreclr/hosts/unixcoreconsole/CMakeLists.txt @@ -0,0 +1,33 @@ +project(unixcoreconsole) + +include_directories(../unixcoreruncommon) + +add_compile_options(-fPIE) + +set(CORECONSOLE_SOURCES + coreconsole.cpp +) + +_add_executable(coreconsole + ${CORECONSOLE_SOURCES} +) + +# FreeBSD and NetBSD implement dlopen(3) in libc +if(NOT CMAKE_SYSTEM_NAME STREQUAL FreeBSD AND NOT CMAKE_SYSTEM_NAME STREQUAL NetBSD) + target_link_libraries(coreconsole + dl + ) +endif(NOT CMAKE_SYSTEM_NAME STREQUAL FreeBSD AND NOT CMAKE_SYSTEM_NAME STREQUAL NetBSD) + +# Libc turns locks into no-ops if pthread was not loaded into process yet. Loading +# pthread by the process executable ensures that all locks are initialized properly. +target_link_libraries(coreconsole + unixcoreruncommon + pthread +) + +add_dependencies(coreconsole + coreclr +) + +install_clr(coreconsole)
\ No newline at end of file diff --git a/src/coreclr/hosts/unixcoreconsole/coreconsole.cpp b/src/coreclr/hosts/unixcoreconsole/coreconsole.cpp new file mode 100644 index 0000000000..e43124d0f2 --- /dev/null +++ b/src/coreclr/hosts/unixcoreconsole/coreconsole.cpp @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// A simple CoreCLR host that runs a managed binary with the same name as this executable but with the *.dll extension +// The dll binary must contain a main entry point. +// + +#include <coreruncommon.h> +#include <string> +#include <string.h> +#include <sys/stat.h> + +// Display the help text +void DisplayUsage() +{ + fprintf( + stderr, + "Runs executables on CoreCLR\n\n" + "Usage: <program> [OPTIONS] [ARGUMENTS]\n" + "Runs <program>.dll on CoreCLR.\n\n" + "Options:\n" + "-_c path to libcoreclr.so and the managed CLR assemblies.\n" + "-_h show this help message. \n"); +} + +// Parse the command line arguments +bool ParseArguments( + const int argc, + const char* argv[], + const char** clrFilesPath, + int* managedAssemblyArgc, + const char*** managedAssemblyArgv) +{ + bool success = true; + + *clrFilesPath = nullptr; + *managedAssemblyArgv = nullptr; + *managedAssemblyArgc = 0; + + for (int i = 1; i < argc; i++) + { + // Check for options. Options to the Unix coreconsole are prefixed with '-_' to match the convention + // used in the Windows version of coreconsole. + if (strncmp(argv[i], "-_", 2) == 0) + { + // Path to the libcoreclr.so and the managed CLR assemblies + if (strcmp(argv[i], "-_c") == 0) + { + i++; + if (i < argc) + { + *clrFilesPath = argv[i]; + } + else + { + fprintf(stderr, "Option %s: missing path\n", argv[i - 1]); + success = false; + break; + } + } + else if (strcmp(argv[i], "-_h") == 0) + { + DisplayUsage(); + success = false; + break; + } + else + { + fprintf(stderr, "Unknown option %s\n", argv[i]); + success = false; + break; + } + } + else + { + // We treat everything starting from the first non-option argument as arguments + // to the managed assembly. + *managedAssemblyArgc = argc - i; + if (*managedAssemblyArgc != 0) + { + *managedAssemblyArgv = &argv[i]; + } + + break; + } + } + + return success; +} + +int main(const int argc, const char* argv[]) +{ + // Make sure we have a full path for argv[0]. + std::string argv0AbsolutePath; + std::string entryPointExecutablePath; + + if (!GetEntrypointExecutableAbsolutePath(entryPointExecutablePath)) + { + perror("Could not get full path to current executable"); + return -1; + } + + if (!GetAbsolutePath(entryPointExecutablePath.c_str(), argv0AbsolutePath)) + { + perror("Could not normalize full path to current executable"); + return -1; + } + + // We will try to load the managed assembly with the same name as this executable + // but with the .dll extension. + std::string programPath(argv0AbsolutePath); + programPath.append(".dll"); + const char* managedAssemblyAbsolutePath = programPath.c_str(); + + // Check if the specified managed assembly file exists + struct stat sb; + if (stat(managedAssemblyAbsolutePath, &sb) == -1) + { + perror("Managed assembly not found"); + return -1; + } + + // Verify that the managed assembly path points to a file + if (!S_ISREG(sb.st_mode)) + { + fprintf(stderr, "The specified managed assembly is not a file\n"); + return -1; + } + + const char* clrFilesPath; + const char** managedAssemblyArgv; + int managedAssemblyArgc; + + if (!ParseArguments( + argc, + argv, + &clrFilesPath, + &managedAssemblyArgc, + &managedAssemblyArgv + )) + { + // Invalid command line + return -1; + } + + std::string clrFilesAbsolutePath; + if(!GetClrFilesAbsolutePath(argv0AbsolutePath.c_str(), clrFilesPath, clrFilesAbsolutePath)) + { + return -1; + } + + int exitCode = ExecuteManagedAssembly( + argv0AbsolutePath.c_str(), + clrFilesAbsolutePath.c_str(), + managedAssemblyAbsolutePath, + managedAssemblyArgc, + managedAssemblyArgv); + + return exitCode; +} diff --git a/src/coreclr/hosts/unixcorerun/.gitmirrorall b/src/coreclr/hosts/unixcorerun/.gitmirrorall new file mode 100644 index 0000000000..9ee5c57b99 --- /dev/null +++ b/src/coreclr/hosts/unixcorerun/.gitmirrorall @@ -0,0 +1 @@ +This folder will be mirrored by the Git-TFS Mirror recursively.
\ No newline at end of file diff --git a/src/coreclr/hosts/unixcorerun/CMakeLists.txt b/src/coreclr/hosts/unixcorerun/CMakeLists.txt new file mode 100644 index 0000000000..b32c9833bf --- /dev/null +++ b/src/coreclr/hosts/unixcorerun/CMakeLists.txt @@ -0,0 +1,33 @@ +project(unixcorerun) + +include_directories(../unixcoreruncommon) + +add_compile_options(-fPIE) + +set(CORERUN_SOURCES + corerun.cpp +) + +_add_executable(corerun + ${CORERUN_SOURCES} +) + +# FreeBSD and NetBSD implement dlopen(3) in libc +if(NOT CMAKE_SYSTEM_NAME STREQUAL FreeBSD AND NOT CMAKE_SYSTEM_NAME STREQUAL NetBSD) + target_link_libraries(corerun + dl + ) +endif(NOT CMAKE_SYSTEM_NAME STREQUAL FreeBSD AND NOT CMAKE_SYSTEM_NAME STREQUAL NetBSD) + +# Libc turns locks into no-ops if pthread was not loaded into process yet. Loading +# pthread by the process executable ensures that all locks are initialized properly. +target_link_libraries(corerun + unixcoreruncommon + pthread +) + +add_dependencies(corerun + coreclr +) + +install_clr(corerun)
\ No newline at end of file diff --git a/src/coreclr/hosts/unixcorerun/corerun.cpp b/src/coreclr/hosts/unixcorerun/corerun.cpp new file mode 100644 index 0000000000..da886d4338 --- /dev/null +++ b/src/coreclr/hosts/unixcorerun/corerun.cpp @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include <coreruncommon.h> +#include <string> +#include <string.h> +#include <sys/stat.h> + +// Display the command line options +void DisplayUsage() +{ + fprintf( + stderr, + "Usage: corerun [OPTIONS] assembly [ARGUMENTS]\n" + "Execute the specified managed assembly with the passed in arguments\n\n" + "Options:\n" + "-c, --clr-path path to the libcoreclr.so and the managed CLR assemblies\n"); +} + +// Parse the command line arguments +bool ParseArguments( + const int argc, + const char* argv[], + const char** clrFilesPath, + const char** managedAssemblyPath, + int* managedAssemblyArgc, + const char*** managedAssemblyArgv) +{ + bool success = false; + + *clrFilesPath = nullptr; + *managedAssemblyPath = nullptr; + *managedAssemblyArgv = nullptr; + *managedAssemblyArgc = 0; + + // The command line must contain at least the current exe name and the managed assembly path + if (argc >= 2) + { + for (int i = 1; i < argc; i++) + { + // Check for an option + if (argv[i][0] == '-') + { + // Path to the libcoreclr.so and the managed CLR assemblies + if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--clr-path") == 0) + { + i++; + if (i < argc) + { + *clrFilesPath = argv[i]; + } + else + { + fprintf(stderr, "Option %s: missing path\n", argv[i - 1]); + break; + } + } + else if (strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) + { + DisplayUsage(); + break; + } + else + { + fprintf(stderr, "Unknown option %s\n", argv[i]); + break; + } + } + else + { + // First argument that is not an option is the managed assembly to execute + *managedAssemblyPath = argv[i]; + + int managedArgvOffset = (i + 1); + *managedAssemblyArgc = argc - managedArgvOffset; + if (*managedAssemblyArgc != 0) + { + *managedAssemblyArgv = &argv[managedArgvOffset]; + } + success = true; + break; + } + } + } + else + { + DisplayUsage(); + } + + return success; +} + +int corerun(const int argc, const char* argv[]) +{ + const char* clrFilesPath; + const char* managedAssemblyPath; + const char** managedAssemblyArgv; + int managedAssemblyArgc; + + if (!ParseArguments( + argc, + argv, + &clrFilesPath, + &managedAssemblyPath, + &managedAssemblyArgc, + &managedAssemblyArgv)) + { + // Invalid command line + return -1; + } + + // Check if the specified managed assembly file exists + struct stat sb; + if (stat(managedAssemblyPath, &sb) == -1) + { + perror("Managed assembly not found"); + return -1; + } + + // Verify that the managed assembly path points to a file + if (!S_ISREG(sb.st_mode)) + { + fprintf(stderr, "The specified managed assembly is not a file\n"); + return -1; + } + + // Make sure we have a full path for argv[0]. + std::string argv0AbsolutePath; + if (!GetAbsolutePath(argv[0], argv0AbsolutePath)) + { + perror("Could not get full path"); + return -1; + } + + std::string clrFilesAbsolutePath; + if(!GetClrFilesAbsolutePath(argv0AbsolutePath.c_str(), clrFilesPath, clrFilesAbsolutePath)) + { + return -1; + } + + std::string managedAssemblyAbsolutePath; + if (!GetAbsolutePath(managedAssemblyPath, managedAssemblyAbsolutePath)) + { + perror("Failed to convert managed assembly path to absolute path"); + return -1; + } + + int exitCode = ExecuteManagedAssembly( + argv0AbsolutePath.c_str(), + clrFilesAbsolutePath.c_str(), + managedAssemblyAbsolutePath.c_str(), + managedAssemblyArgc, + managedAssemblyArgv); + + return exitCode; +} + +int main(const int argc, const char* argv[]) +{ + return corerun(argc, argv); +} diff --git a/src/coreclr/hosts/unixcoreruncommon/.gitmirror b/src/coreclr/hosts/unixcoreruncommon/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/coreclr/hosts/unixcoreruncommon/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/coreclr/hosts/unixcoreruncommon/CMakeLists.txt b/src/coreclr/hosts/unixcoreruncommon/CMakeLists.txt new file mode 100644 index 0000000000..a17e0a0fd6 --- /dev/null +++ b/src/coreclr/hosts/unixcoreruncommon/CMakeLists.txt @@ -0,0 +1,12 @@ +project(unixcoreruncommon) + +add_compile_options(-fPIC) + +_add_library(unixcoreruncommon + STATIC + coreruncommon.cpp +) + +if(CLR_CMAKE_PLATFORM_LINUX) + target_link_libraries(unixcoreruncommon dl) +endif(CLR_CMAKE_PLATFORM_LINUX) diff --git a/src/coreclr/hosts/unixcoreruncommon/coreruncommon.cpp b/src/coreclr/hosts/unixcoreruncommon/coreruncommon.cpp new file mode 100644 index 0000000000..b3a0c07a79 --- /dev/null +++ b/src/coreclr/hosts/unixcoreruncommon/coreruncommon.cpp @@ -0,0 +1,454 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// Code that is used by both the Unix corerun and coreconsole. +// + +#include <cstdlib> +#include <cstring> +#include <assert.h> +#include <dirent.h> +#include <dlfcn.h> +#include <limits.h> +#include <set> +#include <string> +#include <string.h> +#include <sys/stat.h> +#if defined(__FreeBSD__) +#include <sys/types.h> +#include <sys/param.h> +#endif +#if defined(HAVE_SYS_SYSCTL_H) || defined(__FreeBSD__) +#include <sys/sysctl.h> +#endif +#include "coreruncommon.h" +#include "coreclrhost.h" +#include <unistd.h> +#ifndef SUCCEEDED +#define SUCCEEDED(Status) ((Status) >= 0) +#endif // !SUCCEEDED + +// Name of the environment variable controlling server GC. +// If set to 1, server GC is enabled on startup. If 0, server GC is +// disabled. Server GC is off by default. +static const char* serverGcVar = "CORECLR_SERVER_GC"; + +#if defined(__linux__) +#define symlinkEntrypointExecutable "/proc/self/exe" +#elif !defined(__APPLE__) +#define symlinkEntrypointExecutable "/proc/curproc/exe" +#endif + +bool GetEntrypointExecutableAbsolutePath(std::string& entrypointExecutable) +{ + bool result = false; + + entrypointExecutable.clear(); + + // Get path to the executable for the current process using + // platform specific means. +#if defined(__linux__) || (defined(__NetBSD__) && !defined(KERN_PROC_PATHNAME)) + // On Linux, fetch the entry point EXE absolute path, inclusive of filename. + char exe[PATH_MAX]; + ssize_t res = readlink(symlinkEntrypointExecutable, exe, PATH_MAX - 1); + if (res != -1) + { + exe[res] = '\0'; + entrypointExecutable.assign(exe); + result = true; + } + else + { + result = false; + } +#elif defined(__APPLE__) + + // On Mac, we ask the OS for the absolute path to the entrypoint executable + uint32_t lenActualPath = 0; + if (_NSGetExecutablePath(nullptr, &lenActualPath) == -1) + { + // OSX has placed the actual path length in lenActualPath, + // so re-attempt the operation + std::string resizedPath(lenActualPath, '\0'); + char *pResizedPath = const_cast<char *>(resizedPath.c_str()); + if (_NSGetExecutablePath(pResizedPath, &lenActualPath) == 0) + { + entrypointExecutable.assign(pResizedPath); + result = true; + } + } +#elif defined (__FreeBSD__) + static const int name[] = { + CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 + }; + char path[PATH_MAX]; + size_t len; + + len = sizeof(path); + if (sysctl(name, 4, path, &len, nullptr, 0) == 0) + { + entrypointExecutable.assign(path); + result = true; + } + else + { + // ENOMEM + result = false; + } +#elif defined(__NetBSD__) && defined(KERN_PROC_PATHNAME) + static const int name[] = { + CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME, + }; + char path[MAXPATHLEN]; + size_t len; + + len = sizeof(path); + if (sysctl(name, __arraycount(name), path, &len, NULL, 0) != -1) + { + entrypointExecutable.assign(path); + result = true; + } + else + { + result = false; + } +#else + // On non-Mac OS, return the symlink that will be resolved by GetAbsolutePath + // to fetch the entrypoint EXE absolute path, inclusive of filename. + entrypointExecutable.assign(symlinkEntrypointExecutable); + result = true; +#endif + + return result; +} + +bool GetAbsolutePath(const char* path, std::string& absolutePath) +{ + bool result = false; + + char realPath[PATH_MAX]; + if (realpath(path, realPath) != nullptr && realPath[0] != '\0') + { + absolutePath.assign(realPath); + // realpath should return canonicalized path without the trailing slash + assert(absolutePath.back() != '/'); + + result = true; + } + + return result; +} + +bool GetDirectory(const char* absolutePath, std::string& directory) +{ + directory.assign(absolutePath); + size_t lastSlash = directory.rfind('/'); + if (lastSlash != std::string::npos) + { + directory.erase(lastSlash); + return true; + } + + return false; +} + +bool GetClrFilesAbsolutePath(const char* currentExePath, const char* clrFilesPath, std::string& clrFilesAbsolutePath) +{ + std::string clrFilesRelativePath; + const char* clrFilesPathLocal = clrFilesPath; + if (clrFilesPathLocal == nullptr) + { + // There was no CLR files path specified, use the folder of the corerun/coreconsole + if (!GetDirectory(currentExePath, clrFilesRelativePath)) + { + perror("Failed to get directory from argv[0]"); + return false; + } + + clrFilesPathLocal = clrFilesRelativePath.c_str(); + + // TODO: consider using an env variable (if defined) as a fall-back. + // The windows version of the corerun uses core_root env variable + } + + if (!GetAbsolutePath(clrFilesPathLocal, clrFilesAbsolutePath)) + { + perror("Failed to convert CLR files path to absolute path"); + return false; + } + + return true; +} + +void AddFilesFromDirectoryToTpaList(const char* directory, std::string& tpaList) +{ + const char * const tpaExtensions[] = { + ".ni.dll", // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir + ".dll", + ".ni.exe", + ".exe", + }; + + DIR* dir = opendir(directory); + if (dir == nullptr) + { + return; + } + + std::set<std::string> addedAssemblies; + + // Walk the directory for each extension separately so that we first get files with .ni.dll extension, + // then files with .dll extension, etc. + for (int extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++) + { + const char* ext = tpaExtensions[extIndex]; + int extLength = strlen(ext); + + struct dirent* entry; + + // For all entries in the directory + while ((entry = readdir(dir)) != nullptr) + { + // We are interested in files only + switch (entry->d_type) + { + case DT_REG: + break; + + // Handle symlinks and file systems that do not support d_type + case DT_LNK: + case DT_UNKNOWN: + { + std::string fullFilename; + + fullFilename.append(directory); + fullFilename.append("/"); + fullFilename.append(entry->d_name); + + struct stat sb; + if (stat(fullFilename.c_str(), &sb) == -1) + { + continue; + } + + if (!S_ISREG(sb.st_mode)) + { + continue; + } + } + break; + + default: + continue; + } + + std::string filename(entry->d_name); + + // Check if the extension matches the one we are looking for + int extPos = filename.length() - extLength; + if ((extPos <= 0) || (filename.compare(extPos, extLength, ext) != 0)) + { + continue; + } + + std::string filenameWithoutExt(filename.substr(0, extPos)); + + // Make sure if we have an assembly with multiple extensions present, + // we insert only one version of it. + if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end()) + { + addedAssemblies.insert(filenameWithoutExt); + + tpaList.append(directory); + tpaList.append("/"); + tpaList.append(filename); + tpaList.append(":"); + } + } + + // Rewind the directory stream to be able to iterate over it for the next extension + rewinddir(dir); + } + + closedir(dir); +} + +int ExecuteManagedAssembly( + const char* currentExeAbsolutePath, + const char* clrFilesAbsolutePath, + const char* managedAssemblyAbsolutePath, + int managedAssemblyArgc, + const char** managedAssemblyArgv) +{ + // Indicates failure + int exitCode = -1; + +#ifdef _ARM_ + // libunwind library is used to unwind stack frame, but libunwind for ARM + // does not support ARM vfpv3/NEON registers in DWARF format correctly. + // Therefore let's disable stack unwinding using DWARF information + // See https://github.com/dotnet/coreclr/issues/6698 + // + // libunwind use following methods to unwind stack frame. + // UNW_ARM_METHOD_ALL 0xFF + // UNW_ARM_METHOD_DWARF 0x01 + // UNW_ARM_METHOD_FRAME 0x02 + // UNW_ARM_METHOD_EXIDX 0x04 + putenv(const_cast<char *>("UNW_ARM_UNWIND_METHOD=6")); +#endif // _ARM_ + + std::string coreClrDllPath(clrFilesAbsolutePath); + coreClrDllPath.append("/"); + coreClrDllPath.append(coreClrDll); + + if (coreClrDllPath.length() >= PATH_MAX) + { + fprintf(stderr, "Absolute path to libcoreclr.so too long\n"); + return -1; + } + + // Get just the path component of the managed assembly path + std::string appPath; + GetDirectory(managedAssemblyAbsolutePath, appPath); + + // Construct native search directory paths + std::string nativeDllSearchDirs(appPath); + char *coreLibraries = getenv("CORE_LIBRARIES"); + if (coreLibraries) + { + nativeDllSearchDirs.append(":"); + nativeDllSearchDirs.append(coreLibraries); + } + nativeDllSearchDirs.append(":"); + nativeDllSearchDirs.append(clrFilesAbsolutePath); + + std::string tpaList; + AddFilesFromDirectoryToTpaList(clrFilesAbsolutePath, tpaList); + + void* coreclrLib = dlopen(coreClrDllPath.c_str(), RTLD_NOW | RTLD_LOCAL); + if (coreclrLib != nullptr) + { + coreclr_initialize_ptr initializeCoreCLR = (coreclr_initialize_ptr)dlsym(coreclrLib, "coreclr_initialize"); + coreclr_execute_assembly_ptr executeAssembly = (coreclr_execute_assembly_ptr)dlsym(coreclrLib, "coreclr_execute_assembly"); + coreclr_shutdown_ptr shutdownCoreCLR = (coreclr_shutdown_ptr)dlsym(coreclrLib, "coreclr_shutdown"); + + if (initializeCoreCLR == nullptr) + { + fprintf(stderr, "Function coreclr_initialize not found in the libcoreclr.so\n"); + } + else if (executeAssembly == nullptr) + { + fprintf(stderr, "Function coreclr_execute_assembly not found in the libcoreclr.so\n"); + } + else if (shutdownCoreCLR == nullptr) + { + fprintf(stderr, "Function coreclr_shutdown not found in the libcoreclr.so\n"); + } + else + { + // Check whether we are enabling server GC (off by default) + const char* useServerGc = std::getenv(serverGcVar); + if (useServerGc == nullptr) + { + useServerGc = "0"; + } + + // CoreCLR expects strings "true" and "false" instead of "1" and "0". + useServerGc = std::strcmp(useServerGc, "1") == 0 ? "true" : "false"; + + // Allowed property names: + // APPBASE + // - The base path of the application from which the exe and other assemblies will be loaded + // + // TRUSTED_PLATFORM_ASSEMBLIES + // - The list of complete paths to each of the fully trusted assemblies + // + // APP_PATHS + // - The list of paths which will be probed by the assembly loader + // + // APP_NI_PATHS + // - The list of additional paths that the assembly loader will probe for ngen images + // + // NATIVE_DLL_SEARCH_DIRECTORIES + // - The list of paths that will be probed for native DLLs called by PInvoke + // + const char *propertyKeys[] = { + "TRUSTED_PLATFORM_ASSEMBLIES", + "APP_PATHS", + "APP_NI_PATHS", + "NATIVE_DLL_SEARCH_DIRECTORIES", + "AppDomainCompatSwitch", + "System.GC.Server", + }; + const char *propertyValues[] = { + // TRUSTED_PLATFORM_ASSEMBLIES + tpaList.c_str(), + // APP_PATHS + appPath.c_str(), + // APP_NI_PATHS + appPath.c_str(), + // NATIVE_DLL_SEARCH_DIRECTORIES + nativeDllSearchDirs.c_str(), + // AppDomainCompatSwitch + "UseLatestBehaviorWhenTFMNotSpecified", + // System.GC.Server + useServerGc, + }; + + void* hostHandle; + unsigned int domainId; + + int st = initializeCoreCLR( + currentExeAbsolutePath, + "unixcorerun", + sizeof(propertyKeys) / sizeof(propertyKeys[0]), + propertyKeys, + propertyValues, + &hostHandle, + &domainId); + + if (!SUCCEEDED(st)) + { + fprintf(stderr, "coreclr_initialize failed - status: 0x%08x\n", st); + exitCode = -1; + } + else + { + st = executeAssembly( + hostHandle, + domainId, + managedAssemblyArgc, + managedAssemblyArgv, + managedAssemblyAbsolutePath, + (unsigned int*)&exitCode); + + if (!SUCCEEDED(st)) + { + fprintf(stderr, "coreclr_execute_assembly failed - status: 0x%08x\n", st); + exitCode = -1; + } + + st = shutdownCoreCLR(hostHandle, domainId); + if (!SUCCEEDED(st)) + { + fprintf(stderr, "coreclr_shutdown failed - status: 0x%08x\n", st); + exitCode = -1; + } + } + } + + if (dlclose(coreclrLib) != 0) + { + fprintf(stderr, "Warning - dlclose failed\n"); + } + } + else + { + char* error = dlerror(); + fprintf(stderr, "dlopen failed to open the libcoreclr.so with error %s\n", error); + } + + return exitCode; +} diff --git a/src/coreclr/hosts/unixcoreruncommon/coreruncommon.h b/src/coreclr/hosts/unixcoreruncommon/coreruncommon.h new file mode 100644 index 0000000000..fb7f6730b9 --- /dev/null +++ b/src/coreclr/hosts/unixcoreruncommon/coreruncommon.h @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include <string> + +// Get the path to entrypoint executable +bool GetEntrypointExecutableAbsolutePath(std::string& entrypointExecutable); + +// Get absolute path from the specified path. +// Return true in case of a success, false otherwise. +bool GetAbsolutePath(const char* path, std::string& absolutePath); + +// Get directory of the specified path. +// Return true in case of a success, false otherwise. +bool GetDirectory(const char* absolutePath, std::string& directory); + +// +// Get the absolute path to use to locate libcoreclr.so and the CLR assemblies are stored. If clrFilesPath is provided, +// this function will return the absolute path to it. Otherwise, the directory of the current executable is used. +// +// Return true in case of a success, false otherwise. +// +bool GetClrFilesAbsolutePath(const char* currentExePath, const char* clrFilesPath, std::string& clrFilesAbsolutePath); + +// Add all *.dll, *.ni.dll, *.exe, and *.ni.exe files from the specified directory to the tpaList string. +void AddFilesFromDirectoryToTpaList(const char* directory, std::string& tpaList); + +// +// Execute the specified managed assembly. +// +// Parameters: +// currentExePath - Path to the current executable +// clrFilesAbsolutePath - Absolute path to the folder where the libcoreclr.so and CLR managed assemblies are stored +// managedAssemblyPath - Path to the managed assembly to execute +// managedAssemblyArgc - Number of arguments passed to the executed assembly +// managedAssemblyArgv - Array of arguments passed to the executed assembly +// +// Returns: +// ExitCode of the assembly +// +int ExecuteManagedAssembly( + const char* currentExeAbsolutePath, + const char* clrFilesAbsolutePath, + const char* managedAssemblyAbsolutePath, + int managedAssemblyArgc, + const char** managedAssemblyArgv); + + +#if defined(__APPLE__) +#include <mach-o/dyld.h> +static const char * const coreClrDll = "libcoreclr.dylib"; +#else +static const char * const coreClrDll = "libcoreclr.so"; +#endif + |