#include <dlfcn.h>
#include <signal.h>
+#include <dirent.h>
#include <string>
#include <fstream>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/limits.h>
+#include <pthread.h>
-#include <storage.h>
#include <vconf.h>
#include <app_common.h>
+#include <tzplatform_config.h>
#include <Ecore.h>
#include "core_runtime.h"
#include "plugin_manager.h"
#include "path_manager.h"
-#include "log_manager.h"
namespace tizen {
namespace runtime {
static coreclr_shutdown_ptr shutdown = nullptr;
static coreclr_create_delegate_ptr createDelegate = nullptr;
static set_environment_variable_ptr setEnvironmentVariable = nullptr;
+static stop_profile_after_delay_ptr stopProfileAfterDelay = nullptr;
+static set_switch_ptr setSwitch = nullptr;
static void* __coreclrLib = nullptr;
static void* __hostHandle = nullptr;
static unsigned int __domainId = -1;
-static int __fd = -1;
static bool __initialized = false;
static bool __isProfileMode = false;
+PathManager* CoreRuntime::__pm = nullptr;
-#define __XSTR(x) #x
-#define __STR(x) __XSTR(x)
-
-#ifdef NATIVE_LIB_DIR
-static std::string __nativeLibDirectory = __STR(NATIVE_LIB_DIR);
-#endif
-
-#undef __STR
-#undef __XSTR
-
-#if defined (__aarch64__)
-#define ARCHITECTURE_IDENTIFIER "arm64"
-
-#elif defined (__arm__)
-#define ARCHITECTURE_IDENTIFIER "armel"
-
-#elif defined (__x86_64__)
-#define ARCHITECTURE_IDENTIFIER "x64"
-
-#elif defined (__i386__)
-#define ARCHITECTURE_IDENTIFIER "x86"
-
-#else
-#error "Unknown target"
-#endif
-
-static const char* __TIZEN_RID_VERSION_KEY = "db/dotnet/tizen_rid_version";
-
-// The sequence of RID_FALLBACK graphs must be:
-// 1. Tizen + Version + Architecture
-// 2. Tizen + Version
-// 3. OS(tizen, linux, unix) + Architecture
-// 4. OS(tizen, linux, unix)
-// 5. any, base
-static std::string getExtraNativeLibDirs(const std::string& appRoot)
-{
- std::vector<std::string> RID_FALLBACK_GRAPH;
- char* tizen_rid = vconf_get_str(__TIZEN_RID_VERSION_KEY);
- if (tizen_rid) {
- std::vector<std::string> version;
- splitPath(tizen_rid, version);
- std::reverse(std::begin(version), std::end(version));
- for (unsigned int i = 0; i < version.size(); i++) {
- RID_FALLBACK_GRAPH.push_back(std::string("tizen." + version[i] + "-" + ARCHITECTURE_IDENTIFIER));
- RID_FALLBACK_GRAPH.push_back(std::string("tizen." + version[i]));
- }
- free(tizen_rid);
- }
-
- std::vector<std::string> RID_FALLBACK_OS = {"tizen", "linux", "unix"};
- for (unsigned int i = 0; i < RID_FALLBACK_OS.size(); i++) {
- RID_FALLBACK_GRAPH.push_back(std::string(RID_FALLBACK_OS[i] + "-" + ARCHITECTURE_IDENTIFIER));
- RID_FALLBACK_GRAPH.push_back(std::string(RID_FALLBACK_OS[i]));
- }
- RID_FALLBACK_GRAPH.push_back("any");
- RID_FALLBACK_GRAPH.push_back("base");
-
- std::string candidate;
- for (unsigned int i = 0; i < RID_FALLBACK_GRAPH.size(); i++) {
- if (!candidate.empty()) {
- candidate += ":";
- }
- candidate += concatPath(appRoot, "bin/runtimes/" + RID_FALLBACK_GRAPH[i] + "/native");
- }
-
- candidate = candidate + ":" + concatPath(appRoot, "lib/" ARCHITECTURE_IDENTIFIER);
- if (!strncmp(ARCHITECTURE_IDENTIFIER, "arm64", 5)) {
- candidate = candidate + ":" + concatPath(appRoot, "lib/aarch64");
- } else if (!strncmp(ARCHITECTURE_IDENTIFIER, "armel", 5)) {
- candidate = candidate + ":" + concatPath(appRoot, "lib/arm");
- }
-
- return candidate;
-}
-
+#define MAX_DELAY_SEC 100
static std::vector<std::string> __envList;
}
}
-static bool storage_cb(int id, storage_type_e type, storage_state_e state, const char *path, void *user_data)
-{
- int* tmp = (int*)user_data;
- if (type == STORAGE_TYPE_INTERNAL)
- {
- *tmp = id;
- return false;
- }
-
- return true;
-}
-
static void initEnvForSpecialFolder()
{
- int storageId;
- int error;
- char *path = NULL;
-
- error = storage_foreach_device_supported(storage_cb, &storageId);
- if (error != STORAGE_ERROR_NONE) {
- return;
- }
-
- error = storage_get_directory(storageId, STORAGE_DIRECTORY_IMAGES, &path);
- if (error == STORAGE_ERROR_NONE && path != NULL) {
- setenv("XDG_PICTURES_DIR", const_cast<char *>(path), 1);
- free(path);
- path = NULL;
+ const char* path = NULL;
+ if (getenv("XDG_PICTURES_DIR") == NULL) {
+ path = tzplatform_getenv(TZ_USER_IMAGES);
+ if (path) {
+ setenv("XDG_PICTURES_DIR", path, 1);
+ }
}
- error = storage_get_directory(storageId, STORAGE_DIRECTORY_MUSIC, &path);
- if (error == STORAGE_ERROR_NONE && path != NULL) {
- setenv("XDG_MUSIC_DIR", const_cast<char *>(path), 1);
- free(path);
- path = NULL;
+ if (getenv("XDG_MUSIC_DIR") == NULL) {
+ path = tzplatform_getenv(TZ_USER_MUSIC);
+ if (path) {
+ setenv("XDG_MUSIC_DIR", path, 1);
+ }
}
- error = storage_get_directory(storageId, STORAGE_DIRECTORY_VIDEOS, &path);
- if (error == STORAGE_ERROR_NONE && path != NULL) {
- setenv("XDG_VIDEOS_DIR", const_cast<char *>(path), 1);
- free(path);
- path = NULL;
+ if (getenv("XDG_VIDEOS_DIR") == NULL) {
+ path = tzplatform_getenv(TZ_USER_VIDEOS);
+ if (path) {
+ setenv("XDG_VIDEOS_DIR", path, 1);
+ }
}
}
-// terminate candidate process when language changed
-// icu related data (CultureInfo, etc) should be recreated.
-static void langChangedCB(keynode_t *key, void* data)
-{
- _INFO("terminiate candidate process to update language.");
- exit(0);
-}
-
static void setLang()
{
+ //To reduce search overhead of libicuuc.so.xx
+ setenv("CLR_ICU_VERSION_OVERRIDE", "build", 1);
+
char* lang = vconf_get_str(VCONFKEY_LANGSET);
if (!lang) {
_ERR("Fail to get language from vconf");
return "";
}
+static void removeDebugPipe()
+{
+ DIR *dir;
+ struct dirent* entry;
+ char debugPipeFiles[PATH_MAX];;
+ sprintf(debugPipeFiles, "/tmp/clr-debug-pipe-%d-", getpid());
+
+ dir = opendir("/tmp");
+ if (dir == nullptr) {
+ _ERR("Fail to open /tmp directory");
+ return;
+ }
+
+ while ((entry = readdir(dir)) != nullptr) {
+ std::string path = concatPath("/tmp", entry->d_name);
+ if (path.find(debugPipeFiles) != std::string::npos) {
+ if (!removeFile(path)) {
+ _ERR("Fail to remove file (%s)", path.c_str());
+ }
+ }
+ }
+
+ closedir(dir);
+}
+
void preload()
{
typedef void (*PreloadDelegate)();
} else {
preloadDelegate();
}
+
+ pluginPreload();
}
-bool initializeCoreClr(const char* appId,
- const char* assemblyProbePaths,
- const char* NIProbePaths,
- const char* pinvokeProbePaths,
- const char* tpaList)
+bool initializeCoreClr(PathManager* pm, const std::string& tpa)
{
+ bool ncdbStartupHook = isNCDBStartupHookProvided();
+
const char *propertyKeys[] = {
"TRUSTED_PLATFORM_ASSEMBLIES",
"APP_PATHS",
"APP_NI_PATHS",
"NATIVE_DLL_SEARCH_DIRECTORIES",
- "AppDomainCompatSwitch"
+ "AppDomainCompatSwitch",
+ ncdbStartupHook ? "STARTUP_HOOKS" : "" // must be the last one
};
const char *propertyValues[] = {
- tpaList,
- assemblyProbePaths,
- NIProbePaths,
- pinvokeProbePaths,
- "UseLatestBehaviorWhenTFMNotSpecified"
+ tpa.c_str(),
+ pm->getAppPaths().c_str(),
+ pm->getAppNIPaths().c_str(),
+ pm->getNativeDllSearchingPaths().c_str(),
+ "UseLatestBehaviorWhenTFMNotSpecified",
+ ncdbStartupHook ? getNCDBStartupHook() : "" // must be the last one
};
std::string selfPath = readSelfPath();
int st = initializeClr(selfPath.c_str(),
- appId,
- sizeof(propertyKeys) / sizeof(propertyKeys[0]),
+ "TizenDotnetApp",
+ sizeof(propertyKeys) / sizeof(propertyKeys[0]) - (ncdbStartupHook ? 0 : 1),
propertyKeys,
propertyValues,
&__hostHandle,
return -1;
}
- // Intiailize ecore first (signal handlers, etc.) before runtime init.
- ecore_init();
-
// set language environment to support ICU
setLang();
// So, plugin initialize should be called before creating threads.
if (initializePluginManager(appType) < 0) {
_ERR("Failed to initialize PluginManager");
+ return -1;
}
// checkInjection checks dotnet-launcher run mode
// 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
+ // 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__
putenv(const_cast<char *>("COMPlus_UseDefaultBaseAddr=1"));
#endif // USE_DEFAULT_BASE_ADDR
+ // Disable config cache to set environment after coreclr_initialize()
+ putenv(const_cast<char *>("COMPlus_DisableConfigCache=1"));
+
+ // Set environment variable for System.Environment.SpecialFolder
+ initEnvForSpecialFolder();
+
// read string from external file and set them to environment value.
setEnvFromFile();
- if (initializePathManager(std::string(), std::string(), std::string()) < 0) {
- _ERR("Failed to initialize PathManager");
+ try {
+ __pm = new PathManager();
+ } catch (const std::exception& e) {
+ _ERR("Failed to create PathManager");
return -1;
}
- if (!pluginHasLogControl()) {
- if (initializeLogManager() < 0) {
- _ERR("Failed to initnialize LogManager");
- return -1;
- }
+ char* pluginDllPaths = pluginGetDllPath();
+ if (pluginDllPaths && pluginDllPaths[0] != '\0') {
+ __pm->addPlatformAssembliesPaths(pluginDllPaths, true);
+ }
+
+ char* pluginNativePaths = pluginGetNativeDllSearchingPath();
+ if (pluginNativePaths && pluginNativePaths[0] != '\0') {
+ __pm->addNativeDllSearchingPaths(pluginNativePaths, true);
}
- std::string libCoreclr(concatPath(getRuntimeDir(), "libcoreclr.so"));
+ char* pluginExtraDllPaths = pluginGetExtraDllPath();
+ if (pluginExtraDllPaths && pluginExtraDllPaths[0] != '\0') {
+ __pm->setExtraDllPaths(pluginExtraDllPaths);
+ }
+
+ std::string libCoreclr(concatPath(__pm->getRuntimePath(), "libcoreclr.so"));
__coreclrLib = dlopen(libCoreclr.c_str(), RTLD_NOW | RTLD_LOCAL);
if (__coreclrLib == nullptr) {
char *err = dlerror();
_ERR("dlopen failed to open libcoreclr.so with error %s", err);
- if (access(libCoreclr.c_str(), R_OK) == -1)
- _ERR("access '%s': %s\n", libCoreclr.c_str(), strerror(errno));
return -1;
}
_INFO("libcoreclr dlopen and dlsym success");
- // Set environment for System.Environment.SpecialFolder
- // Below function creates dbus connection by callging storage API.
- // If dbus connection is created bofere fork(), forked process cannot use dbus.
- // To avoid gdbus blocking issue, below function should be called after fork()
- initEnvForSpecialFolder();
-
- __fd = open("/proc/self", O_DIRECTORY);
- if (__fd < 0) {
- _ERR("Failed to open /proc/self");
- return -1;
+ std::string tpa;
+ char* pluginTPA = pluginGetTPA();
+ if (pluginTPA && pluginTPA[0] != '\0') {
+ tpa = std::string(pluginTPA);
+ } else {
+ addAssembliesFromDirectories(__pm->getPlatformAssembliesPaths(), tpa);
}
- std::string tpa = getTPA();
- std::string runtimeDir = getRuntimeDir();
- std::string appName = std::string("dotnet-launcher-") + std::to_string(getpid());
- std::string appRoot = std::string("/proc/self/fd/") + std::to_string(__fd);
- std::string appBin = concatPath(appRoot, "bin");
- std::string appLib = concatPath(appRoot, "lib");
- std::string appTac = concatPath(appBin, TAC_SYMLINK_SUB_DIR);
- std::string probePath = appRoot + ":" + appBin + ":" + appLib + ":" + appTac;
- std::string NIprobePath = concatPath(appBin, APP_NI_SUB_DIR) + ":" + concatPath(appLib, APP_NI_SUB_DIR) + ":" + appTac;
- std::string nativeLibPath = runtimeDir + ":" + __nativeLibDirectory + ":" + getExtraNativeLibDirs(appRoot) + ":" + appBin + ":" + appLib;
-
- if (!initializeCoreClr(appName.c_str(), probePath.c_str(), NIprobePath.c_str(), nativeLibPath.c_str(), tpa.c_str())) {
+ if (!initializeCoreClr(__pm, tpa)) {
_ERR("Failed to initialize coreclr");
return -1;
}
+ // VD has their own signal handler.
+ if (!pluginHasLogControl()) {
+ registerSigHandler();
+ }
+
int st = createDelegate(__hostHandle, __domainId, "Tizen.Runtime", "Tizen.Runtime.Environment", "SetEnvironmentVariable", (void**)&setEnvironmentVariable);
if (st < 0 || setEnvironmentVariable == nullptr) {
_ERR("Create delegate for Tizen.Runtime.dll -> Tizen.Runtime.Environment -> SetEnvironmentVariable failed (0x%08x)", st);
return -1;
}
- __initialized = true;
+ st = createDelegate(__hostHandle, __domainId, "Tizen.Runtime", "Tizen.Runtime.Profiler", "StopProfileAfterDelay", (void**)&stopProfileAfterDelay);
+ if (st < 0 || stopProfileAfterDelay == nullptr) {
+ _ERR("Create delegate for Tizen.Runtime.dll -> Tizen.Runtime.Profiler -> StopProfileAfterDelay failed (0x%08x)", st);
+ return -1;
+ }
+
+ st = createDelegate(__hostHandle, __domainId, "Tizen.Runtime", "Tizen.Runtime.AppSetting", "SetSwitch", (void**)&setSwitch);
+ if (st < 0 || setSwitch == nullptr) {
+ _ERR("Create delegate for Tizen.Runtime.dll -> Tizen.Runtime.AppSetting -> SetSwitch failed (0x%08x)", st);
+ return -1;
+ }
if (launchMode == LaunchMode::loader) {
- // terminate candidate process if language is changed.
- // CurrentCulture created for preloaded dlls should be updated.
- vconf_notify_key_changed(VCONFKEY_LANGSET, langChangedCB, NULL);
+ // preload libraries and manage dlls for optimizing startup time
+ preload();
- pluginPreload();
- preload(); // Preload common managed code
+ // The debug pipe created in the candidate process has a "User" label.
+ // As a result, smack deny occurs when app process try to access the debug pipe.
+ // Also, since debugging is performed only in standalone mode,
+ // the debug pipe doesnot be used in the candidate process.
+ // So, to avoid smack deny error, delete unused debug pipe files.
+ removeDebugPipe();
}
+ __initialized = true;
+
_INFO("CoreRuntime initialize success");
return 0;
void CoreRuntime::finalize()
{
- if (!__initialized) {
- _ERR("Runtime is not initialized");
- return;
- }
-
// call plugin finalize function to notify finalize to plugin
// dlclose shoud be done after coreclr shutdown to avoid breaking signal chain
pluginFinalize();
checkOnTerminate = true;
// workaround : to prevent crash while process terminate on profiling mode,
- // kill process immediately.
+ // kill process immediately.
// see https://github.com/dotnet/coreclr/issues/26687
if (__isProfileMode) {
_INFO("shutdown process immediately.");
}
finalizePluginManager();
- finalizePathManager();
+
+ delete __pm;
+ __pm = NULL;
__envList.clear();
_INFO("CoreRuntime finalized");
}
-int CoreRuntime::launch(const char* appId, const char* root, const char* path, int argc, char* argv[])
+int CoreRuntime::launch(const char* appId, const char* root, const char* path, int argc, char* argv[], bool profile)
{
if (!__initialized) {
_ERR("Runtime is not initialized");
return -1;
}
- // launchpad override stdout and stderr to journalctl before launch application.
- // we have to re-override that to input pipe for logging thread.
- // if LogManager is not initialized, below redirectFD will return 0;
- if (redirectFD() < 0) {
- _ERR("Failed to redirect FD");
- return -1;
- }
-
- // VD has their own signal handler.
- if (!pluginHasLogControl()) {
- registerSigHandler();
- }
-
pluginSetAppInfo(appId, path);
- // override root path for application launch mode (candidate / standalone mode)
- if (__fd >= 0) {
- int fd2 = open(root, O_DIRECTORY);
- dup3(fd2, __fd, O_CLOEXEC);
- if (fd2 >= 0)
- close(fd2);
- }
+ // temporal root path is overrided to real application root path
+ __pm->setAppRootPath(root);
// set application data path to coreclr environment.
// application data path can be changed by owner. So, we have to set data path just before launching.
char* localDataPath = app_get_data_path();
if (localDataPath != nullptr) {
setEnvironmentVariable("XDG_DATA_HOME", localDataPath);
+
+ // set profile.data path and collect/use it if it non-exists/exists.
+ if (profile) {
+ char multiCoreJitProfile[strlen(localDataPath) + strlen(PROFILE_BASENAME) + 1];
+ memcpy(multiCoreJitProfile, localDataPath, strlen(localDataPath) + 1);
+ strncat(multiCoreJitProfile, PROFILE_BASENAME, strlen(PROFILE_BASENAME));
+
+ setEnvironmentVariable("COMPlus_MultiCoreJitProfile", multiCoreJitProfile);
+ setEnvironmentVariable("COMPlus_MultiCoreJitMinNumCpus", "1");
+
+ if (exist(multiCoreJitProfile)) {
+ setEnvironmentVariable("COMPlus_MultiCoreJitNoProfileGather", "1");
+ _INFO("MCJ playing start for %s", appId);
+ } else {
+ setEnvironmentVariable("COMPlus_MultiCoreJitNoProfileGather", "0");
+ // stop profiling and write collected data after delay if env value is set.
+ char *env = getenv("CLR_MCJ_PROFILE_WRITE_DELAY");
+ if (env != nullptr) {
+ int delay = std::atoi(env);
+ // To avoid undefined behavior by out-of-range input(atoi), set max delay value to 100.
+ if (delay > 0) {
+ if (delay > MAX_DELAY_SEC) delay = MAX_DELAY_SEC;
+ stopProfileAfterDelay(delay);
+ }
+ }
+ _INFO("MCJ recording start for %s", appId);
+ }
+ }
free(localDataPath);
}
- vconf_ignore_key_changed(VCONFKEY_LANGSET, langChangedCB);
+ if (exist(__pm->getAppRootPath() + "/bin/" + DISABLE_IPV6_FILE)) {
+ setSwitch("System.Net.DisableIPv6", true);
+ }
+
+ setSwitch("Switch.System.Diagnostics.StackTrace.ShowILOffsets", true);
pluginBeforeExecute();