Skip to content

Commit

Permalink
[wasm] ManagedToNativeGenerator: Skip unmanaged dlls (#81066)
Browse files Browse the repository at this point in the history
* [wasm] ManagedToNativeGenerator: Skip unmanaged dlls

.. instead crashing with an exception like:

```
src/mono/wasm/build/WasmApp.Native.targets(296,5): error MSB4018: (NETCORE_ENGINEERING_TELEMETRY=Build) The "ManagedToNativeGenerator" task failed unexpectedly.
System.BadImageFormatException: This PE image is not a managed executable.
   at System.Reflection.MetadataLoadContext.LoadFromStreamCore(Stream peStream)
   at System.Reflection.MetadataLoadContext.LoadFromAssemblyPath(String assemblyPath)
   at PInvokeTableGenerator.Generate(String[] pinvokeModules, String[] assemblies, String outputPath) in /_/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs:line 42
   at ManagedToNativeGenerator.ExecuteInternal() in /_/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs:line 68
   at ManagedToNativeGenerator.Execute() in /_/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs:line 53
   at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask)
```

The wasm targets currently are not able to differentiate the managed
assemblies from the unmanaged ones. so it needs to be handled here.

* [wasm] WasmAppHost: Add support for host arguments

* Drop reallocation of managed dlls.

Co-authored-by: Marek Fišera <mara@neptuo.com>
  • Loading branch information
radical and maraf authored Jan 24, 2023
1 parent e463920 commit d793586
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 13 deletions.
8 changes: 7 additions & 1 deletion src/mono/wasm/host/CommonConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal sealed class CommonConfiguration
public WasmHost Host { get; init; }
public HostConfig HostConfig { get; init; }
public WasmHostProperties HostProperties { get; init; }
public IEnumerable<string> HostArguments { get; init; }

private string? hostArg;
private string? _runtimeConfigPath;
Expand All @@ -30,11 +31,13 @@ internal sealed class CommonConfiguration

private CommonConfiguration(string[] args)
{
List<string> hostArgsList = new();
var options = new OptionSet
{
{ "debug|d", "Start debug server", _ => Debugging = true },
{ "host|h=", "Host config name", v => hostArg = v },
{ "runtime-config|r=", "runtimeconfig.json path for the app", v => _runtimeConfigPath = v }
{ "runtime-config|r=", "runtimeconfig.json path for the app", v => _runtimeConfigPath = v },
{ "extra-host-arg=", "Extra argument to be passed to the host", hostArgsList.Add },
};

RemainingArgs = options.Parse(args);
Expand Down Expand Up @@ -95,6 +98,9 @@ private CommonConfiguration(string[] args)
if (!Enum.TryParse(HostConfig.HostString, ignoreCase: true, out WasmHost wasmHost))
throw new CommandLineException($"Unknown host {HostConfig.HostString} in config named {HostConfig.Name}");
Host = wasmHost;

hostArgsList.AddRange(HostConfig.HostArguments);
HostArguments = hostArgsList;
}

public ProxyOptions ToProxyOptions()
Expand Down
5 changes: 2 additions & 3 deletions src/mono/wasm/host/JSEngineHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ public static async Task<int> InvokeAsync(CommonConfiguration commonArgs,

private async Task<int> RunAsync()
{
string[] engineArgs = Array.Empty<string>();

string engineBinary = _args.Host switch
{
WasmHost.V8 => "v8",
Expand Down Expand Up @@ -79,9 +77,10 @@ private async Task<int> RunAsync()
args.Add("--expose_wasm");
}

args.AddRange(_args.CommonConfig.HostArguments);

args.Add(_args.JSPath!);

args.AddRange(engineArgs);
if (_args.Host is WasmHost.V8 or WasmHost.JavaScriptCore)
{
// v8/jsc want arguments to the script separated by "--", others don't
Expand Down
2 changes: 2 additions & 0 deletions src/mono/wasm/host/RuntimeConfigJson.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
Expand Down Expand Up @@ -32,6 +33,7 @@ internal sealed record WasmHostProperties(

internal sealed record HostConfig(string? Name, [property: JsonPropertyName("host")] string? HostString)
{
[property: JsonPropertyName("host-args")] public string[] HostArguments { get; set; } = Array.Empty<string>();
// using an explicit property because the deserializer doesn't like
// extension data in the record constructor
[property: JsonExtensionData] public Dictionary<string, JsonElement>? Properties { get; set; }
Expand Down
10 changes: 7 additions & 3 deletions src/tasks/WasmAppBuilder/IcallTableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public IcallTableGenerator(Func<string, string> fixupSymbolName, TaskLoggingHelp
// The runtime icall table should be generated using
// mono --print-icall-table
//
public IEnumerable<string> Generate(string? runtimeIcallTableFile, string[] assemblies, string? outputPath)
public IEnumerable<string> Generate(string? runtimeIcallTableFile, IEnumerable<string> assemblies, string? outputPath)
{
_icalls.Clear();
_signatures.Clear();
Expand All @@ -44,9 +44,13 @@ public IEnumerable<string> Generate(string? runtimeIcallTableFile, string[] asse

var resolver = new PathAssemblyResolver(assemblies);
using var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib");
foreach (var aname in assemblies)
foreach (var asmPath in assemblies)
{
var a = mlc.LoadFromAssemblyPath(aname);
if (!File.Exists(asmPath))
throw new LogAsErrorException($"Cannot find assembly {asmPath}");

Log.LogMessage(MessageImportance.Low, $"Loading {asmPath} to scan for icalls");
var a = mlc.LoadFromAssemblyPath(asmPath);
foreach (var type in a.GetTypes())
ProcessType(type);
}
Expand Down
50 changes: 47 additions & 3 deletions src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

public class ManagedToNativeGenerator : Task
{
[Required]
public string[]? Assemblies { get; set; }
public string[] Assemblies { get; set; } = Array.Empty<string>();

public string? RuntimeIcallTableFile { get; set; }

Expand Down Expand Up @@ -62,12 +65,14 @@ public override bool Execute()

private void ExecuteInternal()
{
List<string> managedAssemblies = FilterOutUnmanagedBinaries(Assemblies);

var pinvoke = new PInvokeTableGenerator(FixupSymbolName, Log);
var icall = new IcallTableGenerator(FixupSymbolName, Log);

IEnumerable<string> cookies = Enumerable.Concat(
pinvoke.Generate(PInvokeModules, Assemblies!, PInvokeOutputPath!),
icall.Generate(RuntimeIcallTableFile, Assemblies!, IcallOutputPath)
pinvoke.Generate(PInvokeModules, managedAssemblies, PInvokeOutputPath!),
icall.Generate(RuntimeIcallTableFile, managedAssemblies, IcallOutputPath)
);

var m2n = new InterpToNativeGenerator(Log);
Expand Down Expand Up @@ -110,4 +115,43 @@ public string FixupSymbolName(string name)
_symbolNameFixups[name] = fixedName;
return fixedName;
}

private List<string> FilterOutUnmanagedBinaries(string[] assemblies)
{
List<string> managedAssemblies = new(assemblies.Length);
foreach (string asmPath in Assemblies)
{
if (!File.Exists(asmPath))
throw new LogAsErrorException($"Cannot find assembly {asmPath}");

try
{
if (!IsManagedAssembly(asmPath))
{
Log.LogMessage(MessageImportance.Low, $"Skipping unmanaged {asmPath}.");
continue;
}
}
catch (Exception ex)
{
Log.LogMessage(MessageImportance.Low, $"Failed to read assembly {asmPath}: {ex}");
throw new LogAsErrorException($"Failed to read assembly {asmPath}: {ex.Message}");
}

managedAssemblies.Add(asmPath);
}

return managedAssemblies;
}

private static bool IsManagedAssembly(string filePath)
{
if (!File.Exists(filePath))
return false;

using FileStream fileStream = File.OpenRead(filePath);
using PEReader reader = new(fileStream, PEStreamOptions.Default);
return reader.HasMetadata;
}

}
11 changes: 8 additions & 3 deletions src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public PInvokeTableGenerator(Func<string, string> fixupSymbolName, TaskLoggingHe
_fixupSymbolName = fixupSymbolName;
}

public IEnumerable<string> Generate(string[] pinvokeModules, string[] assemblies, string outputPath)
public IEnumerable<string> Generate(string[] pinvokeModules, IEnumerable<string> assemblies, string outputPath)
{
var modules = new Dictionary<string, string>();
foreach (var module in pinvokeModules)
Expand All @@ -37,9 +37,14 @@ public IEnumerable<string> Generate(string[] pinvokeModules, string[] assemblies

var resolver = new PathAssemblyResolver(assemblies);
using var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib");
foreach (var aname in assemblies)

foreach (var asmPath in assemblies)
{
var a = mlc.LoadFromAssemblyPath(aname);
if (!File.Exists(asmPath))
throw new LogAsErrorException($"Cannot find assembly {asmPath}");

Log.LogMessage(MessageImportance.Low, $"Loading {asmPath} to scan for pinvokes");
var a = mlc.LoadFromAssemblyPath(asmPath);
foreach (var type in a.GetTypes())
CollectPInvokes(pinvokes, callbacks, signatures, type);
}
Expand Down

0 comments on commit d793586

Please sign in to comment.