Mono.Cecil AssemblyDefinition.ReadAssembly(): No symbol found for file - c#

I am using Mono.Cecil 0.10.3.0, the latest version from nuget.
This line:
var ad = AssemblyDefinition.ReadAssembly(#"C:\path\to\my\library.dll", new ReaderParameters { ReadSymbols = true });
throws exception:
Mono.Cecil.Cil.SymbolsNotFoundException was unhandled by user code
HResult=-2147024894
Message=No symbol found for file: C:\path\to\my\library.dll
Source=Mono.Cecil
StackTrace:
at Mono.Cecil.Cil.DefaultSymbolReaderProvider.GetSymbolReader(ModuleDefinition module, String fileName)
at Mono.Cecil.ModuleReader.ReadSymbols(ModuleDefinition module, ReaderParameters parameters)
at Mono.Cecil.ModuleReader.CreateModule(Image image, ReaderParameters parameters)
at Mono.Cecil.ModuleDefinition.ReadModule(String fileName, ReaderParameters parameters)
at Mono.Cecil.AssemblyDefinition.ReadAssembly(String fileName, ReaderParameters parameters)
What does this error mean? Is it not finding library.pdb? Because the .pdb file exists.
Here's another strange this about this issue. The ReadAssembly() call will work fine in one version of the code set, but then I will create a new branch of code in git, and try the same line in the new branch, and it will fail.

I am not sure this will help you, and I have never used this personally, but I looked through the code here and commented it for your amusement.
static void ReadSymbols(ModuleDefinition module, ReaderParameters parameters)
{
var symbol_reader_provider = parameters.SymbolReaderProvider;
if (symbol_reader_provider == null && parameters.ReadSymbols)
symbol_reader_provider = new DefaultSymbolReaderProvider(); // we get here
if (symbol_reader_provider != null)
{
module.SymbolReaderProvider = symbol_reader_provider;
var reader = parameters.SymbolStream != null
? symbol_reader_provider.GetSymbolReader(module, parameters.SymbolStream)
: symbol_reader_provider.GetSymbolReader(module, module.FileName); // we get here
...
}
...
}
Implementation of GetSymbolReader
public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName)
{
...
// this just changes the extension
var pdb_file_name = Mixin.GetPdbFileName(fileName);
// this should be true
if (File.Exists(pdb_file_name))
{
if (Mixin.IsPortablePdb(Mixin.GetPdbFileName(fileName)))
return new PortablePdbReaderProvider().GetSymbolReader(module, fileName);
try
{
return SymbolProvider.GetReaderProvider(SymbolKind.NativePdb).GetSymbolReader(module, fileName);
}
catch (Exception)
{
// We might not include support for native pdbs.
}
}
// can't find the pdb file, your error
if (throw_if_no_symbol)
throw new SymbolsNotFoundException(string.Format("No symbol found for file: {0}", fileName));
return null;
}
Definition of GetPdbFileName
public static string GetPdbFileName(string assemblyFileName)
{
return Path.ChangeExtension(assemblyFileName, ".pdb");
}
So as you can see, something is amiss here. All this code does is
Change the extension of your file name to look for the PBD,
Checks if it the PBD file exists,
If not, throws the exception you are getting
I think you need to double check everything, also note, the
// We might not include support for native pdbs.
It's the only other way this error can throw.

Related

Google.Protobuf.dll is right next to my .dll but it is not being found or loaded

I have a Framework 4.8 C# app that uses ClearScript to allow JavaScript to be used as an extension language. I am able to write plugins as DLLs and attach them at runtime, viz
JSE.Script.attach = (Func<string, bool>)Attach;
...
private static bool Attach(string dllPath, string name = "")
{
var status = false;
var htc = new HostTypeCollection();
try
{
var assem = Assembly.Load(AssemblyName.GetAssemblyName(dllPath));
htc.AddAssembly(assem);
if (name.Length == 0)
{
name = assem.FullName.Split(',')[0];
}
JSE.AddHostObject(name, htc); //FIXME checkout the hosttypes
Console.Error.WriteLine($"Attached {dllPath} as {name}");
status = true;
}
catch (ReflectionTypeLoadException rtle)
{
foreach (var item in rtle.LoaderExceptions)
{
Console.Error.WriteLine(item.Message);
T.Fail(item.Message);
}
}
catch (FileNotFoundException fnfe)
{
Console.Error.WriteLine(fnfe.Message);
T.Fail(fnfe.Message);
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message);
T.Fail(e.Message);
}
return status;
}
This permits my scripts to have lines like
attach(".\\Plugin_GoogleAds_Metrics.dll");
H = Plugin_GoogleAds_Metrics.GoogleAds_Metrics.Historical;
H.EnableTrace("GAM");
...
I've made a public repo of the plugin for those interested.
What's not working in this situation is that when I try to execute the plugin's GetAccountInformation method, and execution reaches the GoogleAdsServiceClient googleAdsService = client.GetService(Services.V11.GoogleAdsService); line, an error is thrown complaining about Google.Protobuf, viz
Exception has been thrown by the target of an invocation.
at JScript global code (Script [23] [temp]:5:0) -> acc = H.GetAccountInformation(auths.Item1, 7273576109, true)
at Microsoft.ClearScript.ScriptEngine.ThrowScriptError(IScriptEngineException scriptError)
at Microsoft.ClearScript.Windows.WindowsScriptEngine.ThrowScriptError(Exception exception)
at Microsoft.ClearScript.Windows.WindowsScriptEngine.<>c__DisplayClass57_0`1.<ScriptInvoke>b__0()
at Microsoft.ClearScript.ScriptEngine.ScriptInvokeInternal[T](Func`1 func)
at Microsoft.ClearScript.ScriptEngine.ScriptInvoke[T](Func`1 func)
at Microsoft.ClearScript.Windows.WindowsScriptEngine.ScriptInvoke[T](Func`1 func)
at Microsoft.ClearScript.Windows.WindowsScriptEngine.Execute(UniqueDocumentInfo documentInfo, String code, Boolean evaluate)
at Microsoft.ClearScript.Windows.JScriptEngine.Execute(UniqueDocumentInfo documentInfo, String code, Boolean evaluate)
at Microsoft.ClearScript.ScriptEngine.Evaluate(UniqueDocumentInfo documentInfo, String code, Boolean marshalResult)
at Microsoft.ClearScript.ScriptEngine.Evaluate(DocumentInfo documentInfo, String code)
at Microsoft.ClearScript.ScriptEngine.Evaluate(String documentName, Boolean discard, String code)
at Microsoft.ClearScript.ScriptEngine.Evaluate(String documentName, String code)
at Microsoft.ClearScript.ScriptEngine.Evaluate(String code)
at RulesetRunner.Program.Run(JScriptEngine& jSE, String scriptText, Config cfg, Dictionary`2 settings) in C:\Users\bugma\Source\Repos\Present\BORR\RulesetRunner\RunManagementPartials.cs:line 72
Exception has been thrown by the target of an invocation.
Exception has been thrown by the target of an invocation.
Could not load file or assembly 'Google.Protobuf, Version=3.15.8.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604' or one of its dependencies. The system cannot find the file specified.
So
I am using the latest Google.Ads.GoogleAds library
AutoGenerateBindingRedirects has been set to true in the csproj file
Add-BindingRedirect has been executed in the context of the Plugin's project
The Plugin_GoogleAds_Metrics.dll is in the same folder as the Google.Protobuf.dll
Where to from here?
What fixed this was including Google.Ads.GoogleAds in the calling app. I didn't have to explicitly mention the symbols in the main binary, just have the library in the build. What I expect this did was to include all the relevant DLLs next to the main EXE.
This is definitely not what I wanted. I wanted to be able to hive off all the DLLs into a separate plugin folder and only have them connected when I attached the plugin. Sadly, this does not seem to be achievable at this point. And now I'm wondering about the other plugins I've written that use Google technologies.

Assembly.Load and its weirdness... "Could not find file or assembly" error

Our localization team was trying to use LocBaml (.NET Framework version, 4.6.1) to localize some resources. They kept on getting errors saying "Could not find file or assembly..." So, I looked into it, saw the note that the x.resources.dll file had to be in same directory as x.dll. ("x" just means some name). Tried that, still go the same error. I then built a debug version and also downloaded the .NET source code. Turns out some exception was occuring in the guts of .NET. If I could summarize, it was failing when trying to do Assembly.Load("x"). So I wrote a program trying to duplicate the situation...
using System;
using System.Reflection;
using System.Linq;
namespace Nothing
{
class Loader
{
public static void Main(string[] args)
{
try
{
if (args.Count() == 1)
{
Assembly asm = Assembly.Load(args[0]);
Console.WriteLine($"Name of asembly: {asm.FullName}");
}
else
{
Console.WriteLine("Need to specify filename");
}
}
catch (Exception x)
{
Console.WriteLine("Exception: {0}", x);
}
}
}
}
The file was named AsmLoader.cs and compiled to AsmLoader.exe.
Well from the directory x.dll was in, I typed in \path\to\AsmLoader.exe x.dll and \path\to\AsmLoader.exe x. Same error, "Could not find file or assembly..."
Looked at the stack trace for the exception and saw that "codebase" was an argument for some function on the stack. Gave it a thought, and copied AsmLoader.exe to the same directory as x.dll.
Gave .\AsmLoader.exe x.dll a try..still same error. Remembered that the argument to the exception was just "x". Tried .\AsmLoader.exe x .... bingo... worked. For grins, copied LocBaml.exe and it's .config file to the same directory, and tried .\LocBaml x.resources.dll ... ding, ding, ding... success. Finally.
So, for now, I'll just tell the localiztion team to copy LocBaml to the same directory as the files and all should be good.
However, I can't help but feel this could somehow be solved with code. How can I make changes to the code in the example so that AsmLoader.exe doesn't have to be in the same directory as the DLL I want to load? I had even changed my path environment variable to ensure AsmLoader.exe and both x.dll directories were in the path. That didn't work...
So what do I need to change for it to work in my base program...and then maybe I can do the same for LocBaml...???
Well, I came up with a solution to add an AssemblyResolve event handler to the current app domain. Solved it for this simple example and my rebuilt LocBaml...
In main, add:
AppDomain.CurrentDomain.AssemblyResolve += LoadFromCurrentDirectory;
Implement LoadFromCurrentDirectly like:
static Assembly LoadFromCurrentDirectory(object sender, ResolveEventArgs args)
{
string name = args.Name;
bool bCheckVersion = false;
int idx = name.IndexOf(',');
if (idx != -1)
{
name = name.Substring(0, idx);
bCheckVersion = true;
}
string sCurrentDir = Directory.GetCurrentDirectory();
if (!name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && !name.EndsWith(".exe"))
{
string[] exts = { ".dll", ".exe" };
foreach( string ext in exts)
{
string tryPath = Path.Combine(sCurrentDir, name + ext);
if (File.Exists(tryPath))
{
name = name += ext;
break;
}
}
}
string path = Path.Combine(sCurrentDir, name);
if (!string.IsNullOrEmpty(path) && File.Exists(path))
{
Assembly assembly = Assembly.LoadFrom(path);
if (assembly != null & bCheckVersion)
{
if (assembly.FullName != args.Name)
return null;
}
return assembly;
}
else
{
var reqAsm = args.RequestingAssembly;
if (reqAsm != null)
{
string requestingName = reqAsm.GetName().FullName;
Console.WriteLine($"Could not resolve {name}, {path}, requested by {requestingName}");
}
else
{
Console.WriteLine($"Could not resolve {args.Name}, {path}");
}
}
return null;
}
I'm sure it could be optimized to add a global list of directories, or to use the path when searching for files to load. However, for our use case, works just fine. When I added it to our LocBaml code, it solved the loading problem for us...also got rid of necessity of copying the en\x.resources.dll files to our output directory. As long as we run the program from the output directory, LocBaml finishes parsing.

.NET: Custom assembly resolution failing when loading resources

I have a project which needs to load extra assemblies dynamically at runtime for reflection purposes. I need custom code because the path to the DLLs is not known by the framework. This code works fine with normal DLLs, they load fine and I can reflect over them. However, when I attempt to load types which statically uses embedded resources (i.e. a resx) this code fails.
Without my custom assembly resolution code this works fine. Here is my assembly resolution code:
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
bool isName;
string path = GetAssemblyLoadInfo(args, out isName);
return isName ? Assembly.Load(path) : Assembly.LoadFrom(path);
}
static string GetAssemblyLoadInfo(ResolveEventArgs args, out bool isAssemblyName)
{
isAssemblyName = false;
var assemblyName = new AssemblyName(args.Name);
string path = String.Concat(new FileInfo(DllPath).Directory, "\\", assemblyName.Name, ".dll");
if (File.Exists(path))
{
return path;
}
if (path.EndsWith(".resources.dll"))
{
path = path.Replace(".resources.dll", ".dll");
if (File.Exists(path)) return path;
}
var assemblyLocation = AssemblyLocations.FirstOrDefault(al => al.Name.FullName == assemblyName.FullName);
if (null == assemblyLocation)
{
isAssemblyName = true;
return args.Name;
}
else
{
return assemblyLocation.Location;
}
}
Here is a link to a project which recreates the entire issue:
https://drive.google.com/file/d/0B-mqMIMqm_XHcktyckVZbUNtZ28/view?usp=sharing
Once you download the project, you first need to build TestLibrary, and then run ConsoleApp4. It should work fine and write the string "This is the value of the resource" to the console, which comes from the resx file. However, uncomment line 23 in Program.cs and run it again and it will fail with an exception, which indicates that it failed to load the embedded resources.
The solution in this question solved my issue:
AppDomain.CurrentDomain.AssemblyResolve asking for a <AppName>.resources assembly?
Basically, add the following code to the assembly being loaded:
[assembly: NeutralResourcesLanguage("en-GB", UltimateResourceFallbackLocation.MainAssembly)]

NRefactory - missing file

I am still getting System.IO.FileNotFound Exception when I even try to use NRefactory.
I tried many ways to get it working:
- Used NuGet package, which istalled Mono.Cecil automatically
- downloaded NRefactory and Mono.Cecil, and compiled all DLL's from VS2010.
What am I doing wrong?
Here is error dump:
[System.IO.FileNotFoundException]
{"Could not load file or assembly 'ICSharpCode.NRefactory, Version=4.2.0.8783,
Culture=neutral, PublicKeyToken=efe927acf176eea2' or one of its dependencies.
The system cannot find the file specified.":"ICSharpCode.NRefactory,
Version=4.2.0.8783, Culture=neutral, PublicKeyToken=efe927acf176eea2"}
System.IO.FileNotFoundException
EDIT:
This is code which causes problems:
List<CodeElement> methodsInvokedByGivenMethod = GetInvokedMethods(methodName, fileName);
And this is a body of method:
private List<CodeElement> GetInvokedMethods(string methodName, string itemWhereMethodIs)
{
MessageBox.Show("I am here");
try
{
using (var fs = File.OpenRead(itemWhereMethodIs))
{
using (var parser = ParserFactory.CreateParser(SupportedLanguage.CSharp, new StreamReader(fs)))
{
parser.Parse();
}
}
}
catch (System.IO.FileNotFoundException fnf)
{
// This exception arises in Nrefactory...WTF? 0_0
SetToolStripText("There is exception :(");
return null;
}
}
What is more important, Exception is not being catched by try-catch block within this method, but occurs outside this method -> when it is called, but before any line of code within it.

ILSpy, how to resolve dependencies?

I want to disassemble an entire .NET assembly with ILSpy.
I used this code as base:
http://skysigal.xact-solutions.com/Blog/tabid/427/entryid/2488/Default.aspx
And it works fine, just when I have an assembly that references Npgsql.dll (or any other non-gac assembly), then I get an AssemblyResolutionException.
Failed to resolve assembly: 'Npgsql, Version=2.0.11.92, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7'
I know how I can get the referenced assemblies, but how can I add them to ast ?
// SqlWebAdmin.Models.Decompiler.DecompileAssembly("xy.dll");
public static string DecompileAssembly(string pathToAssembly)
{
//Assembly assembly = Assembly.LoadFrom(pathToAssembly);
System.Reflection.Assembly assembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(pathToAssembly);
//assembly.GetReferencedAssemblies();
//assembly.GetReferencedAssemblies(assembly);
Mono.Cecil.AssemblyDefinition assemblyDefinition =
Mono.Cecil.AssemblyDefinition.ReadAssembly(pathToAssembly);
ICSharpCode.Decompiler.Ast.AstBuilder astBuilder = new ICSharpCode.Decompiler.Ast.AstBuilder(new ICSharpCode.Decompiler.DecompilerContext(assemblyDefinition.MainModule));
astBuilder.AddAssembly(assemblyDefinition);
//new Helpers.RemoveCompilerAttribute().Run(decompiler.CompilationUnit);
using (System.IO.StringWriter output = new System.IO.StringWriter())
{
astBuilder.GenerateCode(new ICSharpCode.Decompiler.PlainTextOutput(output));
string result = output.ToString();
return result;
}
return "";
} // End Function DecompileAssembly
You need to tell Cecil, the underlying metadata reader that ILSpy is using, where your assemblies are. You can write:
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory("path/to/my/assemblies");
var parameters = new ReaderParameters
{
AssemblyResolver = resolver,
};
var assembly = AssemblyDefinition.ReadAssembly(pathToAssembly, parameters);
This is the most natural way to tell Cecil where to resolve referenced assemblies. This way you can remove the line where you load the assembly using System.Reflection, and only use the ILSpy stack.
This is improved #Nayan answer. If you want to ignore missing assemblies, copy this class:
using Mono.Cecil;
public class IgnoringExceptionsAssemblyResolver : DefaultAssemblyResolver
{
public override AssemblyDefinition Resolve(AssemblyNameReference name)
{
try
{
return base.Resolve(name);
}
catch
{
return null;
}
}
}
and use it like that:
var assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters() {
AssemblyResolver = new IgnoringExceptionsAssemblyResolver()
});
In addition to what JB Evain suggested, this code will help in avoiding the exception. All you have to do is handle the exception in resolver.
Not the best way, I admit. But it works for this scenario: "If I am decompiling a DLL on a system where the referred assemblies are not present, the decompilation fails (with exception.) At least, i would like to see the decompile code, for whatever has been resolved."
using System;
using System.Collections.Generic;
using Mono.Cecil;
public class MyAssemblyResolver : BaseAssemblyResolver
{
private readonly IDictionary<string, AssemblyDefinition> cache;
public MyAssemblyResolver()
{
this.cache = new Dictionary<string, AssemblyDefinition>(StringComparer.Ordinal);
}
public override AssemblyDefinition Resolve(AssemblyNameReference name)
{
if (name == null)
throw new ArgumentNullException("name");
AssemblyDefinition assemblyDefinition = null;
if (this.cache.TryGetValue(name.FullName, out assemblyDefinition))
return assemblyDefinition;
try //< -------- My addition to the code.
{
assemblyDefinition = base.Resolve(name);
this.cache[name.FullName] = assemblyDefinition;
}
catch { } //< -------- My addition to the code.
return assemblyDefinition;
}
protected void RegisterAssembly(AssemblyDefinition assembly)
{
if (assembly == null)
throw new ArgumentNullException("assembly");
string fullName = assembly.Name.FullName;
if (this.cache.ContainsKey(fullName))
return;
this.cache[fullName] = assembly;
}
}
And use it like this:
var rp = new Mono.Cecil.ReaderParameters() { AssemblyResolver = new MyAssemblyResolver() };
var assemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(assemblyStream, rp);
var astBuilder = new ICSharpCode.Decompiler.Ast.AstBuilder(
new ICSharpCode.Decompiler.DecompilerContext(assemblyDefinition.MainModule));
astBuilder.AddAssembly(assemblyDefinition);
I would actually like to see an enhancement in the decompiler: it currently ignores the ReaderParameters that user sets, in DefaultAssemblyResolver class.
Usage:
var rp = new Mono.Cecil.ReaderParameters();
var assemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(assemblyStream, rp);
Current DefaultAssemblyResolver code:
public override AssemblyDefinition Resolve(AssemblyNameReference name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
AssemblyDefinition assemblyDefinition;
if (this.cache.TryGetValue(name.FullName, out assemblyDefinition))
{
return assemblyDefinition;
}
assemblyDefinition = base.Resolve(name); // <---------
// Is the `ReaderParameters` object set by user, used to resolve in `base` class?
this.cache[name.FullName] = assemblyDefinition;
return assemblyDefinition;
}
Based on the Mono.Cecil source, I would guess that you could probably handle this using the Mono.Cecil.DefaultAssemblyResolver class.
Instead of this code:
Mono.Cecil.AssemblyDefinition assemblyDefinition =
Mono.Cecil.AssemblyDefinition.ReadAssembly(pathToAssembly);
try this:
Mono.Cecil.AssemblyDefinition assemblyDefinition =
new Mono.Cecil.DefaultAssemblyResolver().Resolve(System.Reflection.AssemblyName.GetAssemblyName(pathToAssembly).ToString());
EDIT
While my original suggestion may or may not work (I've never done it, so no guarantees), you may want to look into the Mono.Addins.CecilReflector.dll assembly from the Mono.Addins project to help mitigate these sort of problems. It is also based on Mono.Cecil (just as ILSpy is) so even though the general premise that Mono.Addins is an extensibility library doesn't meet your needs it may contain some code use for your purposes or at least learn from.

Categories

Resources