-
Notifications
You must be signed in to change notification settings - Fork 1
/
cor_profiler.cpp
3334 lines (2878 loc) · 130 KB
/
cor_profiler.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "cor_profiler.h"
#include <corprof.h>
#include <string>
#include "corhlpr.h"
#include "version.h"
#include "clr_helpers.h"
#include "dd_profiler_constants.h"
#include "dllmain.h"
#include "environment_variables.h"
#include "il_rewriter.h"
#include "il_rewriter_wrapper.h"
#include "integration_loader.h"
#include "logging.h"
#include "metadata_builder.h"
#include "module_metadata.h"
#include "pal.h"
#include "sig_helpers.h"
#include "resource.h"
#include "util.h"
#ifdef MACOS
#include <mach-o/getsect.h>
#include <mach-o/dyld.h>
#endif
namespace trace {
CorProfiler* profiler = nullptr;
//
// ICorProfilerCallback methods
//
HRESULT STDMETHODCALLTYPE
CorProfiler::Initialize(IUnknown* cor_profiler_info_unknown) {
// check if debug mode is enabled
const auto debug_enabled_value =
GetEnvironmentValue(environment::debug_enabled);
if (debug_enabled_value == WStr("1") || debug_enabled_value == WStr("true")) {
debug_logging_enabled = true;
}
// check if dump il rewrite is enabled
const auto dump_il_rewrite_enabled_value =
GetEnvironmentValue(environment::dump_il_rewrite_enabled);
if (dump_il_rewrite_enabled_value == WStr("1") ||
dump_il_rewrite_enabled_value == WStr("true")) {
dump_il_rewrite_enabled = true;
}
CorProfilerBase::Initialize(cor_profiler_info_unknown);
// check if tracing is completely disabled
const WSTRING tracing_enabled =
GetEnvironmentValue(environment::tracing_enabled);
if (tracing_enabled == WStr("0") || tracing_enabled == WStr("false")) {
Info("DATADOG TRACER DIAGNOSTICS - Profiler disabled in ", environment::tracing_enabled);
return E_FAIL;
}
const auto process_name = GetCurrentProcessName();
const auto include_process_names =
GetEnvironmentValues(environment::include_process_names);
// if there is a process inclusion list, attach profiler only if this
// process's name is on the list
if (!include_process_names.empty() &&
!Contains(include_process_names, process_name)) {
Info("DATADOG TRACER DIAGNOSTICS - Profiler disabled: ", process_name, " not found in ",
environment::include_process_names, ".");
return E_FAIL;
}
const auto exclude_process_names =
GetEnvironmentValues(environment::exclude_process_names);
// attach profiler only if this process's name is NOT on the list
if (Contains(exclude_process_names, process_name)) {
Info("DATADOG TRACER DIAGNOSTICS - Profiler disabled: ", process_name, " found in ",
environment::exclude_process_names, ".");
return E_FAIL;
}
// get Profiler interface
HRESULT hr = cor_profiler_info_unknown->QueryInterface<ICorProfilerInfo4>(
&this->info_);
if (FAILED(hr)) {
Warn("DATADOG TRACER DIAGNOSTICS - Failed to attach profiler: interface ICorProfilerInfo4 not found.");
return E_FAIL;
}
Info("Environment variables:");
for (auto&& env_var : env_vars_to_display) {
WSTRING env_var_value = GetEnvironmentValue(env_var);
if (debug_logging_enabled || !env_var_value.empty()) {
Info(" ", env_var, "=", env_var_value);
}
}
const WSTRING azure_app_services_value =
GetEnvironmentValue(environment::azure_app_services);
if (azure_app_services_value == WStr("1")) {
Info("Profiler is operating within Azure App Services context.");
in_azure_app_services = true;
const auto app_pool_id_value =
GetEnvironmentValue(environment::azure_app_services_app_pool_id);
if (app_pool_id_value.size() > 1 && app_pool_id_value.at(0) == '~') {
Info("DATADOG TRACER DIAGNOSTICS - Profiler disabled: ", environment::azure_app_services_app_pool_id,
" ", app_pool_id_value,
" is recognized as an Azure App Services infrastructure process.");
return E_FAIL;
}
const auto cli_telemetry_profile_value = GetEnvironmentValue(
environment::azure_app_services_cli_telemetry_profile_value);
if (cli_telemetry_profile_value == WStr("AzureKudu")) {
Info("DATADOG TRACER DIAGNOSTICS - Profiler disabled: ", app_pool_id_value,
" is recognized as Kudu, an Azure App Services reserved process.");
return E_FAIL;
}
}
// get path to integration definition JSON files
const WSTRING integrations_paths =
GetEnvironmentValue(environment::integrations_path);
if (integrations_paths.empty()) {
Warn("DATADOG TRACER DIAGNOSTICS - Profiler disabled: ", environment::integrations_path,
" environment variable not set.");
return E_FAIL;
}
const auto is_calltarget_enabled = IsCallTargetEnabled();
// Initialize ReJIT handler and define the Rewriter Callback
if (is_calltarget_enabled) {
rejit_handler = new RejitHandler(this->info_, [this](RejitHandlerModule* mod, RejitHandlerModuleMethod* method) {
return this->CallTarget_RewriterCallback(mod, method);
});
} else {
rejit_handler = nullptr;
}
// load all available integrations from JSON files
const std::vector<Integration> all_integrations =
LoadIntegrationsFromEnvironment();
// get list of disabled integration names
const std::vector<WSTRING> disabled_integration_names =
GetEnvironmentValues(environment::disabled_integrations);
// remove disabled integrations
const std::vector<Integration> integrations =
FilterIntegrationsByName(all_integrations, disabled_integration_names);
integration_methods_ =
FlattenIntegrations(integrations, is_calltarget_enabled);
// check if there are any enabled integrations left
if (integration_methods_.empty()) {
Warn("DATADOG TRACER DIAGNOSTICS - Profiler disabled: no enabled integrations found.");
return E_FAIL;
} else {
Debug("Number of Integrations loaded: ", integration_methods_.size());
}
const WSTRING netstandard_enabled =
GetEnvironmentValue(environment::netstandard_enabled);
// temporarily skip the calls into netstandard.dll that were added in
// https://github.com/DataDog/dd-trace-dotnet/pull/753.
// users can opt-in to the additional instrumentation by setting environment
// variable OTEL_TRACE_NETSTANDARD_ENABLED
if (netstandard_enabled != WStr("1") && netstandard_enabled != WStr("true")) {
integration_methods_ = FilterIntegrationsByTargetAssemblyName(
integration_methods_, {WStr("netstandard")});
}
DWORD event_mask = COR_PRF_MONITOR_JIT_COMPILATION |
COR_PRF_DISABLE_TRANSPARENCY_CHECKS_UNDER_FULL_TRUST |
COR_PRF_MONITOR_MODULE_LOADS |
COR_PRF_MONITOR_ASSEMBLY_LOADS |
COR_PRF_DISABLE_ALL_NGEN_IMAGES;
if (is_calltarget_enabled) {
Info("CallTarget instrumentation is enabled.");
event_mask |= COR_PRF_ENABLE_REJIT;
} else {
Info("CallTarget instrumentation is disabled.");
}
if (!EnableInlining()) {
Info("JIT Inlining is disabled.");
event_mask |= COR_PRF_DISABLE_INLINING;
} else {
Info("JIT Inlining is enabled.");
}
if (DisableOptimizations()) {
Info("Disabling all code optimizations.");
event_mask |= COR_PRF_DISABLE_OPTIMIZATIONS;
}
const WSTRING domain_neutral_instrumentation =
GetEnvironmentValue(environment::domain_neutral_instrumentation);
if (domain_neutral_instrumentation == WStr("1") || domain_neutral_instrumentation == WStr("true")) {
instrument_domain_neutral_assemblies = true;
}
// set event mask to subscribe to events and disable NGEN images
// get ICorProfilerInfo6 for net452+
ICorProfilerInfo6* info6;
hr = cor_profiler_info_unknown->QueryInterface<ICorProfilerInfo6>(&info6);
if (SUCCEEDED(hr)) {
Debug("Interface ICorProfilerInfo6 found.");
hr = info6->SetEventMask2(event_mask, COR_PRF_HIGH_ADD_ASSEMBLY_REFERENCES);
if (instrument_domain_neutral_assemblies) {
Info("Note: The ", environment::domain_neutral_instrumentation, " environment variable is not needed when running on .NET Framework 4.5.2 or higher, and will be ignored.");
}
} else {
hr = this->info_->SetEventMask(event_mask);
if (instrument_domain_neutral_assemblies) {
Info("Detected environment variable ", environment::domain_neutral_instrumentation,
"=", domain_neutral_instrumentation);
Info("Enabling automatic instrumentation of methods called from domain-neutral assemblies. ",
"Please ensure that there is only one AppDomain or, if applications are being hosted in IIS, ",
"ensure that all Application Pools have at most one application each. ",
"Otherwise, a sharing violation (HRESULT 0x80131401) may occur.");
}
}
if (FAILED(hr)) {
Warn("DATADOG TRACER DIAGNOSTICS - Failed to attach profiler: unable to set event mask.");
return E_FAIL;
}
runtime_information_ = GetRuntimeInformation(this->info_);
if (process_name == WStr("w3wp.exe") ||
process_name == WStr("iisexpress.exe")) {
is_desktop_iis = runtime_information_.is_desktop();
}
// writing opcodes vector for the IL dumper
#define OPDEF(c, s, pop, push, args, type, l, s1, s2, flow) \
opcodes_names.push_back(s);
#include "opcode.def"
#undef OPDEF
opcodes_names.push_back("(count)"); // CEE_COUNT
opcodes_names.push_back("->"); // CEE_SWITCH_ARG
// we're in!
Info("Profiler attached.");
this->info_->AddRef();
is_attached_.store(true);
profiler = this;
return S_OK;
}
HRESULT STDMETHODCALLTYPE CorProfiler::AssemblyLoadFinished(AssemblyID assembly_id,
HRESULT hr_status) {
if (FAILED(hr_status)) {
// if assembly failed to load, skip it entirely,
// otherwise we can crash the process if module is not valid
CorProfilerBase::AssemblyLoadFinished(assembly_id, hr_status);
return S_OK;
}
if (!is_attached_) {
return S_OK;
}
if (debug_logging_enabled) {
Debug("AssemblyLoadFinished: ", assembly_id, " ", hr_status);
}
// keep this lock until we are done using the module,
// to prevent it from unloading while in use
std::lock_guard<std::mutex> guard(module_id_to_info_map_lock_);
// double check if is_attached_ has changed to avoid possible race condition with shutdown function
if (!is_attached_) {
return S_OK;
}
const auto assembly_info = GetAssemblyInfo(this->info_, assembly_id);
if (!assembly_info.IsValid()) {
return S_OK;
}
ComPtr<IUnknown> metadata_interfaces;
auto hr = this->info_->GetModuleMetaData(assembly_info.manifest_module_id, ofRead | ofWrite,
IID_IMetaDataImport2,
metadata_interfaces.GetAddressOf());
if (FAILED(hr)) {
Warn("AssemblyLoadFinished failed to get metadata interface for module id ", assembly_info.manifest_module_id,
" from assembly ", assembly_info.name);
return S_OK;
}
// Get the IMetaDataAssemblyImport interface to get metadata from the managed assembly
const auto assembly_import = metadata_interfaces.As<IMetaDataAssemblyImport>(
IID_IMetaDataAssemblyImport);
const auto assembly_metadata = GetAssemblyImportMetadata(assembly_import);
if (debug_logging_enabled) {
Debug("AssemblyLoadFinished: AssemblyName=", assembly_info.name, " AssemblyVersion=", assembly_metadata.version.str());
}
if (assembly_info.name == WStr("OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed")) {
// Configure a version string to compare with the profiler version
std::stringstream ss;
ss << assembly_metadata.version.major << '.'
<< assembly_metadata.version.minor << '.'
<< assembly_metadata.version.build;
auto assembly_version = ToWSTRING(ss.str());
// Check that Major.Minor.Build match the profiler version
if (assembly_version == ToWSTRING(PROFILER_VERSION)) {
Info("AssemblyLoadFinished: OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed v", assembly_version, " matched profiler version v", PROFILER_VERSION);
managed_profiler_loaded_app_domains.insert(assembly_info.app_domain_id);
if (runtime_information_.is_desktop() && corlib_module_loaded) {
// Set the managed_profiler_loaded_domain_neutral flag whenever the managed profiler is loaded shared
if (assembly_info.app_domain_id == corlib_app_domain_id) {
Info("AssemblyLoadFinished: OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed was loaded domain-neutral");
managed_profiler_loaded_domain_neutral = true;
}
else {
Info("AssemblyLoadFinished: OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed was not loaded domain-neutral");
}
}
}
else {
Warn("AssemblyLoadFinished: OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed v", assembly_version, " did not match profiler version v", PROFILER_VERSION);
}
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CorProfiler::ModuleLoadFinished(ModuleID module_id,
HRESULT hr_status) {
if (FAILED(hr_status)) {
// if module failed to load, skip it entirely,
// otherwise we can crash the process if module is not valid
CorProfilerBase::ModuleLoadFinished(module_id, hr_status);
return S_OK;
}
if (!is_attached_) {
return S_OK;
}
// keep this lock until we are done using the module,
// to prevent it from unloading while in use
std::lock_guard<std::mutex> guard(module_id_to_info_map_lock_);
// double check if is_attached_ has changed to avoid possible race condition with shutdown function
if (!is_attached_) {
return S_OK;
}
const auto module_info = GetModuleInfo(this->info_, module_id);
if (!module_info.IsValid()) {
return S_OK;
}
if (debug_logging_enabled) {
Debug("ModuleLoadFinished: ", module_id, " ", module_info.assembly.name,
" AppDomain ", module_info.assembly.app_domain_id, " ",
module_info.assembly.app_domain_name);
}
AppDomainID app_domain_id = module_info.assembly.app_domain_id;
// Identify the AppDomain ID of mscorlib which will be the Shared Domain
// because mscorlib is always a domain-neutral assembly
if (!corlib_module_loaded &&
(module_info.assembly.name == WStr("mscorlib") ||
module_info.assembly.name == WStr("System.Private.CoreLib"))) {
corlib_module_loaded = true;
corlib_app_domain_id = app_domain_id;
ComPtr<IUnknown> metadata_interfaces;
auto hr = this->info_->GetModuleMetaData(module_id, ofRead | ofWrite, IID_IMetaDataImport2, metadata_interfaces.GetAddressOf());
// Get the IMetaDataAssemblyImport interface to get metadata from the
// managed assembly
const auto assembly_import = metadata_interfaces.As<IMetaDataAssemblyImport>(IID_IMetaDataAssemblyImport);
const auto assembly_metadata = GetAssemblyImportMetadata(assembly_import);
hr = assembly_import->GetAssemblyProps(
assembly_metadata.assembly_token, &corAssemblyProperty.ppbPublicKey,
&corAssemblyProperty.pcbPublicKey, &corAssemblyProperty.pulHashAlgId,
NULL, 0, NULL, &corAssemblyProperty.pMetaData,
&corAssemblyProperty.assemblyFlags);
if (FAILED(hr)) {
Warn("AssemblyLoadFinished failed to get properties for COR assembly ");
}
corAssemblyProperty.szName = module_info.assembly.name;
Info("COR library: ", corAssemblyProperty.szName, " ",
corAssemblyProperty.pMetaData.usMajorVersion, ".",
corAssemblyProperty.pMetaData.usMinorVersion, ".",
corAssemblyProperty.pMetaData.usRevisionNumber);
return S_OK;
}
// In IIS, the startup hook will be inserted into a method in System.Web (which is domain-neutral)
// but the Datadog.Trace.ClrProfiler.Managed.Loader assembly that the startup hook loads from a
// byte array will be loaded into a non-shared AppDomain.
// In this case, do not insert another startup hook into that non-shared AppDomain
if (module_info.assembly.name == WStr("Datadog.Trace.ClrProfiler.Managed.Loader")) {
Info("ModuleLoadFinished: Datadog.Trace.ClrProfiler.Managed.Loader loaded into AppDomain ",
app_domain_id, " ", module_info.assembly.app_domain_name);
first_jit_compilation_app_domains.insert(app_domain_id);
return S_OK;
}
if (module_info.IsWindowsRuntime()) {
// We cannot obtain writable metadata interfaces on Windows Runtime modules
// or instrument their IL.
Debug("ModuleLoadFinished skipping Windows Metadata module: ", module_id,
" ", module_info.assembly.name);
return S_OK;
}
for (auto&& skip_assembly_pattern : skip_assembly_prefixes) {
if (module_info.assembly.name.rfind(skip_assembly_pattern, 0) == 0) {
Debug("ModuleLoadFinished skipping module by pattern: ", module_id, " ",
module_info.assembly.name);
return S_OK;
}
}
for (auto&& skip_assembly : skip_assemblies) {
if (module_info.assembly.name == skip_assembly) {
Debug("ModuleLoadFinished skipping known module: ", module_id, " ",
module_info.assembly.name);
return S_OK;
}
}
std::vector<IntegrationMethod> filtered_integrations =
FilterIntegrationsByCaller(integration_methods_, module_info.assembly);
if (filtered_integrations.empty()) {
// we don't need to instrument anything in this module, skip it
Debug("ModuleLoadFinished skipping module (filtered by caller): ",
module_id, " ", module_info.assembly.name);
return S_OK;
}
ComPtr<IUnknown> metadata_interfaces;
auto hr = this->info_->GetModuleMetaData(module_id, ofRead | ofWrite,
IID_IMetaDataImport2,
metadata_interfaces.GetAddressOf());
if (FAILED(hr)) {
Warn("ModuleLoadFinished failed to get metadata interface for ", module_id,
" ", module_info.assembly.name);
return S_OK;
}
const auto metadata_import =
metadata_interfaces.As<IMetaDataImport2>(IID_IMetaDataImport);
const auto metadata_emit =
metadata_interfaces.As<IMetaDataEmit2>(IID_IMetaDataEmit);
const auto assembly_import = metadata_interfaces.As<IMetaDataAssemblyImport>(
IID_IMetaDataAssemblyImport);
const auto assembly_emit =
metadata_interfaces.As<IMetaDataAssemblyEmit>(IID_IMetaDataAssemblyEmit);
// don't skip Microsoft.AspNetCore.Hosting so we can run the startup hook and
// subscribe to DiagnosticSource events.
// don't skip Dapper: it makes ADO.NET calls even though it doesn't reference
// System.Data or System.Data.Common
if (module_info.assembly.name != WStr("Microsoft.AspNetCore.Hosting") &&
module_info.assembly.name != WStr("Dapper")) {
filtered_integrations =
FilterIntegrationsByTarget(filtered_integrations, assembly_import);
if (filtered_integrations.empty()) {
// we don't need to instrument anything in this module, skip it
Debug("ModuleLoadFinished skipping module (filtered by target): ",
module_id, " ", module_info.assembly.name);
return S_OK;
}
}
mdModule module;
hr = metadata_import->GetModuleFromScope(&module);
if (FAILED(hr)) {
Warn("ModuleLoadFinished failed to get module metadata token for ",
module_id, " ", module_info.assembly.name);
return S_OK;
}
GUID module_version_id;
hr = metadata_import->GetScopeProps(nullptr, 0, nullptr, &module_version_id);
if (FAILED(hr)) {
Warn("ModuleLoadFinished failed to get module_version_id for ", module_id,
" ", module_info.assembly.name);
return S_OK;
}
ModuleMetadata* module_metadata = new ModuleMetadata(
metadata_import, metadata_emit, assembly_import, assembly_emit,
module_info.assembly.name, app_domain_id,
module_version_id, filtered_integrations, &corAssemblyProperty);
// store module info for later lookup
module_id_to_info_map_[module_id] = module_metadata;
Debug("ModuleLoadFinished stored metadata for ", module_id, " ",
module_info.assembly.name, " AppDomain ",
module_info.assembly.app_domain_id, " ",
module_info.assembly.app_domain_name);
// We call the function to analyze the module and request the ReJIT of integrations defined in this module.
if (IsCallTargetEnabled()) {
CallTarget_RequestRejitForModule(module_id, module_metadata, filtered_integrations);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CorProfiler::ModuleUnloadStarted(ModuleID module_id) {
if (!is_attached_) {
return S_OK;
}
if (debug_logging_enabled) {
const auto module_info = GetModuleInfo(this->info_, module_id);
if (module_info.IsValid()) {
Debug("ModuleUnloadStarted: ", module_id, " ", module_info.assembly.name,
" AppDomain ", module_info.assembly.app_domain_id, " ",
module_info.assembly.app_domain_name);
} else {
Debug("ModuleUnloadStarted: ", module_id);
}
}
// take this lock so we block until the
// module metadata is not longer being used
std::lock_guard<std::mutex> guard(module_id_to_info_map_lock_);
// double check if is_attached_ has changed to avoid possible race condition with shutdown function
if (!is_attached_) {
return S_OK;
}
// remove module metadata from map
auto findRes = module_id_to_info_map_.find(module_id);
if (findRes != module_id_to_info_map_.end()) {
ModuleMetadata* metadata = findRes->second;
// remove appdomain id from managed_profiler_loaded_app_domains set
if (managed_profiler_loaded_app_domains.find(metadata->app_domain_id) !=
managed_profiler_loaded_app_domains.end()) {
managed_profiler_loaded_app_domains.erase(metadata->app_domain_id);
}
module_id_to_info_map_.erase(module_id);
delete metadata;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CorProfiler::Shutdown() {
CorProfilerBase::Shutdown();
// keep this lock until we are done using the module,
// to prevent it from unloading while in use
std::lock_guard<std::mutex> guard(module_id_to_info_map_lock_);
if (rejit_handler != nullptr) {
rejit_handler->Shutdown();
}
Warn("Exiting.");
is_attached_.store(false);
Logger::Shutdown();
return S_OK;
}
HRESULT STDMETHODCALLTYPE CorProfiler::ProfilerDetachSucceeded() {
if (!is_attached_) {
return S_OK;
}
CorProfilerBase::ProfilerDetachSucceeded();
// keep this lock until we are done using the module,
// to prevent it from unloading while in use
std::lock_guard<std::mutex> guard(module_id_to_info_map_lock_);
// double check if is_attached_ has changed to avoid possible race condition with shutdown function
if (!is_attached_) {
return S_OK;
}
Warn("Detaching profiler.");
Logger::Instance()->Flush();
is_attached_.store(false);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CorProfiler::JITCompilationStarted(
FunctionID function_id, BOOL is_safe_to_block) {
if (!is_attached_ || !is_safe_to_block) {
return S_OK;
}
// keep this lock until we are done using the module,
// to prevent it from unloading while in use
std::lock_guard<std::mutex> guard(module_id_to_info_map_lock_);
// double check if is_attached_ has changed to avoid possible race condition with shutdown function
if (!is_attached_) {
return S_OK;
}
ModuleID module_id;
mdToken function_token = mdTokenNil;
HRESULT hr = this->info_->GetFunctionInfo(function_id, nullptr, &module_id,
&function_token);
if (FAILED(hr)) {
Warn("JITCompilationStarted: Call to ICorProfilerInfo4.GetFunctionInfo() failed for ", function_id);
return S_OK;
}
// Verify that we have the metadata for this module
ModuleMetadata* module_metadata = nullptr;
auto findRes = module_id_to_info_map_.find(module_id);
if (findRes != module_id_to_info_map_.end()) {
module_metadata = findRes->second;
}
if (module_metadata == nullptr) {
// we haven't stored a ModuleMetadata for this module,
// so we can't modify its IL
return S_OK;
}
// get function info
const auto caller =
GetFunctionInfo(module_metadata->metadata_import, function_token);
if (!caller.IsValid()) {
return S_OK;
}
if (debug_logging_enabled) {
Debug("JITCompilationStarted: function_id=", function_id,
" token=", function_token, " name=", caller.type.name, ".",
caller.name, "()");
}
// IIS: Ensure that the startup hook is inserted into System.Web.Compilation.BuildManager.InvokePreStartInitMethods.
// This will be the first call-site considered for the startup hook injection,
// which correctly loads Datadog.Trace.ClrProfiler.Managed.Loader into the application's
// own AppDomain because at this point in the code path, the ApplicationImpersonationContext
// has been started.
//
// Note: This check must only run on desktop because it is possible (and the default) to host
// ASP.NET Core in-process, so a new .NET Core runtime is instantiated and run in the same w3wp.exe process
auto valid_startup_hook_callsite = true;
if (is_desktop_iis) {
valid_startup_hook_callsite =
module_metadata->assemblyName == WStr("System.Web") &&
caller.type.name == WStr("System.Web.Compilation.BuildManager") &&
caller.name == WStr("InvokePreStartInitMethods");
} else if (module_metadata->assemblyName == WStr("System") ||
module_metadata->assemblyName == WStr("System.Net.Http")) {
valid_startup_hook_callsite = false;
}
// The first time a method is JIT compiled in an AppDomain, insert our startup
// hook which, at a minimum, must add an AssemblyResolve event so we can find
// OpenTelemetry.AutoInstrumentation.ClrProfiler.Managed.dll and its dependencies on-disk since it
// is no longer provided in a NuGet package
if (valid_startup_hook_callsite &&
first_jit_compilation_app_domains.find(module_metadata->app_domain_id) ==
first_jit_compilation_app_domains.end()) {
bool domain_neutral_assembly = runtime_information_.is_desktop() && corlib_module_loaded && module_metadata->app_domain_id == corlib_app_domain_id;
Info("JITCompilationStarted: Startup hook registered in function_id=", function_id,
" token=", function_token, " name=", caller.type.name, ".",
caller.name, "(), assembly_name=", module_metadata->assemblyName,
" app_domain_id=", module_metadata->app_domain_id,
" domain_neutral=", domain_neutral_assembly);
first_jit_compilation_app_domains.insert(module_metadata->app_domain_id);
hr = RunILStartupHook(module_metadata->metadata_emit, module_id,
function_token);
if (FAILED(hr)) {
Warn("JITCompilationStarted: Call to RunILStartupHook() failed for ", module_id, " ", function_token);
return S_OK;
}
if (is_desktop_iis) {
hr = AddIISPreStartInitFlags(module_id,
function_token);
if (FAILED(hr)) {
Warn("JITCompilationStarted: Call to AddIISPreStartInitFlags() failed for ",
module_id, " ", function_token);
return S_OK;
}
}
}
// we don't actually need to instrument anything in
// Microsoft.AspNetCore.Hosting, it was included only to ensure the startup
// hook is called for AspNetCore applications
if (module_metadata->assemblyName == WStr("Microsoft.AspNetCore.Hosting")) {
return S_OK;
}
// Get valid method replacements for this caller method
const auto method_replacements =
module_metadata->GetMethodReplacementsForCaller(caller);
if (method_replacements.empty()) {
return S_OK;
}
// Perform method insertion calls
hr = ProcessInsertionCalls(module_metadata,
function_id,
module_id,
function_token,
caller,
method_replacements);
if (FAILED(hr)) {
Warn("JITCompilationStarted: Call to ProcessInsertionCalls() failed for ", function_id, " ", module_id, " ", function_token);
return S_OK;
}
// Perform method replacement calls
hr = ProcessReplacementCalls(module_metadata,
function_id,
module_id,
function_token,
caller,
method_replacements);
if (FAILED(hr)) {
Warn("JITCompilationStarted: Call to ProcessReplacementCalls() failed for ", function_id, " ", module_id, " ", function_token);
return S_OK;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CorProfiler::JITInlining(FunctionID callerId,
FunctionID calleeId, BOOL* pfShouldInline) {
if (!is_attached_) {
return S_OK;
}
ModuleID calleeModuleId;
mdToken calleFunctionToken = mdTokenNil;
auto hr = this->info_->GetFunctionInfo(calleeId, NULL, &calleeModuleId,
&calleFunctionToken);
*pfShouldInline = true;
if (FAILED(hr)) {
Warn("*** JITInlining: Failed to get the function info of the calleId: ", calleeId);
return S_OK;
}
if (rejit_handler == nullptr) {
return S_OK;
}
RejitHandlerModule* handlerModule = nullptr;
if (rejit_handler->TryGetModule(calleeModuleId, &handlerModule)) {
RejitHandlerModuleMethod* handlerMethod = nullptr;
if (handlerModule->TryGetMethod(calleFunctionToken, &handlerMethod)) {
Debug("*** JITInlining: Inlining disabled for [ModuleId=", calleeModuleId,
", MethodDef=", TokenStr(&calleFunctionToken), "]");
*pfShouldInline = false;
return S_OK;
}
}
return S_OK;
}
//
// ICorProfilerCallback6 methods
//
HRESULT STDMETHODCALLTYPE CorProfiler::GetAssemblyReferences(
const WCHAR* wszAssemblyPath,
ICorProfilerAssemblyReferenceProvider* pAsmRefProvider) {
if (in_azure_app_services) {
Debug("GetAssemblyReferences skipping entire callback because this is running in Azure App Services, which isn't yet supported for this feature. AssemblyPath=", wszAssemblyPath);
return S_OK;
}
// Convert the assembly path to the assembly name, assuming the assembly name
// is either <assembly_name.ni.dll> or <assembly_name>.dll
auto assemblyPathString = ToString(wszAssemblyPath);
auto filename =
assemblyPathString.substr(assemblyPathString.find_last_of("\\/") + 1);
auto lastNiDllPeriodIndex = filename.rfind(".ni.dll");
auto lastDllPeriodIndex = filename.rfind(".dll");
if (lastNiDllPeriodIndex != std::string::npos) {
filename.erase(lastNiDllPeriodIndex, 7);
} else if (lastDllPeriodIndex != std::string::npos) {
filename.erase(lastDllPeriodIndex, 4);
}
const WSTRING assembly_name = ToWSTRING(filename);
// Skip known framework assemblies that we will not instrument and,
// as a result, will not need an assembly reference to the
// managed profiler
for (auto&& skip_assembly_pattern : skip_assembly_prefixes) {
if (assembly_name.rfind(skip_assembly_pattern, 0) == 0) {
Debug("GetAssemblyReferences skipping module by pattern: Name=",
assembly_name, " Path=", wszAssemblyPath);
return S_OK;
}
}
for (auto&& skip_assembly : skip_assemblies) {
if (assembly_name == skip_assembly) {
Debug("GetAssemblyReferences skipping known assembly: Name=",
assembly_name, " Path=", wszAssemblyPath);
return S_OK;
}
}
// Construct an ASSEMBLYMETADATA structure for the managed profiler that can
// be consumed by the runtime
const AssemblyReference assemblyReference = trace::AssemblyReference(managed_profiler_full_assembly_version);
ASSEMBLYMETADATA assembly_metadata{};
assembly_metadata.usMajorVersion = assemblyReference.version.major;
assembly_metadata.usMinorVersion = assemblyReference.version.minor;
assembly_metadata.usBuildNumber = assemblyReference.version.build;
assembly_metadata.usRevisionNumber = assemblyReference.version.revision;
if (assemblyReference.locale == WStr("neutral")) {
assembly_metadata.szLocale = const_cast<WCHAR*>(WStr("\0"));
assembly_metadata.cbLocale = 0;
} else {
assembly_metadata.szLocale =
const_cast<WCHAR*>(assemblyReference.locale.c_str());
assembly_metadata.cbLocale = (DWORD)(assemblyReference.locale.size());
}
DWORD public_key_size = 8;
if (assemblyReference.public_key == trace::PublicKey()) {
public_key_size = 0;
}
COR_PRF_ASSEMBLY_REFERENCE_INFO asmRefInfo;
asmRefInfo.pbPublicKeyOrToken =
(void*)&assemblyReference.public_key.data[0];
asmRefInfo.cbPublicKeyOrToken = public_key_size;
asmRefInfo.szName = assemblyReference.name.c_str();
asmRefInfo.pMetaData = &assembly_metadata;
asmRefInfo.pbHashValue = nullptr;
asmRefInfo.cbHashValue = 0;
asmRefInfo.dwAssemblyRefFlags = 0;
// Attempt to extend the assembly closure of the provided assembly to include
// the managed profiler
auto hr = pAsmRefProvider->AddAssemblyReference(&asmRefInfo);
if (FAILED(hr)) {
Warn("GetAssemblyReferences failed for call from ", wszAssemblyPath);
return S_OK;
}
Debug("GetAssemblyReferences extending assembly closure for ",
assembly_name, " to include ", asmRefInfo.szName,
". Path=", wszAssemblyPath);
instrument_domain_neutral_assemblies = true;
return S_OK;
}
bool CorProfiler::IsAttached() const { return is_attached_; }
//
// Helper methods
//
HRESULT CorProfiler::ProcessReplacementCalls(
ModuleMetadata* module_metadata,
const FunctionID function_id,
const ModuleID module_id,
const mdToken function_token,
const FunctionInfo& caller,
const std::vector<MethodReplacement> method_replacements) {
ILRewriter rewriter(this->info_, nullptr, module_id, function_token);
bool modified = false;
auto hr = rewriter.Import();
if (FAILED(hr)) {
Warn("ProcessReplacementCalls: Call to ILRewriter.Import() failed for ", module_id, " ", function_token);
return hr;
}
std::string original_code;
if (dump_il_rewrite_enabled) {
original_code =
GetILCodes("*** IL original code for caller: ", &rewriter, caller, module_metadata);
}
// Perform method call replacements
for (auto& method_replacement : method_replacements) {
// Exit early if the method replacement isn't actually doing a replacement
if (method_replacement.wrapper_method.action != WStr("ReplaceTargetMethod")) {
continue;
}
const auto& wrapper_method_key =
method_replacement.wrapper_method.get_method_cache_key();
// Exit early if we previously failed to store the method ref for this wrapper_method
if (module_metadata->IsFailedWrapperMemberKey(wrapper_method_key)) {
continue;
}
// for each IL instruction
for (ILInstr* pInstr = rewriter.GetILList()->m_pNext;
pInstr != rewriter.GetILList(); pInstr = pInstr->m_pNext) {
// only CALL or CALLVIRT
if (pInstr->m_opcode != CEE_CALL && pInstr->m_opcode != CEE_CALLVIRT) {
continue;
}
// get the target function info, continue if its invalid
auto target =
GetFunctionInfo(module_metadata->metadata_import, pInstr->m_Arg32);
if (!target.IsValid()) {
continue;
}
// make sure the type and method names match
if (method_replacement.target_method.type_name != target.type.name ||
method_replacement.target_method.method_name != target.name) {
continue;
}
// we add 3 parameters to every wrapper method: opcode, mdToken, and
// module_version_id
const short added_parameters_count = 3;
auto wrapper_method_signature_size =
method_replacement.wrapper_method.method_signature.data.size();
if (wrapper_method_signature_size < (added_parameters_count + 3)) {
// wrapper signature must have at least 6 bytes
// 0:{CallingConvention}|1:{ParamCount}|2:{ReturnType}|3:{OpCode}|4:{mdToken}|5:{ModuleVersionId}
if (debug_logging_enabled) {
Debug(
"JITCompilationStarted skipping function call: wrapper signature "
"too short. function_id=",
function_id, " token=", function_token,
" wrapper_method=", method_replacement.wrapper_method.type_name,
".", method_replacement.wrapper_method.method_name,
"() wrapper_method_signature_size=",
wrapper_method_signature_size);
}
continue;
}
auto expected_number_args = method_replacement.wrapper_method
.method_signature.NumberOfArguments();
// subtract the last arguments we add to every wrapper
expected_number_args = expected_number_args - added_parameters_count;
if (target.signature.IsInstanceMethod()) {
// We always pass the instance as the first argument