From c1a67330af4abbb0bc36834d395b03818f28b4f7 Mon Sep 17 00:00:00 2001 From: Dudi Keleti Date: Tue, 17 Sep 2024 16:44:00 +0200 Subject: [PATCH] [Dynamic Instrumentation] DEBUG-2249 Line and method probes exploration tests (#5914) ## Summary of changes This PR add line porbe exploration tests. ## Reason for change To verify that we aren't breaking any code with our instrumentation. ## Implementation details We instrument each method with a method probe and each method line with a line probe. ## Test coverage Several open source projects (e.g. protobuf.net). --------- Co-authored-by: Matan Green --- tracer/build/_build/Build.ExplorationTests.cs | 302 ++++++++++++++++-- tracer/build/_build/BuildVariables.cs | 23 +- .../Datadog.Trace/Debugger/LiveDebugger.cs | 3 - .../PDBs/DatadogMetadataReader.cs | 24 +- .../debugger_environment_variables.h | 2 + .../debugger_environment_variables_util.cpp | 5 + .../debugger_environment_variables_util.h | 1 + ...ugger_probes_instrumentation_requester.cpp | 169 ++++++++-- ...ebugger_probes_instrumentation_requester.h | 19 +- .../debugger_rejit_preprocessor.cpp | 10 +- .../fault_tolerant_rewriter.cpp | 15 +- .../rejit_preprocessor.cpp | 22 +- ...Tests.TryFinallyMethodAndLine.verified.txt | 215 +++++++++++++ ...Tests.TryFinallyMethodAndLine.verified.txt | 58 ++++ .../SmokeTests/TryFinallyMethodAndLine.cs | 50 +++ 15 files changed, 833 insertions(+), 85 deletions(-) create mode 100644 tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/snapshots/ProbeTests.TryFinallyMethodAndLine.verified.txt create mode 100644 tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/statuses/ProbeTests.TryFinallyMethodAndLine.verified.txt create mode 100644 tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/TryFinallyMethodAndLine.cs diff --git a/tracer/build/_build/Build.ExplorationTests.cs b/tracer/build/_build/Build.ExplorationTests.cs index 0549e4477ae0..814cef75692f 100644 --- a/tracer/build/_build/Build.ExplorationTests.cs +++ b/tracer/build/_build/Build.ExplorationTests.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Nuke.Common; using Nuke.Common.IO; using Nuke.Common.Tools.DotNet; @@ -32,6 +35,8 @@ partial class Build List = false)] readonly bool ExplorationTestCloneLatest; + const string LineProbesFileName = "exploration_test_line_probes"; + Target SetUpExplorationTests => _ => _ .Description("Setup exploration tests.") @@ -39,8 +44,8 @@ Target SetUpExplorationTests .After(Clean, BuildTracerHome) .Executes(() => { - SetUpExplorationTest(); GitCloneBuild(); + SetUpExplorationTest(); }); void SetUpExplorationTest() @@ -64,7 +69,7 @@ void SetUpExplorationTest() void SetUpExplorationTest_Debugger() { Logger.Information($"Set up exploration test for debugger."); - //TODO TBD + CreateLineProbesIfNeeded(); } void SetUpExplorationTest_ContinuousProfiler() @@ -111,6 +116,8 @@ void GitCloneAndBuild(ExplorationTestDescription testDescription) var source = ExplorationTestCloneLatest ? testDescription.GitRepositoryUrl : $"-b {testDescription.GitRepositoryTag} {testDescription.GitRepositoryUrl}"; var target = $"{ExplorationTestsDirectory}/{testDescription.Name}"; + FileSystemTasks.EnsureCleanDirectory(target); + var cloneCommand = $"clone -q -c advice.detachedHead=false {depth} {submodules} {source} {target}"; GitTasks.Git(cloneCommand); @@ -141,8 +148,7 @@ Target RunExplorationTests FileSystemTasks.EnsureCleanDirectory(TestLogsDirectory); try { - var envVariables = GetEnvironmentVariables(); - RunExplorationTestsGitUnitTest(envVariables); + RunExplorationTestsGitUnitTest(); RunExplorationTestAssertions(); } finally @@ -152,7 +158,7 @@ Target RunExplorationTests }) ; - Dictionary GetEnvironmentVariables() + Dictionary GetEnvironmentVariables(ExplorationTestDescription testDescription, TargetFramework framework) { var envVariables = new Dictionary { @@ -164,7 +170,7 @@ Dictionary GetEnvironmentVariables() switch (ExplorationTestUseCase) { case global::ExplorationTestUseCase.Debugger: - AddDebuggerEnvironmentVariables(envVariables); + AddDebuggerEnvironmentVariables(envVariables, testDescription, framework); break; case global::ExplorationTestUseCase.ContinuousProfiler: AddContinuousProfilerEnvironmentVariables(envVariables); @@ -176,17 +182,26 @@ Dictionary GetEnvironmentVariables() throw new ArgumentOutOfRangeException(nameof(ExplorationTestUseCase), ExplorationTestUseCase, null); } + if (testDescription.EnvironmentVariables != null) + { + foreach (var (key, value) in testDescription.EnvironmentVariables) + { + // Use TryAdd to avoid overriding the environment variables set by the caller + envVariables.TryAdd(key, value); + } + } + return envVariables; } - void RunExplorationTestsGitUnitTest(Dictionary envVariables) + void RunExplorationTestsGitUnitTest() { if (ExplorationTestName.HasValue) { Logger.Information($"Provided exploration test name is {ExplorationTestName}."); var testDescription = ExplorationTestDescription.GetExplorationTestDescription(ExplorationTestName.Value); - RunUnitTest(testDescription, envVariables); + RunUnitTest(testDescription); } else { @@ -194,12 +209,12 @@ void RunExplorationTestsGitUnitTest(Dictionary envVariables) foreach (var testDescription in ExplorationTestDescription.GetAllExplorationTestDescriptions()) { - RunUnitTest(testDescription, envVariables); + RunUnitTest(testDescription); } } } - void RunUnitTest(ExplorationTestDescription testDescription, Dictionary envVariables) + void RunUnitTest(ExplorationTestDescription testDescription) { if (!testDescription.ShouldRun) { @@ -214,28 +229,21 @@ void RunUnitTest(ExplorationTestDescription testDescription, Dictionary envVariables) { DotNetTest( x => @@ -256,6 +264,233 @@ void Test(TargetFramework targetFramework) } } + private void CreateLineProbesIfNeeded() + { + var testDescription = ExplorationTestDescription.GetExplorationTestDescription(global::ExplorationTestName.protobuf); + + if (!testDescription.LineProbesEnabled) + { + Logger.Information($"Skip line probes creation. The test case '{ExplorationTestName.Value}' does not support line scenario at the moment"); + return; + } + + if (ExplorationTestName.HasValue) + { + Logger.Information($"Provided exploration test name is {ExplorationTestName}."); + } + else + { + Logger.Information("Exploration test name is not provided. Running protobuf test case"); + } + + var sw = new Stopwatch(); + sw.Start(); + + CreateLineProbesFile(testDescription); + + sw.Stop(); + Logger.Information("Creating line probes file finished. Took "); + Logger.Information(sw.Elapsed.Minutes > 0 ? $"{sw.Elapsed.Minutes:D2} minutes and {sw.Elapsed.Seconds:D2} seconds." : $"{sw.Elapsed.Seconds:D2} seconds."); + + return; + + static void UpdateProgressBar(double processedWeight, double totalWeight, int totalFiles) + { + double progress = processedWeight / totalWeight; + int progressBarWidth = 50; + int filledWidth = (int)(progress * progressBarWidth); + + int processedFiles = (int)(progress * totalFiles); //estimated + + Console.Write("\r["); + Console.Write(new string('#', filledWidth)); + Console.Write(new string('-', progressBarWidth - filledWidth)); + Console.Write($"] {progress:P1} | Est. Files: {processedFiles}/{totalFiles}"); + } + + void CreateLineProbesFile(ExplorationTestDescription testDescription) + { + Logger.Information($"Creating line probes file for {testDescription.Name}."); + + var frameworks = Framework != null ? new[] { Framework } : testDescription.SupportedFrameworks; + var allCsFiles = Directory.EnumerateFiles($"{ExplorationTestsDirectory}{Path.DirectorySeparatorChar}{testDescription.Name}", "*.cs", SearchOption.AllDirectories). + Where(f => !f.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}")).ToArray(); + + foreach (var framework in frameworks) + { + var testRootPath = testDescription.GetTestTargetPath(ExplorationTestsDirectory, framework, BuildConfiguration); + var tracerAssemblyPath = GetTracerAssemblyPath(framework); + var tracer = Assembly.LoadFile(tracerAssemblyPath); + var extractorType = tracer.GetType("Datadog.Trace.Debugger.Symbols.SymbolExtractor"); + var createMethod = extractorType?.GetMethod("Create", BindingFlags.Static | BindingFlags.Public); + var testAssembliesPaths = GetAllTestAssemblies(testRootPath); + if (testAssembliesPaths.Length == 0) + { + throw new Exception($"Can't find any test assembly for {ExplorationTestsDirectory}/{framework}"); + } + + var metadataReaders = new List>(); + foreach (var testAssemblyPath in testAssembliesPaths) + { + var currentAssembly = Assembly.LoadFile(testAssemblyPath); + var symbolExtractor = createMethod?.Invoke(null, new object[] { currentAssembly }); + if (symbolExtractor == null) + { + throw new Exception($"Could not get SymbolExtractor instance for assembly: {testAssemblyPath}"); + } + + var metadataReader = symbolExtractor.GetType().GetProperty("DatadogMetadataReader", BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(symbolExtractor); + var getMethodAndOffsetMethod = metadataReader?.GetType().GetMethod("GetContainingMethodTokenAndOffset", BindingFlags.Instance | BindingFlags.NonPublic); + if (getMethodAndOffsetMethod == null) + { + throw new Exception("Could not find GetContainingMethodTokenAndOffset"); + } + + var isPdbExist = (bool?)metadataReader.GetType().GetProperty("IsPdbExist", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(metadataReader); + if (!isPdbExist.HasValue || !isPdbExist.Value) + { + Logger.Debug($"Skipping assembly {testAssemblyPath} because there is no PDB info"); + continue; + } + + metadataReaders.Add(Tuple.Create(metadataReader, getMethodAndOffsetMethod, currentAssembly.ManifestModule.ModuleVersionId.ToString())); + } + + var lineProbes = new List(); + var locker = new object(); + HashSet noMatchingPdbAssembly = new HashSet(); + + var fileWeights = allCsFiles.ToDictionary( + file => file, + file => (double)File.ReadLines(file).Count() + ); + double totalWeight = fileWeights.Values.Sum(); + double processedWeight = 0; + + foreach (var csFile in allCsFiles) + { + bool fileProcessed = false; + foreach (var metadataReader in metadataReaders) + { + var pdbPath = (string)metadataReader?.Item1?.GetType().GetProperty("PdbFullPath", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(metadataReader.Item1); + if (string.IsNullOrEmpty(pdbPath)) + { + continue; + } + + var assemblyName = Path.GetFileNameWithoutExtension(pdbPath); + if (string.IsNullOrEmpty(assemblyName)) + { + continue; + } + + if (!csFile.Contains(assemblyName!)) + { + // if the metadata reader isn't the correct one, continue. + // it's not a bulletproof but probably enough for exploration test + if (noMatchingPdbAssembly.Add(assemblyName)) + { + Logger.Debug($"Skipping assembly {assemblyName} because there is no matching PDB info"); + } + continue; + } + + fileProcessed = true; + int numberOfLines = (int)fileWeights[csFile]; + Parallel.For(0, numberOfLines, () => new List(), (i, state, localList) => + { + int? byteCodeOffset = null; + // ReSharper disable once ExpressionIsAlwaysNull + var args = new object[] { csFile, i, /*column*/ null, byteCodeOffset }; + var method = metadataReader.Item2.Invoke(metadataReader.Item1, args); + + // i.e. we got a method and bytecode offset + if (args[3] != null && method != null) + { + localList.Add($"{metadataReader.Item3},{(int)method},{(int)args[3]}"); + } + + return localList; + }, localList => + { + lock (locker) + { + lineProbes.AddRange(localList); + } + }); + } + + if (fileProcessed) + { + processedWeight += fileWeights[csFile]; + UpdateProgressBar(processedWeight, totalWeight, allCsFiles.Count()); + } + } + + if (lineProbes.Count > 0) + { + File.WriteAllText(Path.Combine(testRootPath, LineProbesFileName), + string.Join(Environment.NewLine, lineProbes)); + lineProbes.Clear(); + } + + Console.WriteLine(); + } + } + } + + string GetTracerAssemblyPath(TargetFramework framework) + { + TargetFramework tracerFramework = null; + if (framework.IsGreaterThanOrEqualTo(TargetFramework.NET6_0)) + { + tracerFramework = TargetFramework.NET6_0; + } + + else if (framework.IsGreaterThanOrEqualTo(TargetFramework.NETCOREAPP3_1)) + { + tracerFramework = TargetFramework.NETCOREAPP3_1; + } + + else if (framework == TargetFramework.NETSTANDARD2_0 || + framework == TargetFramework.NETCOREAPP2_1 || + framework == TargetFramework.NETCOREAPP3_0) + { + tracerFramework = TargetFramework.NETSTANDARD2_0; + } + + else if (framework == TargetFramework.NET461 || + framework == TargetFramework.NET462) + { + tracerFramework = TargetFramework.NET461; + } + + if (tracerFramework == null) + { + throw new Exception("Can't determined the correct tracer framework version"); + } + + var extension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dll" : "so"; + return MonitoringHomeDirectory / tracerFramework / "Datadog.Trace." + extension; + } + + static string[] GetAllTestAssemblies(string rootPath) + { + return Directory.EnumerateFiles(rootPath, "*.*", SearchOption.AllDirectories) + .Where(f => !Exclude(f) && IsSupportedExtension(f)).ToArray(); + + bool Exclude(string path) + { + return path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"); + } + + bool IsSupportedExtension(string path) + { + var extensions = new[] { ".dll", ".exe", ".so" }; + return extensions.Any(ext => string.Equals(Path.GetExtension(path), ext, StringComparison.OrdinalIgnoreCase)); + } + } + void RunExplorationTestAssertions() { switch (ExplorationTestUseCase) @@ -301,7 +536,7 @@ public enum ExplorationTestUseCase public enum ExplorationTestName { - eShopOnWeb, protobuf, cake, swashbuckle, paket, RestSharp, serilog, polly, automapper, /*ilspy*/ + eShopOnWeb, protobuf, cake, swashbuckle, paket, RestSharp, serilog, polly, automapper } class ExplorationTestDescription @@ -318,6 +553,7 @@ class ExplorationTestDescription public string[] TestsToIgnore { get; set; } public bool ShouldRun { get; set; } = true; + public bool LineProbesEnabled { get; set; } public string GetTestTargetPath(AbsolutePath explorationTestsDirectory, TargetFramework framework, Configuration buildConfiguration) { @@ -375,7 +611,14 @@ public static ExplorationTestDescription GetExplorationTestDescription(Explorati IsGitSubmodulesRequired = true, PathToUnitTestProject = "csharp/src/Google.Protobuf.Test", SupportedFrameworks = new[] { TargetFramework.NETCOREAPP2_1 }, - ShouldRun = false // Dictates that this exploration test should not take part in the CI + TestsToIgnore = new string[] + { + "Google.Protobuf.CodedInputStreamTest.MaliciousRecursion", + "Google.Protobuf.CodedInputStreamTest.MaliciousRecursion_UnknownFields", + "Google.Protobuf.CodedInputStreamTest.RecursionLimitAppliedWhileSkippingGroup", + "Google.Protobuf.JsonParserTest.MaliciousRecursion" + }, + LineProbesEnabled = true }, ExplorationTestName.cake => new ExplorationTestDescription() { @@ -452,17 +695,6 @@ public static ExplorationTestDescription GetExplorationTestDescription(Explorati // Workaround for https://github.com/dotnet/runtime/issues/95653 EnvironmentVariables = new[] { ("DD_CLR_ENABLE_INLINING", "0") }, }, - //ExplorationTestName.ilspy => new ExplorationTestDescription() - //{ - // Name = ExplorationTestName.ilspy, - // GitRepositoryUrl = "https://github.com/icsharpcode/ILSpy.git", - // GitRepositoryTag = "v7.1", - // IsGitSubmodulesRequired = true, - // PathToUnitTestProject = "ICSharpCode.Decompiler.Tests", - // IsTestedByVSTest = true, - // TestsToIgnore = new[] { "UseMc", "_net45", "ImplicitConversions", "ExplicitConversions", "ICSharpCode_Decompiler", "NewtonsoftJson_pcl_debug", "NRefactory_CSharp", "Random_TestCase_1", "AsyncForeach", "AsyncStreams", "AsyncUsing", "CS9_ExtensionGetEnumerator", "IndexRangeTest", "InterfaceTests", "UsingVariables" }, - // SupportedFrameworks = new[] { TargetFramework.NET461 }, - //}, _ => throw new ArgumentOutOfRangeException(nameof(name), name, null) }; diff --git a/tracer/build/_build/BuildVariables.cs b/tracer/build/_build/BuildVariables.cs index 79be5a97f484..7f07cd7dc9f9 100644 --- a/tracer/build/_build/BuildVariables.cs +++ b/tracer/build/_build/BuildVariables.cs @@ -1,17 +1,26 @@ using System.Collections.Generic; +using System.IO; using Nuke.Common; -using Logger = Serilog.Log; partial class Build { - public void AddDebuggerEnvironmentVariables(Dictionary envVars) + public void AddDebuggerEnvironmentVariables(Dictionary envVars, ExplorationTestDescription description, TargetFramework framework) { AddTracerEnvironmentVariables(envVars); - if (!DisableDynamicInstrumentationProduct) + if (DisableDynamicInstrumentationProduct) { - envVars.Add("DD_DYNAMIC_INSTRUMENTATION_ENABLED", "1"); - envVars.Add("DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL", "1"); + return; + } + + envVars.Add("DD_DYNAMIC_INSTRUMENTATION_ENABLED", "1"); + envVars.Add("DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL", "1"); + + if (description.LineProbesEnabled) + { + envVars.Add("DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL_LINES", "1"); + var testRootPath = description.GetTestTargetPath(ExplorationTestsDirectory, framework, BuildConfiguration); + envVars.Add("DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL_LINES_PATH", Path.Combine(testRootPath, LineProbesFileName)); } envVars.Add("COMPlus_DbgEnableMiniDump", "1"); @@ -22,6 +31,10 @@ public void AddDebuggerEnvironmentVariables(Dictionary envVars) { envVars.Add("DD_INTERNAL_FAULT_TOLERANT_INSTRUMENTATION_ENABLED", "true"); } + + // Uncomment to get rejit verify files + // envVars.Add("DD_WRITE_INSTRUMENTATION_TO_DISK", "1"); + // envVars.Add("CopyingOriginalModulesEnabled", "1"); } public void AddContinuousProfilerEnvironmentVariables(Dictionary envVars) diff --git a/tracer/src/Datadog.Trace/Debugger/LiveDebugger.cs b/tracer/src/Datadog.Trace/Debugger/LiveDebugger.cs index aebb970e0c44..a4dda3a0e03d 100644 --- a/tracer/src/Datadog.Trace/Debugger/LiveDebugger.cs +++ b/tracer/src/Datadog.Trace/Debugger/LiveDebugger.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using Datadog.Trace.Agent.DiscoveryService; @@ -26,8 +25,6 @@ using Datadog.Trace.DogStatsd; using Datadog.Trace.Logging; using Datadog.Trace.RemoteConfigurationManagement; -using Datadog.Trace.Util; -using Datadog.Trace.Vendors.dnlib.DotNet; using Datadog.Trace.Vendors.StatsdClient; using ProbeInfo = Datadog.Trace.Debugger.Expressions.ProbeInfo; diff --git a/tracer/src/Datadog.Trace/PDBs/DatadogMetadataReader.cs b/tracer/src/Datadog.Trace/PDBs/DatadogMetadataReader.cs index 40ba0bc5ab7c..3f4102f5da02 100644 --- a/tracer/src/Datadog.Trace/PDBs/DatadogMetadataReader.cs +++ b/tracer/src/Datadog.Trace/PDBs/DatadogMetadataReader.cs @@ -53,6 +53,9 @@ private DatadogMetadataReader(PEReader peReader, MetadataReader metadataReader, IsPdbExist = PdbReader != null || DnlibPdbReader != null; } + /// + /// Gets the pdb path if exists, otherwise the assembly location + /// internal string? PdbFullPath { get; } internal MetadataReader MetadataReader { get; } @@ -76,7 +79,7 @@ private DatadogMetadataReader(PEReader peReader, MetadataReader metadataReader, if (peReader.TryOpenAssociatedPortablePdb(assembly.Location, File.OpenRead, out var metadataReaderProvider, out var pdbPath)) { pdbReader = metadataReaderProvider!.GetMetadataReader(MetadataReaderOptions.Default, MetadataStringDecoder.DefaultUTF8); - return new DatadogMetadataReader(peReader, metadataReader, pdbReader, pdbPath, null, null); + return new DatadogMetadataReader(peReader, metadataReader, pdbReader, pdbPath ?? assembly.Location, null, null); } if (!TryFindPdbFile(assembly.Location, out var pdbFullPath)) @@ -366,6 +369,12 @@ private MethodDefinitionHandle GetMoveNextMethod(MethodDefinition methodDef) if (PdbReader != null) { + var normalizeFilePath = GetNormalizedPath(filePath); + if (string.IsNullOrEmpty(normalizeFilePath)) + { + return null; + } + const int methodDefTablePrefix = 0x06000000; foreach (MethodDefinitionHandle methodDefinitionHandle in MetadataReader.MethodDefinitions) { @@ -373,11 +382,17 @@ private MethodDefinitionHandle GetMoveNextMethod(MethodDefinition methodDef) foreach (VendoredMicrosoftCode.System.Reflection.Metadata.SequencePoint sequencePoint in methodDebugInformation.GetSequencePoints()) { - if (sequencePoint.IsHidden || GetDocumentName(sequencePoint.Document) != filePath) + if (sequencePoint.IsHidden) { continue; } + var normalizeDocumentPath = GetNormalizedPath(GetDocumentName(sequencePoint.Document)); + if (!string.Equals(normalizeDocumentPath, normalizeFilePath, StringComparison.OrdinalIgnoreCase)) + { + break; + } + // Check if the line and column match if (sequencePoint.StartLine <= line && sequencePoint.EndLine >= line && (column.HasValue == false || (sequencePoint.StartColumn <= column && sequencePoint.EndColumn >= column))) @@ -390,6 +405,11 @@ private MethodDefinitionHandle GetMoveNextMethod(MethodDefinition methodDef) } return null; + + string? GetNormalizedPath(string? path) + { + return path == null ? null : Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + } } internal IList? GetDocuments() diff --git a/tracer/src/Datadog.Tracer.Native/debugger_environment_variables.h b/tracer/src/Datadog.Tracer.Native/debugger_environment_variables.h index 6ce73c39e589..2a8cf5a0335f 100644 --- a/tracer/src/Datadog.Tracer.Native/debugger_environment_variables.h +++ b/tracer/src/Datadog.Tracer.Native/debugger_environment_variables.h @@ -13,6 +13,8 @@ namespace environment // Determine whether to enter "instrument all" mode where the Debugger instrumentation // is applied to every jit compiled method. Only useful for testing purposes. Default is false. const WSTRING internal_instrument_all_enabled = WStr("DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL"); + const WSTRING internal_instrument_all_lines_enabled = WStr("DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL_LINES"); + const WSTRING internal_instrument_all_lines_path = WStr("DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL_LINES_PATH"); // Determines if the Dynamic Instrumentation (aka live debugger) is enabled. const WSTRING debugger_enabled = WStr("DD_DYNAMIC_INSTRUMENTATION_ENABLED"); diff --git a/tracer/src/Datadog.Tracer.Native/debugger_environment_variables_util.cpp b/tracer/src/Datadog.Tracer.Native/debugger_environment_variables_util.cpp index 252c73de7b2e..36ffa5540c21 100644 --- a/tracer/src/Datadog.Tracer.Native/debugger_environment_variables_util.cpp +++ b/tracer/src/Datadog.Tracer.Native/debugger_environment_variables_util.cpp @@ -26,4 +26,9 @@ bool IsDebuggerInstrumentAllEnabled() CheckIfTrue(GetEnvironmentValue(environment::internal_instrument_all_enabled)); } +bool IsDebuggerInstrumentAllLinesEnabled() +{ + CheckIfTrue(GetEnvironmentValue(environment::internal_instrument_all_lines_enabled)); +} + } // namespace debugger \ No newline at end of file diff --git a/tracer/src/Datadog.Tracer.Native/debugger_environment_variables_util.h b/tracer/src/Datadog.Tracer.Native/debugger_environment_variables_util.h index 56381a106154..7a6c6486568b 100644 --- a/tracer/src/Datadog.Tracer.Native/debugger_environment_variables_util.h +++ b/tracer/src/Datadog.Tracer.Native/debugger_environment_variables_util.h @@ -11,6 +11,7 @@ namespace debugger bool IsDebuggerEnabled(); bool IsExceptionReplayEnabled(); bool IsDebuggerInstrumentAllEnabled(); +bool IsDebuggerInstrumentAllLinesEnabled(); } // namespace debugger diff --git a/tracer/src/Datadog.Tracer.Native/debugger_probes_instrumentation_requester.cpp b/tracer/src/Datadog.Tracer.Native/debugger_probes_instrumentation_requester.cpp index 654d45992714..e1a6682b3b1e 100644 --- a/tracer/src/Datadog.Tracer.Native/debugger_probes_instrumentation_requester.cpp +++ b/tracer/src/Datadog.Tracer.Native/debugger_probes_instrumentation_requester.cpp @@ -10,7 +10,12 @@ #include "debugger_rejit_handler_module_method.h" #include "debugger_rejit_preprocessor.h" #include "fault_tolerant_method_duplicator.h" +#include "fault_tolerant_tracker.h" +#include "iast/iast_util.h" #include "logger.h" + +#include +#include #include namespace debugger @@ -97,25 +102,74 @@ WSTRING DebuggerProbesInstrumentationRequester::GenerateRandomProbeId() return converted; } +auto& DebuggerProbesInstrumentationRequester::GetExplorationTestLineProbes(const WSTRING& filename) +{ + std::call_once(explorationLinesInitFlag, [this, fn = filename] { + DebuggerProbesInstrumentationRequester::InitializeExplorationTestLineProbes(fn); + }); + return explorationLineProbes; +} + +void DebuggerProbesInstrumentationRequester::InitializeExplorationTestLineProbes(const WSTRING& filename) +{ +#ifdef _WIN32 + std::ifstream file(filename); +#else + std::ifstream file(ToString(filename).c_str()); +#endif + + if (!file.is_open()) + { + Logger::Error("InitializeExplorationTestLineProbes: Unable to open file: ", filename); + return; + } + + std::string line; + while (std::getline(file, line)) + { + std::istringstream lineStream(line); + std::string guid; + std::string methodTokenStr; + std::string bytecodeOffsetStr; + + // Split the line by commas + if (std::getline(lineStream, guid, ',') && std::getline(lineStream, methodTokenStr, ',') && + std::getline(lineStream, bytecodeOffsetStr, ',')) + { + auto methodToken = static_cast(std::stoul(methodTokenStr)); + int bytecodeOffset = std::stoi(bytecodeOffsetStr); + + auto key = std::make_pair(guid, methodToken); + explorationLineProbes[key].push_back(bytecodeOffset); + } + } + + file.close(); +} + /** * \brief For Testing-Purposes. Requests ReJIT for the given method if certain checks are met. Relevant when the * environment variable `DD_INTERNAL_DEBUGGER_INSTRUMENT_ALL` is set to true. \param module_info the ModuleInfo of the * module entering into instrumentation-all. \param module_id the ModuleID of the module entering into * instrumentation-all. \param function_token the mdToken of the method entering into instrumentation-all. */ -void DebuggerProbesInstrumentationRequester::PerformInstrumentAllIfNeeded(const ModuleID& module_id, - const mdToken& function_token) +void DebuggerProbesInstrumentationRequester::PerformInstrumentAllIfNeeded(const ModuleID& module_id, mdToken& function_token) { - if (!IsDebuggerInstrumentAllEnabled()) + try { - return; - } + if (!IsDebuggerInstrumentAllEnabled()) + { + return; + } - const auto& module_info = GetModuleInfo(m_corProfiler->info_, module_id); - const auto assembly_name = module_info.assembly.name; + const auto& module_info = GetModuleInfo(m_corProfiler->info_, module_id); + const auto assembly_name = module_info.assembly.name; + + if (IsCoreLibOr3rdParty(assembly_name)) + { + return; + } - if (!IsCoreLibOr3rdParty(assembly_name)) - { ComPtr metadataInterfaces; auto hr = m_corProfiler->info_->GetModuleMetaData(module_id, ofRead | ofWrite, IID_IMetaDataImport2, metadataInterfaces.GetAddressOf()); @@ -125,7 +179,7 @@ void DebuggerProbesInstrumentationRequester::PerformInstrumentAllIfNeeded(const auto assemblyImport = metadataInterfaces.As(IID_IMetaDataAssemblyImport); auto assemblyEmit = metadataInterfaces.As(IID_IMetaDataAssemblyEmit); - Logger::Debug("Temporaly allocating the ModuleMetadata for injection. ModuleId=", module_id, + Logger::Debug("Temporally allocating the ModuleMetadata for injection. ModuleId=", module_id, " ModuleName=", module_info.assembly.name); std::unique_ptr module_metadata = std::make_unique( @@ -143,9 +197,10 @@ void DebuggerProbesInstrumentationRequester::PerformInstrumentAllIfNeeded(const hr = caller.method_signature.TryParse(); if (FAILED(hr)) { - Logger::Warn( - " * DebuggerProbesInstrumentationRequester::PerformInstrumentAllIfNeeded: The method signature: ", - caller.method_signature.str(), " cannot be parsed."); + Logger::Warn(" * DebuggerProbesInstrumentationRequester::PerformInstrumentAllIfNeeded: [MethodDef=", + shared::TokenStr(&function_token), ", Type=", caller.type.name, ", Method=", caller.name, "]", + ": could not parse method signature."); + Logger::Debug(" Method signature is: ", caller.method_signature.str()); return; } @@ -180,9 +235,82 @@ void DebuggerProbesInstrumentationRequester::PerformInstrumentAllIfNeeded(const /* is_exact_signature_match */ false)); const auto numReJITs = m_debugger_rejit_preprocessor->RequestRejitForLoadedModules( - std::vector{module_id}, std::vector{methodProbe}, - /* enqueueInSameThread */ true); - Logger::Debug("Instrument-All: Total number of ReJIT Requested: ", numReJITs); + std::vector{module_id}, std::vector{methodProbe}, /* enqueueInSameThread */ true); + + Logger::Debug("Instrument-All: ReJIT Requested for: ", methodProbe->target_method.method_name, + ". ProbeId:", methodProbe->probeId, ". Numbers of ReJits: ", numReJITs); + + if (!IsDebuggerInstrumentAllLinesEnabled()) + { + return; + } + + const auto lineProbesPath = GetEnvironmentValue(environment::internal_instrument_all_lines_path); + const shared::WSTRING& probeFilePath = WStr("line_exploration_file_path"); + + auto lineProbesMap = GetExplorationTestLineProbes(lineProbesPath); + + WCHAR moduleName[MAX_PACKAGE_NAME]; + ULONG nSize; + GUID mvid; + hr = metadataImport->GetScopeProps(moduleName, MAX_PACKAGE_NAME, &nSize, &mvid); + + if (FAILED(hr)) + { + Logger::Warn("Can't read module scope props for module ID: ", module_id); + } + else + { + auto original_function_token = function_token; + + if (fault_tolerant::FaultTolerantTracker::Instance()->IsInstrumentedMethod(module_id, function_token)) + { + function_token = + fault_tolerant::FaultTolerantTracker::Instance()->GetKickoffMethodFromInstrumentedMethod( + module_id, function_token); + } + + auto lowerMvid = iast::ToLower(ToString(mvid)); + lowerMvid = lowerMvid.substr(1, lowerMvid.length() - 2); + auto key = std::make_pair(lowerMvid, function_token); + auto it = lineProbesMap.find(key); + if (it == lineProbesMap.end()) + { + Logger::Warn("No line probes found in method: ", caller.type.name, ".", caller.name); + } + else + { + std::vector> lineProbeDefinitions; + auto bytecodeOffsets = it->second; + for (const auto& offset : bytecodeOffsets) + { + const auto& lineProbe = std::make_shared(LineProbeDefinition( + GenerateRandomProbeId(), offset, offset, mvid, original_function_token, probeFilePath)); + lineProbeDefinitions.push_back(lineProbe); + } + + std::promise> promise; + std::future> future = promise.get_future(); + m_debugger_rejit_preprocessor->EnqueuePreprocessLineProbes(std::vector{module_id}, lineProbeDefinitions, + &promise); + const auto& lineProbeRequests = future.get(); + + // RequestRejit + auto requestRejitPromise = std::make_shared>(); + std::future requestRejitFuture = requestRejitPromise->get_future(); + std::vector requests(lineProbeRequests.size()); + std::copy(lineProbeRequests.begin(), lineProbeRequests.end(), requests.begin()); + m_debugger_rejit_preprocessor->EnqueueRequestRejit(requests, requestRejitPromise); + // wait and get the value from the future + requestRejitFuture.get(); + Logger::Debug("Instrument-All: ReJIT Requested for: ", methodProbe->target_method.method_name, + ". Numbers of line probes: ", lineProbeDefinitions.size()); + } + } + } + catch (...) + { + Logger::Error("PerformInstrumentAllIfNeeded: fail for module: ", module_id, ". method: ", function_token); } } @@ -192,7 +320,7 @@ DebuggerProbesInstrumentationRequester::DebuggerProbesInstrumentationRequester( std::shared_ptr fault_tolerant_method_duplicator) : m_corProfiler(corProfiler), m_debugger_rejit_preprocessor( - std::make_unique(corProfiler, rejit_handler, work_offloader)), + std::make_unique(corProfiler, rejit_handler, work_offloader)), m_rejit_handler(rejit_handler), m_work_offloader(work_offloader), m_fault_tolerant_method_duplicator(fault_tolerant_method_duplicator) @@ -424,8 +552,7 @@ void DebuggerProbesInstrumentationRequester::AddMethodProbes(debugger::DebuggerM auto promise = std::make_shared>>(); std::future> future = promise->get_future(); - m_debugger_rejit_preprocessor->EnqueuePreprocessRejitRequests(modules.Ref(), methodProbeDefinitions, - promise); + m_debugger_rejit_preprocessor->EnqueuePreprocessRejitRequests(modules.Ref(), methodProbeDefinitions, promise); const auto& methodProbeRequests = future.get(); @@ -471,8 +598,8 @@ void DebuggerProbesInstrumentationRequester::AddLineProbes(debugger::DebuggerLin const shared::WSTRING& probeId = shared::WSTRING(current.probeId); const shared::WSTRING& probeFilePath = shared::WSTRING(current.probeFilePath); - const auto& lineProbe = std::make_shared(LineProbeDefinition(probeId, current.bytecodeOffset, current.lineNumber, - current.mvid, current.methodId, probeFilePath)); + const auto& lineProbe = std::make_shared(LineProbeDefinition( + probeId, current.bytecodeOffset, current.lineNumber, current.mvid, current.methodId, probeFilePath)); lineProbeDefinitions.push_back(lineProbe); } diff --git a/tracer/src/Datadog.Tracer.Native/debugger_probes_instrumentation_requester.h b/tracer/src/Datadog.Tracer.Native/debugger_probes_instrumentation_requester.h index 36fade86f1eb..a7d37dd747e7 100644 --- a/tracer/src/Datadog.Tracer.Native/debugger_probes_instrumentation_requester.h +++ b/tracer/src/Datadog.Tracer.Native/debugger_probes_instrumentation_requester.h @@ -7,6 +7,8 @@ #include "debugger_members.h" #include "fault_tolerant_method_duplicator.h" +#include + // forward declaration namespace fault_tolerant @@ -17,6 +19,17 @@ class FaultTolerantMethodDuplicator; namespace debugger { +struct pair_hash +{ + template + std::size_t operator()(const std::pair& p) const + { + auto hash1 = std::hash{}(p.first); + auto hash2 = std::hash{}(p.second); + return hash1 ^ (hash2 << 1); + } +}; + class DebuggerProbesInstrumentationRequester { private: @@ -28,6 +41,8 @@ class DebuggerProbesInstrumentationRequester std::shared_ptr m_work_offloader = nullptr; std::shared_ptr m_fault_tolerant_method_duplicator = nullptr; bool is_debugger_or_exception_debugging_enabled = false; + std::unordered_map, std::vector, pair_hash> explorationLineProbes; + std::once_flag explorationLinesInitFlag; static bool IsCoreLibOr3rdParty(const WSTRING& assemblyName); static WSTRING GenerateRandomProbeId(); @@ -53,7 +68,9 @@ class DebuggerProbesInstrumentationRequester debugger::DebuggerMethodSpanProbeDefinition* spanProbes, int spanProbesLength, debugger::DebuggerRemoveProbesDefinition* removeProbes, int removeProbesLength); static int GetProbesStatuses(WCHAR** probeIds, int probeIdsLength, debugger::DebuggerProbeStatus* probeStatuses); - void PerformInstrumentAllIfNeeded(const ModuleID& module_id, const mdToken& function_token); + void PerformInstrumentAllIfNeeded(const ModuleID& module_id, mdToken& function_token); + void InitializeExplorationTestLineProbes(const WSTRING& filename); + auto& GetExplorationTestLineProbes(const WSTRING& filename); const std::vector>& GetProbes() const; DebuggerRejitPreprocessor* GetPreprocessor(); void RequestRejitForLoadedModule(ModuleID moduleId); diff --git a/tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp b/tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp index ad460b448c91..e7168932521d 100644 --- a/tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp +++ b/tracer/src/Datadog.Tracer.Native/debugger_rejit_preprocessor.cpp @@ -438,7 +438,7 @@ std::tuple DebuggerRejitPreprocessor::Transf auto caller = GetFunctionInfo(metadataImport, moveNextMethod); if (!caller.IsValid()) { - Logger::Error("DebuggerRejitPreprocessor::TransformKickOffToMoveNext: The methoddef: ", + Logger::Error("DebuggerRejitPreprocessor::TransformKickOffToMoveNext: The MethodDef: ", shared::TokenStr(&moveNextMethod), " is not valid!"); return {E_FAIL, mdMethodDefNil, FunctionInfo()}; } @@ -446,8 +446,10 @@ std::tuple DebuggerRejitPreprocessor::Transf const auto hr = caller.method_signature.TryParse(); if (FAILED(hr)) { - Logger::Error("DebuggerRejitPreprocessor::TransformKickOffToMoveNext: The method signature: ", - caller.method_signature.str(), " cannot be parsed."); + Logger::Error( + " * DebuggerRejitPreprocessor::TransformKickOffToMoveNext: [MethodDef=", shared::TokenStr(&moveNextMethod), + ", Type=", caller.type.name, ", Method=", caller.name, "]", ": could not parse method signature."); + Logger::Debug(" Method signature is: ", caller.method_signature.str()); return {hr, mdMethodDefNil, FunctionInfo()}; } @@ -476,7 +478,7 @@ std::tuple DebuggerRejitPreprocessor::PickMe if (moveNextMethod == mdMethodDefNil) { - Logger::Info("DebuggerRejitPreprocessor::TransformMethodToRejit: MoveNextMethod didn't found. Assuming it's a non-async method"); + Logger::Debug("DebuggerRejitPreprocessor::TransformMethodToRejit: MoveNextMethod didn't found. Assuming it's a non-async method"); return {S_OK, methodDef, functionInfo}; } diff --git a/tracer/src/Datadog.Tracer.Native/fault_tolerant_rewriter.cpp b/tracer/src/Datadog.Tracer.Native/fault_tolerant_rewriter.cpp index 65a2fc2c1251..22bb73d033fd 100644 --- a/tracer/src/Datadog.Tracer.Native/fault_tolerant_rewriter.cpp +++ b/tracer/src/Datadog.Tracer.Native/fault_tolerant_rewriter.cpp @@ -541,15 +541,16 @@ HRESULT FaultTolerantRewriter::RewriteInternal(RejitHandlerModule* moduleHandler pFunctionControl->SetILFunctionBody(methodSize, pMethodBytes); // substitute the methodHandler of the instrumented duplication with the original (the one of the kickoff) - if (!moduleHandler->TryGetMethod(methodIdOfKickoff, &methodHandler)) - { - Logger::Warn("FaultTolerantRewriter::RewriteInternal(): Failed to substitute the methodHandler of the instrumented duplication with the original's."); - return S_FALSE; - } - InjectSuccessfulInstrumentationLambda injectSuccessfulInstrumentation = - [this](RejitHandlerModule* moduleHandler, RejitHandlerModuleMethod* methodHandler, + [this, kickOffId = methodIdOfKickoff](RejitHandlerModule* moduleHandler, + RejitHandlerModuleMethod* methodHandler, ICorProfilerFunctionControl* pFunctionControl, ICorProfilerInfo* pCorProfilerInfo, LPCBYTE pbILMethod) -> HRESULT { + if (!moduleHandler->TryGetMethod(kickOffId, &methodHandler)) + { + Logger::Warn("FaultTolerantRewriter::RewriteInternal(): Failed to substitute the methodHandler of the instrumented duplication with the original's."); + return S_FALSE; + } + return this->InjectSuccessfulInstrumentation(moduleHandler, methodHandler, pFunctionControl, pCorProfilerInfo, pbILMethod); }; diff --git a/tracer/src/Datadog.Tracer.Native/rejit_preprocessor.cpp b/tracer/src/Datadog.Tracer.Native/rejit_preprocessor.cpp index e73b66bae4cf..545b2b190d85 100644 --- a/tracer/src/Datadog.Tracer.Native/rejit_preprocessor.cpp +++ b/tracer/src/Datadog.Tracer.Native/rejit_preprocessor.cpp @@ -218,13 +218,19 @@ void RejitPreprocessor::EnqueueFaultTolerantMethods( { const auto originalMethod = fault_tolerant::FaultTolerantTracker::Instance()->GetOriginalMethod(moduleInfo.id, methodDef); + const auto& originalMethodNewFunctionInfo = FunctionInfo( + originalMethod, functionInfo.name, functionInfo.type, functionInfo.signature, + functionInfo.function_spec_signature, functionInfo.method_def_id, functionInfo.method_signature); RejitPreprocessor::EnqueueNewMethod(definition, metadataImport, metadataEmit, moduleInfo, typeDef, - rejitRequests, originalMethod, functionInfo, moduleHandler); + rejitRequests, originalMethod, originalMethodNewFunctionInfo, + moduleHandler); const auto instrumentedMethod = fault_tolerant::FaultTolerantTracker::Instance()->GetInstrumentedMethod(moduleInfo.id, methodDef); + const auto& instrumentedMethodNewFunctionInfo = FunctionInfo(instrumentedMethod, functionInfo.name, functionInfo.type, functionInfo.signature, functionInfo.function_spec_signature, functionInfo.method_def_id, functionInfo.method_signature); RejitPreprocessor::EnqueueNewMethod(definition, metadataImport, metadataEmit, moduleInfo, typeDef, - rejitRequests, instrumentedMethod, functionInfo, moduleHandler); + rejitRequests, instrumentedMethod, instrumentedMethodNewFunctionInfo, + moduleHandler); } } @@ -322,7 +328,7 @@ void RejitPreprocessor::ProcessTypeDefForRejit( const auto caller = GetFunctionInfo(metadataImport, methodDef); if (!caller.IsValid()) { - Logger::Warn(" * Skipping ", shared::TokenStr(&methodDef), ": the methoddef is not valid!"); + Logger::Warn(" * Skipping ", shared::TokenStr(&methodDef), ": could not get function info for MethodDef token."); continue; } @@ -332,8 +338,10 @@ void RejitPreprocessor::ProcessTypeDefForRejit( auto hr = functionInfo.method_signature.TryParse(); if (FAILED(hr)) { - Logger::Warn(" * Skipping ", functionInfo.method_signature.str(), - ": the method signature cannot be parsed."); + Logger::Warn( + " * Skipping [ModuleId=", moduleInfo.id, ", MethodDef=", shared::TokenStr(&methodDef), + ", Type=", caller.type.name, ", Method=", caller.name, "]", ": could not parse method signature."); + Logger::Debug(" Method signature is: ", functionInfo.method_signature.str()); continue; } @@ -381,8 +389,8 @@ void RejitPreprocessor::ProcessTypeDefForRejit( moduleHandler->SetModuleMetadata(moduleMetadata); } - Logger::Info("Method enqueued for ReJIT for ", target_method.type.name, ".", target_method.method_name, "(", - (target_method.signature_types.size() - 1), " params)."); + Logger::Info("Method enqueued for ReJIT for ", caller.type.name, ".", caller.name, + "(", caller.method_signature.NumberOfArguments(), " params)."); EnqueueNewMethod(definition, metadataImport, metadataEmit, moduleInfo, typeDef, rejitRequests, methodDef, functionInfo, moduleHandler); diff --git a/tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/snapshots/ProbeTests.TryFinallyMethodAndLine.verified.txt b/tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/snapshots/ProbeTests.TryFinallyMethodAndLine.verified.txt new file mode 100644 index 000000000000..666f7a9d27dc --- /dev/null +++ b/tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/snapshots/ProbeTests.TryFinallyMethodAndLine.verified.txt @@ -0,0 +1,215 @@ +[ + { + "dd.span_id": "ScrubbedValue", + "dd.trace_id": "ScrubbedValue", + "ddsource": "dd_debugger", + "debugger": { + "snapshot": { + "captures": { + "entry": { + "arguments": { + "message": "ScrubbedValue", + "this": { + "type": "TryFinallyMethodAndLine", + "value": "TryFinallyMethodAndLine" + } + } + }, + "return": { + "arguments": { + "message": "ScrubbedValue", + "this": { + "type": "TryFinallyMethodAndLine", + "value": "TryFinallyMethodAndLine" + } + }, + "locals": { + "ctx": { + "type": "ParseContext", + "value": "ParseContext" + } + } + } + }, + "duration": "ScrubbedValue", + "id": "ScrubbedValue", + "language": "dotnet", + "probe": { + "id": "ScrubbedValue", + "location": { + "method": "ReadRawMessageOriginal", + "type": "Samples.Probes.TestRuns.SmokeTests.TryFinallyMethodAndLine" + }, + "version": 0 + }, + "stack": "ScrubbedValue", + "timestamp": "ScrubbedValue" + } + }, + "logger": { + "method": "ReadRawMessageOriginal", + "name": "Samples.Probes.TestRuns.SmokeTests.TryFinallyMethodAndLine", + "thread_id": "ScrubbedValue", + "thread_name": "ScrubbedValue", + "version": "2" + }, + "message": "ScrubbedValue", + "service": "probes" + }, + { + "dd.span_id": "ScrubbedValue", + "dd.trace_id": "ScrubbedValue", + "ddsource": "dd_debugger", + "debugger": { + "snapshot": { + "captures": { + "lines": { + "17": { + "arguments": { + "message": "ScrubbedValue", + "this": { + "type": "TryFinallyMethodAndLine", + "value": "TryFinallyMethodAndLine" + } + }, + "locals": { + "ctx": { + "isNull": "true", + "type": "ParseContext" + } + } + } + } + }, + "duration": "ScrubbedValue", + "id": "ScrubbedValue", + "language": "dotnet", + "probe": { + "id": "ScrubbedValue", + "location": { + "file": "TryFinallyMethodAndLine.cs", + "lines": [ + "17" + ] + }, + "version": 0 + }, + "stack": "ScrubbedValue", + "timestamp": "ScrubbedValue" + } + }, + "logger": { + "method": "ReadRawMessageOriginal", + "name": "Samples.Probes.TestRuns.SmokeTests.TryFinallyMethodAndLine", + "thread_id": "ScrubbedValue", + "thread_name": "ScrubbedValue", + "version": "2" + }, + "message": "ScrubbedValue", + "service": "probes" + }, + { + "dd.span_id": "ScrubbedValue", + "dd.trace_id": "ScrubbedValue", + "ddsource": "dd_debugger", + "debugger": { + "snapshot": { + "captures": { + "lines": { + "20": { + "arguments": { + "message": "ScrubbedValue", + "this": { + "type": "TryFinallyMethodAndLine", + "value": "TryFinallyMethodAndLine" + } + }, + "locals": { + "ctx": { + "type": "ParseContext", + "value": "ParseContext" + } + } + } + } + }, + "duration": "ScrubbedValue", + "id": "ScrubbedValue", + "language": "dotnet", + "probe": { + "id": "ScrubbedValue", + "location": { + "file": "TryFinallyMethodAndLine.cs", + "lines": [ + "20" + ] + }, + "version": 0 + }, + "stack": "ScrubbedValue", + "timestamp": "ScrubbedValue" + } + }, + "logger": { + "method": "ReadRawMessageOriginal", + "name": "Samples.Probes.TestRuns.SmokeTests.TryFinallyMethodAndLine", + "thread_id": "ScrubbedValue", + "thread_name": "ScrubbedValue", + "version": "2" + }, + "message": "ScrubbedValue", + "service": "probes" + }, + { + "dd.span_id": "ScrubbedValue", + "dd.trace_id": "ScrubbedValue", + "ddsource": "dd_debugger", + "debugger": { + "snapshot": { + "captures": { + "lines": { + "24": { + "arguments": { + "message": "ScrubbedValue", + "this": { + "type": "TryFinallyMethodAndLine", + "value": "TryFinallyMethodAndLine" + } + }, + "locals": { + "ctx": { + "type": "ParseContext", + "value": "ParseContext" + } + } + } + } + }, + "duration": "ScrubbedValue", + "id": "ScrubbedValue", + "language": "dotnet", + "probe": { + "id": "ScrubbedValue", + "location": { + "file": "TryFinallyMethodAndLine.cs", + "lines": [ + "24" + ] + }, + "version": 0 + }, + "stack": "ScrubbedValue", + "timestamp": "ScrubbedValue" + } + }, + "logger": { + "method": "ReadRawMessageOriginal", + "name": "Samples.Probes.TestRuns.SmokeTests.TryFinallyMethodAndLine", + "thread_id": "ScrubbedValue", + "thread_name": "ScrubbedValue", + "version": "2" + }, + "message": "ScrubbedValue", + "service": "probes" + } +] \ No newline at end of file diff --git a/tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/statuses/ProbeTests.TryFinallyMethodAndLine.verified.txt b/tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/statuses/ProbeTests.TryFinallyMethodAndLine.verified.txt new file mode 100644 index 000000000000..c8c32dfec317 --- /dev/null +++ b/tracer/test/Datadog.Trace.Debugger.IntegrationTests/Approvals/statuses/ProbeTests.TryFinallyMethodAndLine.verified.txt @@ -0,0 +1,58 @@ +[ + { + "ddsource": "dd_debugger", + "debugger": { + "diagnostics": { + "exception": null, + "probeId": "17c1e39c-e46c-828e-4e02-21be0f3b5358", + "probeVersion": 0, + "runtimeId": "scrubbed", + "status": "EMITTING" + } + }, + "message": "Emitted probe 17c1e39c-e46c-828e-4e02-21be0f3b5358.", + "service": "probes" + }, + { + "ddsource": "dd_debugger", + "debugger": { + "diagnostics": { + "exception": null, + "probeId": "1828a3f0-e94b-eb91-81e3-12bcf19ac41a", + "probeVersion": 0, + "runtimeId": "scrubbed", + "status": "EMITTING" + } + }, + "message": "Emitted probe 1828a3f0-e94b-eb91-81e3-12bcf19ac41a.", + "service": "probes" + }, + { + "ddsource": "dd_debugger", + "debugger": { + "diagnostics": { + "exception": null, + "probeId": "3410cda1-5b13-a34e-6f84-a54adf7a0ea0", + "probeVersion": 0, + "runtimeId": "scrubbed", + "status": "EMITTING" + } + }, + "message": "Emitted probe 3410cda1-5b13-a34e-6f84-a54adf7a0ea0.", + "service": "probes" + }, + { + "ddsource": "dd_debugger", + "debugger": { + "diagnostics": { + "exception": null, + "probeId": "8286d046-9740-a3e4-95cf-ff46699c73c4", + "probeVersion": 0, + "runtimeId": "scrubbed", + "status": "EMITTING" + } + }, + "message": "Emitted probe 8286d046-9740-a3e4-95cf-ff46699c73c4.", + "service": "probes" + } +] \ No newline at end of file diff --git a/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/TryFinallyMethodAndLine.cs b/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/TryFinallyMethodAndLine.cs new file mode 100644 index 000000000000..7a54d26954a2 --- /dev/null +++ b/tracer/test/test-applications/debugger/dependency-libs/Samples.Probes.TestRuns/SmokeTests/TryFinallyMethodAndLine.cs @@ -0,0 +1,50 @@ +namespace Samples.Probes.TestRuns.SmokeTests +{ + [LogLineProbeTestData(lineNumber: 17, phase: 1)] + [LogLineProbeTestData(lineNumber: 20, phase: 1)] + [LogLineProbeTestData(lineNumber: 24, phase: 1)] + internal class TryFinallyMethodAndLine : IRun + { + public void Run() + { + string message = "my name is slim shady"; + ReadRawMessageOriginal(message); + } + + [LogMethodProbeTestData] + public void ReadRawMessageOriginal(string message) + { + ParseContext.Initialize(this, out ParseContext ctx); + try + { + ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message); + } + finally + { + ctx.CopyStateTo(this); + } + } + } + + internal class ParsingPrimitivesMessages + { + public static void ReadRawMessage(ref ParseContext parseContext, string message) + { + return; + } + } + + internal class ParseContext + { + public static void Initialize(TryFinallyMethodAndLine tryFinallyMethodAndLine, out ParseContext parseContext) + { + parseContext = new ParseContext(); + return; + } + + public void CopyStateTo(TryFinallyMethodAndLine tryFinallyMethodAndLine) + { + return; + } + } +}