* Draft to implement support to symbolOptions from dap.
* removing debugger.launch
* Adding tests and fix compilation error
* Adding test case.
* Fix test cases, and implement support to PDBChecksum used on nuget.org to get symbols.
* merge
* Fixing tests.
* Tests are timing out.
* Apply suggestions from code review
Co-authored-by: Larry Ewing <lewing@microsoft.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
* adressing @radical comments.
* Addressing @radical comment.
* Addressing @radical comments.
* Apply suggestions from code review
Co-authored-by: Ankit Jain <radical@gmail.com>
* Addressing @radical comments.
* Addressing @radical comments
Changing when the symbols from symbol server is loaded because it takes a long time to load, as there are a lot of assemblies loaded not found on symbol servers.
* use MicrosoftCodeAnalysisCSharpVersion for scripting package.
* Adding more tests as asked by @radical
Removing timeout change as @radical has split it into 2 files
Fixing test behavior, when justMyCode is disabled but the symbols are not loaded from symbol server.
* [wasm] some cleanup
- Don't call `UpdateSymbolStore` from `DebugStore..ctor` because that
gets called multiple times in `LoadStore`, but only once instance gets
used.
- Use an isolated symbol cache path per test
* remove debug spew
* Addressing radical comment.
Co-authored-by: Larry Ewing <lewing@microsoft.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
<runtimewinx64MicrosoftNETCoreRuntimeJITToolsVersion>1.0.0-alpha.1.23066.1</runtimewinx64MicrosoftNETCoreRuntimeJITToolsVersion>
<runtimeosx110arm64MicrosoftNETCoreRuntimeJITToolsVersion>1.0.0-alpha.1.23066.1</runtimeosx110arm64MicrosoftNETCoreRuntimeJITToolsVersion>
<runtimeosx1012x64MicrosoftNETCoreRuntimeJITToolsVersion>1.0.0-alpha.1.23066.1</runtimeosx1012x64MicrosoftNETCoreRuntimeJITToolsVersion>
+
+ <!-- BrowserDebugProxy libs -->
+ <MicrosoftExtensionsLoggingVersion>3.1.7</MicrosoftExtensionsLoggingVersion>
+ <MicrosoftSymbolStoreVersion>1.0.406601</MicrosoftSymbolStoreVersion>
</PropertyGroup>
</Project>
if (minfo)
loc = mono_debug_method_lookup_location (minfo, sp->il_offset);
- if (!loc) {
- PRINT_DEBUG_MSG (1, "[%p] No line number info for il offset %x, continuing single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
+ if (!loc) { //we should not continue single stepping because the client side can have symbols loaded dynamically
+ PRINT_DEBUG_MSG (1, "[%p] No line number info for il offset %x, don't know if it's in the same line single stepping.\n", (gpointer) (gsize) mono_native_thread_id_get (), sp->il_offset);
req->last_method = method;
- hit = FALSE;
+ req->last_line = -1;
+ return hit;
} else if (loc && method == req->last_method && loc->row == req->last_line) {
int nframes;
rt_callbacks.ss_calculate_framecount (tls, ctx, FALSE, NULL, &nframes);
<_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Newtonsoft.Json.dll" />
<_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.Scripting.dll" />
<_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.Scripting.dll" />
+ <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.SymbolStore.dll" />
+ <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.FileFormats.dll" />
<PackageFile Include="@(_browserDebugHostFiles)" TargetPath="tools\$(NetCoreAppCurrent)\" />
</ItemGroup>
try
{
var loggerFactory = context.RequestServices.GetService<ILoggerFactory>();
- context.Request.Query.TryGetValue("urlSymbolServer", out StringValues urlSymbolServerList);
- var proxy = new DebuggerProxy(loggerFactory, urlSymbolServerList.ToList(), runtimeId, options: options);
+ var proxy = new DebuggerProxy(loggerFactory, runtimeId, options: options);
System.Net.WebSockets.WebSocket ideSocket = await context.WebSockets.AcceptWebSocketAsync();
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.7.0" />
- <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.7" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="$(MicrosoftCodeAnalysisCSharpVersion)" />
+ <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingVersion)" />
+ <PackageReference Include="Microsoft.SymbolStore" Version="$(MicrosoftSymbolStoreVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
- <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisCSharpVersion)" />
</ItemGroup>
<ItemGroup>
using System.Reflection;
using System.Diagnostics;
using System.Text;
+using Microsoft.SymbolStore;
+using Microsoft.SymbolStore.SymbolStores;
+using Microsoft.FileFormats.PE;
+using Microsoft.Extensions.Primitives;
using Microsoft.NET.WebAssembly.Webcil;
namespace Microsoft.WebAssembly.Diagnostics
internal sealed class MethodInfo
{
private MethodDefinition methodDef;
- internal SourceFile Source { get; }
+ internal SourceFile Source { get; set; }
public SourceId SourceId => Source.SourceId;
public string Name { get; }
public MethodDebugInformation DebugInformation;
public MethodDefinitionHandle methodDefHandle;
- private MetadataReader pdbMetadataReader;
- private bool hasDebugInformation;
+ internal MetadataReader pdbMetadataReader;
+ internal bool hasDebugInformation;
public SourceLocation StartLocation { get; set; }
public SourceLocation EndLocation { get; set; }
private ParameterInfo[] _parametersInfo;
public int KickOffMethod { get; }
internal bool IsCompilerGenerated { get; }
- private readonly AsyncScopeDebugInformation[] _asyncScopes;
+ private AsyncScopeDebugInformation[] _asyncScopes { get; set; }
public MethodInfo(AssemblyInfo assembly, string methodName, int methodToken, TypeInfo type, MethodAttributes attrs)
{
this.Assembly = assembly;
this.methodDef = asmMetadataReader.GetMethodDefinition(methodDefHandle);
this.Attributes = methodDef.Attributes;
- if (pdbMetadataReader != null && !methodDefHandle.ToDebugInformationHandle().IsNil)
- {
- this.DebugInformation = pdbMetadataReader.GetMethodDebugInformation(methodDefHandle.ToDebugInformationHandle());
- hasDebugInformation = true;
- }
this.Source = source;
this.Token = token;
this.methodDefHandle = methodDefHandle;
this.Name = assembly.EnCGetString(methodDef.Name);
this.pdbMetadataReader = pdbMetadataReader;
+ UpdatePdbInformation(methodDefHandle);
if (hasDebugInformation && !DebugInformation.GetStateMachineKickoffMethod().IsNil)
this.KickOffMethod = asmMetadataReader.GetRowNumber(DebugInformation.GetStateMachineKickoffMethod());
else
this.KickOffMethod = -1;
this.IsEnCMethod = false;
this.TypeInfo = type;
- if (HasSequencePoints && source != null)
+ DebuggerAttrInfo = new DebuggerAttributesInfo();
+ foreach (var cattr in methodDef.GetCustomAttributes())
{
- var sps = DebugInformation.GetSequencePoints();
- SequencePoint start = sps.First();
- SequencePoint end = sps.First();
- source.BreakableLines.Add(start.StartLine);
- foreach (SequencePoint sp in sps)
+ var ctorHandle = asmMetadataReader.GetCustomAttribute(cattr).Constructor;
+ if (ctorHandle.Kind == HandleKind.MemberReference)
{
- if (source.BreakableLines.Last<int>() != sp.StartLine)
- source.BreakableLines.Add(sp.StartLine);
-
- if (sp.IsHidden)
- continue;
-
- if (sp.StartLine < start.StartLine)
- start = sp;
- else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn)
- start = sp;
-
- if (end.EndLine == SequencePoint.HiddenLine)
- end = sp;
- if (sp.EndLine > end.EndLine)
- end = sp;
- else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn)
- end = sp;
- }
-
- StartLocation = new SourceLocation(this, start);
- EndLocation = new SourceLocation(this, end);
-
- DebuggerAttrInfo = new DebuggerAttributesInfo();
- foreach (var cattr in methodDef.GetCustomAttributes())
- {
- var ctorHandle = asmMetadataReader.GetCustomAttribute(cattr).Constructor;
- if (ctorHandle.Kind == HandleKind.MemberReference)
+ var container = asmMetadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent;
+ var name = assembly.EnCGetString(asmMetadataReader.GetTypeReference((TypeReferenceHandle)container).Name);
+ switch (name)
{
- var container = asmMetadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent;
- var name = assembly.EnCGetString(asmMetadataReader.GetTypeReference((TypeReferenceHandle)container).Name);
- switch (name)
- {
- case "DebuggerHiddenAttribute":
- DebuggerAttrInfo.HasDebuggerHidden = true;
- break;
- case "DebuggerStepThroughAttribute":
- DebuggerAttrInfo.HasStepThrough = true;
- break;
- case "DebuggerNonUserCodeAttribute":
- DebuggerAttrInfo.HasNonUserCode = true;
- break;
- case "DebuggerStepperBoundaryAttribute":
- DebuggerAttrInfo.HasStepperBoundary = true;
- break;
- case nameof(CompilerGeneratedAttribute):
- IsCompilerGenerated = true;
- break;
- }
-
- }
- }
- DebuggerAttrInfo.ClearInsignificantAttrFlags();
- }
- if (pdbMetadataReader != null)
- {
- localScopes = pdbMetadataReader.GetLocalScopes(methodDefHandle);
- byte[] scopeDebugInformation =
- (from cdiHandle in pdbMetadataReader.GetCustomDebugInformation(methodDefHandle)
- let cdi = pdbMetadataReader.GetCustomDebugInformation(cdiHandle)
- where pdbMetadataReader.GetGuid(cdi.Kind) == PortableCustomDebugInfoKinds.StateMachineHoistedLocalScopes
- select pdbMetadataReader.GetBlobBytes(cdi.Value)).FirstOrDefault();
-
- if (scopeDebugInformation != null)
- {
- _asyncScopes = new AsyncScopeDebugInformation[scopeDebugInformation.Length / 8];
- for (int i = 0; i < _asyncScopes.Length; i++)
- {
- int scopeOffset = BitConverter.ToInt32(scopeDebugInformation, i * 8);
- int scopeLen = BitConverter.ToInt32(scopeDebugInformation, (i * 8) + 4);
- _asyncScopes[i] = new AsyncScopeDebugInformation(scopeOffset, scopeOffset + scopeLen);
+ case "DebuggerHiddenAttribute":
+ DebuggerAttrInfo.HasDebuggerHidden = true;
+ break;
+ case "DebuggerStepThroughAttribute":
+ DebuggerAttrInfo.HasStepThrough = true;
+ break;
+ case "DebuggerNonUserCodeAttribute":
+ DebuggerAttrInfo.HasNonUserCode = true;
+ break;
+ case "DebuggerStepperBoundaryAttribute":
+ DebuggerAttrInfo.HasStepperBoundary = true;
+ break;
+ case nameof(CompilerGeneratedAttribute):
+ IsCompilerGenerated = true;
+ break;
}
}
-
- _asyncScopes ??= Array.Empty<AsyncScopeDebugInformation>();
}
+ if (!hasDebugInformation)
+ DebuggerAttrInfo.HasNonUserCode = true;
+ DebuggerAttrInfo.ClearInsignificantAttrFlags();
}
public bool ContainsAsyncScope(int oneBasedIdx, int offset)
return paramsInfo;
}
- public void UpdateEnC(MetadataReader pdbMetadataReaderParm, int methodIdx)
+ public void UpdatePdbInformation(MethodDefinitionHandle methodDefHandleParm)
{
- this.DebugInformation = pdbMetadataReaderParm.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(methodIdx));
- this.pdbMetadataReader = pdbMetadataReaderParm;
- this.IsEnCMethod = true;
- if (HasSequencePoints)
+ if (pdbMetadataReader == null || methodDefHandleParm.ToDebugInformationHandle().IsNil)
+ return;
+ DebugInformation = pdbMetadataReader.GetMethodDebugInformation(methodDefHandleParm.ToDebugInformationHandle());
+ if (Source == null && !DebugInformation.Document.IsNil)
+ {
+ var document = pdbMetadataReader.GetDocument(DebugInformation.Document);
+ var documentName = pdbMetadataReader.GetString(document.Name);
+ Source = Assembly.GetOrAddSourceFile(DebugInformation.Document, documentName);
+ Source.AddMethod(this);
+ }
+ hasDebugInformation = true;
+ if (HasSequencePoints && Source != null)
{
var sps = DebugInformation.GetSequencePoints();
SequencePoint start = sps.First();
SequencePoint end = sps.First();
+ Source.BreakableLines.Add(start.StartLine);
foreach (SequencePoint sp in sps)
{
+ if (Source.BreakableLines.Last<int>() != sp.StartLine)
+ Source.BreakableLines.Add(sp.StartLine);
+
+ if (sp.IsHidden)
+ continue;
+
if (sp.StartLine < start.StartLine)
start = sp;
else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn)
start = sp;
+ if (end.EndLine == SequencePoint.HiddenLine)
+ end = sp;
if (sp.EndLine > end.EndLine)
end = sp;
else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn)
StartLocation = new SourceLocation(this, start);
EndLocation = new SourceLocation(this, end);
}
- localScopes = pdbMetadataReader.GetLocalScopes(MetadataTokens.MethodDefinitionHandle(methodIdx));
+ localScopes = pdbMetadataReader.GetLocalScopes(methodDefHandleParm);
+
+ byte[] scopeDebugInformation =
+ (from cdiHandle in pdbMetadataReader.GetCustomDebugInformation(methodDefHandleParm)
+ let cdi = pdbMetadataReader.GetCustomDebugInformation(cdiHandle)
+ where pdbMetadataReader.GetGuid(cdi.Kind) == PortableCustomDebugInfoKinds.StateMachineHoistedLocalScopes
+ select pdbMetadataReader.GetBlobBytes(cdi.Value)).FirstOrDefault();
+
+ if (scopeDebugInformation != null)
+ {
+ _asyncScopes = new AsyncScopeDebugInformation[scopeDebugInformation.Length / 8];
+ for (int i = 0; i < _asyncScopes.Length; i++)
+ {
+ int scopeOffset = BitConverter.ToInt32(scopeDebugInformation, i * 8);
+ int scopeLen = BitConverter.ToInt32(scopeDebugInformation, (i * 8) + 4);
+ _asyncScopes[i] = new AsyncScopeDebugInformation(scopeOffset, scopeOffset + scopeLen);
+ }
+ }
+
+ _asyncScopes ??= Array.Empty<AsyncScopeDebugInformation>();
+ }
+
+ public void UpdateEnC(MetadataReader pdbMetadataReaderParm, int methodIdx)
+ {
+ this.DebugInformation = pdbMetadataReaderParm.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(methodIdx));
+ this.pdbMetadataReader = pdbMetadataReaderParm;
+ this.IsEnCMethod = true;
+ UpdatePdbInformation(MetadataTokens.MethodDefinitionHandle(methodIdx));
}
public SourceLocation GetLocationByIl(int pos)
private readonly IDisposable peReaderOrWebcilReader;
internal MetadataReader asmMetadataReader { get; }
internal MetadataReader pdbMetadataReader { get; set; }
+
internal List<Tuple<MetadataReader, MetadataReader>> enCMetadataReader = new List<Tuple<MetadataReader, MetadataReader>>();
private int debugId;
internal int PdbAge { get; }
internal System.Guid PdbGuid { get; }
+ internal bool IsPortableCodeView { get; }
internal string PdbName { get; }
internal bool CodeViewInformationAvailable { get; }
public bool TriedToLoadSymbolsOnDemand { get; set; }
private readonly Dictionary<int, SourceFile> _documentIdToSourceFileTable = new Dictionary<int, SourceFile>();
+ public PdbChecksum[] PdbChecksums { get; }
public static AssemblyInfo FromBytes(MonoProxy monoProxy, SessionId sessionId, byte[] assembly, byte[] pdb, ILogger logger, CancellationToken token)
{
{
var entries = peReader.ReadDebugDirectory();
CodeViewDebugDirectoryData? codeViewData = null;
- if (entries.Length > 0)
+ var isPortableCodeView = false;
+ List<PdbChecksum> pdbChecksums = new();
+ foreach (var entry in peReader.ReadDebugDirectory())
{
- var codeView = entries[0];
- if (codeView.Type == DebugDirectoryEntryType.CodeView)
+ if (entry.Type == DebugDirectoryEntryType.CodeView)
+ {
+ codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
+ if (entry.IsPortableCodeView)
+ isPortableCodeView = true;
+ }
+ if (entry.Type == DebugDirectoryEntryType.PdbChecksum)
{
- codeViewData = peReader.ReadCodeViewDebugDirectoryData(codeView);
+ var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
+ pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray()));
}
}
var asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader);
}
}
- var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, codeViewData, pdbMetadataReader, logger);
+ var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger);
return assemblyInfo;
}
{
var entries = wcReader.ReadDebugDirectory();
CodeViewDebugDirectoryData? codeViewData = null;
- if (entries.Length > 0)
+ var isPortableCodeView = false;
+ List<PdbChecksum> pdbChecksums = new();
+ foreach (var entry in entries)
{
var codeView = entries[0];
if (codeView.Type == DebugDirectoryEntryType.CodeView)
{
codeViewData = wcReader.ReadCodeViewDebugDirectoryData(codeView);
+ if (codeView.IsPortableCodeView)
+ isPortableCodeView = true;
}
}
var asmMetadataReader = wcReader.GetMetadataReader();
}
}
- var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, codeViewData, pdbMetadataReader, logger);
+ var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger);
return assemblyInfo;
}
return asmDef.GetAssemblyName().Name + ".dll";
}
- private unsafe AssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, CodeViewDebugDirectoryData? codeViewData, MetadataReader pdbMetadataReader, ILogger logger)
+ private unsafe AssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, CodeViewDebugDirectoryData? codeViewData, PdbChecksum[] pdbChecksums, bool isPortableCodeView, MetadataReader pdbMetadataReader, ILogger logger)
: this(logger)
{
peReaderOrWebcilReader = owningReader;
PdbName = codeViewData.Value.Path;
CodeViewInformationAvailable = true;
}
+ IsPortableCodeView = isPortableCodeView;
+ PdbChecksums = pdbChecksums;
this.asmMetadataReader = asmMetadataReader;
Name = name;
logger.LogTrace($"Info: loading AssemblyInfo with name {Name}");
}
}
}
- private SourceFile GetOrAddSourceFile(DocumentHandle doc, string documentName)
+ public SourceFile GetOrAddSourceFile(DocumentHandle doc, string documentName)
{
if (_documentIdToSourceFileTable.TryGetValue(documentName.GetHashCode(), out SourceFile source))
return source;
- var src = new SourceFile(this, _documentIdToSourceFileTable.Count, doc, GetSourceLinkUrl(documentName), documentName);
+ var src = new SourceFile(this, _documentIdToSourceFileTable.Count, doc, documentName, sourceLinkMappings);
_documentIdToSourceFileTable[documentName.GetHashCode()] = src;
return src;
}
}
}
- private Uri GetSourceLinkUrl(string document)
- {
- if (sourceLinkMappings.TryGetValue(document, out string url))
- return new Uri(url);
-
- foreach (KeyValuePair<string, string> sourceLinkDocument in sourceLinkMappings)
- {
- string key = sourceLinkDocument.Key;
-
- if (!key.EndsWith("*"))
- {
- continue;
- }
-
- string keyTrim = key.TrimEnd('*');
-
- if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase))
- {
- string docUrlPart = document.Replace(keyTrim, "");
- return new Uri(sourceLinkDocument.Value.TrimEnd('*') + docUrlPart);
- }
- }
-
- return null;
- }
-
public TypeInfo CreateTypeInfo(TypeDefinitionHandle typeHandle, TypeDefinition type)
{
var typeInfo = new TypeInfo(this, typeHandle, type, asmMetadataReader, logger);
return res;
}
- internal void UpdatePdbInformation(Stream streamToReadFrom)
+ internal async Task LoadPDBFromSymbolServer(DebugStore debugStore, CancellationToken token)
{
- var pdbStream = new MemoryStream();
- streamToReadFrom.CopyTo(pdbStream);
- pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
+ try
+ {
+ if (TriedToLoadSymbolsOnDemand)
+ return;
+ var pdbName = Path.GetFileName(PdbName);
+ var pdbGuid = PdbGuid.ToString("N").ToUpperInvariant() + (IsPortableCodeView ? "FFFFFFFF" : PdbAge);
+ var key = $"{pdbName}/{pdbGuid}/{pdbName}";
+ SymbolStoreFile file = await debugStore.symbolStore.GetFile(new SymbolStoreKey(key, PdbName, false, PdbChecksums), token);
+ TriedToLoadSymbolsOnDemand = true;
+ if (file == null)
+ return;
+ var pdbStream = new MemoryStream();
+ file.Stream.Position = 0;
+ await file.Stream.CopyToAsync(pdbStream, token);
+ pdbStream.Position = 0;
+ pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
+ if (pdbMetadataReader == null)
+ return;
+ ProcessSourceLink();
+ foreach (var method in this.Methods)
+ {
+ method.Value.pdbMetadataReader = pdbMetadataReader;
+ method.Value.UpdatePdbInformation(method.Value.methodDefHandle);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"Failed to load symbols from symbol server. ({ex.Message})");
+ }
}
- }
+
+}
internal sealed class SourceFile
{
private static readonly Regex regexForEscapeFileName = new(@"([:/])", RegexOptions.Compiled);
public string DotNetUrlEscaped { get; init; }
public Uri Url { get; init; }
- public Uri SourceLinkUri { get; init; }
+ public Uri SourceLinkUri { get; set; }
public int Id { get; }
public string AssemblyName => assembly.Name;
public SourceId SourceId => new SourceId(assembly.Id, this.Id);
public IEnumerable<MethodInfo> Methods => this.methods.Values;
+ private static SHA256 _sha256 = System.Security.Cryptography.SHA256.Create();
+ private string _relativePath;
- internal SourceFile(AssemblyInfo assembly, int id, DocumentHandle docHandle, Uri sourceLinkUri, string documentName)
+ internal SourceFile(AssemblyInfo assembly, int id, DocumentHandle docHandle, string documentName, Dictionary<string, string> sourceLinkMappings)
{
this.methods = new Dictionary<int, MethodInfo>();
- this.SourceLinkUri = sourceLinkUri;
+ GetSourceLinkUrl(documentName, sourceLinkMappings);
this.assembly = assembly;
this.Id = id;
this.doc = assembly.pdbMetadataReader.GetDocument(docHandle);
string escapedDocumentName = EscapePathForUri(documentName.Replace("\\", "/"));
this.FileUriEscaped = $"file://{(OperatingSystem.IsWindows() ? "/" : "")}{escapedDocumentName}";
this.DotNetUrlEscaped = $"dotnet://{assembly.Name}/{escapedDocumentName}";
- this.Url = new Uri(File.Exists(documentName) ? FileUriEscaped : DotNetUrlEscaped, UriKind.Absolute);
+ if (!File.Exists(documentName) && SourceLinkUri != null)
+ {
+ string sourceLinkCachedPathPartial = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SourceServer", GetHashOfString(SourceLinkUri.AbsoluteUri));
+ string sourceLinkCachedPath = Path.Combine(sourceLinkCachedPathPartial, _relativePath);
+ if (File.Exists(sourceLinkCachedPath)) //first try to find on cache using relativePath as it's done by VS while debugging
+ {
+ this.FilePath = sourceLinkCachedPath;
+ escapedDocumentName = EscapePathForUri(this.FilePath.Replace("\\", "/"));
+ }
+ else
+ {
+ sourceLinkCachedPath = Path.Combine(sourceLinkCachedPathPartial, Path.GetFileName(_relativePath));
+ if (File.Exists(sourceLinkCachedPath)) //second try to find on cache without relativePath as it's done by VS when using "Go To Definition (F12)"
+ {
+ this.FilePath = sourceLinkCachedPath;
+ escapedDocumentName = EscapePathForUri(this.FilePath.Replace("\\", "/"));
+ }
+ }
+ this.FileUriEscaped = $"file://{(OperatingSystem.IsWindows() ? "/" : "")}{escapedDocumentName}";
+ }
+ this.Url = new Uri(File.Exists(this.FilePath) ? FileUriEscaped : DotNetUrlEscaped, UriKind.Absolute);
+ }
+
+ private void GetSourceLinkUrl(string document, Dictionary<string, string> sourceLinkMappings)
+ {
+ if (sourceLinkMappings.TryGetValue(document, out string url))
+ {
+ SourceLinkUri = new Uri(url);
+ return;
+ }
+
+ foreach (KeyValuePair<string, string> sourceLinkDocument in sourceLinkMappings)
+ {
+ string key = sourceLinkDocument.Key;
+
+ if (!key.EndsWith("*", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ string keyTrim = key.TrimEnd('*');
+
+ if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase))
+ {
+ _relativePath = document.Replace(keyTrim, "");
+ SourceLinkUri = new Uri(sourceLinkDocument.Value.TrimEnd('*') + _relativePath);
+ return;
+ }
+ }
+ }
+
+ private static string GetHashOfString(string str)
+ {
+ byte[] bytes = _sha256.ComputeHash(UnicodeEncoding.Unicode.GetBytes(str));
+ StringBuilder builder = new StringBuilder(bytes.Length*2);
+ foreach (byte b in bytes)
+ {
+ builder.Append(b.ToString("x2"));
+ }
+ return builder.ToString();
}
private static string EscapePathForUri(string path)
{
internal List<AssemblyInfo> assemblies = new List<AssemblyInfo>();
private readonly ILogger logger;
- private readonly MonoProxy monoProxy;
+ internal readonly MonoProxy monoProxy;
+ private readonly ITracer _tracer;
+ internal Microsoft.SymbolStore.SymbolStores.SymbolStore symbolStore;
+ // The constructor can get invoked multiple times, but only *one* of
+ // the instances will be used.
+ // So, keep this light, and repeatable
public DebugStore(MonoProxy monoProxy, ILogger logger)
{
this.logger = logger;
this.monoProxy = monoProxy;
+ this._tracer = new Tracer(logger);
}
private sealed class DebugItem
}
public string ToUrl(SourceLocation location) => location != null ? GetFileById(location.Id).Url.OriginalString : "";
+
+ internal async Task ReloadAllPDBsFromSymbolServersAndSendSources(MonoProxy monoProxy, SessionId id, ExecutionContext context, CancellationToken token)
+ {
+ if (symbolStore == null)
+ return;
+ monoProxy.SendLog(id, "Loading symbols from symbol servers.", token);
+ foreach (var asm in assemblies.Where(asm => asm.pdbMetadataReader == null))
+ {
+ asm.TriedToLoadSymbolsOnDemand = false; //force to load again because added another symbol server
+ await asm.LoadPDBFromSymbolServer(this, token);
+ foreach (var source in asm.Sources)
+ await monoProxy.OnSourceFileAdded(id, source, context, token);
+ }
+ monoProxy.SendLog(id, "Symbols from symbol servers completely loaded.", token);
+ }
+
+ internal void UpdateSymbolStore(List<string> urlSymbolServerList, string cachePathSymbolServer)
+ {
+ symbolStore = null;
+ foreach (var urlServer in urlSymbolServerList)
+ {
+ if (string.IsNullOrEmpty(urlServer))
+ continue;
+ try
+ {
+ symbolStore = new HttpSymbolStore(_tracer, symbolStore, new Uri($"{urlServer}/"), null);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"Failed to create HttpSymbolStore for this URL - {urlServer} - {ex.Message}");
+ }
+ }
+ if (!string.IsNullOrEmpty(cachePathSymbolServer))
+ {
+ try
+ {
+ symbolStore = new CacheSymbolStore(_tracer, symbolStore, cachePathSymbolServer);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"Failed to create CacheSymbolStore for this path - {cachePathSymbolServer} - {ex.Message}");
+ }
+ }
+ }
+ public sealed class Tracer : ITracer
+ {
+ private readonly ILogger _logger;
+
+ public Tracer(ILogger logger)
+ {
+ this._logger = logger;
+ }
+
+ public void WriteLine(string message) => _logger.LogTrace(message);
+
+ public void WriteLine(string format, params object[] arguments) => _logger.LogTrace(format, arguments);
+
+ public void Information(string message) => _logger.LogTrace(message);
+
+ public void Information(string format, params object[] arguments) => _logger.LogTrace(format, arguments);
+
+ public void Warning(string message) => _logger.LogTrace(message);
+
+ public void Warning(string format, params object[] arguments) => _logger.LogTrace(format, arguments);
+
+ public void Error(string message) => _logger.LogTrace(message);
+
+ public void Error(string format, params object[] arguments) => _logger.LogTrace(format, arguments);
+
+ public void Verbose(string message) => _logger.LogTrace(message);
+
+ public void Verbose(string format, params object[] arguments) => _logger.LogTrace(format, arguments);
+ }
}
}
{
internal MonoProxy MonoProxy { get; }
- public DebuggerProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList, int runtimeId = 0, string loggerId = "", ProxyOptions options = null)
+ public DebuggerProxy(ILoggerFactory loggerFactory, int runtimeId = 0, string loggerId = "", ProxyOptions options = null)
{
string suffix = loggerId.Length > 0 ? $"-{loggerId}" : string.Empty;
- MonoProxy = new MonoProxy(loggerFactory.CreateLogger($"DevToolsProxy{suffix}"), urlSymbolServerList, runtimeId, loggerId, options);
+ MonoProxy = new MonoProxy(loggerFactory.CreateLogger($"DevToolsProxy{suffix}"), runtimeId, loggerId, options);
}
public Task Run(Uri browserUri, WebSocket ideSocket, CancellationTokenSource cts)
if (visitCount == 0)
{
if (node is MemberAccessExpressionSyntax maes
- && node.Kind() == SyntaxKind.SimpleMemberAccessExpression
+ && node.IsKind(SyntaxKind.SimpleMemberAccessExpression)
&& !(node.Parent is MemberAccessExpressionSyntax)
&& !(node.Parent is InvocationExpressionSyntax)
&& !(node.Parent is ElementAccessExpressionSyntax))
// this fails with `"a)"`
// because the code becomes: return (a));
// and the returned expression from GetExpressionFromSyntaxTree is `a`!
- if (expressionTree.Kind() == SyntaxKind.IdentifierName || expressionTree.Kind() == SyntaxKind.ThisExpression)
+ if (expressionTree.IsKind(SyntaxKind.IdentifierName) || expressionTree.IsKind(SyntaxKind.ThisExpression))
{
string varName = expressionTree.ToString();
JObject value = await resolver.Resolve(varName, token);
syntaxTree = replacer.ReplaceVars(syntaxTree, memberAccessValues, identifierValues, null, null);
// eg. "this.dateTime", " dateTime.TimeOfDay"
- if (expressionTree.Kind() == SyntaxKind.SimpleMemberAccessExpression && replacer.memberAccesses.Count == 1)
+ if (expressionTree.IsKind(SyntaxKind.SimpleMemberAccessExpression) && replacer.memberAccesses.Count == 1)
{
return memberAccessValues[0];
}
internal sealed class FirefoxMonoProxy : MonoProxy
{
- public FirefoxMonoProxy(ILogger logger, string loggerId = null, ProxyOptions options = null) : base(logger, null, loggerId: loggerId, options: options)
+ public FirefoxMonoProxy(ILogger logger, string loggerId = null, ProxyOptions options = null) : base(logger, loggerId: loggerId, options: options)
{
}
{
internal class MonoProxy : DevToolsProxy
{
- private IList<string> urlSymbolServerList;
+ internal List<string> UrlSymbolServerList { get; private set; }
+ internal string CachePathSymbolServer { get; private set; }
private HashSet<SessionId> sessions = new HashSet<SessionId>();
private static readonly string[] s_executionContextIndependentCDPCommandNames = { "DotnetDebugger.setDebuggerProperty", "DotnetDebugger.runTests" };
protected Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext>();
protected readonly ProxyOptions _options;
- public MonoProxy(ILogger logger, IList<string> urlSymbolServerList, int runtimeId = 0, string loggerId = "", ProxyOptions options = null) : base(logger, loggerId)
+ public MonoProxy(ILogger logger, int runtimeId = 0, string loggerId = "", ProxyOptions options = null) : base(logger, loggerId)
{
- this.urlSymbolServerList = urlSymbolServerList ?? new List<string>();
+ UrlSymbolServerList = new List<string>();
RuntimeId = runtimeId;
_options = options;
_defaultPauseOnExceptions = PauseOnExceptionsKind.Unset;
{
type,
args = new JArray(JObject.FromObject(new
- {
- type = "string",
- value = message,
- })),
+ {
+ type = "string",
+ value = message,
+ })),
executionContextId = context.Id
});
SendEvent(sessionId, "Runtime.consoleAPICalled", o, token);
bool? is_default = aux_data["isDefault"]?.Value<bool>();
if (is_default == true)
{
- await OnDefaultContext(sessionId, new ExecutionContext(new MonoSDBHelper (this, logger, sessionId), id, aux_data, _defaultPauseOnExceptions), token);
+ await OnDefaultContext(sessionId, new ExecutionContext(new MonoSDBHelper(this, logger, sessionId), id, aux_data, _defaultPauseOnExceptions), token);
}
}
return true;
{
await RuntimeReady(sessionId, token);
await SendResume(sessionId, token);
+ if (!JustMyCode)
+ await ReloadSymbolsFromSymbolServer(sessionId, GetContext(sessionId), token);
return true;
}
case "mono_wasm_fire_debugger_agent_message":
if (!contexts.TryGetValue(id, out ExecutionContext context) && !s_executionContextIndependentCDPCommandNames.Contains(method))
{
- if (method == "Debugger.setPauseOnExceptions")
+ if (method == "Debugger.setPauseOnExceptions")
{
string state = args["state"].Value<string>();
var pauseOnException = GetPauseOnExceptionsStatusFromString(state);
switch (property.Key)
{
case "JustMyCodeStepping":
- SetJustMyCode(id, (bool) property.Value, token);
- break;
+ await SetJustMyCode(id, (bool)property.Value, context, token);
+ break;
default:
logger.LogDebug($"DotnetDebugger.setDebuggerProperty failed for {property.Key} with value {property.Value}");
- break;
+ break;
}
}
return true;
SendResponse(id, Result.Err("ApplyUpdate failed."), token);
return true;
}
- case "DotnetDebugger.addSymbolServerUrl":
+ case "DotnetDebugger.setSymbolOptions":
{
- string url = args["url"]?.Value<string>();
- if (!string.IsNullOrEmpty(url) && !urlSymbolServerList.Contains(url))
- urlSymbolServerList.Add(url);
+ SendResponse(id, Result.OkFromObject(new { }), token);
+ CachePathSymbolServer = args["symbolOptions"]?["cachePath"]?.Value<string>();
+ var urls = args["symbolOptions"]?["searchPaths"]?.Value<JArray>();
+ if (urls == null)
+ return true;
+ UrlSymbolServerList.Clear();
+ UrlSymbolServerList.AddRange(urls.Values<string>());
+ if (!JustMyCode)
+ {
+ if (!await IsRuntimeAlreadyReadyAlready(id, token))
+ return true;
+ return await ReloadSymbolsFromSymbolServer(id, context, token);
+ }
return true;
}
case "DotnetDebugger.getMethodLocation":
return method.StartsWith("DotnetDebugger.", StringComparison.OrdinalIgnoreCase);
}
+ private async Task<bool> ReloadSymbolsFromSymbolServer(SessionId id, ExecutionContext context, CancellationToken token)
+ {
+ DebugStore store = await LoadStore(id, true, token);
+ store.UpdateSymbolStore(UrlSymbolServerList, CachePathSymbolServer);
+ await store.ReloadAllPDBsFromSymbolServersAndSendSources(this, id, context, token);
+ return true;
+ }
+
private async Task<bool> ApplyUpdates(MessageId id, JObject args, CancellationToken token)
{
var context = GetContext(id);
return applyUpdates;
}
- private void SetJustMyCode(MessageId id, bool isEnabled, CancellationToken token)
+ private async Task SetJustMyCode(MessageId id, bool isEnabled, ExecutionContext context, CancellationToken token)
{
+ if (JustMyCode != isEnabled && isEnabled == false)
+ {
+ JustMyCode = isEnabled;
+ if (await IsRuntimeAlreadyReadyAlready(id, token))
+ await ReloadSymbolsFromSymbolServer(id, context, token);
+ }
JustMyCode = isEnabled;
SendResponse(id, Result.OkFromObject(new { justMyCodeEnabled = JustMyCode }), token);
}
return true;
}
- protected virtual async Task<bool> ShouldSkipMethod(SessionId sessionId, ExecutionContext context, EventKind event_kind, int j, MethodInfoWithDebugInformation method, CancellationToken token)
+ protected virtual async Task<bool> ShouldSkipMethod(SessionId sessionId, ExecutionContext context, EventKind event_kind, int frameNumber, MethodInfoWithDebugInformation method, CancellationToken token)
{
var shouldReturn = await SkipMethod(
isSkippable: context.IsSkippingHiddenMethod,
if (shouldReturn)
return true;
- if (j == 0 && method?.Info.DebuggerAttrInfo.DoAttributesAffectCallStack(JustMyCode) == true)
+ if (frameNumber != 0)
+ return false;
+
+ if (method?.Info?.DebuggerAttrInfo?.DoAttributesAffectCallStack(JustMyCode) == true)
{
if (method.Info.DebuggerAttrInfo.ShouldStepOut(event_kind))
{
context.IsResumedAfterBp = true;
}
}
+ else
+ {
+ if (!JustMyCode && method?.Info?.DebuggerAttrInfo?.HasNonUserCode == true && !method.Info.hasDebugInformation)
+ {
+ if (event_kind == EventKind.Step)
+ context.IsSkippingHiddenMethod = true;
+ if (await SkipMethod(isSkippable: true, shouldBeSkipped: true, StepKind.Out))
+ return true;
+ }
+ }
return false;
async Task<bool> SkipMethod(bool isSkippable, bool shouldBeSkipped, StepKind stepKind)
{
return false;
}
- internal async Task<MethodInfo> LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token)
- {
- ExecutionContext context = GetContext(sessionId);
- if (urlSymbolServerList.Count == 0)
- return null;
- if (asm.TriedToLoadSymbolsOnDemand || !asm.CodeViewInformationAvailable)
- return null;
- asm.TriedToLoadSymbolsOnDemand = true;
- var pdbName = Path.GetFileName(asm.PdbName);
-
- foreach (string urlSymbolServer in urlSymbolServerList)
- {
- string downloadURL = $"{urlSymbolServer}/{pdbName}/{asm.PdbGuid.ToString("N").ToUpperInvariant() + asm.PdbAge}/{pdbName}";
-
- try
- {
- using HttpResponseMessage response = await HttpClient.GetAsync(downloadURL, token);
- if (!response.IsSuccessStatusCode)
- {
- Log("info", $"Unable to download symbols on demand url:{downloadURL} assembly: {asm.Name}");
- continue;
- }
-
- using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync(token);
- asm.UpdatePdbInformation(streamToReadFrom);
- foreach (SourceFile source in asm.Sources)
- {
- var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData));
- await SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token);
- }
- return asm.GetMethodByToken(method_token);
- }
- catch (Exception e)
- {
- Log("info", $"Unable to load symbols on demand exception: {e} url:{downloadURL} assembly: {asm.Name}");
- }
- break;
- }
-
- Log("info", $"Unable to load symbols on demand assembly: {asm.Name}");
- return null;
- }
-
protected void OnDefaultContextUpdate(SessionId sessionId, ExecutionContext context)
{
if (UpdateContext(sessionId, context, out ExecutionContext previousContext))
{
var comparer = new SourceLocation.LocationComparer();
// if column is specified the frontend wants the exact matches
- // and will clear the bp if it isn't close enoug
+ // and will clear the bp if it isn't close enough
var bpLocations = store.FindBreakpointLocations(req, ifNoneFoundThenFindNext);
IEnumerable<IGrouping<SourceId, SourceLocation>> locations = bpLocations.Distinct(comparer)
.OrderBy(l => l.Column)
var method = asm.GetMethodByToken(methodToken);
- if (method == null && !asm.HasSymbols)
- {
- try
- {
- method = await proxy.LoadSymbolsOnDemand(asm, methodToken, sessionId, token);
- }
- catch (Exception e)
- {
- logger.LogDebug($"Unable to find method token: {methodToken} assembly name: {asm.Name} exception: {e}");
- return null;
- }
- }
-
string methodName = await GetMethodName(methodId, token);
//get information from runtime
method ??= await CreateMethodInfoFromRuntimeInformation(asm, methodId, methodName, methodToken, token);
_logger.LogInformation($"{messagePrefix} launching proxy for {con_str}");
- _debuggerProxy = new DebuggerProxy(loggerFactory, null, loggerId: Id);
+ _debuggerProxy = new DebuggerProxy(loggerFactory, loggerId: Id);
TestHarnessProxy.RegisterNewProxy(Id, _debuggerProxy);
var browserUri = new Uri(con_str);
WebSocket? ideSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
private const int DefaultTestTimeoutMs = 1 * 60 * 1000;
protected TimeSpan TestTimeout = TimeSpan.FromMilliseconds(DefaultTestTimeoutMs);
protected ITestOutputHelper _testOutput;
+ protected readonly TestEnvironment _env;
static string s_debuggerTestAppPath;
static int s_idCounter = -1;
}
}
+ public static string TempPath => Path.Combine(Path.GetTempPath(), "dbg-tests-tmp");
+ static DebuggerTestBase()
+ {
+ if (Directory.Exists(TempPath))
+ Directory.Delete(TempPath, recursive: true);
+ }
+
public DebuggerTestBase(ITestOutputHelper testOutput, string driver = "debugger-driver.html")
{
+ _env = new TestEnvironment(testOutput);
_testOutput = testOutput;
Id = Interlocked.Increment(ref s_idCounter);
// the debugger is working in locale of the debugged application. For example Datetime.ToString()
await insp.OpenSessionAsync(fn, TestTimeout);
}
- public virtual async Task DisposeAsync() => await insp.ShutdownAsync().ConfigureAwait(false);
+ public virtual async Task DisposeAsync()
+ {
+ await insp.ShutdownAsync().ConfigureAwait(false);
+ _env.Dispose();
+ }
public Task Ready() => startTask;
Assert.Equal(res.Value["justMyCodeEnabled"], enabled);
}
+
+ internal async Task SetSymbolOptions(JObject param)
+ {
+ var res = await cli.SendCommand("DotnetDebugger.setSymbolOptions", param, token);
+ Assert.True(res.IsOk);
+ }
+
internal async Task CheckEvaluateFail(string id, params (string expression, string message)[] args)
{
foreach (var arg in args)
{
public static readonly string? DebuggerTestPath = Environment.GetEnvironmentVariable("DEBUGGER_TEST_PATH");
public static readonly string? TestLogPath = Environment.GetEnvironmentVariable("TEST_LOG_PATH");
+ public static readonly bool SkipCleanup = Environment.GetEnvironmentVariable("SKIP_CLEANUP") == "1" ||
+ Environment.GetEnvironmentVariable("SKIP_CLEANUP") == "true";
}
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
"dotnet://debugger-test.dll/debugger-async-test.cs", line_pause, column_pause,
$"DebuggerTests.AsyncTests.ContinueWithTests.{method_name}");
}
-
+
[ConditionalTheory(nameof(RunningOnChrome))]
[InlineData(112, 16, 114, 16, "HiddenLinesInAnAsyncBlock")]
[InlineData(130, 16, 133, 16, "HiddenLinesJustBeforeANestedAsyncBlock")]
step_into2["callFrames"][0]["location"]["lineNumber"].Value<int>()
);
}
+
+ [ConditionalTheory(nameof(RunningOnChrome))]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task SteppingIntoLibrarySymbolsLoadedFromSymbolServer(bool justMyCode)
+ {
+ string cachePath = _env.CreateTempDirectory("symbols-cache");
+ _testOutput.WriteLine($"** Using cache path: {cachePath}");
+ var searchPaths = new JArray
+ {
+ "https://symbols.nuget.org/download/symbols"
+ };
+ var waitForScript = WaitForScriptParsedEventsAsync(new string [] { "JArray.cs" });
+ var symbolOptions = JObject.FromObject(new { symbolOptions = JObject.FromObject(new { cachePath, searchPaths })});
+ await SetJustMyCode(justMyCode);
+ await SetSymbolOptions(symbolOptions);
+
+ await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] TestLoadSymbols:Run'); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 1572, 8,
+ "TestLoadSymbols.Run"
+ );
+ if (!justMyCode)
+ await waitForScript;
+
+ await StepAndCheck(StepKind.Into, justMyCode ? "dotnet://debugger-test.dll/debugger-test.cs" : "dotnet://Newtonsoft.Json.dll/JArray.cs", justMyCode ? 1575 : 350, justMyCode ? 8 : 12, justMyCode ? "TestLoadSymbols.Run" : "Newtonsoft.Json.Linq.JArray.Add",
+ locals_fn: async (locals) =>
+ {
+ if (!justMyCode)
+ await CheckObject(locals, "this", "Newtonsoft.Json.Linq.JArray", description: "[]");
+ else
+ await CheckObject(locals, "array", "Newtonsoft.Json.Linq.JArray", description: "[\n \"Manual text\"\n]");
+ }, times: 2
+ );
+ }
+
+ [ConditionalFact(nameof(RunningOnChrome))]
+ public async Task SteppingIntoLibraryWithoutSymbolsAndStepAgainAfterLoadSymbols()
+ {
+ string cachePath = _env.CreateTempDirectory("symbols-cache");
+ _testOutput.WriteLine($"** Using cache path: {cachePath}");
+ var searchPaths = new JArray
+ {
+ "https://symbols.nuget.org/download/symbols"
+ };
+ var waitForScript = WaitForScriptParsedEventsAsync(new string [] { "JArray.cs" });
+ var symbolOptions = JObject.FromObject(new { symbolOptions = JObject.FromObject(new { cachePath, searchPaths })});
+ await SetJustMyCode(false);
+
+ await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] TestLoadSymbols:Run'); invoke_static_method ('[debugger-test] TestLoadSymbols:Run'); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 1572, 8,
+ "TestLoadSymbols.Run"
+ );
+ await StepAndCheck(StepKind.Into, "dotnet://debugger-test.dll/debugger-test.cs", 1575, 8, "TestLoadSymbols.Run",
+ locals_fn: async (locals) =>
+ {
+ await CheckObject(locals, "array", "Newtonsoft.Json.Linq.JArray", description: "[\n \"Manual text\"\n]");
+ }, times: 2
+ );
+
+ await SetSymbolOptions(symbolOptions);
+ await waitForScript;
+
+ await SendCommandAndCheck(null, "Debugger.resume",
+ "dotnet://debugger-test.dll/debugger-test.cs", 1572, 8,
+ "TestLoadSymbols.Run"
+ );
+
+ await StepAndCheck(StepKind.Into, "dotnet://Newtonsoft.Json.dll/JArray.cs", 350, 12, "Newtonsoft.Json.Linq.JArray.Add",
+ locals_fn: async (locals) =>
+ {
+ await CheckObject(locals, "this", "Newtonsoft.Json.Linq.JArray", description: "[]");
+ }, times: 2
+ );
+ }
+
+ [ConditionalFact(nameof(RunningOnChrome))]
+ public async Task SteppingIntoLibrarySymbolsLoadedFromSymbolServerAddOtherSymbolServerAndStepAgain()
+ {
+ string cachePath = _env.CreateTempDirectory("symbols-cache");
+ _testOutput.WriteLine($"** Using cache path: {cachePath}");
+
+ var searchPaths = new JArray
+ {
+ "https://symbols.nuget.org/download/symbols"
+ };
+ var waitForScript = WaitForScriptParsedEventsAsync(new string [] { "JArray.cs" });
+ var symbolOptions = JObject.FromObject(new { symbolOptions = JObject.FromObject(new { cachePath, searchPaths })});
+ await SetJustMyCode(false);
+ await SetSymbolOptions(symbolOptions);
+
+ await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] TestLoadSymbols:Run'); invoke_static_method ('[debugger-test] TestLoadSymbols:Run'); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 1572, 8,
+ "TestLoadSymbols.Run"
+ );
+
+ await waitForScript;
+
+ await StepAndCheck(StepKind.Into, "dotnet://Newtonsoft.Json.dll/JArray.cs", 350, 12, "Newtonsoft.Json.Linq.JArray.Add",
+ locals_fn: async (locals) =>
+ {
+ await CheckObject(locals, "this", "Newtonsoft.Json.Linq.JArray", description: "[]");
+ }, times: 2
+ );
+
+ searchPaths.Add("https://msdl.microsoft.com/download/symbols");
+ symbolOptions = JObject.FromObject(new { symbolOptions = JObject.FromObject(new { cachePath, searchPaths })});
+ await SetSymbolOptions(symbolOptions);
+
+ await SendCommandAndCheck(null, "Debugger.resume",
+ "dotnet://debugger-test.dll/debugger-test.cs", 1572, 8,
+ "TestLoadSymbols.Run"
+ );
+
+ await StepAndCheck(StepKind.Into, "dotnet://Newtonsoft.Json.dll/JArray.cs", 350, 12, "Newtonsoft.Json.Linq.JArray.Add",
+ locals_fn: async (locals) =>
+ {
+ await CheckObject(locals, "this", "Newtonsoft.Json.Linq.JArray", description: "[]");
+ }, times: 2
+ );
+ }
+
+ [ConditionalTheory(nameof(RunningOnChrome))]
+ [InlineData("https://symbols.nuget.org/download/symbols", "")]
+ // Symbols are already loaded, so setting urls = [] won't affect it
+ [InlineData]
+ [InlineData("", "https://microsoft.com/non-existant/symbols")]
+ public async Task SteppingIntoLibrarySymbolsLoadedFromSymbolServerRemoveSymbolServerAndStepAgain(params string[] secondServers)
+ {
+ string cachePath = _env.CreateTempDirectory("symbols-cache");
+ _testOutput.WriteLine($"Using cachePath: {cachePath}");
+ var searchPaths = new JArray
+ {
+ "https://symbols.nuget.org/download/symbols",
+ "https://msdl.microsoft.com/download/bad-non-existant",
+ "https://msdl.microsoft.com/download/symbols"
+ };
+ var waitForScript = WaitForScriptParsedEventsAsync(new string [] { "JArray.cs" });
+ var symbolOptions = JObject.FromObject(new { symbolOptions = JObject.FromObject(new { cachePath, searchPaths })});
+ await SetJustMyCode(false);
+ await SetSymbolOptions(symbolOptions);
+
+ await EvaluateAndCheck(
+ "window.setTimeout(function() { invoke_static_method ('[debugger-test] TestLoadSymbols:Run'); invoke_static_method ('[debugger-test] TestLoadSymbols:Run'); }, 1);",
+ "dotnet://debugger-test.dll/debugger-test.cs", 1572, 8,
+ "TestLoadSymbols.Run"
+ );
+
+ await waitForScript;
+
+ await StepAndCheck(StepKind.Into, "dotnet://Newtonsoft.Json.dll/JArray.cs", 350, 12, "Newtonsoft.Json.Linq.JArray.Add",
+ locals_fn: async (locals) =>
+ {
+ await CheckObject(locals, "this", "Newtonsoft.Json.Linq.JArray", description: "[]");
+ }, times: 2
+ );
+ searchPaths.Clear();
+ foreach (string secondServer in secondServers)
+ searchPaths.Add(secondServer);
+
+ symbolOptions = JObject.FromObject(new { symbolOptions = JObject.FromObject(new { cachePath, searchPaths })});
+ await SetSymbolOptions(symbolOptions);
+
+ await SendCommandAndCheck(null, "Debugger.resume",
+ "dotnet://debugger-test.dll/debugger-test.cs", 1572, 8,
+ "TestLoadSymbols.Run"
+ );
+
+ await StepAndCheck(StepKind.Into, "dotnet://Newtonsoft.Json.dll/JArray.cs", 350, 12, "Newtonsoft.Json.Linq.JArray.Add",
+ locals_fn: async (locals) =>
+ {
+ await CheckObject(locals, "this", "Newtonsoft.Json.Linq.JArray", description: "[]");
+ }, times: 2
+ );
+ }
}
}
--- /dev/null
+// 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.IO;
+using System.Linq;
+using Xunit.Abstractions;
+
+namespace DebuggerTests;
+
+public class TestEnvironment : IDisposable
+{
+ private bool _disposed;
+ private readonly string _tempPath;
+ private readonly ITestOutputHelper _testOutput;
+
+ public TestEnvironment(ITestOutputHelper testOutput)
+ {
+ _testOutput = testOutput;
+ _tempPath = Path.Combine(DebuggerTestBase.TempPath, Guid.NewGuid().ToString());
+ if (Directory.Exists(_tempPath))
+ Directory.Delete(_tempPath, recursive: true);
+
+ Directory.CreateDirectory(_tempPath);
+ }
+
+ public void Dispose()
+ {
+ if (_disposed || EnvironmentVariables.SkipCleanup)
+ return;
+
+ Directory.Delete(_tempPath, recursive: true);
+ _disposed = true;
+ }
+
+ public string CreateTempDirectory(string relativeDir, params string[] relativePathParts)
+ {
+ string newPath = Path.Combine(_tempPath, relativeDir, Path.Combine(relativePathParts));
+ Directory.CreateDirectory(newPath);
+ return newPath;
+ }
+}
var n = new ToStringOverridenN();
System.Diagnostics.Debugger.Break();
}
-}
\ No newline at end of file
+}
+public class TestLoadSymbols
+{
+ public static void Run()
+ {
+ var array = new Newtonsoft.Json.Linq.JArray();
+ var text = new Newtonsoft.Json.Linq.JValue("Manual text");
+ var date = new Newtonsoft.Json.Linq.JValue(new DateTime(2000, 5, 23));
+
+ System.Diagnostics.Debugger.Break();
+
+ array.Add(text);
+ array.Add(date);
+ }
+}
<WasmGenerateAppBundle>true</WasmGenerateAppBundle>
<OutputType>library</OutputType>
<WasmEmitSymbolMap>true</WasmEmitSymbolMap>
+ <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
+ <ItemGroup>
+ <!-- keep this version to make sure it will pause in the expected line -->
+ <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
+ </ItemGroup>
<ItemGroup>
<WasmExtraFilesToDeploy Include="debugger-driver.html" />
<WasmAssembliesToBundle Include="$(OutDir)\debugger-test-with-non-user-code-class.dll" />
<WasmAssembliesToBundle Condition="!$([MSBuild]::IsOSPlatform('windows'))" Include="$(OutDir)\debugger-test-with-colon-in-source-name.dll" />
<WasmAssembliesToBundle Include="$(OutDir)\debugger-test-vb.dll" />
+ <WasmAssembliesToBundle Include="$(OutDir)\Newtonsoft.Json.dll" />
<WasmAssembliesToBundle Include="$(MicrosoftNetCoreAppRuntimePackRidDir)\lib\$(NetCoreappCurrent)\System.Runtime.InteropServices.JavaScript.dll" />
<!-- Assemblies only dynamically loaded -->