This project is read-only.

PeWriter fails in certain cases

Topics: Metadata Model, PE Writer
Jun 17, 2009 at 7:45 PM

Hi,
I am trying to use the following metadata mutator over various assemblies:

internal class FailingMutator : MetadataMutator
{
	public FailingMutator(IMetadataHost host)
		: base(host)
	{
	}

	protected override void Visit(TypeDefinition typeDefinition)
	{
		if (TypeHelper.TypeVisibilityAsTypeMemberVisibility(typeDefinition) == TypeMemberVisibility.Assembly) {
			typeDefinition.Name = host.NameTable.GetNameFor("JusticeForAll");
		}

		base.Visit(typeDefinition);
	}
}

The mutator is added to PeToPe sample and PeToPe is run against Microsoft.Cci.PeReader.dll compiled binary (it fails against PdbWriter, PdbReader and many others). I am using the latest of CciMetadata taken from SVN, revision reads 22153.


The intent is to anonymize internal types by giving them the same name. It appears the PeWriter fails to do so if more than 1 internal type is found in mutated assembly. The error manifests itself in firing an exception:
System.Collections.Generic.KeyNotFoundException was unhandled
  Message="The given key was not present in the dictionary."
  Source="mscorlib"
  StackTrace:
       at System.ThrowHelper.ThrowKeyNotFoundException()
       at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
       at Microsoft.Cci.PeWriter.GetFieldDefIndex(IFieldDefinition field) in C:\Projects\ccimetadata\Sources\PeWriter\PeWriter.cs:line 842
       at Microsoft.Cci.PeWriter.GetFieldToken(IFieldReference fieldReference) in C:\Projects\ccimetadata\Sources\PeWriter\PeWriter.cs:line 877
       at Microsoft.Cci.PeWriter.SerializeMethodBodyIL(IMethodBody methodBody) in C:\Projects\ccimetadata\Sources\PeWriter\PeWriter.cs:line 3187
       at Microsoft.Cci.PeWriter.SerializeMethodBody(IMethodBody methodBody, BinaryWriter writer) in C:\Projects\ccimetadata\Sources\PeWriter\PeWriter.cs:line 2881
       at Microsoft.Cci.PeWriter.SerializeMethodBodies() in C:\Projects\ccimetadata\Sources\PeWriter\PeWriter.cs:line 2857
       at Microsoft.Cci.PeWriter.WritePeToStream(IModule module, IMetadataHost host, Stream stream, ISourceLocationProvider sourceLocationProvider, ILocalScopeProvider localScopeProvider, IPdbWriter pdbWriter) in C:\Projects\ccimetadata\Sources\PeWriter\PeWriter.cs:line 220
       at PeToPe.Program.Main(String[] args) in C:\Projects\ccimetadata\Samples\PeToPe\Program.cs:line 56
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
       
Is there something that can be done to resolve it?
Thanks.

Jun 18, 2009 at 7:23 AM

I'm afraid that CCI simply does not support this scenario as is, since type interning needs every type to have a unique assembly qualified name. One thing you can do is to actually modify the PeWriter class to do the renaming.

Jun 24, 2009 at 8:43 PM

OK, the situation with unique type names is understandable.


Now I am trying very basic scenario with renaming methods, the uniqueness of the method names is provided and renaming doesn’t affect those with special names, run-times and such:

  private IName GetChangedName(IName name)

 {

           return host.NameTable.GetNameFor(name.Value + "_");

  }

private bool ShouldChangeName(IMethodDefinition method)
{
	if (ShouldChangeName(method.Visibility) &&
		!(method.IsRuntimeSpecial || method.IsRuntimeInternal || method.IsRuntimeImplemented ||
		method.IsSpecialName || method.Name.Value.IndexOf('.') >= 0)) {
		return true;
	}

	return false;
}

public override MethodDefinition Visit(MethodDefinition methodDefinition)
{
	Trace.WriteLine("MethodDefinition Visit(" + methodDefinition.ToString() + ") <-");
	string methodName = methodDefinition.Name.Value;

	if (ShouldChangeName(methodDefinition as IMethodDefinition)) {
		IName name;
		uint internedKey = methodDefinition.InternedKey;

		if (methodToName.TryGetValue(internedKey, out name)) {
			methodDefinition.Name = name;
		} else {
			name = GetChangedName(methodDefinition.Name);
			Trace.WriteLine(string.Format("method    : {0} -> {1}", methodDefinition.Name.Value, name.Value));
			methodDefinition.Name = name;
			methodToName.Add(internedKey, name);
		}
	}

	Trace.WriteLine("MethodDefinition Visit(" + methodDefinition.ToString() + ") ->");
	return base.Visit(methodDefinition);
}

The renaming works as expected as long as the methods that get renamed are the methods of simple class type. As soon as the method of generics class is renamed and code has references to this methods the peverify run and app execution fails. peverify complains that in[IL] "token" cannot be resolved.

Here is a snippet of the assembly that gets updated by mutator above:

class Program
{
	static void Main(string[] args)
	{
		BadBoy<int> r = new BadBoy<int>();
	}
}

class BadBoy<T>
{
	public event EventHandler<EventArgs> Handler;

	public BadBoy()
	{
		Handler += new EventHandler<EventArgs>(BadBoy_Handler);
		Handler(this, EventArgs.Empty);
	}

	private void Test()
	{
		BadBoy_Handler(this, EventArgs.Empty);
	}

	private void BadBoy_Handler(object sender, EventArgs e)
	{
		Console.WriteLine(typeof(T).Name);
	}
}

The result of the renaming is changing the names of BadBoy_Handler to BadBoy_Handler_ and Main to Main_.

What would be your advise on this? My understanding was that the changing of the method name (as long as the name is unique and valid) shall not affect the IL code, which is not even being touched yet.

Thanks.

 

 

 

Jun 25, 2009 at 3:13 PM

I suspect that the problem you are running into is that you are not updating the references to the renamed methods. Since references are symbolic (except when the reference is the same object as the definition), they will be come dangling once you rename a method (or type).

In non-generic cases, for an assembly produced by the PE reader, all references to methods defined in the same assembly will actually be the method definition objects, which is why your technique works. For references to methods from generic types, however, the refererence is usually in the form of a "specialized" method definition. Specialized method definitions are not visited as method definitions, but as method references.

Jun 25, 2009 at 8:01 PM

Thanks,

It seems I am going in right direction. I, actually, was trying to override Visit for MethodReference(s) as well and surprised by the fact that for the (as it seemed) same method BadBoy_Handler I got 2 separate InternedKey during Visit(MethodReference) and Visit(MethodDefinition), which is a bit confusing.


What is the proper way to recognize between method references and method definitions referring to the same method so accurate renaming can be applied?

Aug 6, 2009 at 4:26 PM

If you want to know that something is a method defintion, check it type: "x is IMethodDefinition". If you want to know if a reference and a definition are equal, in the sense that the reference refers to the definition, check that both have the same intern key.