1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5//
6// Code that is used by both the Unix corerun and coreconsole.
7//
8
9#include <cstdlib>
10#include <cstring>
11#include <assert.h>
12#include <dirent.h>
13#include <dlfcn.h>
14#include <limits.h>
15#include <set>
16#include <string>
17#include <string.h>
18#include <sys/stat.h>
19#if defined(__FreeBSD__)
20#include <sys/types.h>
21#include <sys/param.h>
22#endif
23#if defined(HAVE_SYS_SYSCTL_H) || defined(__FreeBSD__)
24#include <sys/sysctl.h>
25#endif
26#include "coreruncommon.h"
27#include "coreclrhost.h"
28#include <unistd.h>
29#ifndef SUCCEEDED
30#define SUCCEEDED(Status) ((Status) >= 0)
31#endif // !SUCCEEDED
32
33// Name of the environment variable controlling server GC.
34// If set to 1, server GC is enabled on startup. If 0, server GC is
35// disabled. Server GC is off by default.
36static const char* serverGcVar = "COMPlus_gcServer";
37
38// Name of environment variable to control "System.Globalization.Invariant"
39// Set to 1 for Globalization Invariant mode to be true. Default is false.
40static const char* globalizationInvariantVar = "CORECLR_GLOBAL_INVARIANT";
41
42#if defined(__linux__)
43#define symlinkEntrypointExecutable "/proc/self/exe"
44#elif !defined(__APPLE__)
45#define symlinkEntrypointExecutable "/proc/curproc/exe"
46#endif
47
48bool GetEntrypointExecutableAbsolutePath(std::string& entrypointExecutable)
49{
50 bool result = false;
51
52 entrypointExecutable.clear();
53
54 // Get path to the executable for the current process using
55 // platform specific means.
56#if defined(__APPLE__)
57
58 // On Mac, we ask the OS for the absolute path to the entrypoint executable
59 uint32_t lenActualPath = 0;
60 if (_NSGetExecutablePath(nullptr, &lenActualPath) == -1)
61 {
62 // OSX has placed the actual path length in lenActualPath,
63 // so re-attempt the operation
64 std::string resizedPath(lenActualPath, '\0');
65 char *pResizedPath = const_cast<char *>(resizedPath.c_str());
66 if (_NSGetExecutablePath(pResizedPath, &lenActualPath) == 0)
67 {
68 entrypointExecutable.assign(pResizedPath);
69 result = true;
70 }
71 }
72#elif defined (__FreeBSD__)
73 static const int name[] = {
74 CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1
75 };
76 char path[PATH_MAX];
77 size_t len;
78
79 len = sizeof(path);
80 if (sysctl(name, 4, path, &len, nullptr, 0) == 0)
81 {
82 entrypointExecutable.assign(path);
83 result = true;
84 }
85 else
86 {
87 // ENOMEM
88 result = false;
89 }
90#elif defined(__NetBSD__) && defined(KERN_PROC_PATHNAME)
91 static const int name[] = {
92 CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME,
93 };
94 char path[MAXPATHLEN];
95 size_t len;
96
97 len = sizeof(path);
98 if (sysctl(name, __arraycount(name), path, &len, NULL, 0) != -1)
99 {
100 entrypointExecutable.assign(path);
101 result = true;
102 }
103 else
104 {
105 result = false;
106 }
107#else
108 // On other OSs, return the symlink that will be resolved by GetAbsolutePath
109 // to fetch the entrypoint EXE absolute path, inclusive of filename.
110 result = GetAbsolutePath(symlinkEntrypointExecutable, entrypointExecutable);
111#endif
112
113 return result;
114}
115
116bool GetAbsolutePath(const char* path, std::string& absolutePath)
117{
118 bool result = false;
119
120 char realPath[PATH_MAX];
121 if (realpath(path, realPath) != nullptr && realPath[0] != '\0')
122 {
123 absolutePath.assign(realPath);
124 // realpath should return canonicalized path without the trailing slash
125 assert(absolutePath.back() != '/');
126
127 result = true;
128 }
129
130 return result;
131}
132
133bool GetDirectory(const char* absolutePath, std::string& directory)
134{
135 directory.assign(absolutePath);
136 size_t lastSlash = directory.rfind('/');
137 if (lastSlash != std::string::npos)
138 {
139 directory.erase(lastSlash);
140 return true;
141 }
142
143 return false;
144}
145
146bool GetClrFilesAbsolutePath(const char* currentExePath, const char* clrFilesPath, std::string& clrFilesAbsolutePath)
147{
148 std::string clrFilesRelativePath;
149 const char* clrFilesPathLocal = clrFilesPath;
150 if (clrFilesPathLocal == nullptr)
151 {
152 // There was no CLR files path specified, use the folder of the corerun/coreconsole
153 if (!GetDirectory(currentExePath, clrFilesRelativePath))
154 {
155 perror("Failed to get directory from argv[0]");
156 return false;
157 }
158
159 clrFilesPathLocal = clrFilesRelativePath.c_str();
160
161 // TODO: consider using an env variable (if defined) as a fall-back.
162 // The windows version of the corerun uses core_root env variable
163 }
164
165 if (!GetAbsolutePath(clrFilesPathLocal, clrFilesAbsolutePath))
166 {
167 perror("Failed to convert CLR files path to absolute path");
168 return false;
169 }
170
171 return true;
172}
173
174void AddFilesFromDirectoryToTpaList(const char* directory, std::string& tpaList)
175{
176 const char * const tpaExtensions[] = {
177 ".ni.dll", // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir
178 ".dll",
179 ".ni.exe",
180 ".exe",
181 };
182
183 DIR* dir = opendir(directory);
184 if (dir == nullptr)
185 {
186 return;
187 }
188
189 std::set<std::string> addedAssemblies;
190
191 // Walk the directory for each extension separately so that we first get files with .ni.dll extension,
192 // then files with .dll extension, etc.
193 for (int extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++)
194 {
195 const char* ext = tpaExtensions[extIndex];
196 int extLength = strlen(ext);
197
198 struct dirent* entry;
199
200 // For all entries in the directory
201 while ((entry = readdir(dir)) != nullptr)
202 {
203 // We are interested in files only
204 switch (entry->d_type)
205 {
206 case DT_REG:
207 break;
208
209 // Handle symlinks and file systems that do not support d_type
210 case DT_LNK:
211 case DT_UNKNOWN:
212 {
213 std::string fullFilename;
214
215 fullFilename.append(directory);
216 fullFilename.append("/");
217 fullFilename.append(entry->d_name);
218
219 struct stat sb;
220 if (stat(fullFilename.c_str(), &sb) == -1)
221 {
222 continue;
223 }
224
225 if (!S_ISREG(sb.st_mode))
226 {
227 continue;
228 }
229 }
230 break;
231
232 default:
233 continue;
234 }
235
236 std::string filename(entry->d_name);
237
238 // Check if the extension matches the one we are looking for
239 int extPos = filename.length() - extLength;
240 if ((extPos <= 0) || (filename.compare(extPos, extLength, ext) != 0))
241 {
242 continue;
243 }
244
245 std::string filenameWithoutExt(filename.substr(0, extPos));
246
247 // Make sure if we have an assembly with multiple extensions present,
248 // we insert only one version of it.
249 if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end())
250 {
251 addedAssemblies.insert(filenameWithoutExt);
252
253 tpaList.append(directory);
254 tpaList.append("/");
255 tpaList.append(filename);
256 tpaList.append(":");
257 }
258 }
259
260 // Rewind the directory stream to be able to iterate over it for the next extension
261 rewinddir(dir);
262 }
263
264 closedir(dir);
265}
266
267const char* GetEnvValueBoolean(const char* envVariable)
268{
269 const char* envValue = std::getenv(envVariable);
270 if (envValue == nullptr)
271 {
272 envValue = "0";
273 }
274 // CoreCLR expects strings "true" and "false" instead of "1" and "0".
275 return (std::strcmp(envValue, "1") == 0 || strcasecmp(envValue, "true") == 0) ? "true" : "false";
276}
277
278int ExecuteManagedAssembly(
279 const char* currentExeAbsolutePath,
280 const char* clrFilesAbsolutePath,
281 const char* managedAssemblyAbsolutePath,
282 int managedAssemblyArgc,
283 const char** managedAssemblyArgv)
284{
285 // Indicates failure
286 int exitCode = -1;
287
288#ifdef _ARM_
289 // libunwind library is used to unwind stack frame, but libunwind for ARM
290 // does not support ARM vfpv3/NEON registers in DWARF format correctly.
291 // Therefore let's disable stack unwinding using DWARF information
292 // See https://github.com/dotnet/coreclr/issues/6698
293 //
294 // libunwind use following methods to unwind stack frame.
295 // UNW_ARM_METHOD_ALL 0xFF
296 // UNW_ARM_METHOD_DWARF 0x01
297 // UNW_ARM_METHOD_FRAME 0x02
298 // UNW_ARM_METHOD_EXIDX 0x04
299 putenv(const_cast<char *>("UNW_ARM_UNWIND_METHOD=6"));
300#endif // _ARM_
301
302 std::string coreClrDllPath(clrFilesAbsolutePath);
303 coreClrDllPath.append("/");
304 coreClrDllPath.append(coreClrDll);
305
306 if (coreClrDllPath.length() >= PATH_MAX)
307 {
308 fprintf(stderr, "Absolute path to libcoreclr.so too long\n");
309 return -1;
310 }
311
312 // Get just the path component of the managed assembly path
313 std::string appPath;
314 GetDirectory(managedAssemblyAbsolutePath, appPath);
315
316 std::string tpaList;
317 if (strlen(managedAssemblyAbsolutePath) > 0)
318 {
319 // Target assembly should be added to the tpa list. Otherwise corerun.exe
320 // may find wrong assembly to execute.
321 // Details can be found at https://github.com/dotnet/coreclr/issues/5631
322 tpaList = managedAssemblyAbsolutePath;
323 tpaList.append(":");
324 }
325
326 // Construct native search directory paths
327 std::string nativeDllSearchDirs(appPath);
328 char *coreLibraries = getenv("CORE_LIBRARIES");
329 if (coreLibraries)
330 {
331 nativeDllSearchDirs.append(":");
332 nativeDllSearchDirs.append(coreLibraries);
333 if (std::strcmp(coreLibraries, clrFilesAbsolutePath) != 0)
334 {
335 AddFilesFromDirectoryToTpaList(coreLibraries, tpaList);
336 }
337 }
338
339 nativeDllSearchDirs.append(":");
340 nativeDllSearchDirs.append(clrFilesAbsolutePath);
341
342 AddFilesFromDirectoryToTpaList(clrFilesAbsolutePath, tpaList);
343
344 void* coreclrLib = dlopen(coreClrDllPath.c_str(), RTLD_NOW | RTLD_LOCAL);
345 if (coreclrLib != nullptr)
346 {
347 coreclr_initialize_ptr initializeCoreCLR = (coreclr_initialize_ptr)dlsym(coreclrLib, "coreclr_initialize");
348 coreclr_execute_assembly_ptr executeAssembly = (coreclr_execute_assembly_ptr)dlsym(coreclrLib, "coreclr_execute_assembly");
349 coreclr_shutdown_2_ptr shutdownCoreCLR = (coreclr_shutdown_2_ptr)dlsym(coreclrLib, "coreclr_shutdown_2");
350
351 if (initializeCoreCLR == nullptr)
352 {
353 fprintf(stderr, "Function coreclr_initialize not found in the libcoreclr.so\n");
354 }
355 else if (executeAssembly == nullptr)
356 {
357 fprintf(stderr, "Function coreclr_execute_assembly not found in the libcoreclr.so\n");
358 }
359 else if (shutdownCoreCLR == nullptr)
360 {
361 fprintf(stderr, "Function coreclr_shutdown_2 not found in the libcoreclr.so\n");
362 }
363 else
364 {
365 // Check whether we are enabling server GC (off by default)
366 const char* useServerGc = GetEnvValueBoolean(serverGcVar);
367
368 // Check Globalization Invariant mode (false by default)
369 const char* globalizationInvariant = GetEnvValueBoolean(globalizationInvariantVar);
370
371 // Allowed property names:
372 // APPBASE
373 // - The base path of the application from which the exe and other assemblies will be loaded
374 //
375 // TRUSTED_PLATFORM_ASSEMBLIES
376 // - The list of complete paths to each of the fully trusted assemblies
377 //
378 // APP_PATHS
379 // - The list of paths which will be probed by the assembly loader
380 //
381 // APP_NI_PATHS
382 // - The list of additional paths that the assembly loader will probe for ngen images
383 //
384 // NATIVE_DLL_SEARCH_DIRECTORIES
385 // - The list of paths that will be probed for native DLLs called by PInvoke
386 //
387 const char *propertyKeys[] = {
388 "TRUSTED_PLATFORM_ASSEMBLIES",
389 "APP_PATHS",
390 "APP_NI_PATHS",
391 "NATIVE_DLL_SEARCH_DIRECTORIES",
392 "System.GC.Server",
393 "System.Globalization.Invariant",
394 };
395 const char *propertyValues[] = {
396 // TRUSTED_PLATFORM_ASSEMBLIES
397 tpaList.c_str(),
398 // APP_PATHS
399 appPath.c_str(),
400 // APP_NI_PATHS
401 appPath.c_str(),
402 // NATIVE_DLL_SEARCH_DIRECTORIES
403 nativeDllSearchDirs.c_str(),
404 // System.GC.Server
405 useServerGc,
406 // System.Globalization.Invariant
407 globalizationInvariant,
408 };
409
410 void* hostHandle;
411 unsigned int domainId;
412
413 int st = initializeCoreCLR(
414 currentExeAbsolutePath,
415 "unixcorerun",
416 sizeof(propertyKeys) / sizeof(propertyKeys[0]),
417 propertyKeys,
418 propertyValues,
419 &hostHandle,
420 &domainId);
421
422 if (!SUCCEEDED(st))
423 {
424 fprintf(stderr, "coreclr_initialize failed - status: 0x%08x\n", st);
425 exitCode = -1;
426 }
427 else
428 {
429 st = executeAssembly(
430 hostHandle,
431 domainId,
432 managedAssemblyArgc,
433 managedAssemblyArgv,
434 managedAssemblyAbsolutePath,
435 (unsigned int*)&exitCode);
436
437 if (!SUCCEEDED(st))
438 {
439 fprintf(stderr, "coreclr_execute_assembly failed - status: 0x%08x\n", st);
440 exitCode = -1;
441 }
442
443 int latchedExitCode = 0;
444 st = shutdownCoreCLR(hostHandle, domainId, &latchedExitCode);
445 if (!SUCCEEDED(st))
446 {
447 fprintf(stderr, "coreclr_shutdown failed - status: 0x%08x\n", st);
448 exitCode = -1;
449 }
450
451 if (exitCode != -1)
452 {
453 exitCode = latchedExitCode;
454 }
455 }
456 }
457
458 if (dlclose(coreclrLib) != 0)
459 {
460 fprintf(stderr, "Warning - dlclose failed\n");
461 }
462 }
463 else
464 {
465 const char* error = dlerror();
466 fprintf(stderr, "dlopen failed to open the libcoreclr.so with error %s\n", error);
467 }
468
469 return exitCode;
470}
471