From 39bd46aae0daa3e18d8756b8cf288fe151f589ca Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 18 Feb 2026 18:53:11 +0100 Subject: [PATCH 1/7] rebased + exit code handling --- src/coreclr/hosts/corerun/corerun.cpp | 2 +- src/coreclr/hosts/corerun/wasm/libCorerun.js | 22 +---- src/mono/browser/runtime/logging.ts | 2 +- src/mono/sample/wasm/Directory.Build.targets | 28 ++++--- src/mono/sample/wasm/browser/wwwroot/main.js | 5 +- .../sample/wasm/console-node/wwwroot/main.mjs | 2 +- .../corehost/browserhost/CMakeLists.txt | 18 +++-- .../corehost/browserhost/browserhost.cpp | 28 ++++++- src/native/corehost/browserhost/host/host.ts | 14 ++-- src/native/corehost/browserhost/host/index.ts | 6 -- .../browserhost/libBrowserHost.footer.js | 5 +- .../corehost/browserhost/loader/assets.ts | 39 ++++++++- .../corehost/browserhost/loader/exit.ts | 4 +- .../corehost/browserhost/loader/index.ts | 4 +- .../corehost/browserhost/loader/logging.ts | 51 +++++++++--- .../corehost/browserhost/loader/polyfills.ts | 4 + src/native/corehost/browserhost/loader/run.ts | 5 +- src/native/corehost/corehost.proj | 2 +- .../Common/JavaScript/cross-module/index.ts | 2 + .../Common/JavaScript/types/ems-ambient.ts | 20 +++-- .../libs/Common/JavaScript/types/exchange.ts | 8 +- .../System.Native.Browser/diagnostics/exit.ts | 39 ++++----- .../diagnostics/index.ts | 4 +- .../diagnostics/symbolicate.ts | 81 ++++++++++++++++++- .../libSystem.Native.Browser.Utils.footer.js | 1 + .../libSystem.Native.Browser.footer.js | 2 + .../System.Native.Browser/native/crypto.ts | 4 +- .../System.Native.Browser/native/index.ts | 24 ++++++ .../native/scheduling.ts | 6 +- .../libs/System.Native.Browser/utils/host.ts | 4 +- .../interop/invoke-js.ts | 6 +- .../interop/utils.ts | 20 ----- 32 files changed, 316 insertions(+), 146 deletions(-) diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index c0ee6506726282..a5ce53aedd8b44 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -613,7 +613,7 @@ static int run(const configuration& config) // The NodeJS process is kept alive by pending async work via safeSetTimeout() -> runtimeKeepalivePush() // The actual exit code would be set by SystemJS_ResolveMainPromise if the managed Main() is async. // Or in Module.onExit handler when managed Main() is synchronous. - return 0; + return exit_code; #else // TARGET_BROWSER return corerun_shutdown(exit_code); #endif // TARGET_BROWSER diff --git a/src/coreclr/hosts/corerun/wasm/libCorerun.js b/src/coreclr/hosts/corerun/wasm/libCorerun.js index 088fdc9849b0e7..e87b3591456067 100644 --- a/src/coreclr/hosts/corerun/wasm/libCorerun.js +++ b/src/coreclr/hosts/corerun/wasm/libCorerun.js @@ -9,7 +9,8 @@ function libCoreRunFactory() { "$FS", "$NODEFS", "$NODERAWFS", - "corerun_shutdown" + "corerun_shutdown", + "BrowserHost_ShutdownDotnet", ]; const mergeCoreRun = { $CORERUN: { @@ -25,28 +26,11 @@ function libCoreRunFactory() { } ENV["DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"] = "true"; - const originalExitJS = exitJS; - exitJS = (status, implicit) => { - if (!implicit) { - EXITSTATUS = status; - ABORT = true; - if (dotnetBrowserUtilsExports.abortBackgroundTimers) { - dotnetBrowserUtilsExports.abortBackgroundTimers(); - } - } - if (!keepRuntimeAlive()) { - ABORT = true; - var latched = _corerun_shutdown(EXITSTATUS || 0); - if (EXITSTATUS === undefined) { - EXITSTATUS = latched; - } - } - return originalExitJS(EXITSTATUS, implicit); - }; }, }, $CORERUN__postset: "CORERUN.selfInitialize()", $CORERUN__deps: commonDeps, + BrowserHost_ShutdownDotnet: (exitCode) => _corerun_shutdown(exitCode), }; const patchNODERAWFS = { cwd: () => { diff --git a/src/mono/browser/runtime/logging.ts b/src/mono/browser/runtime/logging.ts index acb184e9630cbf..403d0b61bda3ef 100644 --- a/src/mono/browser/runtime/logging.ts +++ b/src/mono/browser/runtime/logging.ts @@ -185,7 +185,7 @@ function performDeferredSymbolMapParsing () { //# chrome //# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4 - regexes.push(/(?[a-z]+:\/\/[^ )]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/); + regexes.push(/(?[a-z]+:\/\/[a-zA-Z0-9.:/_]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/); //# .wasm-function[8962] regexes.push(/(?<[^ >]+>[.:]wasm-function\[(?[0-9]+)\])/); diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index f0772311623403..3d837180021724 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -54,19 +54,18 @@ - + + + <_MonoBuildDir>$(ArtifactsObjDir)mono\browser.wasm.$(Configuration)\out\lib\ + <_CoreCLRBuildDir>$(ArtifactsObjDir)coreclr\browser.wasm.$(Configuration)\libs-native\ + + + + + + <_ScriptExt Condition="'$(OS)' == 'Windows_NT'">.cmd <_ScriptExt Condition="'$(OS)' != 'Windows_NT'">.sh @@ -74,6 +73,9 @@ <_SampleProject Condition="'$(_SampleProject)' == ''">$(MSBuildProjectFile) <_SampleAssembly Condition="'$(_SampleAssembly)' == ''">$(TargetFileName) + + + diff --git a/src/mono/sample/wasm/browser/wwwroot/main.js b/src/mono/sample/wasm/browser/wwwroot/main.js index 1435d9eff08f90..e4e1737456fa58 100644 --- a/src/mono/sample/wasm/browser/wwwroot/main.js +++ b/src/mono/sample/wasm/browser/wwwroot/main.js @@ -27,8 +27,9 @@ try { const exports = await getAssemblyExports("Wasm.Browser.Sample"); await exports.Sample.Test.PrintMeaning(delay(2000).then(() => 42)); - console.log("Program has exited normally."); - await dotnet.runMainAndExit(); + const exitCode = await dotnet.run(); + console.log(`Program has exited with code ${exitCode}.`); + exit(exitCode); } catch (err) { exit(2, err); diff --git a/src/mono/sample/wasm/console-node/wwwroot/main.mjs b/src/mono/sample/wasm/console-node/wwwroot/main.mjs index d3cfafe5f61896..39fb7bbe5e067d 100644 --- a/src/mono/sample/wasm/console-node/wwwroot/main.mjs +++ b/src/mono/sample/wasm/console-node/wwwroot/main.mjs @@ -6,4 +6,4 @@ import { dotnet } from './_framework/dotnet.js' await dotnet .withDiagnosticTracing(false) .withApplicationArguments("dotnet", "is", "great!") - .run() \ No newline at end of file + .runMainAndExit(); diff --git a/src/native/corehost/browserhost/CMakeLists.txt b/src/native/corehost/browserhost/CMakeLists.txt index 1870c40091b6f3..acf9a486f0375b 100644 --- a/src/native/corehost/browserhost/CMakeLists.txt +++ b/src/native/corehost/browserhost/CMakeLists.txt @@ -55,7 +55,7 @@ set(SHARED_LIB_DESTINATION set(SHARED_CLR_DESTINATION ${CLR_ARTIFACTS_BIN_DIR}/coreclr/browser.wasm.${CMAKE_BUILD_RUNTIME_CONFIGURATION}/sharedFramework) -# these dependencies assume that you built `libs.native+clr.runtime` subsets first +# CoreCLR runtime .a libraries LIST(APPEND NATIVE_LIBS ${SHARED_CLR_DESTINATION}/libcoreclr_static.a ${SHARED_CLR_DESTINATION}/libnativeresourcestring.a @@ -63,6 +63,10 @@ LIST(APPEND NATIVE_LIBS ${SHARED_CLR_DESTINATION}/libcoreclrminipal.a ${SHARED_CLR_DESTINATION}/libcoreclrpal.a ${SHARED_CLR_DESTINATION}/libminipal.a +) + +# Shared platform .a libraries +list(APPEND NATIVE_LIBS ${SHARED_LIB_DESTINATION}/libSystem.Native.Browser.a ${SHARED_LIB_DESTINATION}/libSystem.Runtime.InteropServices.JavaScript.Native.a ${SHARED_LIB_DESTINATION}/libSystem.Native.a @@ -95,18 +99,13 @@ set_target_properties(browserhost PROPERTIES if (UPPERCASE_CMAKE_BUILD_TYPE STREQUAL DEBUG) target_link_options(browserhost PRIVATE -# -sVERBOSE -# -sASSERTIONS=1 - --emit-symbol-map +# add -sVERBOSE=1 to debug linking issues + -sASSERTIONS=1 -sLLD_REPORT_UNDEFINED -sERROR_ON_UNDEFINED_SYMBOLS=1 ) endif () -# add -sVERBOSE=1 to debug linking issues -# WASM-TODO -emit-llvm -# WASM-TODO --source-map-base http://microsoft.com -# WASM-TODO -sSEPARATE_DWARF=1, -gseparate-dwarf target_link_options(browserhost PRIVATE -sINITIAL_MEMORY=134217728 -sMAXIMUM_MEMORY=2147483648 @@ -121,6 +120,7 @@ target_link_options(browserhost PRIVATE -sEXPORTED_FUNCTIONS=${CMAKE_EMCC_EXPORTED_FUNCTIONS} -sEXPORT_NAME=createDotnetRuntime -sENVIRONMENT=web,webview,worker,node,shell + --emit-symbol-map -Wl,-error-limit=0) target_link_libraries(browserhost PRIVATE @@ -135,6 +135,8 @@ install(TARGETS browserhost DESTINATION corehost COMPONENT runtime) install(TARGETS browserhost DESTINATION sharedFramework COMPONENT runtime) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dotnet.native.wasm DESTINATION corehost COMPONENT runtime) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dotnet.native.wasm DESTINATION sharedFramework COMPONENT runtime) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dotnet.native.js.symbols DESTINATION corehost COMPONENT runtime) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dotnet.native.js.symbols DESTINATION sharedFramework COMPONENT runtime) set_source_files_properties(${BROWSERHOST_SOURCES} PROPERTIES OBJECT_DEPENDS "${JS_SYSTEM_NATIVE_BROWSER};${JS_SYSTEM_BROWSER_UTILS};${JS_SYSTEM_RUNTIME_INTEROPSERVICES_JAVASCRIPT_NATIVE};${JS_BROWSER_HOST};${JS_SYSTEM_NATIVE_BROWSER_EXPOST}") diff --git a/src/native/corehost/browserhost/browserhost.cpp b/src/native/corehost/browserhost/browserhost.cpp index f16ebfafa9f8e4..8f593fa643fe8c 100644 --- a/src/native/corehost/browserhost/browserhost.cpp +++ b/src/native/corehost/browserhost/browserhost.cpp @@ -108,7 +108,7 @@ extern "C" void* BrowserHost_CreateHostContract(void) return &host_contract; } -extern "C" int BrowserHost_InitializeCoreCLR(int propertiesCount, const char** propertyKeys, const char** propertyValues) +extern "C" int BrowserHost_InitializeDotnet(int propertiesCount, const char** propertyKeys, const char** propertyValues) { coreclr_set_error_writer(log_error_info); @@ -122,15 +122,35 @@ extern "C" int BrowserHost_InitializeCoreCLR(int propertiesCount, const char** p return 0; } +static bool executeAssemblyFailed = false; extern "C" int BrowserHost_ExecuteAssembly(const char* assemblyPath, int argc, const char** argv) { - int ignore_exit_code = 0; - int retval = coreclr_execute_assembly(CurrentClrInstance, CurrentAppDomainId, argc, argv, assemblyPath, (uint32_t*)&ignore_exit_code); + int exit_code = 0; + int retval = coreclr_execute_assembly(CurrentClrInstance, CurrentAppDomainId, argc, argv, assemblyPath, (uint32_t*)&exit_code); if (retval < 0) { std::fprintf(stderr, "coreclr_execute_assembly failed - Error: 0x%08x\n", retval); + executeAssemblyFailed = true; return -1; } - return 0; + return exit_code; +} + +extern "C" int BrowserHost_ShutdownDotnet(int exit_code) +{ + if (executeAssemblyFailed) + { + return exit_code; + } + + int latched_exit_code = exit_code; + int result = coreclr_shutdown_2(CurrentClrInstance, CurrentAppDomainId, &latched_exit_code); + if (result < 0) + { + std::fprintf(stderr, "coreclr_shutdown_2 failed - Error: 0x%08x\n", result); + exit_code = -1; + } + + return exit_code; } diff --git a/src/native/corehost/browserhost/host/host.ts b/src/native/corehost/browserhost/host/host.ts index a8107f16ee36e4..421138d900c4e9 100644 --- a/src/native/corehost/browserhost/host/host.ts +++ b/src/native/corehost/browserhost/host/host.ts @@ -51,7 +51,7 @@ export function initializeCoreCLR(): number { buffers.push(valuePtr as any); } - const res = _ems_._BrowserHost_InitializeCoreCLR(propertyCount, appctx_keys, appctx_values); + const res = _ems_._BrowserHost_InitializeDotnet(propertyCount, appctx_keys, appctx_values); for (const buf of buffers) { _ems_._free(buf as any); } @@ -81,14 +81,14 @@ export async function runMain(mainAssemblyName?: string, args?: string[]): Promi ptrs.push(ptr); _ems_.HEAPU32[(argsvPtr >>> 2) + i] = ptr; } - const res = _ems_._BrowserHost_ExecuteAssembly(mainAssemblyNamePtr, args.length, argsvPtr); + _ems_.EXITSTATUS = _ems_._BrowserHost_ExecuteAssembly(mainAssemblyNamePtr, args.length, argsvPtr); for (const ptr of ptrs) { _ems_._free(ptr); } - if (res != 0) { + if (_ems_.EXITSTATUS == -1) { const reason = new Error("Failed to execute assembly"); - _ems_.dotnetApi.exit(res, reason); + _ems_.dotnetApi.exit(-1, reason); throw reason; } @@ -107,9 +107,9 @@ export async function runMain(mainAssemblyName?: string, args?: string[]): Promi } export async function runMainAndExit(mainAssemblyName?: string, args?: string[]): Promise { - const res = await runMain(mainAssemblyName, args); + const exitCode = await runMain(mainAssemblyName, args); try { - _ems_.dotnetApi.exit(0, null); + _ems_.dotnetApi.exit(exitCode, null); } catch (error: any) { // do not propagate ExitStatus exception if (!error || typeof error.status !== "number") { @@ -118,6 +118,6 @@ export async function runMainAndExit(mainAssemblyName?: string, args?: string[]) } return error.status; } - return res; + return exitCode; } diff --git a/src/native/corehost/browserhost/host/index.ts b/src/native/corehost/browserhost/host/index.ts index 04e131fb67782c..e638eb884c83fe 100644 --- a/src/native/corehost/browserhost/host/index.ts +++ b/src/native/corehost/browserhost/host/index.ts @@ -63,12 +63,6 @@ function setupEmscripten() { for (const key in loaderConfig.environmentVariables) { _ems_.ENV[key] = loaderConfig.environmentVariables[key]; } - - _ems_.Module.preInit = [() => { - _ems_.FS.createPath("/", loaderConfig.virtualWorkingDirectory!, true, true); - _ems_.FS.chdir(loaderConfig.virtualWorkingDirectory!); - }, ...(_ems_.Module.preInit || [])]; - } export { BrowserHost_ExternalAssemblyProbe } from "./assets"; diff --git a/src/native/corehost/browserhost/libBrowserHost.footer.js b/src/native/corehost/browserhost/libBrowserHost.footer.js index b42073d47ae8f9..f400eb4798b0dd 100644 --- a/src/native/corehost/browserhost/libBrowserHost.footer.js +++ b/src/native/corehost/browserhost/libBrowserHost.footer.js @@ -21,8 +21,9 @@ function libBrowserHostFactory() { let explicitDeps = [ "wasm_load_icu_data", "BrowserHost_CreateHostContract", - "BrowserHost_InitializeCoreCLR", - "BrowserHost_ExecuteAssembly" + "BrowserHost_InitializeDotnet", + "BrowserHost_ExecuteAssembly", + "BrowserHost_ShutdownDotnet", ]; let commonDeps = [ "$DOTNET", diff --git a/src/native/corehost/browserhost/loader/assets.ts b/src/native/corehost/browserhost/loader/assets.ts index 90352ff8a9cf5a..2c51273887d51c 100644 --- a/src/native/corehost/browserhost/loader/assets.ts +++ b/src/native/corehost/browserhost/loader/assets.ts @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback, InstantiateWasmSuccessCallback } from "./types"; +import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback, InstantiateWasmSuccessCallback, SymbolsAsset } from "./types"; -import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module } from "./cross-module"; +import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module, dotnetDiagnosticsExports } from "./cross-module"; import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_NODE, browserVirtualAppBase } from "./per-module"; import { createPromiseCompletionSource, delay } from "./promise-completion-source"; import { locateFile, makeURLAbsoluteWithApplicationBase } from "./bootstrap"; @@ -144,6 +144,19 @@ export async function fetchVfs(asset: AssemblyAsset): Promise { } } +export async function fetchNativeSymbols(asset: SymbolsAsset): Promise { + totalAssetsToDownload++; + const assetInternal = asset as AssetEntryInternal; + if (assetInternal.name && !asset.resolvedUrl) { + asset.resolvedUrl = locateFile(assetInternal.name); + } + assetInternal.behavior = "symbols"; + assetInternal.isOptional = assetInternal.isOptional || loaderConfig.ignorePdbLoadErrors; + const table = await fetchText(assetInternal); + onDownloadedAsset(); + dotnetDiagnosticsExports.installNativeSymbols(table || ""); +} + async function fetchBytes(asset: AssetEntryInternal): Promise { dotnetAssert.check(asset && asset.resolvedUrl, "Bad asset.resolvedUrl"); const response = await loadResource(asset); @@ -158,8 +171,21 @@ async function fetchBytes(asset: AssetEntryInternal): Promise return new Uint8Array(buffer); } +async function fetchText(asset: AssetEntryInternal): Promise { + dotnetAssert.check(asset && asset.resolvedUrl, "Bad asset.resolvedUrl"); + const response = await loadResource(asset); + if (!response.ok) { + if (asset.isOptional) { + dotnetLogger.warn(`Optional resource '${asset.name}' failed to load from '${asset.resolvedUrl}'. HTTP status: ${response.status} ${response.statusText}`); + return null; + } + throw new Error(`Failed to load resource '${asset.name}' from '${asset.resolvedUrl}'. HTTP status: ${response.status} ${response.statusText}`); + } + return response.text(); +} + function loadResource(asset: AssetEntryInternal): Promise { - if ("dotnetwasm" === asset.behavior) { + if (noThrottleNoRetry[asset.behavior]) { // `response.arrayBuffer()` can't be called twice. return loadResourceFetch(asset); } @@ -316,6 +342,7 @@ const behaviorToBlazorAssetTypeMap: { [key: string]: WebAssemblyBootResourceType "icu": "globalization", "vfs": "configuration", "manifest": "manifest", + "symbols": "pdb", "dotnetwasm": "dotnetwasm", "js-module-dotnet": "dotnetjs", "js-module-native": "dotnetjs", @@ -330,5 +357,11 @@ const behaviorToContentTypeMap: { [key: string]: string | undefined } = { "icu": "application/octet-stream", "vfs": "application/octet-stream", "manifest": "application/json", + "symbols": "text/plain; charset=utf-8", "dotnetwasm": "application/wasm", }; + +const noThrottleNoRetry: { [key: string]: number | undefined } = { + "dotnetwasm": 1, + "symbols": 1, +}; diff --git a/src/native/corehost/browserhost/loader/exit.ts b/src/native/corehost/browserhost/loader/exit.ts index 7f400e338d3fb1..111c85919999fa 100644 --- a/src/native/corehost/browserhost/loader/exit.ts +++ b/src/native/corehost/browserhost/loader/exit.ts @@ -117,7 +117,7 @@ export function exit(exitCode: number, reason: any): void { unregisterExit(); if (!alreadySilent) { if (runtimeState.onExitListeners.length === 0 && !runtimeState.dotnetReady) { - dotnetLogger.error(`Exiting during runtime startup: ${message}`); + dotnetLogger.error("Exiting during runtime startup: ", message); dotnetLogger.debug(() => stack); } for (const listener of runtimeState.onExitListeners) { @@ -135,7 +135,7 @@ export function exit(exitCode: number, reason: any): void { dotnetLoaderExports.abortStartup(reason); } } catch (err) { - dotnetLogger.warn("dotnet.js exit() failed", err); + dotnetLogger.warn("dotnet.js exit() failed: ", err); // don't propagate any failures } diff --git a/src/native/corehost/browserhost/loader/index.ts b/src/native/corehost/browserhost/loader/index.ts index 6d0479fac95b62..457f1a1c8825c9 100644 --- a/src/native/corehost/browserhost/loader/index.ts +++ b/src/native/corehost/browserhost/loader/index.ts @@ -16,7 +16,7 @@ import GitHash from "consts:gitHash"; import { loaderConfig, getLoaderConfig } from "./config"; import { exit, isExited, isRuntimeRunning, addOnExitListener, registerExit, quitNow } from "./exit"; import { invokeLibraryInitializers } from "./lib-initializers"; -import { check, error, info, warn, debug, fastCheck } from "./logging"; +import { check, error, info, warn, debug, fastCheck, normalizeException } from "./logging"; import { dotnetAssert, dotnetLoaderExports, dotnetLogger, dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module"; import { rejectRunMainPromise, resolveRunMainPromise, getRunMainPromise, abortStartup } from "./run"; @@ -66,6 +66,7 @@ export function dotnetInitializeModule(): RuntimeAPI { addOnExitListener, abortStartup, quitNow, + normalizeException, }; Object.assign(dotnetLoaderExports, loaderFunctions); const logger: LoggerType = { @@ -112,6 +113,7 @@ export function dotnetInitializeModule(): RuntimeAPI { dotnetLoaderExports.addOnExitListener, dotnetLoaderExports.abortStartup, dotnetLoaderExports.quitNow, + dotnetLoaderExports.normalizeException, ]; } diff --git a/src/native/corehost/browserhost/loader/logging.ts b/src/native/corehost/browserhost/loader/logging.ts index 65ee07bc4ea8de..8a6ef450ac9ed8 100644 --- a/src/native/corehost/browserhost/loader/logging.ts +++ b/src/native/corehost/browserhost/loader/logging.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { loaderConfig } from "./config"; +import { dotnetDiagnosticsExports } from "./cross-module"; export function check(condition: unknown, message: string): asserts condition { if (!condition) { @@ -41,16 +42,48 @@ export function warn(msg: string, ...data: any) { console.warn(prefix + msg, ...data); } -export function error(msg: string, ...data: any) { - if (data && data.length > 0 && data[0] && typeof data[0] === "object") { - // don't log silent errors - if (data[0].silent) { - return; +export function error(msg: string, reason: any) { + if (reason.silent) { + return; + } + console.error(prefix + msg, normalizeException(reason)); +} + +export function normalizeException(reason: any) { + let res = "unknown exception"; + let stack: string | undefined; + if (reason) { + if (typeof reason === "object" && reason.status === undefined) { + if (stack !== undefined) { + stack = reason.stack; + } else { + stack = new Error().stack + ""; + } } - if (data[0].toString) { - console.error(prefix + msg, data[0].toString()); - return; + if (reason.message) { + res = reason.message; + } else if (typeof reason.toString === "function") { + res = reason.toString(); + } else { + res = reason + ""; } + if (stack) { + // Some JS runtimes insert the error message at the top of the stack, some don't, + // so normalize it by using the stack as the result if it already contains the error + if (stack.startsWith(res)) + res = symbolicateStackTrace(stack); + else + res += "\n" + symbolicateStackTrace(stack); + } else { + res = symbolicateStackTrace(res); + } + } + return res; +} + +function symbolicateStackTrace(message: string): string { + if (dotnetDiagnosticsExports.symbolicateStackTrace) { + return dotnetDiagnosticsExports.symbolicateStackTrace(message); } - console.error(prefix + msg, ...data); + return message; } diff --git a/src/native/corehost/browserhost/loader/polyfills.ts b/src/native/corehost/browserhost/loader/polyfills.ts index bae7aed2320ba7..aebf928b616a9f 100644 --- a/src/native/corehost/browserhost/loader/polyfills.ts +++ b/src/native/corehost/browserhost/loader/polyfills.ts @@ -153,6 +153,10 @@ export function responseLike(url: string, body: ArrayBuffer | string | null, opt return Promise.resolve(JSON.parse(body)); }, text: () => { + if (typeof body !== "string" && typeof globalThis.TextDecoder !== "undefined") { + const decoder = new globalThis.TextDecoder("utf-8"); + return Promise.resolve(decoder.decode(body || new Uint8Array())); + } dotnetAssert.check(body !== null && typeof body === "string", "Response body is not a string."); return Promise.resolve(body); } diff --git a/src/native/corehost/browserhost/loader/run.ts b/src/native/corehost/browserhost/loader/run.ts index 5d6838b5ef7969..9b3eb0b8a986e6 100644 --- a/src/native/corehost/browserhost/loader/run.ts +++ b/src/native/corehost/browserhost/loader/run.ts @@ -8,7 +8,7 @@ import { exit, runtimeState } from "./exit"; import { createPromiseCompletionSource } from "./promise-completion-source"; import { getIcuResourceName } from "./icu"; import { loaderConfig, validateLoaderConfig } from "./config"; -import { fetchDll, fetchIcu, fetchPdb, fetchVfs, fetchWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded } from "./assets"; +import { fetchDll, fetchIcu, fetchNativeSymbols, fetchPdb, fetchVfs, fetchWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded } from "./assets"; import { initPolyfills } from "./polyfills"; import { validateWasmFeatures } from "./bootstrap"; @@ -45,6 +45,9 @@ export async function createRuntime(downloadOnly: boolean): Promise { if (loaderConfig.resources.jsModuleDiagnostics && loaderConfig.resources.jsModuleDiagnostics.length > 0) { const diagnosticsModule = await loadDotnetModule(loaderConfig.resources.jsModuleDiagnostics[0]); diagnosticsModule.dotnetInitializeModule(dotnetInternals); + if (loaderConfig.resources.wasmSymbols && loaderConfig.resources.wasmSymbols.length > 0) { + await fetchNativeSymbols(loaderConfig.resources.wasmSymbols[0]); + } } const nativeModulePromise: Promise = loadDotnetModule(loaderConfig.resources.jsModuleNative[0]); const runtimeModulePromise: Promise = loadDotnetModule(loaderConfig.resources.jsModuleRuntime[0]); diff --git a/src/native/corehost/corehost.proj b/src/native/corehost/corehost.proj index d91a4a5414573e..8ac8407f544579 100644 --- a/src/native/corehost/corehost.proj +++ b/src/native/corehost/corehost.proj @@ -181,7 +181,7 @@ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(LibrariesSharedFrameworkDir)*.dat" /> <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(HostSharedFrameworkDir)libBrowserHost.a" /> <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(HostSharedFrameworkDir)dotnet.native.js" /> - + <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(HostSharedFrameworkDir)dotnet.native.js.symbols" /> <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(HostSharedFrameworkDir)dotnet.native.wasm" /> diff --git a/src/native/libs/Common/JavaScript/cross-module/index.ts b/src/native/libs/Common/JavaScript/cross-module/index.ts index e2a5f86213910f..5ebcece430ac25 100644 --- a/src/native/libs/Common/JavaScript/cross-module/index.ts +++ b/src/native/libs/Common/JavaScript/cross-module/index.ts @@ -136,6 +136,7 @@ export function dotnetUpdateInternalsSubscriber() { addOnExitListener: table[14], abortStartup: table[15], quitNow: table[16], + normalizeException: table[17], }; Object.assign(dotnetLoaderExports, loaderExportsLocal); Object.assign(logger, loggerLocal); @@ -179,6 +180,7 @@ export function dotnetUpdateInternalsSubscriber() { function diagnosticsExportsFromTable(table: DiagnosticsExportsTable, interop: DiagnosticsExports): void { const interopLocal: DiagnosticsExports = { symbolicateStackTrace: table[0], + installNativeSymbols: table[1], }; Object.assign(interop, interopLocal); } diff --git a/src/native/libs/Common/JavaScript/types/ems-ambient.ts b/src/native/libs/Common/JavaScript/types/ems-ambient.ts index 15b4f9dcd8611b..880fed15927092 100644 --- a/src/native/libs/Common/JavaScript/types/ems-ambient.ts +++ b/src/native/libs/Common/JavaScript/types/ems-ambient.ts @@ -29,8 +29,9 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & { _SystemJS_ExecuteBackgroundJobCallback: () => void; _SystemJS_ExecuteFinalizationCallback: () => void; _BrowserHost_CreateHostContract: () => VoidPtr; - _BrowserHost_InitializeCoreCLR: (propertiesCount: number, propertyKeys: CharPtrPtr, propertyValues: CharPtrPtr) => number; + _BrowserHost_InitializeDotnet: (propertiesCount: number, propertyKeys: CharPtrPtr, propertyValues: CharPtrPtr) => number; _BrowserHost_ExecuteAssembly: (mainAssemblyNamePtr: number, argsLength: number, argsPtr: number) => number; + _BrowserHost_ShutdownDotnet: (exitCode: number) => number; _wasm_load_icu_data: (dataPtr: VoidPtr) => number; _SystemInteropJS_GetManagedStackTrace: (args: JSMarshalerArguments) => void; _SystemInteropJS_CallDelegate: (args: JSMarshalerArguments) => void; @@ -38,7 +39,6 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & { _SystemInteropJS_ReleaseJSOwnedObjectByGCHandle: (args: JSMarshalerArguments) => void; _SystemInteropJS_BindAssemblyExports: (args: JSMarshalerArguments) => void; _SystemInteropJS_CallJSExport: (methodHandle: CSFnHandle, args: JSMarshalerArguments) => void; - _corerun_shutdown: (code: number) => void; FS: { createPath: (parent: string, path: string, canRead?: boolean, canWrite?: boolean) => string; @@ -47,9 +47,18 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & { } ENV: any; - DOTNET: any; - DOTNET_INTEROP: any; - BROWSER_HOST: any; + DOTNET: { + lastScheduledTimerId?: number; + lastScheduledThreadPoolId?: number; + lastScheduledFinalizationId?: number; + cryptoWarnOnce?: boolean; + isAborting?: boolean; + gitHash?: string; + } + DOTNET_INTEROP: { + gitHash?: string; + }; + BROWSER_HOST: {}; Module: EmscriptenModuleInternal; ENVIRONMENT_IS_NODE: boolean; @@ -74,6 +83,7 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & { _exit: (exitCode: number, implicit?: boolean) => void; abort: (reason: any) => void; ___trap: () => void; + ___funcs_on_exit: () => void; safeSetTimeout: (func: Function, timeout: number) => number; exitJS: (status: number, implicit?: boolean | number) => void; runtimeKeepalivePop: () => void; diff --git a/src/native/libs/Common/JavaScript/types/exchange.ts b/src/native/libs/Common/JavaScript/types/exchange.ts index fe634c54feff20..b228f7e2e06f34 100644 --- a/src/native/libs/Common/JavaScript/types/exchange.ts +++ b/src/native/libs/Common/JavaScript/types/exchange.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { check, error, info, warn, debug, fastCheck } from "../../../../corehost/browserhost/loader/logging"; +import type { check, error, info, warn, debug, fastCheck, normalizeException } from "../../../../corehost/browserhost/loader/logging"; import type { resolveRunMainPromise, rejectRunMainPromise, getRunMainPromise, abortStartup } from "../../../../corehost/browserhost/loader/run"; import type { addOnExitListener, isExited, isRuntimeRunning, quitNow } from "../../../../corehost/browserhost/loader/exit"; @@ -18,7 +18,7 @@ import type { resolveOrRejectPromise } from "../../../System.Runtime.InteropServ import type { cancelPromise } from "../../../System.Runtime.InteropServices.JavaScript.Native/interop/cancelable-promise"; import type { abortInteropTimers } from "../../../System.Runtime.InteropServices.JavaScript.Native/interop/scheduling"; -import type { symbolicateStackTrace } from "../../../System.Native.Browser/diagnostics/symbolicate"; +import type { installNativeSymbols, symbolicateStackTrace } from "../../../System.Native.Browser/diagnostics/symbolicate"; import type { EmsAmbientSymbolsType } from "../types"; export type RuntimeExports = { @@ -67,6 +67,7 @@ export type LoaderExports = { addOnExitListener: typeof addOnExitListener, abortStartup: typeof abortStartup, quitNow: typeof quitNow, + normalizeException: typeof normalizeException } export type LoaderExportsTable = [ @@ -87,6 +88,7 @@ export type LoaderExportsTable = [ typeof addOnExitListener, typeof abortStartup, typeof quitNow, + typeof normalizeException, ] export type BrowserHostExports = { @@ -161,8 +163,10 @@ export type BrowserUtilsExportsTable = [ export type DiagnosticsExportsTable = [ typeof symbolicateStackTrace, + typeof installNativeSymbols, ] export type DiagnosticsExports = { symbolicateStackTrace: typeof symbolicateStackTrace, + installNativeSymbols: typeof installNativeSymbols, } diff --git a/src/native/libs/System.Native.Browser/diagnostics/exit.ts b/src/native/libs/System.Native.Browser/diagnostics/exit.ts index fb210820f91ac7..5507fa89f6d338 100644 --- a/src/native/libs/System.Native.Browser/diagnostics/exit.ts +++ b/src/native/libs/System.Native.Browser/diagnostics/exit.ts @@ -5,7 +5,6 @@ import type { LoaderConfigInternal } from "./types"; import { dotnetLogger, dotnetLoaderExports, dotnetApi, dotnetBrowserUtilsExports, dotnetRuntimeExports } from "./cross-module"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB } from "./per-module"; import { teardownProxyConsole } from "./console-proxy"; -import { symbolicateStackTrace } from "./symbolicate"; let loaderConfig: LoaderConfigInternal = null as any; export function registerExit() { @@ -53,33 +52,23 @@ function onExit(exitCode: number, reason: any, silent: boolean): boolean { return true; } -function logExitReason(exit_code: number, reason: any) { - if (exit_code !== 0 && reason) { - const exitStatus = isExitStatus(reason); - if (typeof reason === "string") { - dotnetLogger.error(reason); +function logExitReason(exitCode: number, reason: any) { + if (exitCode !== 0 && reason) { + const hasExitStatus = typeof reason === "object" && reason.status !== undefined; + if (dotnetLoaderExports.normalizeException) { + reason = dotnetLoaderExports.normalizeException(reason); } else { - if (reason.stack === undefined && !exitStatus) { - reason.stack = new Error().stack + ""; - } - const message = reason.message - ? symbolicateStackTrace(reason.message + "\n" + reason.stack) - : reason.toString(); - - if (exitStatus) { - dotnetLogger.debug(message); - } else { - dotnetLogger.error(message); - } + reason = reason + ""; + } + const msg = "dotnet exited with: " + exitCode; + if (hasExitStatus) { + dotnetLogger.debug(msg, reason); + } else { + dotnetLogger.error(msg, reason); } } } -function isExitStatus(reason: any): boolean { - const ExitStatus = dotnetBrowserUtilsExports.getExitStatus(); - return ExitStatus && reason instanceof ExitStatus; -} - function logExitCode(exitCode: number): void { const message = loaderConfig.logExitCode ? "WASM EXIT " + exitCode @@ -136,7 +125,7 @@ function fatalHandler(event: any, reason: any, type: string) { } reason.stack = reason.stack + "";// string conversion (it could be getter) if (!reason.silent) { - dotnetLogger.error("Unhandled error:", reason); + dotnetLogger.error("Unhandled error: ", reason); dotnetApi.exit(1, reason); } } catch (error: any) { @@ -164,6 +153,6 @@ async function flushNodeStreams() { await Promise.race([Promise.all([stdoutFlushed, stderrFlushed]), timeout]); clearTimeout(timeoutId); } catch (err) { - dotnetLogger.error(`flushing std* streams failed: ${err}`); + dotnetLogger.error("flushing std* streams failed: ", err); } } diff --git a/src/native/libs/System.Native.Browser/diagnostics/index.ts b/src/native/libs/System.Native.Browser/diagnostics/index.ts index 8550abfb275891..c011132f3df71a 100644 --- a/src/native/libs/System.Native.Browser/diagnostics/index.ts +++ b/src/native/libs/System.Native.Browser/diagnostics/index.ts @@ -8,7 +8,7 @@ import GitHash from "consts:gitHash"; import { dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module"; import { registerExit } from "./exit"; -import { symbolicateStackTrace } from "./symbolicate"; +import { installNativeSymbols, symbolicateStackTrace } from "./symbolicate"; import { installLoggingProxy } from "./console-proxy"; export function dotnetInitializeModule(internals: InternalExchange): void { @@ -23,6 +23,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void { internals[InternalExchangeIndex.DiagnosticsExportsTable] = diagnosticsExportsToTable({ symbolicateStackTrace, + installNativeSymbols, }); dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber); @@ -34,6 +35,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void { // keep in sync with diagnosticsExportsFromTable() return [ map.symbolicateStackTrace, + map.installNativeSymbols, ]; } } diff --git a/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts b/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts index d7a76cbbd38b66..2752eb352e195d 100644 --- a/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts +++ b/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts @@ -1,8 +1,83 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -export function symbolicateStackTrace(stack: string): string { - // WASM-TODO: implement symbolication https://github.com/dotnet/runtime/issues/122647 - return stack; +const symbol_map = new Map(); +let symbolTable: string | undefined; +const regexes: RegExp[] = []; + +export function installNativeSymbols(table: string) { + symbolTable = table; } +export function symbolicateStackTrace(message: string): string { + const origMessage = message; + initSymbolMap(); + + if (symbol_map.size === 0) + return message; + + try { + + for (let i = 0; i < regexes.length; i++) { + const newRaw = message.replace(regexes[i], (substring, ...args) => { + const groups = args.find(arg => { + return typeof (arg) == "object" && arg.replaceSection !== undefined; + }); + + if (groups === undefined) + return substring; + + const funcNum = groups.funcNum; + const replaceSection = groups.replaceSection; + const name = symbol_map.get(Number(funcNum)); + + if (name === undefined) + return substring; + + return substring.replace(replaceSection, `${name} (${replaceSection})`); + }); + + if (newRaw !== origMessage) + return newRaw; + } + + return origMessage; + } catch (error) { + console.debug(`failed to symbolicate: ${error}`); + return message; + } +} + +function initSymbolMap() { + if (!symbolTable) + return; + + // V8 + // at :wasm-function[1900]:0x83f63 + // at dlfree (:wasm-function[18739]:0x2328ef) + regexes.push(/at (?[^:()]+:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)((?![^)a-fA-F\d])|$)/g); + + //# 5: WASM [009712b2], function #111 (''), pc=0x7c16595c973 (+0x53), pos=38740 (+11) + regexes.push(/(?:WASM \[[\da-zA-Z]+\], (?function #(?[\d]+) \(''\)))/g); + + //# chrome + //# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4 + regexes.push(/(?[a-z]+:\/\/[a-zA-Z0-9.:/_]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/g); + + //# .wasm-function[8962] + regexes.push(/(?<[^ >]+>[.:]wasm-function\[(?[0-9]+)\])/g); + + const text = symbolTable; + symbolTable = undefined; + try { + text.split(/[\r\n]/).forEach((line: string) => { + const parts: string[] = line.split(/:/); + if (parts.length < 2) + return; + + parts[1] = parts.splice(1).join(":").replace(/\\([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))); + symbol_map.set(Number(parts[0]), parts[1]); + }); + } catch (exc) { + } +} diff --git a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.js b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.js index e269e08ec7d850..b8ca15bb6bfc96 100644 --- a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.js +++ b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.js @@ -22,6 +22,7 @@ function libBrowserUtilsFactory() { "$DOTNET", "GetDotNetRuntimeContractDescriptor", "_exit", + "abort", "__trap", "$readI53FromU64", "$readI53FromI64", diff --git a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js index 36238d87189f35..1a6d0b0928ed2b 100644 --- a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js +++ b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js @@ -18,10 +18,12 @@ function libDotnetFactory() { libNativeBrowser(exports); let commonDeps = [ + "$FS", "$BROWSER_UTILS", "SystemJS_ExecuteTimerCallback", "SystemJS_ExecuteBackgroundJobCallback", "SystemJS_ExecuteFinalizationCallback", + "__funcs_on_exit", ]; const mergeDotnet = { $DOTNET: { diff --git a/src/native/libs/System.Native.Browser/native/crypto.ts b/src/native/libs/System.Native.Browser/native/crypto.ts index 013ba19e46f873..650b5470dabc3a 100644 --- a/src/native/libs/System.Native.Browser/native/crypto.ts +++ b/src/native/libs/System.Native.Browser/native/crypto.ts @@ -11,9 +11,9 @@ export function SystemJS_RandomBytes(bufferPtr: number, bufferLength: number): n const batchedQuotaMax = 65536; if (!globalThis.crypto || !globalThis.crypto.getRandomValues) { - if (!_ems_.DOTNET["cryptoWarnOnce"]) { + if (!_ems_.DOTNET.cryptoWarnOnce) { _ems_.dotnetLogger.debug("This engine doesn't support crypto.getRandomValues. Please use a modern version or provide polyfill for 'globalThis.crypto.getRandomValues'."); - _ems_.DOTNET["cryptoWarnOnce"] = true; + _ems_.DOTNET.cryptoWarnOnce = true; } return -1; } diff --git a/src/native/libs/System.Native.Browser/native/index.ts b/src/native/libs/System.Native.Browser/native/index.ts index 302b6ea425504b..ca0241ba848b07 100644 --- a/src/native/libs/System.Native.Browser/native/index.ts +++ b/src/native/libs/System.Native.Browser/native/index.ts @@ -27,10 +27,34 @@ export function dotnetInitializeModule(internals: InternalExchange): void { }); _ems_.dotnetUpdateInternals(internals, _ems_.dotnetUpdateInternalsSubscriber); + setupEmscripten(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars function nativeBrowserExportsToTable(map: NativeBrowserExports): NativeBrowserExportsTable { // keep in sync with nativeBrowserExportsFromTable() return [ ]; } + + function setupEmscripten() { + _ems_.Module.preInit = [() => { + if (_ems_.dotnetApi.getConfig) { + const virtualWorkingDirectory = _ems_.dotnetApi.getConfig().virtualWorkingDirectory; + _ems_.FS.createPath("/", virtualWorkingDirectory!, true, true); + _ems_.FS.chdir(virtualWorkingDirectory!); + } + + const orig_funcs_on_exit = _ems_.___funcs_on_exit; + // it would be better to use addOnExit(), but it's called too late. + _ems_.___funcs_on_exit = () => { + // this will prevent more timers (like finalizer) to get scheduled during thread destructor + if (_ems_.dotnetBrowserUtilsExports.abortBackgroundTimers) { + _ems_.dotnetBrowserUtilsExports.abortBackgroundTimers(); + } + _ems_.EXITSTATUS = _ems_._BrowserHost_ShutdownDotnet(_ems_.EXITSTATUS || 0); + orig_funcs_on_exit(); + }; + + }, ...(_ems_.Module.preInit || [])]; + } } diff --git a/src/native/libs/System.Native.Browser/native/scheduling.ts b/src/native/libs/System.Native.Browser/native/scheduling.ts index fab98ca141d587..2e3bb534286c80 100644 --- a/src/native/libs/System.Native.Browser/native/scheduling.ts +++ b/src/native/libs/System.Native.Browser/native/scheduling.ts @@ -4,7 +4,7 @@ import { _ems_ } from "../../Common/JavaScript/ems-ambient"; export function SystemJS_ScheduleTimer(shortestDueTimeMs: number): void { - if (_ems_.ABORT) { + if (_ems_.ABORT || _ems_.DOTNET.isAborting) { // runtime is shutting down return; } @@ -30,7 +30,7 @@ export function SystemJS_ScheduleTimer(shortestDueTimeMs: number): void { } export function SystemJS_ScheduleBackgroundJob(): void { - if (_ems_.ABORT) { + if (_ems_.ABORT || _ems_.DOTNET.isAborting) { // runtime is shutting down return; } @@ -56,7 +56,7 @@ export function SystemJS_ScheduleBackgroundJob(): void { } export function SystemJS_ScheduleFinalization(): void { - if (_ems_.ABORT) { + if (_ems_.ABORT || _ems_.DOTNET.isAborting) { // runtime is shutting down return; } diff --git a/src/native/libs/System.Native.Browser/utils/host.ts b/src/native/libs/System.Native.Browser/utils/host.ts index 2db4f6476176e0..ef76432e137d9a 100644 --- a/src/native/libs/System.Native.Browser/utils/host.ts +++ b/src/native/libs/System.Native.Browser/utils/host.ts @@ -14,7 +14,7 @@ export function getExitStatus(): new (exitCode: number) => any { } export function runBackgroundTimers(): void { - if (_ems_.ABORT) { + if (_ems_.ABORT || _ems_.DOTNET.isAborting) { // runtime is shutting down return; } @@ -32,6 +32,7 @@ export function runBackgroundTimers(): void { } export function abortBackgroundTimers(): void { + _ems_.DOTNET.isAborting = true; if (_ems_.DOTNET.lastScheduledTimerId) { globalThis.clearTimeout(_ems_.DOTNET.lastScheduledTimerId); _ems_.runtimeKeepalivePop(); @@ -53,6 +54,7 @@ export function abortPosix(exitCode: number, reason: any, nativeReady: boolean): try { _ems_.ABORT = true; _ems_.EXITSTATUS = exitCode; + _ems_.DOTNET.isAborting = true; if (exitCode === 0 && nativeReady) { _ems_._exit(0); return; diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js.ts index db9cc557fa16b0..e355d9e3e44157 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js.ts @@ -3,13 +3,13 @@ import BuildConfiguration from "consts:configuration"; -import { dotnetBrowserUtilsExports, dotnetApi, dotnetAssert, dotnetLogger, VoidPtrNull, Module } from "./cross-module"; +import { dotnetBrowserUtilsExports, dotnetApi, dotnetAssert, dotnetLogger, VoidPtrNull, Module, dotnetLoaderExports } from "./cross-module"; import type { BindingClosureJS, BoundMarshalerToJs, JSFnHandle, JSFunctionSignature, JSHandle, JSMarshalerArguments, VoidPtr, WrappedJSFunction } from "./types"; import { MarshalerType, MeasuredBlock } from "./types"; import { getSig, getSignatureArgumentCount, getSignatureFunctionName, getSignatureHandle, getSignatureModuleName, getSignatureType, getSignatureVersion, isReceiverShouldFree, jsInteropState } from "./marshal"; -import { assertJsInterop, assertRuntimeRunning, endMeasure, fixupPointer, normalizeException, startMeasure } from "./utils"; +import { assertJsInterop, assertRuntimeRunning, endMeasure, fixupPointer, startMeasure } from "./utils"; import { bindArgMarshalToJs } from "./marshal-to-js"; import { boundJsFunctionSymbol, getJSObjectFromJSHandle, importedJsFunctionSymbol, jsImportWrapperByFnHandle } from "./gc-handles"; import { bindArgMarshalToCs, marshalExceptionToCs } from "./marshal-to-cs"; @@ -29,7 +29,7 @@ export function bindJSImportST(signature: JSFunctionSignature): VoidPtr { bindJsImport(signature); return VoidPtrNull; } catch (ex: any) { - return dotnetBrowserUtilsExports.stringToUTF16Ptr(normalizeException(ex)); + return dotnetBrowserUtilsExports.stringToUTF16Ptr(dotnetLoaderExports.normalizeException(ex)); } } diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts index 425c7267825bdd..64f1d5f1b649b3 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts @@ -11,26 +11,6 @@ export function fixupPointer(signature: any, shiftAmount: number): any { return ((signature as any) >>> shiftAmount) as any; } -export function normalizeException(ex: any) { - let res = "unknown exception"; - if (ex) { - res = ex.toString(); - const stack = ex.stack; - if (stack) { - // Some JS runtimes insert the error message at the top of the stack, some don't, - // so normalize it by using the stack as the result if it already contains the error - if (stack.startsWith(res)) - res = stack; - else - res += "\n" + stack; - } - if (dotnetDiagnosticsExports.symbolicateStackTrace) { - res = dotnetDiagnosticsExports.symbolicateStackTrace(res); - } - } - return res; -} - export function isRuntimeRunning(): boolean { return dotnetLoaderExports.isRuntimeRunning(); } From 5445ea3669655a7fb6bc74fd70e4a0f06999a6a2 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 18 Feb 2026 20:47:56 +0100 Subject: [PATCH 2/7] fix synchronous main for browserhost --- src/coreclr/vm/assembly.cpp | 10 ++++++++++ src/native/corehost/browserhost/browserhost.cpp | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp index 3d953678348dbf..8679532eb25830 100644 --- a/src/coreclr/vm/assembly.cpp +++ b/src/coreclr/vm/assembly.cpp @@ -1190,6 +1190,10 @@ struct Param LPWSTR *wzArgs; } param; +#if defined(TARGET_BROWSER) +extern "C" void SystemJS_ResolveMainPromise(int exitCode); +#endif // TARGET_BROWSER + static void RunMainInternal(Param* pParam) { MethodDescCallSite threadStart(pParam->pFD); @@ -1223,6 +1227,9 @@ static void RunMainInternal(Param* pParam) // Set the return value to 0 instead of returning random junk *pParam->piRetVal = 0; threadStart.Call(&stackVar); +#if defined(TARGET_BROWSER) + SystemJS_ResolveMainPromise(*pParam->piRetVal); +#endif // TARGET_BROWSER } // WASM-TODO: remove this // https://github.com/dotnet/runtime/issues/121064 @@ -1257,6 +1264,9 @@ static void RunMainInternal(Param* pParam) // Call the main method *pParam->piRetVal = (INT32)threadStart.Call_RetArgSlot(&stackVar); SetLatchedExitCode(*pParam->piRetVal); +#if defined(TARGET_BROWSER) + SystemJS_ResolveMainPromise(*pParam->piRetVal); +#endif // TARGET_BROWSER } GCPROTECT_END(); diff --git a/src/native/corehost/browserhost/browserhost.cpp b/src/native/corehost/browserhost/browserhost.cpp index 8f593fa643fe8c..fc4f9b008f804a 100644 --- a/src/native/corehost/browserhost/browserhost.cpp +++ b/src/native/corehost/browserhost/browserhost.cpp @@ -149,8 +149,8 @@ extern "C" int BrowserHost_ShutdownDotnet(int exit_code) if (result < 0) { std::fprintf(stderr, "coreclr_shutdown_2 failed - Error: 0x%08x\n", result); - exit_code = -1; + return -1; } - return exit_code; + return latched_exit_code; } From f472136bdbdf7b043086e0c7e7f416a279885f59 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 18 Feb 2026 20:50:08 +0100 Subject: [PATCH 3/7] Update src/native/corehost/browserhost/loader/logging.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/native/corehost/browserhost/loader/logging.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/corehost/browserhost/loader/logging.ts b/src/native/corehost/browserhost/loader/logging.ts index 8a6ef450ac9ed8..218f26dbf3c992 100644 --- a/src/native/corehost/browserhost/loader/logging.ts +++ b/src/native/corehost/browserhost/loader/logging.ts @@ -43,7 +43,7 @@ export function warn(msg: string, ...data: any) { } export function error(msg: string, reason: any) { - if (reason.silent) { + if (reason && typeof reason === "object" && reason.silent) { return; } console.error(prefix + msg, normalizeException(reason)); From a016a2233c26a9aa119c5ccf7804fd7a6296eb96 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Wed, 18 Feb 2026 20:51:05 +0100 Subject: [PATCH 4/7] Update src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../libs/System.Native.Browser/diagnostics/symbolicate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts b/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts index 2752eb352e195d..f9d6a6d4f9a094 100644 --- a/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts +++ b/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts @@ -62,7 +62,7 @@ function initSymbolMap() { //# chrome //# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4 - regexes.push(/(?[a-z]+:\/\/[a-zA-Z0-9.:/_]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/g); + regexes.push(/(?[a-z]+:\/\/[a-zA-Z0-9.:/_~-]*:wasm-function\[(?\d+)\]:0x[a-fA-F\d]+)/g); //# .wasm-function[8962] regexes.push(/(?<[^ >]+>[.:]wasm-function\[(?[0-9]+)\])/g); From 1c6d239298949da0c6eed8cbae9d3b1e19606298 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 18 Feb 2026 20:53:06 +0100 Subject: [PATCH 5/7] feedback --- src/native/corehost/browserhost/browserhost.cpp | 1 + src/native/corehost/browserhost/loader/logging.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/native/corehost/browserhost/browserhost.cpp b/src/native/corehost/browserhost/browserhost.cpp index fc4f9b008f804a..e687913cce4913 100644 --- a/src/native/corehost/browserhost/browserhost.cpp +++ b/src/native/corehost/browserhost/browserhost.cpp @@ -125,6 +125,7 @@ extern "C" int BrowserHost_InitializeDotnet(int propertiesCount, const char** pr static bool executeAssemblyFailed = false; extern "C" int BrowserHost_ExecuteAssembly(const char* assemblyPath, int argc, const char** argv) { + executeAssemblyFailed = false; int exit_code = 0; int retval = coreclr_execute_assembly(CurrentClrInstance, CurrentAppDomainId, argc, argv, assemblyPath, (uint32_t*)&exit_code); diff --git a/src/native/corehost/browserhost/loader/logging.ts b/src/native/corehost/browserhost/loader/logging.ts index 218f26dbf3c992..9fe4985fdff427 100644 --- a/src/native/corehost/browserhost/loader/logging.ts +++ b/src/native/corehost/browserhost/loader/logging.ts @@ -54,7 +54,7 @@ export function normalizeException(reason: any) { let stack: string | undefined; if (reason) { if (typeof reason === "object" && reason.status === undefined) { - if (stack !== undefined) { + if (reason.stack !== undefined) { stack = reason.stack; } else { stack = new Error().stack + ""; From ac44b7178ca0340ee0b6670a550a2714e00a0bbf Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Wed, 18 Feb 2026 21:14:29 +0100 Subject: [PATCH 6/7] feedback --- src/native/corehost/browserhost/loader/logging.ts | 4 ++-- src/native/libs/System.Native.Browser/diagnostics/exit.ts | 2 +- .../libs/System.Native.Browser/diagnostics/symbolicate.ts | 5 ++++- .../interop/utils.ts | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/native/corehost/browserhost/loader/logging.ts b/src/native/corehost/browserhost/loader/logging.ts index 9fe4985fdff427..949c454f99beaa 100644 --- a/src/native/corehost/browserhost/loader/logging.ts +++ b/src/native/corehost/browserhost/loader/logging.ts @@ -54,8 +54,8 @@ export function normalizeException(reason: any) { let stack: string | undefined; if (reason) { if (typeof reason === "object" && reason.status === undefined) { - if (reason.stack !== undefined) { - stack = reason.stack; + if (reason.stack === undefined) { + stack = reason.stack + ""; } else { stack = new Error().stack + ""; } diff --git a/src/native/libs/System.Native.Browser/diagnostics/exit.ts b/src/native/libs/System.Native.Browser/diagnostics/exit.ts index 5507fa89f6d338..344f822fefe224 100644 --- a/src/native/libs/System.Native.Browser/diagnostics/exit.ts +++ b/src/native/libs/System.Native.Browser/diagnostics/exit.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { LoaderConfigInternal } from "./types"; -import { dotnetLogger, dotnetLoaderExports, dotnetApi, dotnetBrowserUtilsExports, dotnetRuntimeExports } from "./cross-module"; +import { dotnetLogger, dotnetLoaderExports, dotnetApi, dotnetRuntimeExports } from "./cross-module"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB } from "./per-module"; import { teardownProxyConsole } from "./console-proxy"; diff --git a/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts b/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts index f9d6a6d4f9a094..6059e1aa1c95ce 100644 --- a/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts +++ b/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { dotnetLogger } from "./cross-module"; + const symbol_map = new Map(); let symbolTable: string | undefined; const regexes: RegExp[] = []; @@ -43,7 +45,7 @@ export function symbolicateStackTrace(message: string): string { return origMessage; } catch (error) { - console.debug(`failed to symbolicate: ${error}`); + dotnetLogger.debug(`failed to symbolicate: ${error}`); return message; } } @@ -79,5 +81,6 @@ function initSymbolMap() { symbol_map.set(Number(parts[0]), parts[1]); }); } catch (exc) { + dotnetLogger.debug(`failed to parse symbol table: ${exc}`); } } diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts index 64f1d5f1b649b3..68dbc8e1add241 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts @@ -3,7 +3,7 @@ import type { TimeStamp } from "./types"; -import { dotnetAssert, dotnetDiagnosticsExports, dotnetLoaderExports, Module } from "./cross-module"; +import { dotnetAssert, dotnetLoaderExports, Module } from "./cross-module"; import { jsInteropState } from "./marshal"; import { ENVIRONMENT_IS_WEB } from "./per-module"; From a4d29759623faa7a2fa1d3b0bfe21a3827fcd06a Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Thu, 19 Feb 2026 11:18:12 +0100 Subject: [PATCH 7/7] Update src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts Co-authored-by: Radek Doulik --- .../libs/System.Native.Browser/diagnostics/symbolicate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts b/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts index 6059e1aa1c95ce..4d8034c0ab4938 100644 --- a/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts +++ b/src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts @@ -72,7 +72,7 @@ function initSymbolMap() { const text = symbolTable; symbolTable = undefined; try { - text.split(/[\r\n]/).forEach((line: string) => { + text.split(/\r?\n|\r/).forEach((line: string) => { const parts: string[] = line.split(/:/); if (parts.length < 2) return;