Add support on Windows to install atexit handler(s) early in application’s startup.
authorlateralusX <lateralusx.github@gmail.com>
Thu, 20 Oct 2016 10:48:09 +0000 (12:48 +0200)
committerlateralusX <lateralusx.github@gmail.com>
Thu, 20 Oct 2016 10:49:09 +0000 (12:49 +0200)
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.

mono/mini/driver.c
mono/mini/mini-runtime.c
mono/mini/mini-windows.c
mono/mini/mini.h

index e31628dc795b53151c8028fd03e04d7386e705c8..c1a0ae05a913727056f12a5a6049e4f68019fd96 100644 (file)
@@ -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]);
index 231b44d65ecda7a9d32996d8283a86df082d2682..c0f0e04ec9c039b8d666d1cc7b202901c1bc7af0 100644 (file)
@@ -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 */
index 2aa3a7b75d1bc21d177500be8388a7448a6991fd..d9e44f3b6ce9649937034ded86c0e8c7f51dacfd 100644 (file)
@@ -13,6 +13,7 @@
 #include <config.h>
 #include <signal.h>
 #include <math.h>
+#include <conio.h>
 
 #include <mono/metadata/assembly.h>
 #include <mono/metadata/loader.h>
 #include <mmsystem.h>
 #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)
 {
index dd24cd540b12b9ff16146e1192d68942ef79c60e..d1236ef1b539160015486b6fe33566675c981a53 100644 (file)
@@ -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);