lokad / ilpack Goto Github PK
View Code? Open in Web Editor NEWSerialize .NET Core assemblies
License: MIT License
Serialize .NET Core assemblies
License: MIT License
We tries to sort types by dependencies prior to actual serialization:
ILPack/src/AssemblyGenerator.Types.cs
Lines 47 to 54 in 4763b17
Dependency solver method:
ILPack/src/AssemblyGenerator.Types.cs
Lines 13 to 45 in 4763b17
As you see, this method should fail to handle constructed generic types correctly (e.g. IPage
in class jot_2 : ICompiledJot<IPage>
if all dependent types and jot_2
would be in the same assembly) and throw cyclic dependency exception. Since, our current unit tests are more complex, I've tried completely removing the sorting by assuming metadata are already sorted for a given assembly and it worked.
Question: Is my assumption correct? If so, I will remove type dependency resolution completely. If not, I'll improve it.
I was trying to generate a dynamic assembly containing a type that inherits from another type whose constructor is protected. This fails with an exception in AssemblyMetadata.GetMethodOrConstructorSignature
because that method only considers public methods and constructors. The fix is to pass the appropriate flags to GetMethods/GetConstructors. That would probably be BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static
for GetMethods and BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
for GetConstructors.
Initial code had some problems to support executable assemblies (e.g. console applications). The main reason was we need to know entry-point method handle ahead of its serialization to able to serialize <Module>
type as first type in the assembly. See:
ILPack/src/AssemblyGenerator.cs
Lines 43 to 58 in d02493d
With #31, we could reserve metadata tokens prior to serialization. So, this could help us to support executable assemblies.
Use of this on .NET Framework from a strong-name assembly fails. Please strong name this assembly.
I've dumped twice the same generated program in two assemblies, one in net462
, the other one with Lokad.ILPack
. The problem can be seen by inspecting the jot_2
class in both dumped assemblies.
With the one generated through net462
we have the following decompiled line (from dotpeek):
task1 = page3 = pageRest11.GetPage(obj1, num5 = (int) num3, obj5);
While Lokad.ILPack
generates the following.
task1 = page3 = pageRest11.GetPage(_param2, num5 = (int) num3, (CancellationToken) _param5);
_param5
being the last argument of the run method. Attached the copy of the two generated assembly.
After emitting a fairly trivial assembly using ILPack, running peverify.dll on that assembly produces this output:
peverify D:\git\MessagePack-CSharp\bin\DynamicCodeDumper\Debug\net5.0\DynamicObjectResolver.dll
Copyright (c) Microsoft Corporation. All rights reserved.
[HRESULT 0x80070002] - The system cannot find the file specified.
1 Error(s) Verifying D:\git\MessagePack-CSharp\bin\DynamicCodeDumper\Debug\net5.0\DynamicObjectResolver.dll
In contrast, running peverify.dll on the exact same dynamic assembly except written out by AssemblyBuilder.Save
, peverify succeeds:
peverify "D:\git\MessagePack-CSharp\bin\DynamicCodeDumper\Debug\net472\MessagePack.Resolvers.DynamicObjectResolver.dll"
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
All Classes and Methods in D:\git\MessagePack-CSharp\bin\DynamicCodeDumper\Debug\net472\MessagePack.Resolvers.DynamicObjectResolver.dll Verified.
This is same as issue #13
This is a great library to fill the gap of assembly serialization.
Are there any plans to publish it on NuGet?
[IL]: Error [CallCtor]: [.ProcessBookDelegate::.ctor(object, native int)][offset 0x00000001] call to .ctor only allowed to initialize this pointer from within a .ctor. Try newobj.
[IL]: Error [StackUnexpected]: [.ProcessBookDelegate::.ctor(object, native int)][offset 0x00000007][found ref '[TriAxis.RunSharp.Tests.12_Delegates.GenBookstore]ProcessBookDelegate'] Unexpected type on the stack.
[IL]: Error [ThisUninitReturn]: [.ProcessBookDelegate::.ctor(object, native int)][offset 0x0000000C] Return from .ctor when this is uninitialized.
[IL]: Error [CallCtor]: [.ProcessBookDelegate::.ctor(object, native int)][offset 0x00000001] call to .ctor only allowed to initialize this pointer from within a .ctor. Try newobj.
[IL]: Error [StackUnexpected]: [.ProcessBookDelegate::.ctor(object, native int)][offset 0x00000007][found ref '[TriAxis.RunSharp.Tests.12_Delegates.GenBookstore]ProcessBookDelegate'] Unexpected type on the stack.
[IL]: Error [ThisUninitReturn]: [.ProcessBookDelegate::.ctor(object, native int)][offset 0x0000000C] Return from .ctor when this is uninitialized.
TriAxis.RunSharp.Tests.12_Delegates.GenBookstore.zip
Source code:
public delegate void ProcessBookDelegate();
public class Test
{
public static void Main()
{
}
}
ildasm:
// =============== CLASS MEMBERS DECLARATION ===================
.class public auto ansi sealed ProcessBookDelegate
extends [System.Runtime]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname
instance void .ctor(object 'object',
native int 'method') runtime managed
{
} // end of method ProcessBookDelegate::.ctor
.method public hidebysig newslot virtual
instance class [System.Runtime]System.IAsyncResult
BeginInvoke(class [System.Runtime]System.AsyncCallback callback,
object 'object') runtime managed
{
} // end of method ProcessBookDelegate::BeginInvoke
.method public hidebysig newslot virtual
instance void EndInvoke(class [System.Runtime]System.IAsyncResult result) runtime managed
{
} // end of method ProcessBookDelegate::EndInvoke
.method public hidebysig newslot virtual
instance void Invoke() runtime managed
{
} // end of method ProcessBookDelegate::Invoke
} // end of class ProcessBookDelegate
.class public auto ansi beforefieldinit Test
extends [System.Runtime]System.Object
{
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 13 (0xd)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ldarg.0
IL_0007: call instance void Test::$$ctor$PST06000007()
IL_000c: ret
} // end of method Test::.ctor
.method public hidebysig static void Main() cil managed
{
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method Test::Main
.method privatescope hidebysig instance void
$$ctor$PST06000007() cil managed
{
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method Test::$$ctor
} // end of class Test
The same code compiled for .NET 5 in VS generates almost same IL but passes ILVerification without errors:
// =============== CLASS MEMBERS DECLARATION ===================
.class public auto ansi sealed ProcessBookDelegate
extends [System.Runtime]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname
instance void .ctor(object 'object',
native int 'method') runtime managed
{
} // end of method ProcessBookDelegate::.ctor
.method public hidebysig newslot virtual
instance class [System.Runtime]System.IAsyncResult
BeginInvoke(class [System.Runtime]System.AsyncCallback callback,
object 'object') runtime managed
{
} // end of method ProcessBookDelegate::BeginInvoke
.method public hidebysig newslot virtual
instance void EndInvoke(class [System.Runtime]System.IAsyncResult result) runtime managed
{
} // end of method ProcessBookDelegate::EndInvoke
.method public hidebysig newslot virtual
instance void Invoke() runtime managed
{
} // end of method ProcessBookDelegate::Invoke
} // end of class ProcessBookDelegate
.class public auto ansi beforefieldinit Test
extends [System.Runtime]System.Object
{
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Object::.ctor()
IL_0006: ret
} // end of method Test::.ctor
.method public hidebysig static void Main() cil managed
{
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method Test::Main
} // end of class Test
Re-saving the erroneous IL in dnSpy generates assembly that passes ILVerification:
unchanged.zip
// =============== CLASS MEMBERS DECLARATION ===================
.class public auto ansi sealed ProcessBookDelegate
extends [System.Private.CoreLib]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname
instance void .ctor(object 'object',
native int 'method') runtime managed
{
} // end of method ProcessBookDelegate::.ctor
.method public hidebysig newslot virtual
instance class [System.Private.CoreLib]System.IAsyncResult
BeginInvoke(class [System.Private.CoreLib]System.AsyncCallback callback,
object 'object') runtime managed
{
} // end of method ProcessBookDelegate::BeginInvoke
.method public hidebysig newslot virtual
instance void EndInvoke(class [System.Private.CoreLib]System.IAsyncResult result) runtime managed
{
} // end of method ProcessBookDelegate::EndInvoke
.method public hidebysig newslot virtual
instance void Invoke() runtime managed
{
} // end of method ProcessBookDelegate::Invoke
} // end of class ProcessBookDelegate
.class public auto ansi beforefieldinit Test
extends [System.Private.CoreLib]System.Object
{
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 13 (0xd)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
IL_0006: ldarg.0
IL_0007: call instance void Test::$$ctor$PST06000007()
IL_000c: ret
} // end of method Test::.ctor
.method public hidebysig static void Main() cil managed
{
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method Test::Main
.method privatescope hidebysig instance void
$$ctor$PST06000007() cil managed
{
// Code size 1 (0x1)
.maxstack 8
IL_0000: ret
} // end of method Test::$$ctor
} // end of class Test
The only difference in this fix is using System.Private.CoreLib
library instead of System.Runtime
and that it's a dll, not exe.
Expected behavior: generated by ILPack assembly should pass IL verification without issues.
During working #9, I realized a major problem in the code architecture. All serialization logic works in-order. With latest fixes serializing a type works like that:
Meaning that, a simple code which uses singleton pattern cannot be serialized:
public class MyClass {
private static MyClass _instance;
public static MyClass GetInstance() {
if (_instance == null) {
_instance = new MyClass();
}
return _instance;
}
}
Because, during serializing the GetInstance
method (see 3rd step), MyClass
type is not added into _typeHandles
dictionary which is used for type resolution.
The following code is the root of the problem:
ILPack/src/AssemblyGenerator.Types.cs
Lines 80 to 119 in bc83341
Relevant unit test can be found in self-reference-unit-test
branch. I didn't merge the changes into develop
branch incase you set up CI pipeline which relies on all tests passed.
A major refactoring is required to support cyclic-references with placeholder classes to "reserve" metadata tokens. This refactoring will increase the coverage of generic support as well such as:
public class Vector: IEquatable<Vector> {
// ...
}
Current API is misleading. Suppose a developer needs to integrate ILPack
into a project. Since, generator methods (AssemblyGenerator.GenerateAssemblyBytes
and AssemblyGenerator.GenerateAssembly
) are instance methods, the developer might assume AssemblyGenerator
does some caching between each generator method calls. So, he probably creates a single instance and use it to maximize efficiency. Also, he need to deal with IDisposable
pattern for his single instance.
In contrast to above story, let's have a look at the facts:
AssemblyGenerator
does not cache anything (or at least currently).IDisposable
implementation is purely redundant. It's there to dispose a-never-used MemoryStream
which was created in constructor.So, I suggest to refactor current API or create a clean API from scratch. My suggestions are:
IDisposable
and unused MemoryStream
.Hi Vermorel,
please see/use attached patch, if it's desired. It's just a performance improvement as I was profiling around a bit and I noticed,
ReadInstructions will allocate/GC free an enormous amount of GC objects, as the Delegate (Func) uses a boxing for itself each time as it seems.
Unfortunately, I made ByteBuffer and MethodBodyReader public. I know that's not fine.
Maybe you would find a nicer solution, I currently don't have much time :)
Many Thanks
ILPack appears to fail at processing types that contains methods with generic parameters. The unit tests RewriteTest.AssemblyName
fails with:
System.TypeInitializationException : The type initializer for 'Lokad.ILPack.Tests.RewriteTest' threw an exception.
---- System.ArgumentException : Type cannot be found: "T" of "TestSubject.MyClass, TestSubject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
Parameter name: type
Yet, attempting to solve the problem by adding the following lines to AssemblyGenerator.Types.cs
:
foreach (var inf in type.GetMethods().SelectMany(t => t.GetGenericArguments()))
{
yield return inf;
}
Generates the exception:
System.TypeInitializationException : The type initializer for 'Lokad.ILPack.Tests.RewriteTest' threw an exception.
---- System.ArgumentException : Cyclic connections are not allowed
Instance methods are serialized as global methods. So, following verification errors are reported by PEVerify for sample_factorial.dll:
[MD]: Error: Global item (field,method) must be Static. [token:0x06000001]
[MD]: Error: Global method marked Abstract,Virtual. [token:0x06000001]
[MD]: Error: Global item (field,method) must be Static. [token:0x06000002]
[MD]: Error: Global constructor. [token:0x06000002]
4 Error(s) Verifying sample_factorial.dll
Related IL DASM dump:
Global functions
-------------------------------------------------------
Method #1 (06000001)
-------------------------------------------------------
MethodName: myfactorial (06000001)
Flags : [Public] [Virtual] [ReuseSlot] (00000046)
RVA : 0x00002050
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: I4
No arguments.
Method #2 (06000002)
-------------------------------------------------------
MethodName: .ctor (06000002)
Flags : [Public] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001806)
RVA : 0x00002092
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Expected dump:
TypeDef #1 (02000002)
-------------------------------------------------------
TypDefName: FactorialAssembly.CFactorial (02000002)
Flags : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100001)
Extends : 0100000C [TypeRef] System.Object
Method #1 (06000001)
-------------------------------------------------------
MethodName: myfactorial (06000001)
Flags : [Public] [HideBySig] [ReuseSlot] (00000086)
RVA : 0x00002050
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: I4
No arguments.
Method #2 (06000002)
-------------------------------------------------------
MethodName: .ctor (06000002)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA : 0x0000207c
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
OperandType.InlineString
requires user string metadata token which is allocated by GetOrAddUserString
(#US
in .NET metadata). Current implementation use strings in #Strings
heap which is allocated by GetOrAddString
.
Some of the metadata tables have requirements on sort order. ILPack doesn't currently ensure these rules aren't violated. The main two that seem to be causing issues is the order of the generic parameters table and the interface map.
These are difficult to reproduce in the unit tests because the ordering and interleaving of things can be affected by other things in the project. Here's how to reproduce both issues in Sandbox.
Add the following to SandboxSubject\MyClass.cs, just below MyClass
:
public class GPOrder1
{
public void Function1<T>()
{
}
public void Function2<T>()
{
}
}
public class GPOrder2<T>
{
}
Result:
Metadata table GenericParam not sorted.
at System.Reflection.Throw.InvalidOperation_TableNotSorted(TableIndex tableIndex)
at System.Reflection.Metadata.Ecma335.MetadataBuilder.ValidateGenericParamTable()
at System.Reflection.Metadata.Ecma335.MetadataBuilder.ValidateOrder()
at System.Reflection.Metadata.Ecma335.MetadataRootBuilder.Serialize(BlobBuilder builder, Int32 methodBodyStreamRva, Int32 mappedFieldDataStreamRva)
For the interface map problem, remove the above code and add this instead:
interface Itf1
{
}
interface Itf2
{
}
class MyImpl : Itf2, Itf1
{
}
Result:
Metadata table InterfaceImpl not sorted.
at System.Reflection.Throw.InvalidOperation_TableNotSorted(TableIndex tableIndex)
at System.Reflection.Metadata.Ecma335.MetadataBuilder.ValidateInterfaceImplTable()
at System.Reflection.Metadata.Ecma335.MetadataBuilder.ValidateOrder()
at System.Reflection.Metadata.Ecma335.MetadataRootBuilder.Serialize(BlobBuilder builder, Int32 methodBodyStreamRva, Int32 mappedFieldDataStreamRva)
Hi @vermorel,
With the recent pull requests I've been sending I've been trying to keep them narrowly targeted but I realize this is probably causing some merge issues (especially with the unit tests). I just wanted to check if you prefer things less targeted or if there's anything else that'd make the process smoother.
One thing I was thinking is the tests would merge better if they weren't all in one file. After you've merged the current set of PRs I'll split things up into partial classes which should help in future.
Also... is logging an issue the best way to discuss stuff like this?
Brad
See the stack trace.
Exception: The given key 'Envision.Back.Pages.IPage result' was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at Lokad.ILPack.IL.MethodBodyWriter.Write(BlobBuilder writer, IReadOnlyList`1 il, Func`2 getString, IReadOnlyDictionary`2 typeHandles, IReadOnlyDictionary`2 ctorRefHandles, IReadOnlyDictionary`2 fieldHandles, IReadOnlyDictionary`2 methodHandles) in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\IL\MethodBodyWriter.cs:line 112
at Lokad.ILPack.IL.MethodBodyStreamWriter.AddMethodBody(MethodBase methodBase) in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\IL\MethodBodyStreamWriter.cs:line 56
at Lokad.ILPack.AssemblyGenerator.GetOrCreateMethod(MethodInfo methodInfo) in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\AssemblyGenerator.Methods.cs:line 59
at Lokad.ILPack.AssemblyGenerator.CreatePropertiesForType(PropertyInfo[] properties) in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\AssemblyGenerator.Properties.cs:line 64
at Lokad.ILPack.AssemblyGenerator.GetOrCreateType(Type type) in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\AssemblyGenerator.Types.cs:line 79
at Lokad.ILPack.AssemblyGenerator.CreateTypes(Type[] types) in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\AssemblyGenerator.Types.cs:line 13
at Lokad.ILPack.AssemblyGenerator.CreateModules(Module[] moduleInfo) in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\AssemblyGenerator.Modules.cs:line 21
at Lokad.ILPack.AssemblyGenerator.GenerateAssemblyBytes() in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\AssemblyGenerator.cs:line 88
at Lokad.ILPack.AssemblyGenerator.GenerateAssembly(String path) in D:\rainbow_buildAgent\work\8daee5e699edf9c0\src\AssemblyGenerator.cs:line 115
at ...
IPage is a type of our runtime system not created in the dynamic assembly.
I tried using this in StreamJsonRpc to dump out a dynamically written assembly.
It failed with the exception below:
Test Name: JsonRpcProxyGenerationTests.CallMethod_IntInt_Int
Test FullName: StreamJsonRpc.Tests (netcoreapp2.1).JsonRpcProxyGenerationTests.JsonRpcProxyGenerationTests.CallMethod_IntInt_Int
Test Source: D:\git\streamjsonrpc\src\StreamJsonRpc.Tests\JsonRpcProxyGenerationTests.cs : line 166
Test Outcome: Failed
Test Duration: 0:00:00
Test Name: JsonRpcProxyGenerationTests.CallMethod_IntInt_Int
Test Outcome: Failed
Result StackTrace:
at Lokad.ILPack.Metadata.AssemblyMetadata.GetTypeHandle(Type type)
at Lokad.ILPack.AssemblyGenerator.CreateCustomAttributes(EntityHandle parent, IEnumerable`1 attributes)
at Lokad.ILPack.AssemblyGenerator.GenerateAssemblyBytes(Assembly assembly)
at Lokad.ILPack.AssemblyGenerator.GenerateAssembly(Assembly assembly, String path)
at StreamJsonRpc.ProxyGeneration.Get(TypeInfo serviceInterface) in D:\git\streamjsonrpc\src\StreamJsonRpc\ProxyGeneration.cs:line 362
at StreamJsonRpc.JsonRpc.Attach[T](Stream sendingStream, Stream receivingStream) in D:\git\streamjsonrpc\src\StreamJsonRpc\JsonRpc.cs:line 573
at StreamJsonRpc.JsonRpc.Attach[T](Stream stream) in D:\git\streamjsonrpc\src\StreamJsonRpc\JsonRpc.cs:line 556
at JsonRpcProxyGenerationTests..ctor(ITestOutputHelper logger) in D:\git\streamjsonrpc\src\StreamJsonRpc.Tests\JsonRpcProxyGenerationTests.cs:line 30
Result Message:
System.ArgumentException : Type cannot be found: "System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute, rpcProxies_cdac9d9d-b20f-49ff-9b2d-330b04eb391c, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
Parameter name: type
As the title says,
only class attributes are serialized.
Hi Lokad,
sorry for not providing a PR yet.
Provided attached patch will fix not being able to write calls to functions with 'void*' ptrs in signature / params.
Many Thanks again for this lib :) :)
Sascha (mf-RDP)
Given
namespace NestedClassTest
{
public class Outer
{
public class Inner
{
public static int V;
}
}
}
For a reference to Outer.Inner.V
, the C# compiler issues:
IL_0006: ldsfld int32 NestedClassTest.Outer/Inner::V
ILPack issues:
IL_0005: ldsfld int32 [NestedClassTest]NestedClassTest.Inner::V
At load time, this throws an exception "Could not load type NestedClassTest.Inner".
Hello. Recently I've tried to test your implementation of assembly saving logic and found little issue.
When I try to save my type, that works in runtime, I get an exception on saving setter method of my type. Example of my code here. Maybe I've missed something on generation steps of my type. I'm totaly new in this features. :)
parameterDef = _metadataBuilder.AddParameter(parameter.Attributes,GetString(parameter.Name),i);
Initial code used PublicKeyToken
to determine if a referenced assembly is actually the core library (e.g. "mscorlib"). This wrong approach is made its way to till to date. PublicKeyToken
is actually hash of public key and it's what used during signing. So, PublicKeyToken
is actually same for assemblies which Microsoft used same private key.
We should use fully qualified assembly name instead of PublicKeyToken
. But, there is another problem that should be fixed as well. For example, System.Random
resides in System.Runtime.Extensions
assembly. If we try to get the assembly with:
var asm = typeof(System.Random).Assembly.GetName();
Console.WriteLine(asm.FullName);
// On .NET Core, prints:
// System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
So, mapping System.Private.CoreLib
to System.Runtime
will lead a crash. Because, System.Runtime
does not have any reference for System.Random
.
So, correct core library mapping is actually somewhat more complex. One of the possible way is to build a type dictionary for known assemblies like:
var knownAssemblyNames = new []{ "System.Runtime", "System.Runtime.Extensions", /* ... */ };
foreach (var name in knownAssemblyNames) {
var asm = Assembly.Load(name);
var types = asm.GetTypes();
AddKnownTypeReference(name, types);
}
When full metadata has accessed of a serialized assembly, an exception is thrown.
Following snippet demonstrate this error.
var asm = Assembly.LoadFile(path);
var types = asm.GetTypes(); // ERROR
Related exception stack trace:
System.Reflection.ReflectionTypeLoadException : Unable to load one or more of the requested types.
Invalid assembly public key. (Exception from HRESULT: 0x8013141E)
at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
at System.Reflection.RuntimeModule.GetTypes()
at System.Reflection.Assembly.GetTypes()
at Lokad.ILPack.Tests.AssemblyGeneratorTest.Test()
Improve code quality by applying previously discussed coding conventions of other projects such as including but not limited:
var
keyword)private
)nameof
where applicable (such as exceptions)PropertyInfo.GetGetMethod()
and PropertyInfo.GetSetMethod()
do not return non-public accessors, so for properties that were generated without a public accessor this line causes a failure. Changing this line to the below fixed my use case, would you mind incorporating that into the source? Thanks.
var eitherAccessor = propertyInfo.GetMethod ?? propertyInfo.SetMethod;
Hi,
attached a patch for a little issue in AssemblyMetadata.cs ("HACK: [vermorel] 2019-07-25.").
It will attach a ref to "netstandard" and make sure the ref to the assemblyname "system.private.corelib" is as per loaded in application current domain.
I noticed problems when AOTing with Mono (system.private.corelib not found) and ILSpy couldn't resolve these references either. With this patch, problems are gone.
I cannot say 100% sure if it will work for all NET versions, tested with netcore31, net50, mono. All ok-
0001-patch-for-reference-assemblies-netstandard-unresolve.zip
Best regards,
S.
Dynamically generated assemblies reference System.Private.CoreLib
assembly first. System.Private.CoreLib
public key token is same as mscorlib
. Since, .NET Core is fundamentally different from .NET Framework (.NET Core supports side-by-side runtime), we shouldn't reference mscorlib
first. Instead, we should reference System.Runtime
assembly which we should extract at its metadata at runtime. Also, we should map all mscorlib
and System.Private.CoreLib
references to System.Runtime
.
Even serializing an assembly with no types does not work. Although, PEVerify cannot find any errors, .NET runtime refuses to load this bare minimum assemblies and throws BadImageFormatException
(technically COR_E_BADIMAGEFORMAT
). So, there should be an error either in PE structure or metadata.
Using this code from a .NET 5 app:
var ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("test"), AssemblyBuilderAccess.Run);
var mb = ab.DefineDynamicModule("test");
var tb = mb.DefineType("Resources");
var fb = tb.DefineInitializedData("Something", new byte[] { 1, 2, 3, 4, 5 }, FieldAttributes.Public | FieldAttributes.Static);
tb.CreateType();
var ag = new Lokad.ILPack.AssemblyGenerator();
ag.GenerateAssembly(ab, "test.dll");
generates a .NETFramework-type library:
(Taken with ILSpy)
Is there a way to force ILPack to generate .NETCore binaries?
Hi Joannès,
I found a bug and fixed this, however, this time the fix is a bit more sophisticated, so I ask you kindly to review.
I tried to submit a PR but did not find out how, with Github.
Let me explain:
Currently, with ILPack this is failing. Please see screenshot below with wrongly generated IL, Type arguments are missing
To fix, I did the following:
changed MethodBodyWriter to make sure constructed types are written, I extended your interface and added some TryGetMethodDefinitionConstructedType etc.
I changed IsReferencedType. For this, I ask you kindly to review as this new approach is also just a best guess :)
Good news is, all tests are passing as per before and the problem is gone. I write quite large assemblies, referencing and inheriting e.g. all types in netstandard.
PS: Please let me know if you need something. It may be this problem also exists for generic methods, I will check this at a later time.
Many Thanks
Sascha
ILPack.zip
Void .ctor(System.String, AdvancedDLSupport.ImplementationOptions, AdvancedDLSupport.Loaders.ILibraryLoader, AdvancedDLSupport.Loaders.ISymbolLoader)
at Lokad.ILPack.IL.MethodBodyReader.GetParameter(Int32 index)
at Lokad.ILPack.IL.MethodBodyReader.GetVariable(Instruction instruction, Int32 index)
at Lokad.ILPack.IL.MethodBodyReader.ReadOperand(Instruction instruction)
at Lokad.ILPack.IL.MethodBodyReader.ReadInstructions()
at Lokad.ILPack.IL.MethodBodyReader.GetInstructions(MethodBase method)
at Lokad.ILPack.IL.MethodBaseExtensions.GetInstructions(MethodBase self)
at Lokad.ILPack.IL.MethodBodyStreamWriter.AddMethodBody(MethodBase methodBase, StandaloneSignatureHandle localVariablesSignature)
at Lokad.ILPack.AssemblyGenerator.CreateConstructor(ConstructorInfo ctor)
at Lokad.ILPack.AssemblyGenerator.CreateConstructors(IEnumerable`1 constructors)
at Lokad.ILPack.AssemblyGenerator.CreateType(Type type)
at Lokad.ILPack.AssemblyGenerator.CreateTypes(IEnumerable`1 types)
at Lokad.ILPack.AssemblyGenerator.CreateModules(IEnumerable`1 moduleInfo)
at Lokad.ILPack.AssemblyGenerator.GenerateAssemblyBytes(Assembly assembly)
at Lokad.ILPack.AssemblyGenerator.GenerateAssembly(Assembly assembly, String path)
It is basically about that code: https://github.com/Lokad/ILPack/blob/master/src/IL/MethodBodyReader.cs#L177
index is 0, while the ctor is not static, therefore it decrements index to the value of -1, which of course results in a IndexOutOfRangeException.
The constructor is in a sealed class and is basically a passthrough constructor to that one:
https://github.com/Firwood-Software/AdvancedDLSupport/blob/master/AdvancedDLSupport/NativeLibraryBase.cs
I don't exactly know what additional information would be helpful?
A reference to a (static) field of a generic class is not serialized correctly. A MissingFieldException
is thrown by the runtime when executing the serialized code. The issue can be reproduced as follows:
Add a class library to the solution named ReferencedSubject
. Then add the following class to this project:
using System;
namespace ReferencedSubject
{
public static class MyReferencedStaticClass<T>
{
public static readonly T Instance;
static MyReferencedStaticClass()
{
MyReferencedStaticClass<T>.Instance = default;
}
}
}
The project file will look like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;netstandard2.0;net46</TargetFrameworks>
</PropertyGroup>
</Project>
Add a project reference to projects TestSubject
and Lokad.ILPack.Tests
so that they reference project ReferencedSubject
.
Add the directive using ReferencedSubject;
and the following property to file MyClass.Properties.cs
of project TestSubject
:
public string FieldAccessOfReferencedType
{
get
{
return MyReferencedStaticClass<string>.Instance;
}
}
Finally, add the following test method to file RewriteTest.Properties.cs
:
[Fact]
public async void StaticProperty()
{
Assert.Equal(default(string), await Invoke(
"",
"x.FieldAccessOfReferencedType"));
}
Executing this test method produces the following output:
Message:
System.MissingFieldException : Field not found: 'ReferencedSubject.MyReferencedStaticClass`1.Instance'.
Stack Trace:
MyClass.get_FieldAccessOfReferencedType()
<<Initialize>>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)
Script`1.RunSubmissionsAsync(ScriptExecutionState executionState, ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, Func`2 catchExceptionOpt, CancellationToken cancellationToken)
RewriteTest.Invoke(String setup, String resultExpression) line 93
RewriteTest.StaticProperty() line 35
For static fields of non-generic types the serialized assembly does not cause an exception. I suspect that the field signature is not written correctly. I have not tested this for instance fields, but I'm quite sure the error is not limited to static fields.
Given
class A
{
volatile int _vfield;
}
C# will generate for a._vfield
the IL
IL_0008: volatile.
IL_000a: ldfld int32 modreq([netstandard]System.Runtime.CompilerServices.IsVolatile) VolatileTest.VolClass::_vfield
ILPack generates:
IL_0007: ldfld int32 [VolatileTest]VolatileTest.VolClass::_vfield
(You can explicitly add the volatile.
instruction -- doesn't matter.)
On load, this fails with a "Field not found" exception.
Any chance that ILPack could add support for saving a DynamicMethod
to disk in a dll form? This would help in analyzing bugs in lightweight code gen.
Hi,
Thanks for the amazing work,
It would be really helpful to have the latest changes including .NET Standard 2.0 as a NuGet package.
The failure of Assembly.GetReferencedAssemblies is hinted at here:
https://github.com/Lokad/ILPack/blob/master/src/Metadata/AssemblyMetadata.cs#L67-L74
In my case, the call to CreateType throws from
https://github.com/Lokad/ILPack/blob/master/src/AssemblyGenerator.Types.cs#L111-114 the error
Referenced assembly cannot be found: System.Linq.Expressions, [...]
In this case, the type is a class A derived from an abstract class B that implements System.Dynamic.IDynamicMetaObjectProvider, which is indeed in System.Linq.Expressions. Assembly.GetReferencedAssemblies does not see this reference, leading ultimately to an exception being thrown from AssemblyMetadata.GetReferencedAssemblyForType(Type type).
Current codebase does not support generic type definitions. We should add generic type definition support with generic parameter constraints. Also, some unit tests are needed.
AssemblyBuilder.Save was able to write version informations, such as the file version or the product version and so on. This is missing here.
Do you still work on ILPack? Do you accept pull requests?
Hi Vermorel,
I have to propose the following diff/patch. I noticed in TryGetRawMetadata there was a null check on 'pi', which has been removed lately (last few days), what causes problems: RuntimeAssembly.GetRawBytes is not present for me, at least not when working on netstandard2.0.
In order to avoid the null deref, we just can 'return false' or apply the proposed attached patch. Proposed only because I just guess that reading the assembly file is the same than what GetRawBytes would do.
Kindest Regards,
Sascha (mf-RDP).
0001-fixes-null-dereference-of-pi-resulting-in-unhandled-.zip
Attempting to serialize a class with a constructor that has the implementation attributes MethodImplAttributes.Runtime | MethodImplAttributes.Managed
results in the method in the assembly having a non-zero RVA. PEVerify indicates this error in the written assembly. An attempt to load the assembly and access the type throws an Exception
with message "Runtime-implemented method with-zero RVA."
Real example: the constructor on a subclass of MulticastDelegate
. Of possible interest: the Invoke
method also is "runtime managed" but PEVerify does not flag it.
To support both .NET Core and .NET Framework, we need to serialize either mscorlib
for .NET Framework and System.Runtime
for .NET Core as core library. Ideally this decision should be taken at runtime without compile-time directives.
The following program:
using System;
using System.Reflection;
using System.Reflection.Emit;
using Lokad.ILPack;
namespace ilpack_repro
{
sealed class Program
{
static void Main(string[] args)
{
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName { Name = "Jot" },
AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("JotModule");
var myType = moduleBuilder.DefineType(
"Test.Type",
TypeAttributes.Public | TypeAttributes.BeforeFieldInit,
typeof(object),
Array.Empty<Type>());
var originalNameField = myType.DefineField(
fieldName: "StringField",
type: typeof(string),
attributes: FieldAttributes.Public
| FieldAttributes.Static
| FieldAttributes.InitOnly);
var ctorBuilder = myType.DefineConstructor(
attributes: MethodAttributes.Public | MethodAttributes.Static,
callingConvention: CallingConventions.Standard,
parameterTypes: Array.Empty<Type>());
var il = ctorBuilder.GetILGenerator();
il.Emit(OpCodes.Ldstr, "foobar");
il.Emit(OpCodes.Stfld, originalNameField);
il.Emit(OpCodes.Ret);
var finalType = myType.CreateType();
new AssemblyGenerator().GenerateAssembly(moduleBuilder.Assembly, "C:\\LokadData\\test.dll");
}
}
}
generates an assembly where various disassembler (dotnetpeek & ilspy) choke on, there is likely something weird behind it.
Based on #84, it seems that we are not emitting the IL that encodes the overriding behavior of a method.
Inline signatures might contain tokens that need to be converted to tokens for the new assembly's metadata.
ILPack/src/IL/MethodBodyWriter.cs
Line 87 in 610c6ac
Hi,
I'm on .NET 5.0, creating some Instructions with EmitCalli, e.g.
il.EmitCalli(OpCodes.Calli, CallingConventions.Standard, null, new Type[1] { typeof(double) }, null);
All were void, no varargs, parameter types primitive + some class.
It seems something gets broken in the code stream.
I can Invoke the dynamically created methods but after writing + reading the serialized assembly I get:
Many Thanks
Sascha
Adding a reference to the rewritten dll from pull request #43 and calling the IntMethod() crashes with System.InvalidProgramException: Common Language Runtime detected an invalid program
.
var x = new MyClass();
x.IntMethod();
Looking at the ildasm listing shows the locals haven't been defined. Here's the original (note the .locals
line)
.method public hidebysig instance int32
IntMethod() cil managed
{
// Code size 7 (0x7)
.maxstack 1
.locals init (int32 V_0)
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // end of method MyClass::IntMethod
here's the rewritten version: (no .locals
)
.method public hidebysig instance int32
IntMethod() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // end of method MyClass::IntMethod
Also, .maxstack is different and wondering if this is just a default and if there might be issues if the function needs more than 8??
I just started using ILPack in a repository of mine, but as soon as I tried to generate an assembly, I started to get this error:
System.Collections.Generic.KeyNotFoundException: The given key 'System.Type GetType()' was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at Lokad.ILPack.IL.MethodBodyWriter.Write(BlobBuilder writer, IReadOnlyList`1 il, Func`2 getString, IReadOnlyDictionary`2 typeHandles, IReadOnlyDictionary`2 ctorRefHandles, IReadOnlyDictionary`2 fieldHandles, IReadOnlyDictionary`2 methodHandles)
at Lokad.ILPack.IL.MethodBodyStreamWriter.AddMethodBody(MethodBase methodBase)
at Lokad.ILPack.AssemblyGenerator.GetOrCreateMethod(MethodInfo methodInfo)
at Lokad.ILPack.AssemblyGenerator.CreateMethods(MethodInfo[] methods)
at Lokad.ILPack.AssemblyGenerator.GetOrCreateType(Type type)
at Lokad.ILPack.AssemblyGenerator.CreateTypes(Type[] types)
at Lokad.ILPack.AssemblyGenerator.CreateModules(Module[] moduleInfo)
at Lokad.ILPack.AssemblyGenerator.GenerateAssemblyBytes(Assembly assembly)
at Lokad.ILPack.AssemblyGenerator.GenerateAssembly(Assembly assembly, String path)
at Broccoli.CauliflowerInterpreter.<>c.<.cctor>b__48_19(Interpreter cauliflower, IValueExpressible[] args) in /home/nick/Desktop/broccoli/Broccoli/CauliflowerBuiltins.cs:line 1381
at Broccoli.ShortCircuitFunction.Invoke(Interpreter broccoli, IValueExpressible[] args) in /home/nick/Desktop/broccoli/Broccoli/Function.cs:line 101
at Broccoli.CauliflowerInterpreter.<>c.<.cctor>b__48_1(Interpreter cauliflower, IValueExpressible[] args) in /home/nick/Desktop/broccoli/Broccoli/CauliflowerBuiltins.cs:line 402
at Broccoli.ShortCircuitFunction.Invoke(Interpreter broccoli, IValueExpressible[] args) in /home/nick/Desktop/broccoli/Broccoli/Function.cs:line 101
at Broccoli.CauliflowerInterpreter.Run(IValueExpressible expr) in /home/nick/Desktop/broccoli/Broccoli/Cauliflower.cs:line 52
at Broccoli.CauliflowerInterpreter.Run(ParseNode node) in /home/nick/Desktop/broccoli/Broccoli/Cauliflower.cs:line 31
at Broccoli.Program.Main(String[] args) in /home/nick/Desktop/broccoli/Broccoli/Program.cs:line 80
After stepping through with a debugger and whatnot, I figured out that the error is happening when it tries to generate the body for this method, specifically the object#GetType()
call.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.