A game about forced loneliness, made by TACStudios
1using System;
2using System.IO;
3using System.Linq;
4using Mono.Cecil;
5using Mono.Cecil.Cil;
6using Mono.Cecil.Pdb;
7
8namespace zzzUnity.Burst.CodeGen
9{
10 /// <summary>
11 /// Provides an assembly resolver with deferred loading and a custom metadata resolver.
12 /// </summary>
13 /// <remarks>
14 /// This class is not thread safe. It needs to be protected outside.
15 /// </remarks>
16#if BURST_COMPILER_SHARED
17 public
18#else
19 internal
20#endif
21 class AssemblyResolver : BaseAssemblyResolver
22 {
23 private readonly ReadingMode _readingMode;
24
25 public AssemblyResolver(ReadingMode readingMode = ReadingMode.Deferred)
26 {
27 _readingMode = readingMode;
28
29 // We remove all setup by Cecil by default (it adds '.' and 'bin')
30 ClearSearchDirectories();
31
32 LoadDebugSymbols = false; // We don't bother loading the symbols by default now, since we use SRM to handle symbols in a more thread safe manner
33 // this is to maintain compatibility with the patch-assemblies path (see BclApp.cs), used by dots runtime
34 }
35
36 public bool LoadDebugSymbols { get; set; }
37
38 protected void ClearSearchDirectories()
39 {
40 foreach (var dir in GetSearchDirectories())
41 {
42 RemoveSearchDirectory(dir);
43 }
44 }
45
46 public AssemblyDefinition LoadFromFile(string path)
47 {
48 return AssemblyDefinition.ReadAssembly(path, CreateReaderParameters());
49 }
50
51 public AssemblyDefinition LoadFromStream(Stream peStream, Stream pdbStream = null, ISymbolReaderProvider customSymbolReader=null)
52 {
53 peStream.Position = 0;
54 if (pdbStream != null)
55 {
56 pdbStream.Position = 0;
57 }
58 var readerParameters = CreateReaderParameters();
59 if (customSymbolReader != null)
60 {
61 readerParameters.ReadSymbols = true;
62 readerParameters.SymbolReaderProvider = customSymbolReader;
63 }
64 try
65 {
66 readerParameters.SymbolStream = pdbStream;
67 return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
68 }
69 catch
70 {
71 readerParameters.ReadSymbols = false;
72 readerParameters.SymbolStream = null;
73 peStream.Position = 0;
74 if (pdbStream != null)
75 {
76 pdbStream.Position = 0;
77 }
78 return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
79 }
80 }
81
82 public override AssemblyDefinition Resolve(AssemblyNameReference name)
83 {
84 var readerParameters = CreateReaderParameters();
85 AssemblyDefinition assemblyDefinition;
86
87 try
88 {
89 assemblyDefinition = Resolve(name, readerParameters);
90 }
91 catch (Exception ex)
92 {
93 if (readerParameters.ReadSymbols == true)
94 {
95 // Attempt to load without symbols
96 readerParameters.ReadSymbols = false;
97 assemblyDefinition = Resolve(name, readerParameters);
98 }
99 else
100 {
101 throw new AssemblyResolutionException(
102 name,
103 new Exception($"Failed to resolve assembly '{name}' in directories: {string.Join(Environment.NewLine, GetSearchDirectories())}", ex));
104 }
105 }
106
107 return assemblyDefinition;
108 }
109
110 public bool TryResolve(AssemblyNameReference name, out AssemblyDefinition assembly)
111 {
112 try
113 {
114 assembly = Resolve(name);
115 return true;
116 }
117 catch (AssemblyResolutionException)
118 {
119 assembly = null;
120 return false;
121 }
122 }
123
124 public new void AddSearchDirectory(string directory)
125 {
126 if (!GetSearchDirectories().Contains(directory))
127 {
128 base.AddSearchDirectory(directory);
129 }
130 }
131
132 private ReaderParameters CreateReaderParameters()
133 {
134 var readerParams = new ReaderParameters
135 {
136 InMemory = true,
137 AssemblyResolver = this,
138 MetadataResolver = new CustomMetadataResolver(this),
139 ReadSymbols = LoadDebugSymbols // We no longer use cecil to read symbol information, prefering SRM thread safe methods, so I`m being explicit here in case the default changes
140 };
141
142 if (LoadDebugSymbols)
143 {
144 readerParams.SymbolReaderProvider = new CustomSymbolReaderProvider(null);
145 }
146
147 readerParams.ReadingMode = _readingMode;
148
149 return readerParams;
150 }
151
152 internal static string NormalizeFilePath(string path)
153 {
154 try
155 {
156 return Path.GetFullPath(new Uri(path).LocalPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
157 }
158 catch (Exception ex)
159 {
160 throw new Exception($"Could not normalize file path: {path}", ex);
161 }
162 }
163
164 private class CustomMetadataResolver : MetadataResolver
165 {
166 public CustomMetadataResolver(IAssemblyResolver assemblyResolver) : base(assemblyResolver)
167 {
168 }
169
170 public override MethodDefinition Resolve(MethodReference method)
171 {
172 if (method is MethodDefinition methodDef)
173 {
174 return methodDef;
175 }
176
177 if (method.GetElementMethod() is MethodDefinition methodDef2)
178 {
179 return methodDef2;
180 }
181
182 return base.Resolve(method);
183 }
184 }
185
186 /// <summary>
187 /// Custom implementation of <see cref="ISymbolReaderProvider"/> to:
188 /// - to load pdb/mdb through a MemoryStream to avoid locking the file on the disk
189 /// - catch any exceptions while loading the symbols and report them back
190 /// </summary>
191 private class CustomSymbolReaderProvider : ISymbolReaderProvider
192 {
193 private readonly Action<string, Exception> _logException;
194
195 public CustomSymbolReaderProvider(Action<string, Exception> logException)
196 {
197 _logException = logException;
198 }
199
200 public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName)
201 {
202 if (string.IsNullOrWhiteSpace(fileName)) return null;
203
204 string pdbFileName = fileName;
205 try
206 {
207 fileName = NormalizeFilePath(fileName);
208 pdbFileName = GetPdbFileName(fileName);
209
210 if (File.Exists(pdbFileName))
211 {
212 var pdbStream = ReadToMemoryStream(pdbFileName);
213 if (IsPortablePdb(pdbStream))
214 return new SafeDebugReaderProvider(new PortablePdbReaderProvider().GetSymbolReader(module, pdbStream));
215
216 return new SafeDebugReaderProvider(new NativePdbReaderProvider().GetSymbolReader(module, pdbStream));
217 }
218 }
219 catch (Exception ex) when (_logException != null)
220 {
221 _logException?.Invoke($"Unable to load symbol `{pdbFileName}`", ex);
222 return null;
223 }
224 return null;
225 }
226
227 private static MemoryStream ReadToMemoryStream(string filename)
228 {
229 return new MemoryStream(File.ReadAllBytes(filename));
230 }
231
232 public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream)
233 {
234 throw new NotSupportedException();
235 }
236
237 private static string GetPdbFileName(string assemblyFileName)
238 {
239 return Path.ChangeExtension(assemblyFileName, ".pdb");
240 }
241
242 private static bool IsPortablePdb(Stream stream)
243 {
244 if (stream.Length < 4L)
245 return false;
246 long position = stream.Position;
247 try
248 {
249 return (int)new BinaryReader(stream).ReadUInt32() == 1112167234;
250 }
251 finally
252 {
253 stream.Position = position;
254 }
255 }
256
257 /// <summary>
258 /// This class is a wrapper around <see cref="ISymbolReader"/> to protect
259 /// against failure while trying to read debug information in Mono.Cecil
260 /// </summary>
261 private class SafeDebugReaderProvider : ISymbolReader
262 {
263 private readonly ISymbolReader _reader;
264
265 public SafeDebugReaderProvider(ISymbolReader reader)
266 {
267 _reader = reader;
268 }
269
270
271 public void Dispose()
272 {
273 try
274 {
275 _reader.Dispose();
276 }
277 catch
278 {
279 // ignored
280 }
281 }
282
283 public ISymbolWriterProvider GetWriterProvider()
284 {
285 // We are not protecting here as we are not suppose to write to PDBs
286 return _reader.GetWriterProvider();
287 }
288
289 public bool ProcessDebugHeader(ImageDebugHeader header)
290 {
291 try
292 {
293 return _reader.ProcessDebugHeader(header);
294 }
295 catch
296 {
297 // ignored
298 }
299
300 return false;
301 }
302
303 public MethodDebugInformation Read(MethodDefinition method)
304 {
305 try
306 {
307 return _reader.Read(method);
308 }
309 catch
310 {
311 // ignored
312 }
313 return null;
314 }
315 }
316 }
317 }
318}