Skip to content

Commit 7f08b77

Browse files
[Java.Interop.Tools.Cecil] DirectoryAssemblyResolver symbol loads (#1176)
Context: 7d42864 Context: dotnet/android#8571 While debugging dotnet/android#8571, I found that 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: var options = new ReaderParameters { // … }; AssemblyDefinition result = ModuleDefinition.ReadModule(viewStream, options) .Assembly; You would need the following in `ReaderParameters options`: * `options.ReadSymbols=true` * `options.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 `options.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 7f08b77

File tree

2 files changed

+112
-17
lines changed

2 files changed

+112
-17
lines changed

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

Lines changed: 59 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,59 +150,100 @@ 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+
// We know the capacity for disposables
189+
var disposables = new List<IDisposable> (
190+
(1 + (loadDebugSymbols ? 1 : 0)) * OpenMemoryMappedViewStream_disposables_Add_calls);
188191
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);
192+
if (loadDebugSymbols) {
193+
LoadSymbols (file, options, f => OpenMemoryMappedViewStream (f, disposables));
194+
}
194195

196+
var viewStream = OpenMemoryMappedViewStream (file, disposables);
195197
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;
200198

199+
// Transfer ownership to `viewStreams` collection
200+
viewStreams.Add (viewStream);
201+
disposables.Remove (viewStream);
202+
if (options.SymbolStream is MemoryMappedViewStream m) {
203+
viewStreams.Add (m);
204+
disposables.Remove (m);
205+
options.SymbolStream = null; // Prevents caller from disposing
206+
}
201207
return result;
202208
} finally {
203-
viewStream?.Dispose ();
209+
for (int i = disposables.Count - 1; i >= 0; i--) {
210+
disposables [i].Dispose ();
211+
}
212+
}
213+
}
214+
215+
static void LoadSymbols (string assemblyPath, ReaderParameters options, Func<string, Stream> getStream)
216+
{
217+
var symbolStream = options.SymbolStream;
218+
if (symbolStream == null) {
219+
var symbolPath = Path.ChangeExtension (assemblyPath, ".pdb");
220+
if (File.Exists (symbolPath)) {
221+
symbolStream = getStream (symbolPath);
222+
}
204223
}
224+
options.ReadSymbols = symbolStream != null;
225+
options.SymbolStream = symbolStream;
226+
}
227+
228+
/// <summary>
229+
/// Number of times OpenMemoryMappedViewStream() calls disposables.Add()
230+
/// </summary>
231+
const int OpenMemoryMappedViewStream_disposables_Add_calls = 3;
232+
233+
static MemoryMappedViewStream OpenMemoryMappedViewStream (string file, List<IDisposable> disposables)
234+
{
235+
// Create stream because CreateFromFile(string, ...) uses FileShare.None which is too strict
236+
var fileStream = new FileStream (file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: false);
237+
disposables.Add (fileStream);
238+
239+
var mappedFile = MemoryMappedFile.CreateFromFile (
240+
fileStream, null, fileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, leaveOpen: true);
241+
disposables.Add (mappedFile);
242+
243+
var viewStream = mappedFile.CreateViewStream (0, 0, MemoryMappedFileAccess.Read);
244+
disposables.Add (viewStream);
245+
246+
return viewStream;
205247
}
206248

207249
public AssemblyDefinition GetAssembly (string 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)