Skip to content

Commit

Permalink
Create a reproduction case for the access violation with .NET 6.0.11 …
Browse files Browse the repository at this point in the history
…x64 on Windows 10 x64 Build 19044, when using Wasmtime from commit ff5abfd9938c78cd75c6c5d6a41565097128c5d3.
  • Loading branch information
kpreisser committed Nov 29, 2022
1 parent 992efb0 commit ccca66d
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

using Wasmtime;

namespace ReproWasmtime.Api
{
internal partial class WasmApi
{
private readonly WasmPluginHost host;

private bool isDisposed;

private bool isActive;

private volatile bool isCancelled = false;

private List<long>? pendingCallbacksRefData;

private int lastErrorCode;

private Exception? lastHostErrorException;

private string? lastWasmErrorMessage;

private bool wasmInstantiantionCompleted;

private Memory? wasmMemory;

private Func<long, int, int>? wasmHandleCallback;

private Action? wasmWasiStartOrInitialize;

private Func<int>? wasmHandleStart;

private Func<int>? wasmHandleStop;

public WasmApi(WasmPluginHost host)
: base()
{
this.host = host;

this.isActive = true;
}

public WasmPluginHost Host
{
get => this.host;
}

public string? LastWasmErrorMessage
{
get => this.lastWasmErrorMessage;
set => this.lastWasmErrorMessage = value;
}

public Action? WasmWasiStartOrInitialize
{
get => this.wasmWasiStartOrInitialize;
}

public void PopulateWasmImports(Linker linker)
{
}

public void PopulateWasmExports(Instance instance)
{
this.wasmWasiStartOrInitialize = instance.GetAction("_initialize") ?? instance.GetAction("_start");

this.wasmInstantiantionCompleted = true;
}

public void WrapWasmCall(
Func<int> wasmInvokeHandler,
bool allowReturnError = false)
{
Debug.Assert(
Monitor.IsEntered(this.Host.SyncRoot),
"A lock on the WasmHost's SyncRoot is required.");

bool localAllowReturnErrors = allowReturnError;

bool isFirst = !this.Host.IsFirstTransition;
if (isFirst)
this.Host.IsFirstTransition = true;

try {
var invokeStatusCode = 0;
string? wasmErrorMessage = null;

bool ctsCanceled = false;

try {
using var cts = new CancellationTokenSource();
object? placeholder2 = null;

invokeStatusCode = wasmInvokeHandler();

if (invokeStatusCode is not 0) {
wasmErrorMessage = this.LastWasmErrorMessage;
this.LastWasmErrorMessage = null;
}
}
finally {
if (Volatile.Read(ref ctsCanceled)) {
throw new TimeoutException();
}
}
}
catch (Exception?) {
throw;
}
finally {
if (isFirst)
this.Host.IsFirstTransition = false;
}

void ExecutePendingCallbacksWithinWasm()
{
if (this.pendingCallbacksRefData?.Count > 0) {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;

using ReproWasmtime.Api;

using Wasmtime;

using WasmModule = Wasmtime.Module;
using WasmEngine = Wasmtime.Engine;

namespace ReproWasmtime
{
internal class WasmPluginHost
{
private readonly object syncRoot = new();

private readonly WasmPluginManager manager;

private readonly object? config;

private readonly string wasmName;

private readonly string? displayName;

private readonly Memory<byte> wasmContent;

private readonly bool wasmContentIsWat;

private readonly string wasmContentPathWithoutFileExtension;

private readonly int wasmTimeout;

private (
Config config,
WasmEngine engine,
WasmModule module,
Linker linker,
Store store
)? wasm;

private WasmApi? api;

private volatile int state;

private bool isFirstTransition;


public WasmPluginHost(
WasmPluginManager manager,
object? wasmLogger,
string wasmName,
string? displayName,
Memory<byte> wasmContent,
bool wasmContentIsWat,
string wasmContentPathWithoutFileExtension)
: base()
{
this.manager = manager;
this.wasmName = wasmName;
this.displayName = displayName;
this.wasmContent = wasmContent;
this.wasmContentIsWat = wasmContentIsWat;
this.wasmContentPathWithoutFileExtension = wasmContentPathWithoutFileExtension;

this.wasmTimeout = -1;
}

public object SyncRoot
{
get => this.syncRoot;
}

public WasmPluginManager Manager
{
get => this.manager;
}

public object? WasmLogger
{
get => null;
}

public WasmEngine? WasmEngine
{
get => this.wasm?.engine;
}

public int WasmTimeout
{
get => this.wasmTimeout;
}

public bool IsFirstTransition
{
get => this.isFirstTransition;
set => this.isFirstTransition = value;
}

public int State
{
get => this.state;
private set => this.state = value;
}

public bool IsRunning
{
get => this.State is 1;
}

public int CyclesToWaitBeforeRestart
{
get;
set;
}

private static (Config, WasmEngine, WasmModule, Linker, Store) CreateWasmComponents(
ReadOnlySpan<byte> content,
bool contentIsWat)
{
const string mainModuleName = "Main";

var config = default(Config);
var engine = default(WasmEngine);
var module = default(WasmModule);
var linker = default(Linker);
var store = default(Store);

try {
config = new Config()
.WithEpochInterruption(true)
.WithMaximumStackSize(2 * 1024 * 1024);

engine = new WasmEngine(config);

if (!contentIsWat) {
module = WasmModule.FromBytes(engine, mainModuleName, content);
}
else {
var textContent = Encoding.UTF8.GetString(content);
module = WasmModule.FromText(engine, mainModuleName, textContent);
}

linker = new Linker(engine);
store = new Store(engine);

linker.AllowShadowing = true;

store.SetEpochDeadline(1);

linker.DefineWasi();

var wasiConfig = new WasiConfiguration();
store.SetWasiConfiguration(wasiConfig);

return (config, engine, module, linker, store);
}
catch {
store?.Dispose();
linker?.Dispose();
module?.Dispose();
engine?.Dispose();
config?.Dispose();

throw;
}
}

public void Start()
{
Debug.Assert(Monitor.IsEntered(this.syncRoot));

if (this.State == default) {
this.State = 1;

try {

this.wasm = CreateWasmComponents(
this.wasmContent.Span,
this.wasmContentIsWat);
}
catch (Exception ex) {
this.State = 2;
return;
}

this.api = new WasmApi(this);
this.api.PopulateWasmImports(this.wasm.Value.linker);

var moduleInstance = default(Instance);
this.api.WrapWasmCall(() => {
moduleInstance = this.wasm.Value.linker.Instantiate(
this.wasm.Value.store,
this.wasm.Value.module);
this.api.PopulateWasmExports(moduleInstance!);
this.api.WasmWasiStartOrInitialize?.Invoke();
return 0;
});
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.IO;
using System.Threading;

namespace ReproWasmtime
{
internal partial class WasmPluginManager
{
public WasmPluginManager(object? pluginHost)
: base()
{
}

public void Start()
{
var thread = new Thread(this.RunWasmInitializerThread);
thread.Start();
}

public void Stop()
{
}

private void RunWasmInitializerThread()
{
byte[] wasmContent = File.ReadAllBytes(@"dotnet-codabix-wasm-build.wasm");
var tmpHost = new WasmPluginHost(this, null, "x", "", wasmContent, false, "");

lock (tmpHost.SyncRoot)
tmpHost.Start();

GC.KeepAlive(tmpHost);

Console.WriteLine("wasmtime_func_call completed! This should not be displayed if the Access Violation occured.");
}
}
}
11 changes: 11 additions & 0 deletions ReproWasmtimeAccessViolation/Content/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;

using ReproWasmtime;

var manager = new WasmPluginManager(null);
manager.Start();
Console.WriteLine(".NET Version: " + RuntimeInformation.FrameworkDescription);
Console.WriteLine("WasmPluginManager started. Please wait...");
Thread.Sleep(-1);
Loading

0 comments on commit ccca66d

Please sign in to comment.