A game about forced loneliness, made by TACStudios
at master 318 lines 11 kB view raw
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}