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