/** * Contains druntime startup and shutdown routines. * * Copyright: Copyright Digital Mars 2000 - 2018. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Walter Bright, Sean Kelly * Source: $(DRUNTIMESRC rt/_dmain2.d) */ /* NOTE: This file has been patched from the original DMD distribution to * work with the GDC compiler. */ module rt.dmain2; import rt.memory; import rt.sections; import core.atomic; import core.stdc.stddef; import core.stdc.stdlib; import core.stdc.string; import core.stdc.stdio; // for printf() import core.stdc.errno : errno; version (Windows) { import core.stdc.wchar_; import core.sys.windows.basetsd : HANDLE; import core.sys.windows.shellapi : CommandLineToArgvW; import core.sys.windows.winbase : FreeLibrary, GetCommandLineW, GetProcAddress, IsDebuggerPresent, LoadLibraryW, LocalFree, WriteFile; import core.sys.windows.wincon : CONSOLE_SCREEN_BUFFER_INFO, GetConsoleOutputCP, GetConsoleScreenBufferInfo; import core.sys.windows.winnls : CP_UTF8, MultiByteToWideChar, WideCharToMultiByte; import core.sys.windows.winnt : WCHAR; import core.sys.windows.winuser : MB_ICONERROR, MessageBoxW; pragma(lib, "shell32.lib"); // needed for CommandLineToArgvW } version (FreeBSD) { import core.stdc.fenv; } version (NetBSD) { import core.stdc.fenv; } version (DragonFlyBSD) { import core.stdc.fenv; } // not sure why we can't define this in one place, but this is to keep this // module from importing core.runtime. struct UnitTestResult { size_t executed; size_t passed; bool runMain; bool summarize; } extern (C) void _d_monitor_staticctor(); extern (C) void _d_monitor_staticdtor(); extern (C) void _d_critical_init(); extern (C) void _d_critical_term(); extern (C) void gc_init(); extern (C) void gc_term(); extern (C) void thread_init() @nogc; extern (C) void thread_term() @nogc; extern (C) void lifetime_init(); extern (C) void rt_moduleCtor(); extern (C) void rt_moduleTlsCtor(); extern (C) void rt_moduleDtor(); extern (C) void rt_moduleTlsDtor(); extern (C) void thread_joinAll(); extern (C) UnitTestResult runModuleUnitTests(); extern (C) void _d_initMonoTime(); version (CRuntime_Microsoft) { extern(C) void init_msvc(); } /* To get out-of-band access to the args[] passed to main(). */ __gshared string[] _d_args = null; extern (C) string[] rt_args() { return _d_args; } // This variable is only ever set by a debugger on initialization so it should // be fine to leave it as __gshared. extern (C) __gshared bool rt_trapExceptions = true; alias void delegate(Throwable) ExceptionHandler; /** * Keep track of how often rt_init/rt_term were called. */ shared size_t _initCount; /********************************************** * Initialize druntime. * If a C program wishes to call D code, and there's no D main(), then it * must call rt_init() and rt_term(). */ extern (C) int rt_init() { /* @@BUG 11380 @@ Need to synchronize rt_init/rt_term calls for version (Shared) druntime, because multiple C threads might initialize different D libraries without knowing about the shared druntime. Also we need to attach any thread that calls rt_init. */ if (atomicOp!"+="(_initCount, 1) > 1) return 1; version (CRuntime_Microsoft) init_msvc(); _d_monitor_staticctor(); _d_critical_init(); try { initSections(); // this initializes mono time before anything else to allow usage // in other druntime systems. _d_initMonoTime(); thread_init(); // TODO: fixme - calls GC.addRange -> Initializes GC initStaticDataGC(); lifetime_init(); rt_moduleCtor(); rt_moduleTlsCtor(); return 1; } catch (Throwable t) { atomicStore!(MemoryOrder.raw)(_initCount, 0); _d_print_throwable(t); } _d_critical_term(); _d_monitor_staticdtor(); return 0; } /********************************************** * Terminate use of druntime. */ extern (C) int rt_term() { if (atomicLoad!(MemoryOrder.raw)(_initCount) == 0) return 0; // was never initialized if (atomicOp!"-="(_initCount, 1)) return 1; try { rt_moduleTlsDtor(); thread_joinAll(); rt_moduleDtor(); gc_term(); thread_term(); return 1; } catch (Throwable t) { _d_print_throwable(t); } finally { finiSections(); _d_critical_term(); _d_monitor_staticdtor(); } return 0; } /********************************************** * Trace handler */ alias Throwable.TraceInfo function(void* ptr) TraceHandler; private __gshared TraceHandler traceHandler = null; /** * Overrides the default trace hander with a user-supplied version. * * Params: * h = The new trace handler. Set to null to use the default handler. */ extern (C) void rt_setTraceHandler(TraceHandler h) { traceHandler = h; } /** * Return the current trace handler */ extern (C) TraceHandler rt_getTraceHandler() { return traceHandler; } /** * This function will be called when an exception is constructed. The * user-supplied trace handler will be called if one has been supplied, * otherwise no trace will be generated. * * Params: * ptr = A pointer to the location from which to generate the trace, or null * if the trace should be generated from within the trace handler * itself. * * Returns: * An object describing the current calling context or null if no handler is * supplied. */ extern (C) Throwable.TraceInfo _d_traceContext(void* ptr = null) { if (traceHandler is null) return null; return traceHandler(ptr); } /*********************************** * Provide out-of-band access to the original C argc/argv * passed to this program via main(argc,argv). */ struct CArgs { int argc; char** argv; } __gshared CArgs _cArgs; extern (C) CArgs rt_cArgs() @nogc { return _cArgs; } /// Type of the D main() function (`_Dmain`). private alias extern(C) int function(char[][] args) MainFunc; /** * Sets up the D char[][] command-line args, initializes druntime, * runs embedded unittests and then runs the given D main() function, * optionally catching and printing any unhandled exceptions. */ extern (C) int _d_run_main(int argc, char** argv, MainFunc mainFunc) { // Set up _cArgs and array of D char[] slices, then forward to _d_run_main2 // Remember the original C argc/argv _cArgs.argc = argc; _cArgs.argv = argv; version (Windows) { /* Because we want args[] to be UTF-8, and Windows doesn't guarantee that, * we ignore argc/argv and go get the Windows command line again as UTF-16. * Then, reparse into wargc/wargs, and then use Windows API to convert * to UTF-8. */ const wCommandLine = GetCommandLineW(); immutable size_t wCommandLineLength = wcslen(wCommandLine); int wargc; auto wargs = CommandLineToArgvW(wCommandLine, &wargc); // assert(wargc == argc); /* argc can be broken by Unicode arguments */ // Allocate args[] on the stack - use wargc char[][] args = (cast(char[]*) alloca(wargc * (char[]).sizeof))[0 .. wargc]; // This is required because WideCharToMultiByte requires int as input. assert(wCommandLineLength <= cast(size_t) int.max, "Wide char command line length must not exceed int.max"); immutable size_t totalArgsLength = WideCharToMultiByte(CP_UTF8, 0, wCommandLine, cast(int)wCommandLineLength, null, 0, null, null); { char* totalArgsBuff = cast(char*) alloca(totalArgsLength); size_t j = 0; foreach (i; 0 .. wargc) { immutable size_t wlen = wcslen(wargs[i]); assert(wlen <= cast(size_t) int.max, "wlen cannot exceed int.max"); immutable int len = WideCharToMultiByte(CP_UTF8, 0, &wargs[i][0], cast(int) wlen, null, 0, null, null); args[i] = totalArgsBuff[j .. j + len]; if (len == 0) continue; j += len; assert(j <= totalArgsLength); WideCharToMultiByte(CP_UTF8, 0, &wargs[i][0], cast(int) wlen, &args[i][0], len, null, null); } } LocalFree(wargs); wargs = null; wargc = 0; } else version (Posix) { // Allocate args[] on the stack char[][] args = (cast(char[]*) alloca(argc * (char[]).sizeof))[0 .. argc]; size_t totalArgsLength = 0; foreach (i, ref arg; args) { arg = argv[i][0 .. strlen(argv[i])]; totalArgsLength += arg.length; } } else static assert(0); return _d_run_main2(args, totalArgsLength, mainFunc); } /** * Windows-specific version for wide command-line arguments, e.g., * from a wmain/wWinMain C entry point. * This wide version uses the specified arguments, unlike narrow * _d_run_main which uses the actual (wide) process arguments instead. */ version (Windows) extern (C) int _d_wrun_main(int argc, wchar** wargv, MainFunc mainFunc) { // Allocate args[] on the stack char[][] args = (cast(char[]*) alloca(argc * (char[]).sizeof))[0 .. argc]; // 1st pass: compute each argument's length as UTF-16 and UTF-8 size_t totalArgsLength = 0; foreach (i; 0 .. argc) { const warg = wargv[i]; const size_t wlen = wcslen(warg) + 1; // incl. terminating null assert(wlen <= cast(size_t) int.max, "wlen cannot exceed int.max"); const int len = WideCharToMultiByte(CP_UTF8, 0, warg, cast(int) wlen, null, 0, null, null); args[i] = (cast(char*) wlen)[0 .. len]; // args[i].ptr = wlen, args[i].length = len totalArgsLength += len; } // Allocate a single buffer for all (null-terminated) argument strings in UTF-8 on the stack char* utf8Buffer = cast(char*) alloca(totalArgsLength); // 2nd pass: convert to UTF-8 and finalize `args` char* utf8 = utf8Buffer; foreach (i; 0 .. argc) { const wlen = cast(int) args[i].ptr; const len = cast(int) args[i].length; WideCharToMultiByte(CP_UTF8, 0, wargv[i], wlen, utf8, len, null, null); args[i] = utf8[0 .. len-1]; // excl. terminating null utf8 += len; } // Set C argc/argv; argv is a new stack-allocated array of UTF-8 C strings char*[] argv = (cast(char**) alloca(argc * (char*).sizeof))[0 .. argc]; foreach (i, ref arg; argv) arg = args[i].ptr; _cArgs.argc = argc; _cArgs.argv = argv.ptr; totalArgsLength -= argc; // excl. null terminator per arg return _d_run_main2(args, totalArgsLength, mainFunc); } private extern (C) int _d_run_main2(char[][] args, size_t totalArgsLength, MainFunc mainFunc) { int result; version (FreeBSD) version (D_InlineAsm_X86) { /* * FreeBSD/i386 sets the FPU precision mode to 53 bit double. * Make it 64 bit extended. */ ushort fpucw; asm { fstsw fpucw; or fpucw, 0b11_00_111111; // 11: use 64 bit extended-precision // 111111: mask all FP exceptions fldcw fpucw; } } version (CRuntime_Microsoft) { // enable full precision for reals version (D_InlineAsm_X86_64) { asm { push RAX; fstcw word ptr [RSP]; or [RSP], 0b11_00_111111; // 11: use 64 bit extended-precision // 111111: mask all FP exceptions fldcw word ptr [RSP]; pop RAX; } } else version (D_InlineAsm_X86) { asm { push EAX; fstcw word ptr [ESP]; or [ESP], 0b11_00_111111; // 11: use 64 bit extended-precision // 111111: mask all FP exceptions fldcw word ptr [ESP]; pop EAX; } } else version (GNU_InlineAsm) { size_t fpu_cw; asm { "fstcw %0" : "=m" (fpu_cw); } fpu_cw |= 0b11_00_111111; // 11: use 64 bit extended-precision // 111111: mask all FP exceptions asm { "fldcw %0" : "=m" (fpu_cw); } } } /* Create a copy of args[] on the stack to be used for main, so that rt_args() * cannot be modified by the user. * Note that when this function returns, _d_args will refer to garbage. */ { _d_args = cast(string[]) args; auto buff = cast(char[]*) alloca(args.length * (char[]).sizeof + totalArgsLength); char[][] argsCopy = buff[0 .. args.length]; auto argBuff = cast(char*) (buff + args.length); size_t j = 0; import rt.config : rt_cmdline_enabled; bool parseOpts = rt_cmdline_enabled!(); foreach (arg; args) { // Do not pass Druntime options to the program if (parseOpts && arg.length >= 6 && arg[0 .. 6] == "--DRT-") continue; // https://issues.dlang.org/show_bug.cgi?id=20459 if (arg == "--") parseOpts = false; argsCopy[j++] = (argBuff[0 .. arg.length] = arg[]); argBuff += arg.length; } args = argsCopy[0..j]; } auto useExceptionTrap = parseExceptionOptions(); version (Windows) { if (IsDebuggerPresent()) useExceptionTrap = false; } void tryExec(scope void delegate() dg) { if (useExceptionTrap) { try { dg(); } catch (Throwable t) { _d_print_throwable(t); result = EXIT_FAILURE; } } else { dg(); } } // NOTE: The lifetime of a process is much like the lifetime of an object: // it is initialized, then used, then destroyed. If initialization // fails, the successive two steps are never reached. However, if // initialization succeeds, then cleanup will occur even if the use // step fails in some way. Here, the use phase consists of running // the user's main function. If main terminates with an exception, // the exception is handled and then cleanup begins. An exception // thrown during cleanup, however, will abort the cleanup process. void runAll() { if (rt_init()) { auto utResult = runModuleUnitTests(); assert(utResult.passed <= utResult.executed); if (utResult.passed == utResult.executed) { if (utResult.summarize) { if (utResult.passed == 0) .fprintf(.stderr, "No unittests run\n"); else .fprintf(.stderr, "%d modules passed unittests\n", cast(int)utResult.passed); } if (utResult.runMain) tryExec({ result = mainFunc(args); }); else result = EXIT_SUCCESS; } else { if (utResult.summarize) .fprintf(.stderr, "%d/%d modules FAILED unittests\n", cast(int)(utResult.executed - utResult.passed), cast(int)utResult.executed); result = EXIT_FAILURE; } } else result = EXIT_FAILURE; if (!rt_term()) result = (result == EXIT_SUCCESS) ? EXIT_FAILURE : result; } tryExec(&runAll); // Issue 10344: flush stdout and return nonzero on failure if (.fflush(.stdout) != 0) { .fprintf(.stderr, "Failed to flush stdout: %s\n", .strerror(.errno)); if (result == 0) { result = EXIT_FAILURE; } } return result; } private void formatThrowable(Throwable t, scope void delegate(in char[] s) nothrow sink) { foreach (u; t) { u.toString(sink); sink("\n"); auto e = cast(Error)u; if (e is null || e.bypassedException is null) continue; sink("=== Bypassed ===\n"); foreach (t2; e.bypassedException) { t2.toString(sink); sink("\n"); } sink("=== ~Bypassed ===\n"); } } private auto parseExceptionOptions() { import rt.config : rt_configOption; import core.internal.parseoptions : rt_parseOption; const optName = "trapExceptions"; auto option = rt_configOption(optName); auto trap = rt_trapExceptions; if (option.length) rt_parseOption(optName, option, trap, ""); return trap; } extern (C) void _d_print_throwable(Throwable t) { // On Windows, a console may not be present to print the output to. // Show a message box instead. If the console is present, convert to // the correct encoding. version (Windows) { static struct WSink { WCHAR* ptr; size_t len; void sink(const scope char[] s) scope nothrow { if (!s.length) return; int swlen = MultiByteToWideChar( CP_UTF8, 0, s.ptr, cast(int)s.length, null, 0); if (!swlen) return; auto newPtr = cast(WCHAR*)realloc(ptr, (this.len + swlen + 1) * WCHAR.sizeof); if (!newPtr) return; ptr = newPtr; auto written = MultiByteToWideChar( CP_UTF8, 0, s.ptr, cast(int)s.length, ptr+len, swlen); len += written; } typeof(ptr) get() { if (ptr) ptr[len] = 0; return ptr; } void free() { .free(ptr); } } HANDLE windowsHandle(int fd) { version (CRuntime_Microsoft) return cast(HANDLE)_get_osfhandle(fd); else return _fdToHandle(fd); } auto hStdErr = windowsHandle(fileno(stderr)); CONSOLE_SCREEN_BUFFER_INFO sbi; bool isConsole = GetConsoleScreenBufferInfo(hStdErr, &sbi) != 0; // ensure the exception is shown at the beginning of the line, while also // checking whether stderr is a valid file int written = fprintf(stderr, "\n"); if (written <= 0) { WSink buf; formatThrowable(t, &buf.sink); if (buf.ptr) { WSink caption; if (t) caption.sink(typeid(t).name); // Avoid static user32.dll dependency for console applications // by loading it dynamically as needed auto user32 = LoadLibraryW("user32.dll"); if (user32) { alias typeof(&MessageBoxW) PMessageBoxW; auto pMessageBoxW = cast(PMessageBoxW) GetProcAddress(user32, "MessageBoxW"); if (pMessageBoxW) pMessageBoxW(null, buf.get(), caption.get(), MB_ICONERROR); } FreeLibrary(user32); caption.free(); buf.free(); } return; } else if (isConsole) { WSink buf; formatThrowable(t, &buf.sink); if (buf.ptr) { uint codepage = GetConsoleOutputCP(); int slen = WideCharToMultiByte(codepage, 0, buf.ptr, cast(int)buf.len, null, 0, null, null); auto sptr = cast(char*)malloc(slen * char.sizeof); if (sptr) { WideCharToMultiByte(codepage, 0, buf.ptr, cast(int)buf.len, sptr, slen, null, null); WriteFile(hStdErr, sptr, slen, null, null); free(sptr); } buf.free(); } return; } } void sink(in char[] buf) scope nothrow { fwrite(buf.ptr, char.sizeof, buf.length, stderr); } formatThrowable(t, &sink); }