From 371115999686e0580a9faf35b4c008223ae602ea Mon Sep 17 00:00:00 2001 From: Peter Honeder Date: Mon, 27 Jul 2015 17:38:11 +0200 Subject: [PATCH] added first version of custom PS hosting code --- src/CMakeLists.txt | 5 + src/host/cmdline/coreclrutil.cpp | 163 ++++++++++++++++++++++++++ src/host/cmdline/coreclrutil.h | 63 ++++++++++ src/host/cmdline/main.cpp | 194 +++++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 src/host/cmdline/coreclrutil.cpp create mode 100644 src/host/cmdline/coreclrutil.h create mode 100644 src/host/cmdline/main.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f23a2b711d..98053e3cc7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,16 +7,21 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") include_directories(../ext-src/gtest/fused-src impl) link_directories(${monad_native_BINARY_DIR}) +# source file definitions set(LIB_SOURCE_FILES impl/getcurrentprocessorid.cpp impl/getusername.cpp impl/terminal.cpp) set(TEST_SOURCE_FILES tests/test-getcurrentprocessid.cpp) set(SOURCE_FILES main.cpp ../ext-src/gtest/fused-src/gtest/gtest-all.cc) +SET(HOST_CMDLINE_SOURCE_FILES host/cmdline/main.cpp host/cmdline/coreclrutil.cpp) +# target definitions add_library(ps SHARED ${LIB_SOURCE_FILES}) add_executable(monad_native ${SOURCE_FILES} ${TEST_SOURCE_FILES}) +add_executable(host_cmdline ${HOST_CMDLINE_SOURCE_FILES}) # add pthread and other libs find_package(Threads) target_link_libraries(monad_native ${CMAKE_THREAD_LIBS_INIT} ps) +target_link_libraries(host_cmdline dl) diff --git a/src/host/cmdline/coreclrutil.cpp b/src/host/cmdline/coreclrutil.cpp new file mode 100644 index 0000000000..d67a1057f8 --- /dev/null +++ b/src/host/cmdline/coreclrutil.cpp @@ -0,0 +1,163 @@ +#include "coreclrutil.h" +#include +#include +#include +#include +#include + +namespace CoreCLRUtil +{ + +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 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); +} + +} // namespace CoreCLRUtil + diff --git a/src/host/cmdline/coreclrutil.h b/src/host/cmdline/coreclrutil.h new file mode 100644 index 0000000000..49a45ab6e1 --- /dev/null +++ b/src/host/cmdline/coreclrutil.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +namespace CoreCLRUtil +{ + +// +// This code is mostly copied and modified from original CoreCLR project's +// code on github: https://github.com/dotnet/coreclr +// + +// +// these function signatures are the entry point API for CoreCLR +// + +// Prototype of the coreclr_initialize function from the libcoreclr.so +typedef int (*InitializeCoreCLRFunction)( + const char* exePath, + const char* appDomainFriendlyName, + int propertyCount, + const char** propertyKeys, + const char** propertyValues, + void** hostHandle, + unsigned int* domainId); + +// Prototype of the coreclr_shutdown function from the libcoreclr.so +typedef int (*ShutdownCoreCLRFunction)( + void* hostHandle, + unsigned int domainId); + +// Prototype of the coreclr_execute_assembly function from the libcoreclr.so +typedef int (*ExecuteAssemblyFunction)( + void* hostHandle, + unsigned int domainId, + int argc, + const char** argv, + const char* managedAssemblyPath, + unsigned int* exitCode); + +// Prototype of coreclr_create_delegate function from the libcoreclr.so +typedef int (*CreateDelegateFunction)( + void* hostHandle, + unsigned int domainId, + const char* entryPointAssemblyName, + const char* entryPointTypeName, + const char* entryPointMethodName, + void** delegate); + +// The name of the CoreCLR native runtime DLL +#if defined(__APPLE__) +constexpr char coreClrDll[] = "libcoreclr.dylib"; +#else +constexpr char coreClrDll[] = "libcoreclr.so"; +#endif + +bool GetAbsolutePath(const char* path, std::string& absolutePath); +bool GetDirectory(const char* absolutePath, std::string& directory); +bool GetClrFilesAbsolutePath(const char* currentExePath, const char* clrFilesPath, std::string& clrFilesAbsolutePath); +void AddFilesFromDirectoryToTpaList(const char* directory, std::string& tpaList); + +} // namespace CoreCLRUtil + diff --git a/src/host/cmdline/main.cpp b/src/host/cmdline/main.cpp new file mode 100644 index 0000000000..29fbfb938c --- /dev/null +++ b/src/host/cmdline/main.cpp @@ -0,0 +1,194 @@ +#include +#include +#include "coreclrutil.h" +#include +#include + +int main(int argc, char** argv) +{ + // TODO: read from command line args + std::string clrAbsolutePath = "/home/peter/gitwd/monad-linux/scripts/exec_env/app_base"; + + // managed assembly arguments + int managedAssemblyArgc = 0; + const char* managedAssemblyArgv[] = { "" }; + + // there are two assemblies involved in hosting PowerShell: + // - Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll + // + this assembly has to be listed as platform assembly + // - System.Management.Automation.dll + std::string powershellBaseAbsolutePath = "/home/peter/gitwd/monad-linux/scripts/exec_env/app_base"; + std::string assemblyLoadContextAssemblyName = "Microsoft.PowerShell.CoreCLR.AssemblyLoadContext"; + std::string assemblyLoadContextAbsolutePath = powershellBaseAbsolutePath + "/" + assemblyLoadContextAssemblyName + ".dll"; + std::string systemManagementAutomationAssemblyName = "System.Management.Automation"; + std::string systemManagementAutomationAbsolutePath = powershellBaseAbsolutePath + "/" + systemManagementAutomationAssemblyName + ".dll"; + + std::string coreClrDllPath = clrAbsolutePath + "/" + CoreCLRUtil::coreClrDll; + if (coreClrDllPath.size() >= PATH_MAX) + { + std::cerr << "Absolute path to CoreCLR library too long" << std::endl; + return 1; + } + std::cout << "coreClrDllPath: " << coreClrDllPath << std::endl; + + // TPA list + // + // The list of platform assemblies must include all CoreCLR assemblies + // and the Microsoft.PowerShell.CoreCLR.AssemblyLoadContext + // + // TODO: move CLR assemblies to separate path during build&run make steps + // TODO: only add assembly load context to TPA list, not any other PS dll + + std::string tpaList; + CoreCLRUtil::AddFilesFromDirectoryToTpaList(clrAbsolutePath.c_str(),tpaList); + std::cout << "tpaList: " << tpaList << std::endl; + + // assembly load paths + // + // All PowerShell assemblies are assumed to be in the same path + + std::string appPath; + if (!CoreCLRUtil::GetDirectory(assemblyLoadContextAbsolutePath.c_str(),appPath)) + { + std::cerr << "failed to get assembly search directory from assembly absolute path" << std::endl; + return 1; + } + std::cout << "appPath: " << appPath << std::endl; + + // search paths for native dlls + // + // Add both the CoreCLR directory and the PowerShell directory to this list + std::string nativeDllSearchDirs = appPath + ":" + clrAbsolutePath; + + // get the absolute path of the current executable + std::string currentExeAbsolutePath; + if (!CoreCLRUtil::GetAbsolutePath(argv[0],currentExeAbsolutePath)) + { + std::cerr << "could not get absolute path of current executable" << std::endl; + return 1; + } + + // open the shared library + void* coreclrLib = dlopen(coreClrDllPath.c_str(), RTLD_NOW|RTLD_LOCAL); + if (coreclrLib == nullptr) + { + char* error = dlerror(); + std::cerr << "dlopen failed to open the CoreCLR library: " << error << std::endl; + return 2; + } + + // query the function pointers + CoreCLRUtil::InitializeCoreCLRFunction initializeCoreCLR = (CoreCLRUtil::InitializeCoreCLRFunction)dlsym(coreclrLib,"coreclr_initialize"); + CoreCLRUtil::ExecuteAssemblyFunction executeAssembly = (CoreCLRUtil::ExecuteAssemblyFunction)dlsym(coreclrLib,"coreclr_execute_assembly"); + CoreCLRUtil::ShutdownCoreCLRFunction shutdownCoreCLR = (CoreCLRUtil::ShutdownCoreCLRFunction)dlsym(coreclrLib,"coreclr_shutdown"); + CoreCLRUtil::CreateDelegateFunction createDelegate = (CoreCLRUtil::CreateDelegateFunction)dlsym(coreclrLib,"coreclr_create_delegate"); + + if (initializeCoreCLR == nullptr) + { + std::cerr << "function coreclr_initialize not found in CoreCLR library" << std::endl; + return 3; + } + if (executeAssembly == nullptr) + { + std::cerr << "function coreclr_execute_assembly not found in CoreCLR library" << std::endl; + return 3; + } + if (shutdownCoreCLR == nullptr) + { + std::cerr << "function coreclr_shutdown not found in CoreCLR library" << std::endl; + return 3; + } + if (createDelegate == nullptr) + { + std::cerr << "function coreclr_create_delegate not found in CoreCLR library" << std::endl; + return 3; + } + + // create list of properties to initialize CoreCLR + const char* propertyKeys[] = { + "TRUSTED_PLATFORM_ASSEMBLIES", + "APP_PATHS", + "APP_NI_PATHS", + "NATIVE_DLL_SEARCH_DIRECTORIES", + "AppDomainCompatSwitch" + }; + + const char* propertyValues[] = { + tpaList.c_str(), + appPath.c_str(), + appPath.c_str(), + nativeDllSearchDirs.c_str(), + "UseLatestBehaviorWhenTFMNotSpecified" + }; + + + // initialize CoreCLR + void* hostHandle; + unsigned int domainId; + int status = initializeCoreCLR( + currentExeAbsolutePath.c_str(), + "ps_cmdline_host", + sizeof(propertyKeys)/sizeof(propertyKeys[0]), + propertyKeys, + propertyValues, + &hostHandle, + &domainId); + + if (0 > status) + { + std::cerr << "coreclr_initialize failed - status: " << std::hex << status << std::endl; + return 4; + } + + // initialize the PS's custom assembly load context + typedef void (*LoaderRunHelperFp)(const char16_t* appPath); + LoaderRunHelperFp loaderDelegate = nullptr; + status = createDelegate( + hostHandle, + domainId, + "Microsoft.PowerShell.CoreCLR.AssemblyLoadContext, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", + "System.Management.Automation.PowerShellAssemblyLoadContextInitializer", + "SetPowerShellAssemblyLoadContext", + (void**)&loaderDelegate); + if (0 > status) + { + std::cerr << "could not create delegate for SetPowerShellAssemblyLoadContext - status: " << std::hex << status << std::endl; + return 4; + } + + loaderDelegate(u"/home/peter/gitwd/monad-linux/scripts/exec_env/app_base"); + + + typedef int (*TestDelegate)(); + TestDelegate testDelegate = nullptr; + status = createDelegate( + hostHandle, + domainId, + "System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", + "System.Management.Automation.Platform", + "IsLinux", + (void**)&testDelegate); + int returnValue = testDelegate(); + std::cout << "returnValue=" << returnValue << std::endl; + + + typedef void (*UnmanagedEntry)(); + UnmanagedEntry unmanagedEntry = nullptr; + status = createDelegate( + hostHandle, + domainId, + "powershell-simple, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", + "ps_hello_world.Program", + "UnmanagedEntry", + (void**)&unmanagedEntry); + if (0 > status) + { + std::cerr << "could not create delegate for UnmanagedEntry - status: " << std::hex << status << std::endl; + return 4; + } + + unmanagedEntry(); + + return 0; +} +