1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
5 #include <coreclrhost.h>
9 using char_t = pal::char_t;
10 using string_t = pal::string_t;
14 configuration() = default;
15 configuration(const configuration&) = delete;
16 configuration(configuration&&) = delete;
17 configuration& operator=(const configuration&) = delete;
18 configuration& operator=(configuration&&) = delete;
22 for (int i = 0; i < entry_assembly_argc; ++i)
24 ::free((void*)entry_assembly_argv[i]);
26 ::free(entry_assembly_argv);
33 // CLR path - user supplied location of coreclr binary and managed assemblies.
36 // The full path to the Supplied managed entry assembly.
37 string_t entry_assembly_fullpath;
39 // Arguments to pass to managed entry assembly.
40 int entry_assembly_argc;
41 const char_t** entry_assembly_argv;
43 // Collection of user-defined key/value pairs that will be appended
44 // to the initialization of the runtime.
45 std::vector<string_t> user_defined_keys;
46 std::vector<string_t> user_defined_values;
48 // Wait for debugger to be attached.
57 // Points to a path containing the CoreCLR binary.
58 const char_t* coreRoot = W("CORE_ROOT");
60 // Points to a path containing additional platform assemblies.
61 const char_t* coreLibraries = W("CORE_LIBRARIES");
63 // Variable used to preload a mock hostpolicy for testing.
64 const char_t* mockHostPolicy = W("MOCK_HOSTPOLICY");
67 static void wait_for_debugger()
69 pal::debugger_state_t state = pal::is_debugger_attached();
70 if (state == pal::debugger_state_t::na)
72 pal::fprintf(stdout, W("Debugger attach is not available on this platform\n"));
75 else if (state == pal::debugger_state_t::not_attached)
77 uint32_t pid = pal::get_process_id();
78 pal::fprintf(stdout, W("Waiting for the debugger to attach (PID: %u). Press any key to continue ...\n"), pid);
80 state = pal::is_debugger_attached();
83 if (state == pal::debugger_state_t::attached)
85 pal::fprintf(stdout, W("Debugger is attached.\n"));
89 pal::fprintf(stdout, W("Debugger failed to attach.\n"));
93 // N.B. It seems that CoreCLR doesn't always use the first instance of an assembly on the TPA list
94 // (for example, ni's may be preferred over il, even if they appear later). Therefore, when building
95 // the TPA only include the first instance of a simple assembly name to allow users the opportunity to
96 // override Framework assemblies by placing dlls in %CORE_LIBRARIES%.
97 static string_t build_tpa(const string_t& core_root, const string_t& core_libraries)
99 static const char_t* const tpa_extensions[] =
101 W(".ni.dll"), // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir
108 std::set<string_t> name_set;
109 pal::stringstream_t tpa_list;
111 // Iterate over all extensions.
112 for (const char_t* const* curr_ext = tpa_extensions; *curr_ext != nullptr; ++curr_ext)
114 const char_t* ext = *curr_ext;
115 const size_t ext_len = pal::strlen(ext);
117 // Iterate over all supplied directories.
118 for (const string_t& dir : { core_libraries, core_root })
123 assert(dir.back() == pal::dir_delim);
124 string_t tmp = pal::build_file_list(dir, ext, [&](const char_t* file)
126 string_t file_local{ file };
128 // Strip the extension.
129 if (pal::string_ends_with(file_local, ext_len, ext))
130 file_local = file_local.substr(0, file_local.length() - ext_len);
132 // Return true if the file is new.
133 return name_set.insert(file_local).second;
141 return tpa_list.str();
144 static bool try_get_export(pal::mod_t mod, const char* symbol, void** fptr)
146 assert(mod != nullptr && symbol != nullptr && fptr != nullptr);
147 *fptr = pal::get_module_symbol(mod, symbol);
148 if (*fptr != nullptr)
151 pal::fprintf(stderr, W("Export '%s' not found.\n"), symbol);
157 const char* _exePath;
159 const char** _propertyKeys;
160 const char** _propertyValues;
161 const char* _managedAssembly;
167 int propertyCount, const char** propertyKeys, const char** propertyValues,
168 const char* managedAssembly, int argc, const char** argv)
169 : _exePath{ exePath }
170 , _propertyCount{ propertyCount }
171 , _propertyKeys{ propertyKeys }
172 , _propertyValues{ propertyValues }
173 , _managedAssembly{ managedAssembly }
178 void dump_details(FILE* fd = stdout)
180 // Using std::fprintf since values have been converted to UTF-8.
181 std::fprintf(fd, "Exe path: %s\n", _exePath);
182 std::fprintf(fd, "Properties:\n");
183 for (int i = 0; i < _propertyCount; ++i)
185 std::fprintf(fd, " %s = %s\n", _propertyKeys[i], _propertyValues[i]);
188 std::fprintf(fd, "Managed assembly: %s\n", _managedAssembly);
189 std::fprintf(fd, "Arguments (%d): ", _argc);
190 for (int i = 0; i < _argc; ++i)
192 std::fprintf(fd, "%s ", _argv[i]);
194 std::fprintf(fd, "\n");
198 // The current CoreCLR instance details.
199 static void* CurrentClrInstance;
200 static unsigned int CurrentAppDomainId;
202 static int run(const configuration& config)
204 platform_specific_actions actions;
206 // Check if debugger attach scenario was requested.
207 if (config.wait_to_debug)
210 string_t exe_path = pal::get_exe_path();
212 // Determine the managed application's path.
216 pal::split_path_to_dir_filename(config.entry_assembly_fullpath, app_path, file);
217 pal::ensure_trailing_delimiter(app_path);
220 // Define the NI app_path.
221 string_t app_path_ni = app_path + W("NI");
222 pal::ensure_trailing_delimiter(app_path_ni);
223 app_path_ni.append(1, pal::env_path_delim);
224 app_path_ni.append(app_path);
226 // Accumulate path for native search path.
227 pal::stringstream_t native_search_dirs;
228 native_search_dirs << app_path << pal::env_path_delim;
231 string_t core_libs = pal::getenv(envvar::coreLibraries);
232 if (!core_libs.empty() && core_libs != app_path)
234 pal::ensure_trailing_delimiter(core_libs);
235 native_search_dirs << core_libs << pal::env_path_delim;
238 // Determine CORE_ROOT.
239 // Check if the path is user supplied and if not try
240 // the CORE_ROOT environment variable.
241 string_t core_root = !config.clr_path.empty()
243 : pal::getenv(envvar::coreRoot);
245 // If CORE_ROOT wasn't supplied use the exe binary path, otherwise
246 // ensure path is valid and add to native search path.
247 if (core_root.empty())
250 pal::split_path_to_dir_filename(exe_path, core_root, file);
251 pal::ensure_trailing_delimiter(core_root);
255 pal::ensure_trailing_delimiter(core_root);
256 native_search_dirs << core_root << pal::env_path_delim;
259 string_t tpa_list = build_tpa(core_root, core_libs);
262 // Load hostpolicy if requested.
263 string_t mock_hostpolicy = pal::getenv(envvar::mockHostPolicy);
264 if (!mock_hostpolicy.empty()
265 && !pal::try_load_hostpolicy(mock_hostpolicy))
271 actions.before_coreclr_load();
273 // Attempt to load CoreCLR.
274 pal::mod_t coreclr_mod;
275 if (!pal::try_load_coreclr(core_root, coreclr_mod))
280 // Get CoreCLR exports
281 coreclr_initialize_ptr coreclr_init_func = nullptr;
282 coreclr_execute_assembly_ptr coreclr_execute_func = nullptr;
283 coreclr_shutdown_2_ptr coreclr_shutdown2_func = nullptr;
284 if (!try_get_export(coreclr_mod, "coreclr_initialize", (void**)&coreclr_init_func)
285 || !try_get_export(coreclr_mod, "coreclr_execute_assembly", (void**)&coreclr_execute_func)
286 || !try_get_export(coreclr_mod, "coreclr_shutdown_2", (void**)&coreclr_shutdown2_func))
291 // Construct CoreCLR properties.
292 pal::string_utf8_t tpa_list_utf8 = pal::convert_to_utf8(std::move(tpa_list));
293 pal::string_utf8_t app_path_utf8 = pal::convert_to_utf8(std::move(app_path));
294 pal::string_utf8_t app_path_ni_utf8 = pal::convert_to_utf8(std::move(app_path_ni));
295 pal::string_utf8_t native_search_dirs_utf8 = pal::convert_to_utf8(native_search_dirs.str());
297 std::vector<pal::string_utf8_t> user_defined_keys_utf8;
298 std::vector<pal::string_utf8_t> user_defined_values_utf8;
299 for (const string_t& str : config.user_defined_keys)
300 user_defined_keys_utf8.push_back(pal::convert_to_utf8(str.c_str()));
301 for (const string_t& str : config.user_defined_values)
302 user_defined_values_utf8.push_back(pal::convert_to_utf8(str.c_str()));
304 // Set base initialization properties.
305 std::vector<const char*> propertyKeys;
306 std::vector<const char*> propertyValues;
308 // TRUSTED_PLATFORM_ASSEMBLIES
309 // - The list of complete paths to each of the fully trusted assemblies
310 propertyKeys.push_back("TRUSTED_PLATFORM_ASSEMBLIES");
311 propertyValues.push_back(tpa_list_utf8.c_str());
314 // - The list of paths which will be probed by the assembly loader
315 propertyKeys.push_back("APP_PATHS");
316 propertyValues.push_back(app_path_utf8.c_str());
319 // - The list of additional paths that the assembly loader will probe for ngen images
320 propertyKeys.push_back("APP_NI_PATHS");
321 propertyValues.push_back(app_path_ni_utf8.c_str());
323 // NATIVE_DLL_SEARCH_DIRECTORIES
324 // - The list of paths that will be probed for native DLLs called by PInvoke
325 propertyKeys.push_back("NATIVE_DLL_SEARCH_DIRECTORIES");
326 propertyValues.push_back(native_search_dirs_utf8.c_str());
328 // Sanity check before adding user-defined properties
329 assert(propertyKeys.size() == propertyValues.size());
331 // Insert user defined properties
332 for (const pal::string_utf8_t& str : user_defined_keys_utf8)
333 propertyKeys.push_back(str.c_str());
334 for (const pal::string_utf8_t& str : user_defined_values_utf8)
335 propertyValues.push_back(str.c_str());
337 assert(propertyKeys.size() == propertyValues.size());
338 int propertyCount = (int)propertyKeys.size();
340 // Construct arguments
341 pal::string_utf8_t exe_path_utf8 = pal::convert_to_utf8(std::move(exe_path));
342 std::vector<pal::string_utf8_t> argv_lifetime;
343 pal::malloc_ptr<const char*> argv_utf8{ pal::convert_argv_to_utf8(config.entry_assembly_argc, config.entry_assembly_argv, argv_lifetime) };
344 pal::string_utf8_t entry_assembly_utf8 = pal::convert_to_utf8(config.entry_assembly_fullpath.c_str());
347 exe_path_utf8.c_str(),
348 propertyCount, propertyKeys.data(), propertyValues.data(),
349 entry_assembly_utf8.c_str(), config.entry_assembly_argc, argv_utf8.get() };
352 result = coreclr_init_func(
353 exe_path_utf8.c_str(),
357 propertyValues.data(),
359 &CurrentAppDomainId);
362 pal::fprintf(stderr, W("BEGIN: coreclr_initialize failed - Error: 0x%08x\n"), result);
363 logger.dump_details();
364 pal::fprintf(stderr, W("END: coreclr_initialize failed - Error: 0x%08x\n"), result);
370 actions.before_execute_assembly(config.entry_assembly_fullpath);
372 result = coreclr_execute_func(
375 config.entry_assembly_argc,
377 entry_assembly_utf8.c_str(),
378 (uint32_t*)&exit_code);
381 pal::fprintf(stderr, W("BEGIN: coreclr_execute_assembly failed - Error: 0x%08x\n"), result);
382 logger.dump_details();
383 pal::fprintf(stderr, W("END: coreclr_execute_assembly failed - Error: 0x%08x\n"), result);
387 actions.after_execute_assembly();
390 int latched_exit_code = 0;
391 result = coreclr_shutdown2_func(CurrentClrInstance, CurrentAppDomainId, &latched_exit_code);
394 pal::fprintf(stderr, W("coreclr_shutdown_2 failed - Error: 0x%08x\n"), result);
399 exit_code = latched_exit_code;
404 // Display the command line options
405 static void display_usage()
409 W("USAGE: corerun [OPTIONS] assembly [ARGUMENTS]\n")
411 W("Execute the managed assembly with the passed in arguments\n")
414 W(" -c, --clr-path - path to CoreCLR binary and managed CLR assemblies.\n")
415 W(" -p, --property - Property to pass to runtime during initialization.\n")
416 W(" If a property value contains spaces, quote the entire argument.\n")
417 W(" May be supplied multiple times. Format: <key>=<value>.\n")
418 W(" -d, --debug - causes corerun to wait for a debugger to attach before executing.\n")
419 W(" -?, -h, --help - show this help.\n")
421 W("The runtime binary is searched for in --clr-path, CORE_ROOT environment variable, then\n")
422 W("in the directory the corerun binary is located.\n")
425 W("Wait for a debugger to attach, provide 2 additional properties for .NET\n")
426 W("runtime initialization, and pass an argument to the HelloWorld.dll assembly.\n")
427 W(" corerun -d -p System.GC.Concurrent=true -p \"FancyProp=/usr/first last/root\" HelloWorld.dll arg1\n")
431 // Parse the command line arguments
432 static bool parse_args(
434 const char_t* argv[],
435 configuration& config)
437 // The command line must contain at least the current exe name and the managed assembly path.
444 for (int i = 1; i < argc; i++)
446 bool is_option = pal::is_cli_option(argv[i][0]);
448 // First argument that is not an option is the managed assembly to execute.
451 if (pal::strcmp(argv[i], W("exec")) == 0)
456 config.entry_assembly_fullpath = pal::get_absolute_path(argv[i]);
457 i++; // Move to next argument.
459 config.entry_assembly_argc = argc - i;
460 config.entry_assembly_argv = (const char_t**)::malloc(config.entry_assembly_argc * sizeof(const char_t*));
461 assert(config.entry_assembly_argv != nullptr);
462 for (int c = 0; c < config.entry_assembly_argc; ++c)
464 config.entry_assembly_argv[c] = pal::strdup(argv[i + c]);
467 // Successfully parsed arguments.
471 const char_t* arg = argv[i];
472 size_t arg_len = pal::strlen(arg);
475 pal::fprintf(stderr, W("Option %s: invalid form\n"), arg);
476 break; // Invalid option
479 const char_t* option = arg + 1;
480 if (option[0] == W('-')) // Handle double '--'
484 if (pal::strcmp(option, W("c")) == 0 || (pal::strcmp(option, W("clr-path")) == 0))
489 config.clr_path = argv[i];
493 pal::fprintf(stderr, W("Option %s: missing path\n"), arg);
497 else if (pal::strcmp(option, W("p")) == 0 || (pal::strcmp(option, W("property")) == 0))
502 pal::fprintf(stderr, W("Option %s: missing property\n"), arg);
506 string_t prop = argv[i];
507 size_t delim_maybe = prop.find(W('='));
508 if (delim_maybe == string_t::npos)
510 pal::fprintf(stderr, W("Option %s: '%s' missing property value\n"), arg, prop.c_str());
514 string_t key = prop.substr(0, delim_maybe);
515 string_t value = prop.substr(delim_maybe + 1);
516 config.user_defined_keys.push_back(std::move(key));
517 config.user_defined_values.push_back(std::move(value));
519 else if (pal::strcmp(option, W("d")) == 0 || (pal::strcmp(option, W("debug")) == 0))
521 config.wait_to_debug = true;
523 else if (pal::strcmp(option, W("st")) == 0)
525 config.self_test = true;
528 else if ((pal::strcmp(option, W("?")) == 0 || (pal::strcmp(option, W("h")) == 0 || (pal::strcmp(option, W("help")) == 0))))
533 // Now we use corerun to run corefx tests instead of dotnet, because last one isn't available for
534 // Tizen/armel. So we need to skip dotnet specific arguments, we patch corerun for it because
535 // Microsoft.DotNet.RemoteExecutor tries to execute binary that it gets from /proc/self/maps, so we
536 // need a binary that will behave like dotnet.
537 else if (pal::strcmp(option, W("--runtimeconfig")) == 0 || pal::strcmp(option, W("--depsfile")) == 0)
544 pal::fprintf(stderr, W("Unknown option %s\n"), arg);
552 // Forward declaration for self testing method.
553 static int self_test();
559 int MAIN(const int argc, const char_t* argv[])
561 configuration config{};
562 if (!parse_args(argc, argv, config))
565 if (config.self_test)
568 int exit_code = run(config);
572 #ifdef TARGET_WINDOWS
573 // Used by CoreShim to determine running CoreCLR details.
574 extern "C" __declspec(dllexport) HRESULT __cdecl GetCurrentClrDetails(void** clrInstance, unsigned int* appDomainId)
576 assert(clrInstance != nullptr && appDomainId != nullptr);
577 *clrInstance = CurrentClrInstance;
578 *appDomainId = CurrentAppDomainId;
581 #endif // TARGET_WINDOWS
584 // Self testing for corerun.
587 #define THROW_IF_FALSE(stmt) if (!(stmt)) throw W(#stmt);
588 #define THROW_IF_TRUE(stmt) if (stmt) throw W(#stmt);
589 static int self_test()
594 configuration config{};
595 const char_t* args[] = { W(""), W("-d"), W("foo") };
596 THROW_IF_FALSE(parse_args(3, args, config));
597 THROW_IF_FALSE(config.wait_to_debug);
598 THROW_IF_FALSE(config.clr_path.empty());
599 THROW_IF_FALSE(!config.entry_assembly_fullpath.empty());
600 THROW_IF_FALSE(config.entry_assembly_argc == 0);
603 configuration config{};
604 const char_t* args[] = { W(""), W("-d"), W("foo"), W("1"), W("2"), W("3") };
605 THROW_IF_FALSE(parse_args(6, args, config));
606 THROW_IF_FALSE(config.wait_to_debug);
607 THROW_IF_FALSE(!config.entry_assembly_fullpath.empty());
608 THROW_IF_FALSE(config.entry_assembly_argc == 3);
611 configuration config{};
612 const char_t* args[] = { W(""), W("--clr-path"), W("path"), W("foo"), W("1") };
613 THROW_IF_FALSE(parse_args(5, args, config));
614 THROW_IF_FALSE(!config.wait_to_debug);
615 THROW_IF_FALSE(config.clr_path == W("path"));
616 THROW_IF_FALSE(!config.entry_assembly_fullpath.empty());
617 THROW_IF_FALSE(config.entry_assembly_argc == 1);
620 configuration config{};
621 const char_t* args[] = { W(""), W("-p"), W("invalid"), W("foo") };
622 THROW_IF_TRUE(parse_args(4, args, config));
625 configuration config{};
626 const char_t* args[] = { W(""), W("-p"), W("empty="), W("foo") };
627 THROW_IF_FALSE(parse_args(4, args, config));
628 THROW_IF_FALSE(config.user_defined_keys.size() == 1);
629 THROW_IF_FALSE(config.user_defined_values.size() == 1);
630 THROW_IF_FALSE(!config.entry_assembly_fullpath.empty());
633 configuration config{};
634 const char_t* args[] = { W(""), W("-p"), W("one=1"), W("foo") };
635 THROW_IF_FALSE(parse_args(4, args, config));
636 THROW_IF_FALSE(config.user_defined_keys.size() == 1);
637 THROW_IF_FALSE(config.user_defined_values.size() == 1);
638 THROW_IF_FALSE(!config.entry_assembly_fullpath.empty());
641 configuration config{};
642 const char_t* args[] = { W(""), W("-p"), W("one=1"), W("--property"), W("System.GC.Concurrent=true"), W("foo") };
643 THROW_IF_FALSE(parse_args(6, args, config));
644 THROW_IF_FALSE(config.user_defined_keys.size() == 2);
645 THROW_IF_FALSE(config.user_defined_values.size() == 2);
646 THROW_IF_FALSE(!config.entry_assembly_fullpath.empty());
651 pal::ensure_trailing_delimiter(path);
652 THROW_IF_FALSE(path.back() == pal::dir_delim);
654 pal::ensure_trailing_delimiter(path);
655 THROW_IF_FALSE(path.back() == pal::dir_delim);
657 pal::ensure_trailing_delimiter(path);
658 THROW_IF_FALSE(path.back() == pal::dir_delim || path.length() == 1);
660 pal::ensure_trailing_delimiter(path);
661 THROW_IF_FALSE(path.back() == pal::dir_delim || path.length() == 1);
664 THROW_IF_FALSE(!pal::string_ends_with(W(""), W(".cd")));
665 THROW_IF_FALSE(pal::string_ends_with(W("ab.cd"), W(".cd")));
666 THROW_IF_FALSE(!pal::string_ends_with(W("ab.cd"), W(".cde")));
667 THROW_IF_FALSE(!pal::string_ends_with(W("ab.cd"), W("ab.cde")));
670 catch (const char_t msg[])
672 pal::fprintf(stderr, W("Fail: %s\n"), msg);
676 pal::fprintf(stdout, W("Self-test passed.\n"));