|
5 | 5 |
|
6 | 6 | using System; |
7 | 7 | using System.Collections.Generic; |
| 8 | +using System.Linq; |
| 9 | +using System.Reflection; |
8 | 10 | using System.Threading; |
9 | 11 | using System.Threading.Tasks; |
10 | 12 | using Datadog.Trace.Agent; |
11 | 13 | using Datadog.Trace.Agent.DiscoveryService; |
12 | 14 | using Datadog.Trace.ClrProfiler; |
13 | 15 | using Datadog.Trace.Configuration; |
| 16 | +using Datadog.Trace.Debugger.Configurations.Models; |
| 17 | +using Datadog.Trace.Debugger.Expressions; |
| 18 | +using Datadog.Trace.Debugger.Instrumentation; |
| 19 | +using Datadog.Trace.Debugger.PInvoke; |
| 20 | +using Datadog.Trace.PlatformHelpers; |
14 | 21 | using Datadog.Trace.Sampling; |
15 | 22 | using Datadog.Trace.Tagging; |
16 | 23 | using Datadog.Trace.Telemetry; |
17 | 24 | using Datadog.Trace.Util; |
| 25 | +using Datadog.Trace.Vendors.dnlib.DotNet; |
| 26 | +using Datadog.Trace.Vendors.dnlib.DotNet.Emit; |
| 27 | +using Datadog.Trace.Vendors.dnlib.DotNet.Pdb.Symbols; |
18 | 28 | using Datadog.Trace.Vendors.StatsdClient; |
19 | 29 |
|
20 | 30 | namespace Datadog.Trace |
@@ -412,6 +422,8 @@ internal Span StartSpan(string operationName, ITags tags = null, ISpanContext pa |
412 | 422 | OperationName = operationName, |
413 | 423 | }; |
414 | 424 |
|
| 425 | + SpanOriginResolution(span); |
| 426 | + |
415 | 427 | // Apply any global tags |
416 | 428 | if (Settings.GlobalTags.Count > 0) |
417 | 429 | { |
@@ -441,5 +453,123 @@ internal Task FlushAsync() |
441 | 453 | { |
442 | 454 | return TracerManager.AgentWriter.FlushTracesAsync(); |
443 | 455 | } |
| 456 | + |
| 457 | + private static void SpanOriginResolution(Span span) |
| 458 | + { |
| 459 | + // Deal with pending snapshot |
| 460 | + if (!string.IsNullOrEmpty(ProbeProcessor.NextSnapshot.Value)) |
| 461 | + { |
| 462 | + var nextSnapshotToBeUploaded = ProbeProcessor.NextSnapshot.Value; |
| 463 | + ProbeProcessor.NextSnapshot.Value = string.Empty; |
| 464 | + nextSnapshotToBeUploaded = nextSnapshotToBeUploaded.Replace("TO_BE_ADDED_SPAN_ID", span.SpanId.ToString()) |
| 465 | + .Replace("TO_BE_ADDED_TRACE_ID", span.TraceId.ToString()); |
| 466 | + Datadog.Trace.Debugger.LiveDebugger.Instance.AddSnapshot("SpanOrigin", nextSnapshotToBeUploaded); |
| 467 | + } |
| 468 | + |
| 469 | + var stackFrames = new System.Diagnostics.StackTrace(); |
| 470 | + |
| 471 | + var encounteredUserCode = false; |
| 472 | + System.Reflection.MethodBase firstNonUserCodeMethod = null; |
| 473 | + System.Reflection.MethodBase firstUserCodeMethod = null; |
| 474 | + |
| 475 | + foreach (var frame in stackFrames.GetFrames()!) |
| 476 | + { |
| 477 | + var method = frame.GetMethod(); |
| 478 | + if (method?.DeclaringType == null) |
| 479 | + { |
| 480 | + continue; |
| 481 | + } |
| 482 | + |
| 483 | + static bool IsUserCode(string methodFullName) // Not Comprehensive Enough |
| 484 | + { |
| 485 | + return !(methodFullName.StartsWith("Microsoft") || |
| 486 | + methodFullName.StartsWith("System") || |
| 487 | + methodFullName.StartsWith("Datadog.Trace") || |
| 488 | + methodFullName.StartsWith("Serilog") || |
| 489 | + methodFullName.StartsWith("MySql.Data")); |
| 490 | + } |
| 491 | + |
| 492 | + if (IsUserCode(method.DeclaringType.FullName)) |
| 493 | + { |
| 494 | + encounteredUserCode = true; |
| 495 | + firstUserCodeMethod = method; |
| 496 | + break; |
| 497 | + } |
| 498 | + |
| 499 | + if (!encounteredUserCode) |
| 500 | + { |
| 501 | + firstNonUserCodeMethod = method; |
| 502 | + } |
| 503 | + } |
| 504 | + |
| 505 | + if (firstNonUserCodeMethod == null || firstUserCodeMethod == null) |
| 506 | + { |
| 507 | + // TODO LOG |
| 508 | + System.Diagnostics.Debugger.Break(); |
| 509 | + return; |
| 510 | + } |
| 511 | + |
| 512 | + var nonUserMethod = firstNonUserCodeMethod; |
| 513 | + var userMethod = firstUserCodeMethod; |
| 514 | + |
| 515 | + var userModule = ModuleDefMD.Load(userMethod.Module.Assembly.ManifestModule); |
| 516 | + var userRid = MDToken.ToRID(userMethod.MetadataToken); |
| 517 | + var userMdMethod = userModule.ResolveMethod(userRid); |
| 518 | + |
| 519 | + if (!userMdMethod.Body.HasInstructions) |
| 520 | + { |
| 521 | + // TODO LOG |
| 522 | + System.Diagnostics.Debugger.Break(); |
| 523 | + return; |
| 524 | + } |
| 525 | + |
| 526 | + var nonUserModule = ModuleDefMD.Load(nonUserMethod.Module.Assembly.ManifestModule); |
| 527 | + var nonUserRid = MDToken.ToRID(nonUserMethod.MetadataToken); |
| 528 | + var nonUserMdMethod = nonUserModule.ResolveMethod(nonUserRid); |
| 529 | + |
| 530 | + // We have to assign the module context to be able to resolve memberRef to memberdef. |
| 531 | + userModule.Context = ModuleDef.CreateModuleContext(); |
| 532 | + var nonUserMethodFullName = nonUserMdMethod.FullName; |
| 533 | + |
| 534 | + var callsToInstrument = userMdMethod.Body.Instructions.Where( |
| 535 | + instruction => instruction.OpCode.FlowControl == FlowControl.Call && |
| 536 | + (((instruction.Operand as IMethod) != null && |
| 537 | + ((instruction.Operand as IMethod)!).FullName == nonUserMethodFullName) || |
| 538 | + (((IMethod)instruction.Operand).DeclaringType.FullName == nonUserMethod.DeclaringType.BaseType.FullName && |
| 539 | + ((IMethod)instruction.Operand).Name == nonUserMethod.Name))); |
| 540 | + |
| 541 | + var calls = callsToInstrument as Instruction[] ?? callsToInstrument.ToArray(); |
| 542 | + if (!calls.Any()) |
| 543 | + { |
| 544 | + // TODO LOG |
| 545 | + System.Diagnostics.Debugger.Break(); |
| 546 | + return; |
| 547 | + } |
| 548 | + |
| 549 | + var userSymbolMethod = Datadog.Trace.Pdb.DatadogPdbReader.CreatePdbReader(userMethod.Module.Assembly).ReadMethodSymbolInfo((int)(userMethod.MetadataToken)); |
| 550 | + |
| 551 | + var lineProbes = calls.Select(call => CreateLineProbe(call, userMethod, userSymbolMethod)).ToArray(); |
| 552 | + |
| 553 | + foreach (var probe in lineProbes) |
| 554 | + { |
| 555 | + ProbeExpressionsProcessor.Instance.AddProbeProcessor(new LogProbe { CaptureSnapshot = true, Id = "SpanOrigin", Where = new Where() }); |
| 556 | + } |
| 557 | + |
| 558 | + var chosenProbe = lineProbes.First(); |
| 559 | + span.Tags.SetTag("source.file_path", chosenProbe.ProbeFilePath); |
| 560 | + span.Tags.SetTag("source.line_number", chosenProbe.LineNumber.ToString()); |
| 561 | + |
| 562 | + // Add probes |
| 563 | + DebuggerNativeMethods.InstrumentProbes( |
| 564 | + Array.Empty<NativeMethodProbeDefinition>(), |
| 565 | + lineProbes, |
| 566 | + Array.Empty<NativeRemoveProbeRequest>()); |
| 567 | + } |
| 568 | + |
| 569 | + private static NativeLineProbeDefinition CreateLineProbe(Instruction callInstruction, MethodBase userMethod, SymbolMethod userSymbolMethod) |
| 570 | + { |
| 571 | + var closestSequencePoint = userSymbolMethod.SequencePoints.Reverse().First(sp => sp.Offset <= callInstruction.Offset); |
| 572 | + return new NativeLineProbeDefinition(Guid.NewGuid().ToString(), userMethod.Module.ModuleVersionId, userMethod.MetadataToken, (int)(closestSequencePoint.Offset), closestSequencePoint.Line, closestSequencePoint.Document.URL); |
| 573 | + } |
444 | 574 | } |
445 | 575 | } |
0 commit comments