remove unnecessary ecore dependency
[platform/core/dotnet/launcher.git] / NativeLauncher / launcher / lib / core_runtime.cc
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17
18 #include <dlfcn.h>
19 #include <signal.h>
20 #include <dirent.h>
21
22 #include <string>
23 #include <fstream>
24 #include <vector>
25 #include <sstream>
26
27 #include <locale>
28 #include <codecvt>
29
30 #include <fcntl.h>
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 #include <unistd.h>
35 #include <linux/limits.h>
36 #include <pthread.h>
37
38 #include <vconf.h>
39 #include <app_common.h>
40 #include <tzplatform_config.h>
41
42 #include "injection.h"
43 #include "utils.h"
44 #include "log.h"
45 #include "core_runtime.h"
46 #include "plugin_manager.h"
47 #include "path_manager.h"
48
49 namespace tizen {
50 namespace runtime {
51 namespace dotnetcore {
52
53 static coreclr_initialize_ptr initializeClr = nullptr;
54 static coreclr_execute_assembly_ptr executeAssembly = nullptr;
55 static coreclr_shutdown_ptr shutdown = nullptr;
56 static coreclr_create_delegate_ptr createDelegate = nullptr;
57 static set_environment_variable_ptr setEnvironmentVariable = nullptr;
58 static stop_profile_after_delay_ptr stopProfileAfterDelay = nullptr;
59 static set_switch_ptr setSwitch = nullptr;
60 static void* __coreclrLib = nullptr;
61 static void* __hostHandle = nullptr;
62 static unsigned int __domainId = -1;
63 static bool __initialized = false;
64 static bool __isProfileMode = false;
65 PathManager* CoreRuntime::__pm = nullptr;
66
67 #define MAX_DELAY_SEC 100
68
69 static std::vector<std::string> __envList;
70
71 static void setEnvFromFile()
72 {
73         std::string envList;
74         std::ifstream inFile(ENV_FILE_PATH);
75
76         __envList.clear();
77
78         if (inFile) {
79                 _INFO("coreclr_env.list is found");
80
81                 std::string token;
82                 while (std::getline(inFile, token, '\n')) {
83                         if (!token.empty()) {
84                                 __envList.push_back(token);
85                         }
86                 }
87
88                 for (unsigned int i = 0; i < __envList.size(); i++) {
89                         putenv(const_cast<char *>(__envList[i].c_str()));
90                 }
91         } else {
92                 _INFO("coreclr_env.list file is not found. skip");
93         }
94 }
95
96 #define _unused(x) ((void)(x))
97
98 static struct sigaction sig_abrt_new;
99 static struct sigaction sig_abrt_old;
100
101 static bool checkOnSigabrt = false;
102 static bool checkOnTerminate = false;
103
104 static void onSigabrt(int signum)
105 {
106         // use unused variable to avoid build warning
107         ssize_t ret = write(STDERR_FILENO, "onSigabrt called\n", 17);
108
109         if (checkOnTerminate) {
110                 ret = write(STDERR_FILENO, "onSigabrt called while terminate. go to exit\n", 45);
111                 _unused(ret);
112                 exit(0);
113         }
114
115         if (checkOnSigabrt) {
116                 ret = write(STDERR_FILENO, "onSigabrt called again. go to exit\n", 35);
117                 _unused(ret);
118                 exit(0);
119         }
120
121         checkOnSigabrt = true;
122         if (sigaction(SIGABRT, &sig_abrt_old, NULL) == 0) {
123                 if (raise(signum) < 0) {
124                         ret = write(STDERR_FILENO, "Fail to raise SIGABRT\n", 22);
125                 }
126         } else {
127                 ret = write(STDERR_FILENO, "Fail to set original SIGABRT handler\n", 37);
128         }
129         _unused(ret);
130 }
131
132 static void registerSigHandler()
133 {
134         sig_abrt_new.sa_handler = onSigabrt;
135         if (sigemptyset(&sig_abrt_new.sa_mask) != 0) {
136                 _ERR("Fail to sigemptyset");
137         }
138
139         if (sigaction(SIGABRT, &sig_abrt_new, &sig_abrt_old) < 0) {
140                 _ERR("Fail to add sig handler");
141         }
142 }
143
144 static void initEnvForSpecialFolder()
145 {
146         const char* path = NULL;
147         if (getenv("XDG_PICTURES_DIR") == NULL) {
148                 path = tzplatform_getenv(TZ_USER_IMAGES);
149                 if (path) {
150                         setenv("XDG_PICTURES_DIR", path, 1);
151                 }
152         }
153
154         if (getenv("XDG_MUSIC_DIR") == NULL) {
155                 path = tzplatform_getenv(TZ_USER_MUSIC);
156                 if (path) {
157                         setenv("XDG_MUSIC_DIR", path, 1);
158                 }
159         }
160
161         if (getenv("XDG_VIDEOS_DIR") == NULL) {
162                 path = tzplatform_getenv(TZ_USER_VIDEOS);
163                 if (path) {
164                         setenv("XDG_VIDEOS_DIR", path, 1);
165                 }
166         }
167 }
168
169 static void setLang()
170 {
171         //To reduce search overhead of libicuuc.so.xx
172         setenv("CLR_ICU_VERSION_OVERRIDE", "build", 1);
173
174         char* lang = vconf_get_str(VCONFKEY_LANGSET);
175         if (!lang) {
176                 _ERR("Fail to get language from vconf");
177                 return;
178         }
179
180         // In order to operate ICU (used for globalization) normally, the following
181         // environment variables must be set before using ICU API.
182         // When running Applicaiton, the following environment variables are set by AppFW.
183         // But when preloading the dll in the candidate process, the following environment variables are not set
184         // As a result, CultureInfo is incorrectly generated and malfunctions.
185         // For example, uloc_getDefault() returns en_US_POSIX, CultureInfo is set to invariant mode.
186         setenv("LANG", const_cast<char *>(lang), 1);
187         setlocale(LC_ALL, const_cast<char *>(lang));
188
189         free(lang);
190 }
191
192 static std::string readSelfPath()
193 {
194         char buff[PATH_MAX];
195         ssize_t len = ::readlink("/proc/self/exe", buff, sizeof(buff)-1);
196         if (len != -1) {
197                 buff[len] = '\0';
198                 return std::string(buff);
199         }
200
201         return "";
202 }
203
204 static void removeDebugPipe()
205 {
206         DIR *dir;
207         struct dirent* entry;
208         char debugPipeFiles[PATH_MAX];;
209         sprintf(debugPipeFiles, "/tmp/clr-debug-pipe-%d-", getpid());
210
211         dir = opendir("/tmp");
212         if (dir == nullptr) {
213                 _ERR("Fail to open /tmp directory");
214                 return;
215         }
216
217         while ((entry = readdir(dir)) != nullptr) {
218                 std::string path = concatPath("/tmp", entry->d_name);
219                 if (path.find(debugPipeFiles) != std::string::npos) {
220                         if (!removeFile(path)) {
221                                 _ERR("Fail to remove file (%s)", path.c_str());
222                         }
223                 }
224         }
225
226         closedir(dir);
227 }
228
229 void preload()
230 {
231         typedef void (*PreloadDelegate)();
232         PreloadDelegate preloadDelegate;
233
234         int ret = createDelegate(__hostHandle,
235                 __domainId,
236                 "Tizen.Runtime",
237                 "Tizen.Runtime.Preloader",
238                 "Preload",
239                 (void**)&preloadDelegate);
240
241         if (ret < 0) {
242                 _ERR("Failed to create delegate for Tizen.Runtime Preload (0x%08x)", ret);
243         } else {
244                 preloadDelegate();
245         }
246
247         pluginPreload();
248 }
249
250 bool initializeCoreClr(PathManager* pm, const std::string& tpa)
251 {
252         bool ncdbStartupHook = isNCDBStartupHookProvided();
253
254         const char *propertyKeys[] = {
255                 "TRUSTED_PLATFORM_ASSEMBLIES",
256                 "APP_PATHS",
257                 "APP_NI_PATHS",
258                 "NATIVE_DLL_SEARCH_DIRECTORIES",
259                 "AppDomainCompatSwitch",
260                 ncdbStartupHook ? "STARTUP_HOOKS" : "" // must be the last one
261         };
262
263         const char *propertyValues[] = {
264                 tpa.c_str(),
265                 pm->getAppPaths().c_str(),
266                 pm->getAppNIPaths().c_str(),
267                 pm->getNativeDllSearchingPaths().c_str(),
268                 "UseLatestBehaviorWhenTFMNotSpecified",
269                 ncdbStartupHook ? getNCDBStartupHook() : "" // must be the last one
270         };
271
272         std::string selfPath = readSelfPath();
273
274         int st = initializeClr(selfPath.c_str(),
275                                                         "TizenDotnetApp",
276                                                         sizeof(propertyKeys) / sizeof(propertyKeys[0]) - (ncdbStartupHook ? 0 : 1),
277                                                         propertyKeys,
278                                                         propertyValues,
279                                                         &__hostHandle,
280                                                         &__domainId);
281
282         if (st < 0) {
283                 _ERR("initialize core clr fail! (0x%08x)", st);
284                 return false;
285         }
286
287         pluginSetCoreclrInfo(__hostHandle, __domainId, createDelegate);
288
289         _INFO("Initialize core clr success");
290         return true;
291 }
292
293 int CoreRuntime::initialize(const char* appType, LaunchMode launchMode)
294 {
295         if (__initialized) {
296                 _ERR("CoreRuntime is already initialized");
297                 return -1;
298         }
299
300         // set language environment to support ICU
301         setLang();
302
303         char *env = nullptr;
304         env = getenv("CORECLR_ENABLE_PROFILING");
305         if (env != nullptr && !strcmp(env, "1")) {
306                 _INFO("profiling mode on");
307                 __isProfileMode = true;
308         }
309
310         // plugin initialize should be called before creating threads.
311         // In case of VD plugins, attaching secure zone is done in the plugin_initialize().
312         // When attaching to a secure zone, if there is a created thread, it will failed.
313         // So, plugin initialize should be called before creating threads.
314         if (initializePluginManager(appType) < 0) {
315                 _ERR("Failed to initialize PluginManager");
316                 return -1;
317         }
318
319         // checkInjection checks dotnet-launcher run mode
320         // At the moment, this mechanism is used only when the Memory Profiler is started.
321         int res = checkInjection();
322         if (res != 0) {
323                 _ERR("Failed to initnialize Memory Profiler");
324                 return -1;
325         }
326
327 #ifdef __arm__
328         // libunwind library is used to unwind stack frame, but libunwind for ARM
329         // does not support ARM vfpv3/NEON registers in DWARF format correctly.
330         // Therefore let's disable stack unwinding using DWARF information
331         // See https://github.com/dotnet/coreclr/issues/6698
332         //
333         // libunwind use following methods to unwind stack frame.
334         // UNW_ARM_METHOD_ALL           0xFF
335         // UNW_ARM_METHOD_DWARF         0x01
336         // UNW_ARM_METHOD_FRAME         0x02
337         // UNW_ARM_METHOD_EXIDX         0x04
338         putenv(const_cast<char *>("UNW_ARM_UNWIND_METHOD=6"));
339 #endif // __arm__
340
341         // Enable diagnostics.
342         // clr create clr-debug-pipe-xxx and dotnet-diagnostics-xxx file under /tmp dir.
343         putenv(const_cast<char *>("COMPlus_EnableDiagnostics=1"));
344
345         // Write Debug.WriteLine to stderr
346         putenv(const_cast<char *>("COMPlus_DebugWriteToStdErr=1"));
347
348 #ifdef USE_DEFAULT_BASE_ADDR
349         putenv(const_cast<char *>("COMPlus_UseDefaultBaseAddr=1"));
350 #endif // USE_DEFAULT_BASE_ADDR
351
352         // Disable config cache to set environment after coreclr_initialize()
353         putenv(const_cast<char *>("COMPlus_DisableConfigCache=1"));
354
355         // Set environment variable for System.Environment.SpecialFolder
356         initEnvForSpecialFolder();
357
358         // read string from external file and set them to environment value.
359         setEnvFromFile();
360
361         try {
362                 __pm = new PathManager();
363         } catch (const std::exception& e) {
364                 _ERR("Failed to create PathManager");
365                 return -1;
366         }
367
368         char* pluginDllPaths = pluginGetDllPath();
369         if (pluginDllPaths && pluginDllPaths[0] != '\0') {
370                 __pm->addPlatformAssembliesPaths(pluginDllPaths, true);
371         }
372
373         char* pluginNativePaths = pluginGetNativeDllSearchingPath();
374         if (pluginNativePaths && pluginNativePaths[0] != '\0') {
375                 __pm->addNativeDllSearchingPaths(pluginNativePaths, true);
376         }
377
378         char* pluginExtraDllPaths = pluginGetExtraDllPath();
379         if (pluginExtraDllPaths && pluginExtraDllPaths[0] != '\0') {
380                 __pm->setExtraDllPaths(pluginExtraDllPaths);
381         }
382
383         std::string libCoreclr(concatPath(__pm->getRuntimePath(), "libcoreclr.so"));
384
385         __coreclrLib = dlopen(libCoreclr.c_str(), RTLD_NOW | RTLD_LOCAL);
386         if (__coreclrLib == nullptr) {
387                 char *err = dlerror();
388                 _ERR("dlopen failed to open libcoreclr.so with error %s", err);
389                 return -1;
390         }
391
392 #define CORELIB_RETURN_IF_NOSYM(type, variable, name) \
393         do { \
394                 variable = (type)dlsym(__coreclrLib, name); \
395                 if (variable == nullptr) { \
396                         _ERR(name " is not found in the libcoreclr.so"); \
397                         return -1; \
398                 } \
399         } while (0)
400
401         CORELIB_RETURN_IF_NOSYM(coreclr_initialize_ptr, initializeClr, "coreclr_initialize");
402         CORELIB_RETURN_IF_NOSYM(coreclr_execute_assembly_ptr, executeAssembly, "coreclr_execute_assembly");
403         CORELIB_RETURN_IF_NOSYM(coreclr_shutdown_ptr, shutdown, "coreclr_shutdown");
404         CORELIB_RETURN_IF_NOSYM(coreclr_create_delegate_ptr, createDelegate, "coreclr_create_delegate");
405
406 #undef CORELIB_RETURN_IF_NOSYM
407
408         _INFO("libcoreclr dlopen and dlsym success");
409
410         std::string tpa;
411         char* pluginTPA = pluginGetTPA();
412         if (pluginTPA && pluginTPA[0] != '\0') {
413                 tpa = std::string(pluginTPA);
414         } else {
415                 addAssembliesFromDirectories(__pm->getPlatformAssembliesPaths(), tpa);
416         }
417
418         if (!initializeCoreClr(__pm, tpa)) {
419                 _ERR("Failed to initialize coreclr");
420                 return -1;
421         }
422
423         // VD has their own signal handler.
424         if (!pluginHasLogControl()) {
425                 registerSigHandler();
426         }
427
428         int st = createDelegate(__hostHandle, __domainId, "Tizen.Runtime", "Tizen.Runtime.Environment", "SetEnvironmentVariable", (void**)&setEnvironmentVariable);
429         if (st < 0 || setEnvironmentVariable == nullptr) {
430                 _ERR("Create delegate for Tizen.Runtime.dll -> Tizen.Runtime.Environment -> SetEnvironmentVariable failed (0x%08x)", st);
431                 return -1;
432         }
433
434         st = createDelegate(__hostHandle, __domainId, "Tizen.Runtime", "Tizen.Runtime.Profiler", "StopProfileAfterDelay", (void**)&stopProfileAfterDelay);
435         if (st < 0 || stopProfileAfterDelay == nullptr) {
436                 _ERR("Create delegate for Tizen.Runtime.dll -> Tizen.Runtime.Profiler -> StopProfileAfterDelay failed (0x%08x)", st);
437                 return -1;
438         }
439
440         st = createDelegate(__hostHandle, __domainId, "Tizen.Runtime", "Tizen.Runtime.AppSetting", "SetSwitch", (void**)&setSwitch);
441         if (st < 0 || setSwitch == nullptr) {
442                 _ERR("Create delegate for Tizen.Runtime.dll -> Tizen.Runtime.AppSetting -> SetSwitch failed (0x%08x)", st);
443                 return -1;
444         }
445
446         if (launchMode == LaunchMode::loader) {
447                 // preload libraries and manage dlls for optimizing startup time
448                 preload();
449
450                 // The debug pipe created in the candidate process has a "User" label.
451                 // As a result, smack deny occurs when app process try to access the debug pipe.
452                 // Also, since debugging is performed only in standalone mode,
453                 // the debug pipe doesnot be used in the candidate process.
454                 // So, to avoid smack deny error, delete unused debug pipe files.
455                 removeDebugPipe();
456         }
457
458         __initialized = true;
459
460         _INFO("CoreRuntime initialize success");
461
462         return 0;
463 }
464
465 void CoreRuntime::finalize()
466 {
467         // call plugin finalize function to notify finalize to plugin
468         // dlclose shoud be done after coreclr shutdown to avoid breaking signal chain
469         pluginFinalize();
470
471         // ignore the signal generated by an exception that occurred during shutdown
472         checkOnTerminate = true;
473
474         // workaround : to prevent crash while process terminate on profiling mode,
475         //                              kill process immediately.
476         // see https://github.com/dotnet/coreclr/issues/26687
477         if (__isProfileMode) {
478                 _INFO("shutdown process immediately.");
479                 _exit(0);
480         }
481
482         if (__hostHandle != nullptr) {
483                 int st = shutdown(__hostHandle, __domainId);
484                 if (st < 0)
485                         _ERR("shutdown core clr fail! (0x%08x)", st);
486                 __hostHandle = nullptr;
487         }
488
489         if (__coreclrLib != nullptr) {
490                 if (dlclose(__coreclrLib) != 0) {
491                         _ERR("libcoreclr.so close failed");
492                 }
493
494                 __coreclrLib = nullptr;
495         }
496
497         finalizePluginManager();
498
499         delete __pm;
500         __pm = NULL;
501
502         __envList.clear();
503
504         _INFO("CoreRuntime finalized");
505 }
506
507 int CoreRuntime::launch(const char* appId, const char* root, const char* path, int argc, char* argv[], bool profile)
508 {
509         if (!__initialized) {
510                 _ERR("Runtime is not initialized");
511                 return -1;
512         }
513
514         if (path == nullptr) {
515                 _ERR("executable path is null");
516                 return -1;
517         }
518
519         if (!isFile(path)) {
520                 _ERR("File not exist : %s", path);
521                 return -1;
522         }
523
524         pluginSetAppInfo(appId, path);
525
526         // temporal root path is overrided to real application root path
527         __pm->setAppRootPath(root);
528
529         // set application data path to coreclr environment.
530         // application data path can be changed by owner. So, we have to set data path just before launching.
531         char* localDataPath = app_get_data_path();
532         if (localDataPath != nullptr) {
533                 setEnvironmentVariable("XDG_DATA_HOME", localDataPath);
534
535                 // set profile.data path and collect/use it if it non-exists/exists.
536                 if (profile) {
537                         char multiCoreJitProfile[strlen(localDataPath) + strlen(PROFILE_BASENAME) + 1];
538                         memcpy(multiCoreJitProfile, localDataPath, strlen(localDataPath) + 1);
539                         strncat(multiCoreJitProfile, PROFILE_BASENAME, strlen(PROFILE_BASENAME));
540
541                         setEnvironmentVariable("COMPlus_MultiCoreJitProfile", multiCoreJitProfile);
542                         setEnvironmentVariable("COMPlus_MultiCoreJitMinNumCpus", "1");
543
544                         if (exist(multiCoreJitProfile)) {
545                                 setEnvironmentVariable("COMPlus_MultiCoreJitNoProfileGather", "1");
546                                 _INFO("MCJ playing start for %s", appId);
547                         } else {
548                                 setEnvironmentVariable("COMPlus_MultiCoreJitNoProfileGather", "0");
549                                 // stop profiling and write collected data after delay if env value is set.
550                                 char *env = getenv("CLR_MCJ_PROFILE_WRITE_DELAY");
551                                 if (env != nullptr) {
552                                         int delay = std::atoi(env);
553                                         // To avoid undefined behavior by out-of-range input(atoi), set max delay value to 100.
554                                         if (delay > 0) {
555                                                 if (delay > MAX_DELAY_SEC) delay = MAX_DELAY_SEC;
556                                                 stopProfileAfterDelay(delay);
557                                         }
558                                 }
559                                 _INFO("MCJ recording start for %s", appId);
560                         }
561                 }
562                 free(localDataPath);
563         }
564
565         if (exist(__pm->getAppRootPath() + "/bin/" + DISABLE_IPV6_FILE)) {
566                 setSwitch("System.Net.DisableIPv6", true);
567         }
568
569         setSwitch("Switch.System.Diagnostics.StackTrace.ShowILOffsets", true);
570
571         pluginBeforeExecute();
572
573         _INFO("execute assembly : %s", path);
574
575         unsigned int ret = 0;
576         int st = executeAssembly(__hostHandle, __domainId, argc, (const char**)argv, path, &ret);
577         if (st < 0)
578                 _ERR("Failed to Execute Assembly %s (0x%08x)", path, st);
579         return ret;
580 }
581
582 }  // namespace dotnetcore
583 }  // namespace runtime
584 }  // namespace tizen