Is there a way to load debug symbols at runtime without any files #710

Closed
opened 2020-12-29 13:46:48 -06:00 by Serg046 · 5 comments
Serg046 commented 2020-12-29 13:46:48 -06:00 (Migrated from github.com)

This is what I am trying to do:

class Program
{
	static void Main(string[] args)
	{
		var asmDef = AssemblyDefinition.ReadAssembly(typeof(Test).Module.FullyQualifiedName,
			new ReaderParameters{ReadSymbols = true});
		asmDef.Name.Name += "Fake";
		var typeDef = asmDef.MainModule.GetType(typeof(Test).FullName, runtimeName: true).Resolve();
		var methodDef = typeDef.Methods.Single(m => m.Name == nameof(Test.Print));
		var consoleMethod = typeof(Console)
			.GetMethods()
			.Single(m => m.Name == nameof(Console.WriteLine) && m.GetParameters().Length == 1
			                                                 && m.GetParameters().Single().ParameterType ==
			                                                 typeof(string));
		methodDef.Body.Instructions.Insert(methodDef.Body.Instructions.Count - 1,
			Instruction.Create(OpCodes.Ldstr, "Injected"));
		methodDef.Body.Instructions.Insert(methodDef.Body.Instructions.Count - 1,
			Instruction.Create(OpCodes.Call, asmDef.MainModule.ImportReference(consoleMethod)));
		
		//This one works fine based on the pdb file
		var asm = WriteFileAssembly(asmDef);

		// But this one fails
		//var asm = WriteMemoryAssembly(asmDef);
		
		var type = asm.GetType(typeof(Test).FullName);
		type.InvokeMember(nameof(Test.Print), BindingFlags.InvokeMethod, null,
			Activator.CreateInstance(type), new object[0]);
	}

	private static Assembly WriteFileAssembly(AssemblyDefinition asmDef)
	{
		using var stream = File.Open("test.dll", FileMode.OpenOrCreate);
		asmDef.Write(stream, new WriterParameters {WriteSymbols = true});
		stream.Position = 0;
		// To avoid implicit load by the name
		File.Delete("renamed.pdb");
		File.Move("test.pdb", "renamed.pdb");
		// -----------------------------------
		var pdb = File.OpenRead("renamed.pdb");
		return AssemblyLoadContext.Default.LoadFromStream(stream, pdb);
	}

	private static Assembly WriteMemoryAssembly(AssemblyDefinition asmDef)
	{
		using var stream = new MemoryStream();
		using var symbolsStream = new MemoryStream();
		asmDef.Write(stream, new WriterParameters
		{
			SymbolStream = symbolsStream,
			SymbolWriterProvider = new SymbolsWriterProvider()
		});
		stream.Position = symbolsStream.Position = 0;
		return AssemblyLoadContext.Default.LoadFromStream(stream, symbolsStream);

		// This one doesn't work too with the same error
		// var pdb = File.OpenRead("renamed.pdb");
		// return AssemblyLoadContext.Default.LoadFromStream(stream, pdb);
	}

	private class SymbolsWriterProvider : ISymbolWriterProvider
	{
		public ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName)
			=> throw new NotSupportedException();

		public ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream)
			=> module.SymbolReader.GetWriterProvider().GetSymbolWriter(module, symbolStream);
	}
}

class Test
{
	public void Print()
	{
		Console.WriteLine("Test");
	}
}

It works fine when I generate a file but It doesn't when I try to load it in memory.
It fails with the following message: Module was built without symbols;
What is the right way to do it?

This is what I am trying to do: ```csharp class Program { static void Main(string[] args) { var asmDef = AssemblyDefinition.ReadAssembly(typeof(Test).Module.FullyQualifiedName, new ReaderParameters{ReadSymbols = true}); asmDef.Name.Name += "Fake"; var typeDef = asmDef.MainModule.GetType(typeof(Test).FullName, runtimeName: true).Resolve(); var methodDef = typeDef.Methods.Single(m => m.Name == nameof(Test.Print)); var consoleMethod = typeof(Console) .GetMethods() .Single(m => m.Name == nameof(Console.WriteLine) && m.GetParameters().Length == 1 && m.GetParameters().Single().ParameterType == typeof(string)); methodDef.Body.Instructions.Insert(methodDef.Body.Instructions.Count - 1, Instruction.Create(OpCodes.Ldstr, "Injected")); methodDef.Body.Instructions.Insert(methodDef.Body.Instructions.Count - 1, Instruction.Create(OpCodes.Call, asmDef.MainModule.ImportReference(consoleMethod))); //This one works fine based on the pdb file var asm = WriteFileAssembly(asmDef); // But this one fails //var asm = WriteMemoryAssembly(asmDef); var type = asm.GetType(typeof(Test).FullName); type.InvokeMember(nameof(Test.Print), BindingFlags.InvokeMethod, null, Activator.CreateInstance(type), new object[0]); } private static Assembly WriteFileAssembly(AssemblyDefinition asmDef) { using var stream = File.Open("test.dll", FileMode.OpenOrCreate); asmDef.Write(stream, new WriterParameters {WriteSymbols = true}); stream.Position = 0; // To avoid implicit load by the name File.Delete("renamed.pdb"); File.Move("test.pdb", "renamed.pdb"); // ----------------------------------- var pdb = File.OpenRead("renamed.pdb"); return AssemblyLoadContext.Default.LoadFromStream(stream, pdb); } private static Assembly WriteMemoryAssembly(AssemblyDefinition asmDef) { using var stream = new MemoryStream(); using var symbolsStream = new MemoryStream(); asmDef.Write(stream, new WriterParameters { SymbolStream = symbolsStream, SymbolWriterProvider = new SymbolsWriterProvider() }); stream.Position = symbolsStream.Position = 0; return AssemblyLoadContext.Default.LoadFromStream(stream, symbolsStream); // This one doesn't work too with the same error // var pdb = File.OpenRead("renamed.pdb"); // return AssemblyLoadContext.Default.LoadFromStream(stream, pdb); } private class SymbolsWriterProvider : ISymbolWriterProvider { public ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName) => throw new NotSupportedException(); public ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream) => module.SymbolReader.GetWriterProvider().GetSymbolWriter(module, symbolStream); } } class Test { public void Print() { Console.WriteLine("Test"); } } ``` It works fine when I generate a file but It doesn't when I try to load it in memory. It fails with the following message: `Module was built without symbols`; What is the right way to do it?
jbevain commented 2021-01-06 16:51:37 -06:00 (Migrated from github.com)

Hey! That's a great question. I'm not really sure to be honest. Maybe when writing to a symbol stream instead of a file Cecil doesn't write the expected information in the debug header (after all, there's no path to write), and then the loader is confused.

Hey! That's a great question. I'm not really sure to be honest. Maybe when writing to a symbol stream instead of a file Cecil doesn't write the expected information in the debug header (after all, there's no path to write), and then the loader is confused.
jbevain commented 2021-01-06 16:55:34 -06:00 (Migrated from github.com)

Maybe try to hardcode a file name in there:

https://github.com/jbevain/cecil/blob/master/Mono.Cecil.Cil/PortablePdb.cs#L304

Instead of writer.BaseStream.GetFileName (), as this will return an empty string on a non file based stream.

And see if then the symbols are loaded?

Maybe try to hardcode a file name in there: https://github.com/jbevain/cecil/blob/master/Mono.Cecil.Cil/PortablePdb.cs#L304 Instead of `writer.BaseStream.GetFileName ()`, as this will return an empty string on a non file based stream. And see if then the symbols are loaded?
jbevain commented 2021-03-15 18:39:21 -05:00 (Migrated from github.com)

@Serg046 Did you find something that worked in the end?

@Serg046 Did you find something that worked in the end?
Serg046 commented 2021-03-17 16:30:12 -05:00 (Migrated from github.com)

I postponed the task where I need this behaviour that's why I didn't answer anything that time. But actually your idea works fine, thank you! What do you think about this https://github.com/jbevain/cecil/pull/735?

I postponed the task where I need this behaviour that's why I didn't answer anything that time. But actually your idea works fine, thank you! What do you think about this https://github.com/jbevain/cecil/pull/735?
jbevain commented 2021-03-17 17:31:43 -05:00 (Migrated from github.com)

This is issue is related to #734 has well. Cecil needs to do a better job at handling filenames for non FileStream scenarios. But I like your fix in the meantime. Thanks!

This is issue is related to #734 has well. Cecil needs to do a better job at handling filenames for non `FileStream` scenarios. But I like your fix in the meantime. Thanks!
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-jbevain/cecil#710
No description provided.