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