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