Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
/// <summary>
/// Module base implementation
/// </summary>
public abstract class Module : IModule, IExportSymbols, IDisposable
public abstract class Module : IModule, IModuleImageInfo, IExportSymbols, IDisposable
{
[Flags]
public enum Flags : byte
Expand All @@ -34,6 +34,7 @@ public enum Flags : byte
}

private Flags _flags;
private bool _isImageInfoAvailable;
private IEnumerable<PdbFileInfo> _pdbFileInfos;
private string _symbolFileName;

Expand All @@ -43,15 +44,17 @@ public enum Flags : byte
public Module(IServiceProvider services)
{
ServiceContainerFactory containerFactory = services.GetService<IServiceManager>().CreateServiceContainerFactory(ServiceScope.Module, services);
containerFactory.AddServiceFactory<PEFile>((services) => ModuleService.GetPEInfo(ImageBase, ImageSize, out _pdbFileInfos, ref _flags));
containerFactory.AddServiceFactory<PEFile>((services) => GetPEInfo());
_serviceContainer = containerFactory.Build();
_serviceContainer.AddService<IModule>(this);
_serviceContainer.AddService<IModuleImageInfo>(this);
_serviceContainer.AddService<IExportSymbols>(this);
}

public virtual void Dispose()
{
_serviceContainer.RemoveService(typeof(IModule));
_serviceContainer.RemoveService(typeof(IModuleImageInfo));
_serviceContainer.RemoveService(typeof(IExportSymbols));
_serviceContainer.DisposeServices();
}
Expand Down Expand Up @@ -212,6 +215,19 @@ BadImageFormatException or

#endregion

#region IModuleImageInfo

bool IModuleImageInfo.IsImageInfoAvailable
{
get
{
Services.GetService<PEFile>();
return _isImageInfoAvailable;
}
}

#endregion

#region IExportSymbols

bool IExportSymbols.TryGetSymbolAddress(string name, out ulong address)
Expand Down Expand Up @@ -301,6 +317,18 @@ protected bool InitializeValue(Flags flag)
return false;
}

private PEFile GetPEInfo()
{
PEFile peFile = ModuleService.GetPEInfo(ImageBase, ImageSize, out _pdbFileInfos, out bool imageInfoAvailable, ref _flags);
_isImageInfoAvailable = imageInfoAvailable &&
(peFile is not null || Target.OperatingSystem != OSPlatform.Windows);
if (!_isImageInfoAvailable && ImageSize > 0)
{
ModuleService.ReportImageInfoUnavailable(ModuleIndex, ImageBase);
}
return peFile;
}

protected abstract ModuleService ModuleService { get; }

public override bool Equals(object obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ internal enum ELFProgramHeaderAttributes : uint

// MachO writable segment attribute
private const uint VmProtWrite = 0x02;
private const ushort ImageDosSignature = 0x5A4D;

private IMemoryService _memoryService;
private ISymbolService _symbolService;
private ReadVirtualCache _versionCache;
private Dictionary<ulong, IModule> _modules;
private IModule[] _sortedByBaseAddress;
private bool _reportedImageInfoUnavailable;

private static readonly byte[] s_versionString = Encoding.ASCII.GetBytes("@(#)Version ");
private static readonly int s_versionLength = s_versionString.Length;
Expand All @@ -57,6 +59,7 @@ public ModuleService(IServiceProvider services)
private void Flush()
{
_versionCache?.Clear();
_reportedImageInfoUnavailable = false;
if (_modules is not null)
{
foreach (IModule module in _modules.Values)
Expand Down Expand Up @@ -240,21 +243,39 @@ private IModule[] GetSortedModules()
/// <param name="address">module base address</param>
/// <param name="size">module size</param>
/// <param name="pdbFileInfos">the pdb records or null</param>
/// <param name="imageInfoAvailable">whether the module image was read sufficiently to determine its type</param>
/// <param name="moduleFlags">module flags</param>
/// <returns>PEImage instance or null</returns>
internal PEFile GetPEInfo(ulong address, ulong size, out IEnumerable<PdbFileInfo> pdbFileInfos, ref Module.Flags moduleFlags)
internal PEFile GetPEInfo(
ulong address,
ulong size,
out IEnumerable<PdbFileInfo> pdbFileInfos,
out bool imageInfoAvailable,
ref Module.Flags moduleFlags)
{
PEFile peFile = null;
imageInfoAvailable = false;

// Start off with no pdb infos and as a native non-PE non-managed module
pdbFileInfos = Array.Empty<PdbFileInfo>();
moduleFlags &= ~(Module.Flags.IsPEImage | Module.Flags.IsManaged | Module.Flags.IsLoadedLayout | Module.Flags.IsFileLayout);

// None of the modules that lldb (on either Linux/MacOS) provides are PEs
if (size > 0 && Target.Host.HostType != HostType.Lldb)
if (Target.Host.HostType == HostType.Lldb)
{
imageInfoAvailable = true;
}
else if (size > 0)
{
// First try getting the PE info as loaded layout (native Windows DLLs and most managed PEs).
peFile = GetPEInfo(isVirtual: true, address, size, out List<PdbFileInfo> pdbs, out Module.Flags flags);
peFile = GetPEInfo(
isVirtual: true,
address,
size,
out List<PdbFileInfo> pdbs,
out bool loadedImageInfoAvailable,
out Module.Flags flags);
imageInfoAvailable = loadedImageInfoAvailable;

// Continue only if marked as a PE. This bit is set regardless of the layout if the module has a PE header/signature.
if ((flags & Module.Flags.IsPEImage) != 0)
Expand All @@ -264,7 +285,14 @@ internal PEFile GetPEInfo(ulong address, ulong size, out IEnumerable<PdbFileInfo
// If PE file is invalid or there are no PDB records, try getting the PE info as file layout. No PDB records can mean
// that either the layout is wrong or that there really no PDB records. If file layout doesn't have any pdb records
// either default to loaded layout PEFile.
PEFile peFileLayout = GetPEInfo(isVirtual: false, address, size, out List<PdbFileInfo> pdbsFileLayout, out Module.Flags flagsFileLayout);
PEFile peFileLayout = GetPEInfo(
isVirtual: false,
address,
size,
out List<PdbFileInfo> pdbsFileLayout,
out bool fileImageInfoAvailable,
out Module.Flags flagsFileLayout);
imageInfoAvailable |= fileImageInfoAvailable;
Debug.Assert((flagsFileLayout & Module.Flags.IsPEImage) != 0);
if (peFileLayout is not null && (peFile is null || pdbsFileLayout.Count > 0))
{
Expand All @@ -284,25 +312,51 @@ internal PEFile GetPEInfo(ulong address, ulong size, out IEnumerable<PdbFileInfo
return peFile;
}

internal void ReportImageInfoUnavailable(int moduleIndex, ulong imageBase)
{
if (!_reportedImageInfoUnavailable)
{
_reportedImageInfoUnavailable = true;
Trace.TraceWarning(
"Module image information is unavailable for module index {0} at {1:X16}; the image could not be read from target memory or acquired from configured symbol sources. Additional failures for this target are suppressed.",
moduleIndex,
imageBase);
}
}

/// <summary>
/// Returns information about the PE file for a specific layout.
/// </summary>
/// <param name="isVirtual">the memory layout of the module</param>
/// <param name="address">module base address</param>
/// <param name="size">module size</param>
/// <param name="pdbs">pdb infos</param>
/// <param name="imageInfoAvailable">whether the image was read sufficiently to determine its type</param>
/// <param name="flags">module flags</param>
/// <returns>PEFile instance or null</returns>
private PEFile GetPEInfo(bool isVirtual, ulong address, ulong size, out List<PdbFileInfo> pdbs, out Module.Flags flags)
private PEFile GetPEInfo(
bool isVirtual,
ulong address,
ulong size,
out List<PdbFileInfo> pdbs,
out bool imageInfoAvailable,
out Module.Flags flags)
{
pdbs = null;
PEFile peFile = null;
pdbs = new List<PdbFileInfo>();
imageInfoAvailable = false;
flags = 0;
try
{
Stream stream = MemoryService.CreateMemoryStream(address, size);
PEFile peFile = new(new StreamAddressSpace(stream), isVirtual);
peFile = new PEFile(new StreamAddressSpace(stream), isVirtual);
if (peFile.DosHeaderMagic != ImageDosSignature)
{
imageInfoAvailable = true;
}
if (peFile.IsValid())
{
imageInfoAvailable = true;
flags |= Module.Flags.IsPEImage;
flags |= peFile.IsILImage ? Module.Flags.IsManaged : Module.Flags.None;
pdbs = peFile.Pdbs.Select((pdb) => pdb.ToPdbFileInfo()).ToList();
Expand All @@ -314,6 +368,7 @@ private PEFile GetPEInfo(bool isVirtual, ulong address, ulong size, out List<Pdb
{
Trace.TraceError($"GetPEInfo: {address:X16} isVirtual {isVirtual} exception {ex.Message}");
}
peFile?.Dispose();
return null;
}

Expand Down
18 changes: 18 additions & 0 deletions src/Microsoft.Diagnostics.DebugServices/IModuleImageInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.DebugServices
{
/// <summary>
/// Provides the availability of image metadata for a module.
/// </summary>
public interface IModuleImageInfo
{
/// <summary>
/// Gets whether the image metadata needed to determine module characteristics is available.
/// When false, properties such as <see cref="IModule.IsManaged"/> and
/// <see cref="IModule.IsFileLayout"/> may contain fallback values.
/// </summary>
bool IsImageInfoAvailable { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,14 @@ private void DisplayModule(IModule module)
{
if (Verbose)
{
IModuleImageInfo imageInfo = module.Services.GetService<IModuleImageInfo>();
bool imageInfoAvailable = imageInfo?.IsImageInfoAvailable ?? true;
WriteLine("{0} {1}", module.ModuleIndex, module.FileName);
WriteLine(" Address: {0:X16}", module.ImageBase);
WriteLine(" ImageSize: {0:X8}", module.ImageSize);
WriteLine(" IsPEImage: {0}", module.IsPEImage);
WriteLine(" IsManaged: {0}", module.IsManaged);
WriteLine(" IsFileLayout: {0}", module.IsFileLayout?.ToString() ?? "<unknown>");
WriteLine(" IsManaged: {0}", imageInfoAvailable ? module.IsManaged.ToString() : "<unknown>");
WriteLine(" IsFileLayout: {0}", imageInfoAvailable ? module.IsFileLayout?.ToString() ?? "<unknown>" : "<unknown>");
WriteLine(" IndexFileSize: {0}", module.IndexFileSize?.ToString("X8") ?? "<none>");
WriteLine(" IndexTimeStamp: {0}", module.IndexTimeStamp?.ToString("X8") ?? "<none>");
WriteLine(" Version: {0}", module.GetVersionData()?.ToString() ?? "<none>");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,28 @@ private static ImmutableDictionary<string, Value> Build(XElement node)
/// <param name="values">test data for the item</param>
/// <param name="instance">object to compare</param>
public void CompareMembers(ImmutableDictionary<string, Value> values, object instance)
{
CompareMembers(values, instance, excludedMemberNames: null);
}

/// <summary>
/// Compares the test data values with the properties in the instance with the same name.
/// </summary>
/// <param name="values">test data for the item</param>
/// <param name="instance">object to compare</param>
/// <param name="excludedMemberNames">member names to exclude from comparison</param>
public void CompareMembers(
ImmutableDictionary<string, Value> values,
object instance,
ISet<string> excludedMemberNames)
{
foreach (KeyValuePair<string, Value> testData in values)
{
string testDataKey = testData.Key;
if (excludedMemberNames?.Contains(testDataKey) == true)
{
continue;
}
if (Version <= Version100 && testDataKey == "VersionData")
{
testDataKey = "GetVersionData";
Expand Down Expand Up @@ -245,7 +263,7 @@ public void CompareMembers(ImmutableDictionary<string, Value> values, object ins
if (testData.Value.IsSubValue)
{
Trace.TraceInformation($"CompareMembers {testDataKey} sub value:");
CompareMembers(testData.Value.Values.Single(), memberValue);
CompareMembers(testData.Value.Values.Single(), memberValue, excludedMemberNames);
}
else
{
Expand Down
13 changes: 10 additions & 3 deletions src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDump.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Diagnostics.DebugServices;
Expand Down Expand Up @@ -36,9 +37,15 @@ public TestDump(TestConfiguration config)
_dumpTargetFactory = new DumpTargetFactory(_host);
serviceContainer.AddService<IDumpTargetFactory>(_dumpTargetFactory);

// Automatically enable symbol server support
_symbolService.AddSymbolServer(timeoutInMinutes: 6, retryCount: 5);
_symbolService.AddCachePath(_symbolService.DefaultSymbolCache);
// Remote symbol acquisition is opt-in so dump tests do not depend on network or machine cache state.
if (string.Equals(
Environment.GetEnvironmentVariable("DOTNET_DIAGNOSTICS_TEST_ENABLE_SYMBOL_SERVER"),
"true",
StringComparison.OrdinalIgnoreCase))
{
_symbolService.AddSymbolServer(timeoutInMinutes: 6, retryCount: 5);
_symbolService.AddCachePath(_symbolService.DefaultSymbolCache);
}
}

public ServiceContainer ServiceContainer => _host.ServiceContainer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public class DebugServicesTests : IDisposable
private const string ListenerName = "DebugServicesTests";

private static readonly string[] s_excludedModules = new string[] { "MpClient.dll", "MpOAV.dll" };
private static readonly ISet<string> s_imageInfoMembers = new HashSet<string>(StringComparer.Ordinal)
{
nameof(IModule.IsManaged),
nameof(IModule.IsFileLayout)
};

private static IEnumerable<object[]> _configurations;

Expand Down Expand Up @@ -78,6 +83,7 @@ public void ModuleTests(TestHost host)
{
IModuleService moduleService = host.Target.Services.GetService<IModuleService>();
Assert.NotNull(moduleService);
int modulesWithAvailableImageInfo = 0;

foreach (ImmutableDictionary<string, TestDataReader.Value> moduleData in host.TestData.Modules)
{
Expand Down Expand Up @@ -122,10 +128,22 @@ public void ModuleTests(TestHost host)
}
}

IModuleImageInfo moduleImageInfo = module.Services.GetService<IModuleImageInfo>();
Assert.NotNull(moduleImageInfo);
ISet<string> excludedMemberNames = null;
if (moduleImageInfo.IsImageInfoAvailable)
{
modulesWithAvailableImageInfo++;
}
else
{
excludedMemberNames = s_imageInfoMembers;
}

if (host.Target.Host.HostType != HostType.Lldb)
{
// Check that the resulting module matches the test data
host.TestData.CompareMembers(moduleData, module);
host.TestData.CompareMembers(moduleData, module, excludedMemberNames);
}

IModule module1 = moduleService.GetModuleFromIndex(module.ModuleIndex);
Expand Down Expand Up @@ -167,7 +185,7 @@ public void ModuleTests(TestHost host)
if (mod.ImageBase == imageBase)
{
// Check that the resulting module matches the test data
host.TestData.CompareMembers(moduleData, mod);
host.TestData.CompareMembers(moduleData, mod, excludedMemberNames);
}
}
}
Expand Down Expand Up @@ -217,6 +235,10 @@ public void ModuleTests(TestHost host)
}
}
}
if (host.Target.Host.HostType != HostType.Lldb)
{
Assert.True(modulesWithAvailableImageInfo > 0, "No module image information was available.");
}
}

[SkippableTheory, MemberData(nameof(GetConfigurations))]
Expand Down
Loading