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