From: lateralusX Date: Thu, 20 Oct 2016 10:48:09 +0000 (+0200) Subject: Add support on Windows to install atexit handler(s) early in application’s startup. X-Git-Url: http://wien.tomnetworks.com/gitweb/?p=mono.git;a=commitdiff_plain;h=bdbea3589f23bbd25e0325a6c1c7cb544e3bddc7 Add support on Windows to install atexit handler(s) early in application’s startup. This is done by adding a new argument to install specific named custom handlers, --handlers=. There is also an argument to describe the supported handlers on current platform, --help-handlers. The supported handlers can be different depending on implementing platform and currently only Windows uses this feature to install an atexit handler waiting for a keypress before exiting process. This is however a generic concept that could be used to run handler code early in the boot process on any platform for other purposes. Adding the argument first in command line makes it possible to do the installation very early in the boot process, this is used by the waitkeypress atexit handler in order to be used in cases where the arguments itself are unsupported or incorrect formatted causing an exit of the process. When running under Visual Studio debugger the console window will not be kept open when the debugee process terminates. Normally this is not a problem, but there are scenarios, especially when having errors in configuration, when you would like the console to be kept open so you can read the output in order to detect the error. Normally there is an option to start the console application from within Visual Studio but not attaching the debugger (CTRL+F5) and by default Visual Studio will then inject a pause in the execution of the process holding up the console, but for some reasons, having more complex solutions like mono, this feature stops working, so it is not possible to keep the console open in that scenario for more complex solutions. This makes it a little problematic to hold the process when working within the debugger in order to look for console output and errors. Since mono can exit the process using exit command and not only by returning from main, the developer needs to set breakpoints on both end of main and in c-runtime exit methods. Even if that is a possible workaround this option won't work when running the process outside the debugger (CTRL+F5). This commit adds a handler argument that can be used on Windows (potential on other platforms as well), atexit-waitkeypress. The waitkeypress will hold the console and process open until a key is pressed and is very useful when running and debugging mono from within Visual Studio. Example of output when running mono’s mono-mini-regression-test with an assembly not found together with –handlers=atexit-waitkeypress command line argument: failed to load assembly: gshared-nofound.exe Overall results: tests: 0, 100% pass, opt combinations: 25 Press any key to continue . . . Currently this is only implemented for Windows, but could be enabled for other platforms, if similar need exist elsewhere. Since this is a standard mono command line option it is possible to inject it using the MONO_ENV_OPTIONS environment variable as well. --- diff --git a/mono/mini/driver.c b/mono/mini/driver.c index e31628dc795..c1a0ae05a91 100644 --- a/mono/mini/driver.c +++ b/mono/mini/driver.c @@ -1274,6 +1274,7 @@ mini_usage (void) #ifdef HOST_WIN32 " --mixed-mode Enable mixed-mode image support.\n" #endif + " --handlers Install custom handlers, use --help-handlers for details.\n" ); } @@ -1556,6 +1557,10 @@ switch_arch (char* argv[], const char* target_arch) } #endif + +#define MONO_HANDLERS_ARGUMENT "--handlers=" +#define MONO_HANDLERS_ARGUMENT_LEN G_N_ELEMENTS(MONO_HANDLERS_ARGUMENT)-1 + /** * mono_main: * @argc: number of arguments in the argv array @@ -1908,6 +1913,15 @@ mono_main (int argc, char* argv[]) } else if (strcmp (argv [i], "--nacl-null-checks-off") == 0){ nacl_null_checks_off = TRUE; #endif + } else if (strncmp (argv [i], MONO_HANDLERS_ARGUMENT, MONO_HANDLERS_ARGUMENT_LEN) == 0) { + //Install specific custom handlers. + if (!mono_runtime_install_custom_handlers (argv[i] + MONO_HANDLERS_ARGUMENT_LEN)) { + fprintf (stderr, "error: " MONO_HANDLERS_ARGUMENT ", one or more unknown handlers: '%s'\n", argv [i]); + return 1; + } + } else if (strcmp (argv [i], "--help-handlers") == 0) { + mono_runtime_install_custom_handlers_usage (); + return 0; } else if (argv [i][0] == '-' && argv [i][1] == '-' && mini_parse_debug_option (argv [i] + 2)) { } else { fprintf (stderr, "Unknown command line option: '%s'\n", argv [i]); diff --git a/mono/mini/mini-runtime.c b/mono/mini/mini-runtime.c index 231b44d65ec..c0f0e04ec9c 100644 --- a/mono/mini/mini-runtime.c +++ b/mono/mini/mini-runtime.c @@ -4256,3 +4256,23 @@ mono_personality (void) /* Not used */ g_assert_not_reached (); } + +// Custom handlers currently only implemented by Windows. +#ifndef HOST_WIN32 +gboolean +mono_runtime_install_custom_handlers (const char *handlers) +{ + return FALSE; +} + +void +mono_runtime_install_custom_handlers_usage (void) +{ + fprintf (stdout, + "Custom Handlers:\n" + " --handlers=HANDLERS Enable handler support, HANDLERS is a comma\n" + " separated list of available handlers to install.\n" + "\n" + "No handlers supported on current platform.\n"); +} +#endif /* HOST_WIN32 */ diff --git a/mono/mini/mini-windows.c b/mono/mini/mini-windows.c index 2aa3a7b75d1..d9e44f3b6ce 100644 --- a/mono/mini/mini-windows.c +++ b/mono/mini/mini-windows.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -56,6 +57,134 @@ #include #endif +#define MONO_HANDLER_DELIMITER ',' +#define MONO_HANDLER_DELIMITER_LEN G_N_ELEMENTS(MONO_HANDLER_DELIMITER)-1 + +#define MONO_HANDLER_ATEXIT_WAIT_KEYPRESS "atexit-waitkeypress" +#define MONO_HANDLER_ATEXIT_WAIT_KEYPRESS_LEN G_N_ELEMENTS(MONO_HANDLER_ATEXIT_WAIT_KEYPRESS)-1 + +// Typedefs used to setup handler table. +typedef void (*handler)(void); + +typedef struct { + const char * cmd; + const int cmd_len; + handler handler; +} HandlerItem; + +#if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) +/** +* atexit_wait_keypress: +* +* This function is installed as an atexit function making sure that the console is not terminated before the end user has a chance to read the result. +* This can be handy in debug scenarios (running from within the debugger) since an exit of the process will close the console window +* without giving the end user a chance to look at the output before closed. +*/ +static void +atexit_wait_keypress (void) +{ + + fflush (stdin); + + printf ("Press any key to continue . . . "); + fflush (stdout); + + _getch (); + + return; +} + +/** +* install_atexit_wait_keypress: +* +* This function installs the wait keypress exit handler. +*/ +static void +install_atexit_wait_keypress (void) +{ + atexit (atexit_wait_keypress); + return; +} + +#else + +/** +* install_atexit_wait_keypress: +* +* Not supported on WINAPI family. +*/ +static void +install_atexit_wait_keypress (void) +{ + return; +} + +#endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */ + +// Table describing handlers that can be installed at process startup. Adding a new handler can be done by adding a new item to the table together with an install handler function. +const HandlerItem g_handler_items[] = { { MONO_HANDLER_ATEXIT_WAIT_KEYPRESS, MONO_HANDLER_ATEXIT_WAIT_KEYPRESS_LEN, install_atexit_wait_keypress }, + { NULL, 0, NULL } }; + +/** + * get_handler_arg_len: + * @handlers: Get length of next handler. + * + * This function calculates the length of next handler included in argument. + * + * Returns: The length of next handler, if available. + */ +static size_t +get_next_handler_arg_len (const char *handlers) +{ + assert (handlers != NULL); + + size_t current_len = 0; + const char *handler = strchr (handlers, MONO_HANDLER_DELIMITER); + if (handler != NULL) { + // Get length of next handler arg. + current_len = (handler - handlers); + } else { + // Consume rest as length of next handler arg. + current_len = strlen (handlers); + } + + return current_len; +} + +/** + * install_custom_handler: + * @handlers: Handlers included in --handler argument, example "atexit-waitkeypress,someothercmd,yetanothercmd". + * @handler_arg_len: Output, length of consumed handler. + * + * This function installs the next handler included in @handlers parameter. + * + * Returns: TRUE on successful install, FALSE on failure or unrecognized handler. + */ +static gboolean +install_custom_handler (const char *handlers, size_t *handler_arg_len) +{ + gboolean result = FALSE; + + assert (handlers != NULL); + assert (handler_arg_len); + + *handler_arg_len = get_next_handler_arg_len (handlers); + for (int current_item = 0; current_item < G_N_ELEMENTS (g_handler_items); ++current_item) { + const HandlerItem * handler_item = &g_handler_items [current_item]; + + if (handler_item->cmd == NULL) + continue; + + if (*handler_arg_len == handler_item->cmd_len && strncmp (handlers, handler_item->cmd, *handler_arg_len) == 0) { + assert (handler_item->handler != NULL); + handler_item->handler (); + result = TRUE; + break; + } + } + return result; +} + void mono_runtime_install_handlers (void) { @@ -69,6 +198,44 @@ mono_runtime_install_handlers (void) #endif } +gboolean +mono_runtime_install_custom_handlers (const char *handlers) +{ + gboolean result = FALSE; + + assert (handlers != NULL); + while (*handlers != '\0') { + size_t handler_arg_len = 0; + + result = install_custom_handler (handlers, &handler_arg_len); + handlers += handler_arg_len; + + if (*handlers == MONO_HANDLER_DELIMITER) + handlers++; + if (!result) + break; + } + + return result; +} + +void +mono_runtime_install_custom_handlers_usage (void) +{ + fprintf (stdout, + "Custom Handlers:\n" + " --handlers=HANDLERS Enable handler support, HANDLERS is a comma\n" + " separated list of available handlers to install.\n" + "\n" +#if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) + "HANDLERS is composed of:\n" + " atexit-waitkeypress Install an atexit handler waiting for a keypress\n" + " before exiting process.\n"); +#else + "No handlers supported on current platform.\n"); +#endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */ +} + void mono_runtime_cleanup_handlers (void) { diff --git a/mono/mini/mini.h b/mono/mini/mini.h index dd24cd540b1..d1236ef1b53 100644 --- a/mono/mini/mini.h +++ b/mono/mini/mini.h @@ -3152,6 +3152,8 @@ gboolean mono_jit_map_is_enabled (void); * Per-OS implementation functions. */ void mono_runtime_install_handlers (void); +gboolean mono_runtime_install_custom_handlers (const char *handlers); +void mono_runtime_install_custom_handlers_usage (void); void mono_runtime_cleanup_handlers (void); void mono_runtime_setup_stat_profiler (void); void mono_runtime_shutdown_stat_profiler (void);