Skip to content

Commit a872d20

Browse files
[Java.Interop.Tools.Cecil] fix symbol loading in DirectoryAssemblyResolver
Context: 7d42864d Context: dotnet/android#8571 While debugging dotnet/android#8571, I found the usage of `MemoryMappedFile` (from 7d42864) broke `.pdb` symbol loading. This is OK, because we'll likely disable symbol loading anyway, but we should at least address our bugs here for the future. In order for the following code to load symbols: AssemblyDefinition result = ModuleDefinition.ReadModule (viewStream, options).Assembly; You would need the following `ReaderParameters`: * `ReadSymbols=true` * `SymbolStream` containing a valid `Stream` to the `.pdb` file To make this work, I had to: * Create a `List<IDisposable>` for bookkeeping. * When successful, we transfer ownership of the `MemoryMappedFile` from the `List<IDisposable>` of `MemoryMappedViewStream` to the `viewStreams` collection. * When unsuccessful, we'd just dispose of the `MemoryMappedViewStream`. * If `ReadWrite=true`, we can just use `File.OpenRead()` for symbols, versus `MemoryMappedViewStream`. Other changes: * Added tests to verify we can load a `.dll` and its symbols with appropriate settings. * Stop looking for `.mdb` files. We no longer support these in .NET 6+. * Removed unnecessary `$""` string interpolation.
1 parent 07c7300 commit a872d20

File tree

2 files changed

+106
-17
lines changed

2 files changed

+106
-17
lines changed

src/Java.Interop.Tools.Cecil/Java.Interop.Tools.Cecil/DirectoryAssemblyResolver.cs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
using Java.Interop.Tools.Diagnostics;
4040

4141
using Mono.Cecil;
42+
using Mono.Cecil.Cil;
4243

4344
namespace Java.Interop.Tools.Cecil {
4445

@@ -149,61 +150,96 @@ public bool AddToCache (AssemblyDefinition assembly)
149150

150151
protected virtual AssemblyDefinition ReadAssembly (string file)
151152
{
152-
bool haveDebugSymbols = loadDebugSymbols &&
153-
(File.Exists (Path.ChangeExtension (file, ".pdb")) ||
154-
File.Exists (file + ".mdb"));
155153
var reader_parameters = new ReaderParameters () {
156154
ApplyWindowsRuntimeProjections = loadReaderParameters.ApplyWindowsRuntimeProjections,
157155
AssemblyResolver = this,
158156
MetadataImporterProvider = loadReaderParameters.MetadataImporterProvider,
159157
InMemory = loadReaderParameters.InMemory,
160158
MetadataResolver = loadReaderParameters.MetadataResolver,
161159
ReadingMode = loadReaderParameters.ReadingMode,
162-
ReadSymbols = haveDebugSymbols,
163160
ReadWrite = loadReaderParameters.ReadWrite,
164161
ReflectionImporterProvider = loadReaderParameters.ReflectionImporterProvider,
165162
SymbolReaderProvider = loadReaderParameters.SymbolReaderProvider,
166-
SymbolStream = loadReaderParameters.SymbolStream,
167163
};
168164
try {
169165
return LoadFromMemoryMappedFile (file, reader_parameters);
170166
} catch (Exception ex) {
171167
logger (
172168
TraceLevel.Verbose,
173169
$"Failed to read '{file}' with debugging symbols. Retrying to load it without it. Error details are logged below.");
174-
logger (TraceLevel.Verbose, $"{ex.ToString ()}");
170+
logger (TraceLevel.Verbose, ex.ToString ());
175171
reader_parameters.ReadSymbols = false;
176172
return LoadFromMemoryMappedFile (file, reader_parameters);
173+
} finally {
174+
reader_parameters.SymbolStream?.Dispose ();
177175
}
178176
}
179177

180178
AssemblyDefinition LoadFromMemoryMappedFile (string file, ReaderParameters options)
181179
{
182180
// We can't use MemoryMappedFile when ReadWrite is true
183181
if (options.ReadWrite) {
182+
if (loadDebugSymbols) {
183+
LoadSymbols (file, options, File.OpenRead);
184+
}
184185
return AssemblyDefinition.ReadAssembly (file, options);
185186
}
186187

187-
MemoryMappedViewStream? viewStream = null;
188+
// Likely 6 entries when symbols exist
189+
var disposables = new List<IDisposable> (capacity: 6);
188190
try {
189-
// Create stream because CreateFromFile(string, ...) uses FileShare.None which is too strict
190-
using var fileStream = new FileStream (file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
191-
using var mappedFile = MemoryMappedFile.CreateFromFile (
192-
fileStream, null, fileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
193-
viewStream = mappedFile.CreateViewStream (0, 0, MemoryMappedFileAccess.Read);
191+
if (loadDebugSymbols) {
192+
LoadSymbols (file, options, f => OpenMemoryMappedViewStream (f, disposables));
193+
}
194194

195+
var viewStream = OpenMemoryMappedViewStream (file, disposables);
195196
AssemblyDefinition result = ModuleDefinition.ReadModule (viewStream, options).Assembly;
196-
viewStreams.Add (viewStream);
197-
198-
// We transferred the ownership of the viewStream to the collection.
199-
viewStream = null;
200197

198+
// Transfer ownership to `viewStreams` collection
199+
viewStreams.Add (viewStream);
200+
disposables.Remove (viewStream);
201+
if (options.SymbolStream is MemoryMappedViewStream m) {
202+
viewStreams.Add (m);
203+
disposables.Remove (m);
204+
options.SymbolStream = null; // Prevents caller from disposing
205+
}
201206
return result;
202207
} finally {
203-
viewStream?.Dispose ();
208+
for (int i = disposables.Count - 1; i >= 0; i--) {
209+
disposables [i].Dispose ();
210+
}
204211
}
205212
}
206213

214+
static void LoadSymbols (string assemblyPath, ReaderParameters options, Func<string, Stream> getStream)
215+
{
216+
var symbolStream = options.SymbolStream;
217+
if (symbolStream == null) {
218+
var symbolPath = Path.ChangeExtension (assemblyPath, ".pdb");
219+
if (File.Exists (symbolPath)) {
220+
symbolStream = getStream (symbolPath);
221+
}
222+
}
223+
options.ReadSymbols = symbolStream != null;
224+
options.SymbolStream = symbolStream;
225+
}
226+
227+
static MemoryMappedViewStream OpenMemoryMappedViewStream (string file, List<IDisposable> disposables)
228+
{
229+
// Create stream because CreateFromFile(string, ...) uses FileShare.None which is too strict
230+
var fileStream = new FileStream (file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: false);
231+
disposables.Add (fileStream);
232+
233+
var mappedFile = MemoryMappedFile.CreateFromFile (
234+
fileStream, null, fileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, leaveOpen: true);
235+
disposables.Add (mappedFile);
236+
237+
var viewStream = mappedFile.CreateViewStream (0, 0, MemoryMappedFileAccess.Read);
238+
disposables.Add (viewStream);
239+
240+
return viewStream;
241+
}
242+
207243
public AssemblyDefinition GetAssembly (string fileName)
208244
{
209245
return Resolve (Path.GetFileNameWithoutExtension (fileName));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Diagnostics;
2+
using System.IO;
3+
using Java.Interop.Tools.Cecil;
4+
using Mono.Cecil;
5+
using NUnit.Framework;
6+
7+
namespace Java.Interop.Tools.JavaCallableWrappersTests
8+
{
9+
[TestFixture]
10+
public class DirectoryAssemblyResolverTests
11+
{
12+
static void Log (TraceLevel level, string message)
13+
{
14+
TestContext.Out.WriteLine ($"{level}: {message}");
15+
16+
if (level == TraceLevel.Error)
17+
Assert.Fail (message);
18+
}
19+
20+
static string assembly_path;
21+
static string symbol_path;
22+
23+
[OneTimeSetUp]
24+
public static void SetUp()
25+
{
26+
var assembly = typeof (DirectoryAssemblyResolverTests).Assembly;
27+
assembly_path = Path.Combine (Path.GetTempPath (), Path.GetFileName (assembly.Location));
28+
symbol_path = Path.ChangeExtension (assembly_path, ".pdb");
29+
30+
File.Copy (assembly.Location, assembly_path, overwrite: true);
31+
File.Copy (Path.ChangeExtension (assembly.Location, ".pdb"), symbol_path, overwrite: true);
32+
}
33+
34+
[OneTimeTearDown]
35+
public static void TearDown ()
36+
{
37+
File.Delete (assembly_path);
38+
File.Delete (symbol_path);
39+
}
40+
41+
[Test]
42+
public void LoadSymbols ([Values (true, false)] bool loadDebugSymbols, [Values (true, false)] bool readWrite)
43+
{
44+
using var resolver = new DirectoryAssemblyResolver (Log, loadDebugSymbols: loadDebugSymbols, new ReaderParameters {
45+
ReadWrite = readWrite
46+
});
47+
48+
var assembly = resolver.Load (assembly_path);
49+
Assert.IsNotNull (assembly);
50+
Assert.AreEqual (loadDebugSymbols, assembly.MainModule.HasSymbols);
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)