Automatic LoaderOptimization.SingleDomain (#4187)

* All non-default app domains use LoaderOptimization.SingleDomain

Solves "Loading this assembly would produce a different grant set from other instances" for IIS .Net Framework applications.
__DDVoidMethodType__  injected to mscorlib.
__DDVoidMethodType__.__DDPatchAppDomainSetup__ use reflection-created delegate that calls
OpenTelemetry.AutoInstrumentation.Loader.AppConfigUpdater.ModifyConfig to modify AppDomainSetup by setting LoaderOptimization.SingleDomain.
That method called from AppDomain.CreateDomain and AppDomainManager.CreateDomainHelper.
OpenTelemetry.AutoInstrumentation.Loader.AppConfigUpdater loader assembly loaded from __DDVoidMethodType__ static ctor.
Added helpers: SignatureBuilder and MemberResolver.

* Added installer parameter to skip GAC registration

Added Asp.Net tests without registration assemblies in GAC.
Removed LoaderOptimization=SingleDomain through registry change for ASP.Net tests.

* Removed LoaderOptimization workaround from test dockerfiles

* Code format applied

* Updated test build to produce no-GAC docker images

* Extended  SignatureBuilder by providing semi-safe wrappers

Moved it to be header-only.
Added unit tests for SignatureBuilder.

* Updated change log and documentation

* Removed Troubleshooting link from IIS instrumentation docs

* Fixed accident docker file changes

* Added comment about callers of AppConfigUpdater.ModifyConfig

* Update CHANGELOG.md typo based on review

Co-authored-by: Chris Ventura <45495992+nrcventura@users.noreply.github.com>

* Smaller cleanups

* Minor names/comments changes based on @zacharycmontoya review

* Fixed formatting

* Make AppConfigUpdater internal

---------

Co-authored-by: Chris Ventura <45495992+nrcventura@users.noreply.github.com>
This commit is contained in:
Igor Kiselev 2025-05-12 04:39:04 -07:00 committed by GitHub
parent 35976eafc7
commit 6e307db05b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 2264 additions and 640 deletions

View File

@ -11,11 +11,16 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h
- Support for [`Npgsql`](https://www.nuget.org/packages/Npgsql/)
metrics instrumentation for versions `6.0.0`+.
- In install script, Install-OpenTelemetryCore accepts optional argument RegisterAssembliesInGAC,
which is true by default. When set to false, assemblies would not be installed in GAC.
- In install script, new function added: Register-AssembliesInGAC. It installs OpenTelemetry assemblies
and dependencies in GAC.
### Changed
- `otel-dotnet-auto-install.sh` now optionally uses `wget` instead of `curl`,
improving compatibility with `mcr.microsoft.com/dotnet/runtime` Alpine images.
- Non-default application domains will be forced to load with LoaderOptimization.SingleDomain
#### Dependency updates

View File

@ -135,7 +135,7 @@ partial class Build
.Executes(() =>
{
var aspNetProject = Solution.GetProjectByName(Projects.Tests.Applications.AspNet);
BuildDockerImage(aspNetProject, "integrated", "classic");
BuildDockerImage(aspNetProject, "integrated-nogac", "classic-nogac", "integrated", "classic");
var wcfProject = Solution.GetProjectByName(Projects.Tests.Applications.WcfIis);
BuildDockerImage(wcfProject);

View File

@ -99,7 +99,3 @@ application pool.
1. Close all external windows and press 'Apply' in the main
'Configuration Editor' view.
1. Restart your application.
## Troubleshooting
See [`troubleshooting.md`](troubleshooting.md#iis---loading-this-assembly-would-produce-a-different-grant-set-from-other-instances).

View File

@ -232,23 +232,3 @@ Sample Diagnostic Output:
For resolving runtime store assembly version conflicts, follow the same solution
as outlined for [Assembly version conflicts](#assembly-version-conflicts) in
this document.
### IIS - Loading this assembly would produce a different grant set from other instances
#### Symptoms
.NET Framework, IIS hosted application crashes and you get an event similar to
the following:
```log
Exception: System.IO.FileLoadException
Message: Loading this assembly would produce a different grant set from other instances.
```
#### Solution
Create a new `DWORD` value called `LoaderOptimization` and give it the value `1`
under the `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework`.
It will allows to load different versions of the same application to different domains.
It might increase CPU and memory usage.

View File

@ -295,6 +295,36 @@ function Is-Greater-Version($v1, $v2) {
return $false;
}
<#
.SYNOPSIS
Installs OpenTelemetry .NET Assemblies in GAC.
.PARAMETER InstallDir
Default: <auto> - the default path is Program Files dir.
Install path of the OpenTelemetry .NET Automatic Instrumentation
Possible values: <auto>, (Custom path)
#>
function Register-AssembliesInGAC() {
$installDir = Get-Current-InstallDir
# Register .NET Framework dlls in GAC
[System.Reflection.Assembly]::Load("System.EnterpriseServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") | Out-Null
$publish = New-Object System.EnterpriseServices.Internal.Publish
$dlls = Get-ChildItem -Path $installDir\netfx\ -Filter *.dll -File
for ($i = 0; $i -lt $dlls.Count; $i++) {
$percentageComplete = $i / $dlls.Count * 100
Write-Progress -Activity "Registering .NET Framework dlls in GAC" `
-Status "Module $($i+1) out of $($dlls.Count). Installing $($dlls[$i].Name):" `
-PercentComplete $percentageComplete
if (Test-AssemblyNotForGAC $dlls[$i].Name) {
continue
}
$publish.GacInstall($dlls[$i].FullName)
}
Write-Progress -Activity "Registering .NET Framework dlls in GAC" -Status "Ready" -Completed
}
<#
.SYNOPSIS
Installs OpenTelemetry .NET Automatic Instrumentation.
@ -308,7 +338,9 @@ function Install-OpenTelemetryCore() {
[Parameter(Mandatory = $false)]
[string]$InstallDir = "<auto>",
[Parameter(Mandatory = $false)]
[string]$LocalPath
[string]$LocalPath,
[Parameter(Mandatory = $false)]
[bool]$RegisterAssembliesInGAC = $true
)
$version = "v{{VERSION}}"
@ -332,23 +364,9 @@ function Install-OpenTelemetryCore() {
# OpenTelemetry service locator
[System.Environment]::SetEnvironmentVariable($ServiceLocatorVariable, $installDir, [System.EnvironmentVariableTarget]::Machine)
# Register .NET Framework dlls in GAC
[System.Reflection.Assembly]::Load("System.EnterpriseServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") | Out-Null
$publish = New-Object System.EnterpriseServices.Internal.Publish
$dlls = Get-ChildItem -Path $installDir\netfx\ -Filter *.dll -File
for ($i = 0; $i -lt $dlls.Count; $i++) {
$percentageComplete = $i / $dlls.Count * 100
Write-Progress -Activity "Registering .NET Framework dlls in GAC" `
-Status "Module $($i+1) out of $($dlls.Count). Installing $($dlls[$i].Name):" `
-PercentComplete $percentageComplete
if (Test-AssemblyNotForGAC $dlls[$i].Name) {
continue
}
$publish.GacInstall($dlls[$i].FullName)
if ($RegisterAssembliesInGAC){
Register-AssembliesInGAC $installDir
}
Write-Progress -Activity "Registering .NET Framework dlls in GAC" -Status "Ready" -Completed
}
catch {
$message = $_
@ -406,7 +424,9 @@ function Uninstall-OpenTelemetryCore() {
function Update-OpenTelemetryCore() {
param(
[Parameter(Mandatory = $false)]
[bool]$RegisterIIS = $false
[bool]$RegisterIIS = $false,
[Parameter(Mandatory = $false)]
[bool]$RegisterAssembliesInGAC = $true
)
$currentInstallVersion = Get-OpenTelemetryInstallVersion
@ -436,7 +456,7 @@ function Update-OpenTelemetryCore() {
Remove-Module OpenTelemetry.Dotnet.Auto
Import-Module $modulePath
Install-OpenTelemetryCore -InstallDir $installDir
Install-OpenTelemetryCore -InstallDir $installDir -RegisterAssembliesInGAC $$RegisterAssembliesInGAC
if ($RegisterIIS) {
Register-OpenTelemetryForIIS
}
@ -660,3 +680,4 @@ Export-ModuleMember -Function Get-OpenTelemetryInstallDirectory
Export-ModuleMember -Function Get-OpenTelemetryInstallVersion
Export-ModuleMember -Function Enable-OpenTelemetryForIISAppPool
Export-ModuleMember -Function Disable-OpenTelemetryForIISAppPool
Export-ModuleMember -Function Register-AssembliesInGAC

View File

@ -0,0 +1,24 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
#if NETFRAMEWORK
namespace OpenTelemetry.AutoInstrumentation.Loader;
/// <summary>
/// Handles update of config files for non-default AppDomain
/// </summary>
internal static class AppConfigUpdater
{
/// <summary>
/// Modify assembly bindings in appDomainSetup.
/// Will be called through reflection when new <see cref="System.AppDomain"/> created.
/// Call done using bytecode modifications for <see cref="AppDomain.CreateDomain(string,System.Security.Policy.Evidence,System.AppDomainSetup)"/>
/// and <see cref="AppDomainManager.CreateDomainHelper(string,System.Security.Policy.Evidence,System.AppDomainSetup)"/>.
/// </summary>
/// <param name="appDomainSetup">appDomainSetup to be updated</param>
public static void ModifyConfig(AppDomainSetup appDomainSetup)
{
appDomainSetup.LoaderOptimization = LoaderOptimization.SingleDomain;
}
}
#endif

View File

@ -177,6 +177,7 @@ add_library("OpenTelemetry.AutoInstrumentation.Native.static" STATIC
il_rewriter_wrapper.cpp
il_rewriter.cpp
integration.cpp
member_resolver.cpp
metadata_builder.cpp
miniutf.cpp
string.cpp

View File

@ -200,6 +200,7 @@
<ClInclude Include="logger.h" />
<ClInclude Include="logger_impl.h" />
<ClInclude Include="macros.h" />
<ClInclude Include="member_resolver.h" />
<ClInclude Include="metadata_builder.h" />
<ClInclude Include="method_rewriter.h" />
<ClInclude Include="miniutf.hpp" />
@ -211,6 +212,7 @@
<ClInclude Include="rejit_handler.h" />
<ClInclude Include="rejit_preprocessor.h" />
<ClInclude Include="rejit_work_offloader.h" />
<ClInclude Include="signature_builder.h" />
<ClInclude Include="startup_hook.h" />
<ClInclude Include="stats.h" />
<ClInclude Include="string.h" />
@ -230,6 +232,7 @@
<ClCompile Include="il_rewriter.cpp" />
<ClCompile Include="il_rewriter_wrapper.cpp" />
<ClCompile Include="integration.cpp" />
<ClCompile Include="member_resolver.cpp" />
<ClCompile Include="metadata_builder.cpp" />
<ClCompile Include="method_rewriter.cpp" />
<ClCompile Include="miniutf.cpp" />

File diff suppressed because it is too large Load Diff

View File

@ -106,6 +106,11 @@ private:
//
HRESULT RunAutoInstrumentationLoader(const ComPtr<IMetaDataEmit2>&, const ModuleID module_id, const mdToken function_token, const FunctionInfo& caller, const ModuleMetadata& module_metadata);
HRESULT GenerateLoaderMethod(const ModuleID module_id, mdMethodDef* ret_method_token);
HRESULT GenerateLoaderType(const ModuleID module_id,
mdTypeDef* loader_type,
mdMethodDef* init_method,
mdMethodDef* patch_app_domain_setup_method);
HRESULT ModifyAppDomainCreate(const ModuleID module_id, mdMethodDef patch_app_domain_setup_method);
HRESULT AddIISPreStartInitFlags(const ModuleID module_id, const mdToken function_token);
#endif

View File

@ -0,0 +1,40 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
#include "member_resolver.h"
namespace trace
{
MemberResolver::MemberResolver(const ComPtr<IMetaDataImport2>& import, const ComPtr<IMetaDataEmit2>& emit)
: metadaImport_(import), metadaEmit_(emit)
{
}
HRESULT MemberResolver::GetTypeRefOrDefByName(mdToken tkResolutionScope, LPCWSTR szName, mdToken* token)
{
if (TypeFromToken(tkResolutionScope) == mdtAssembly)
{
tkResolutionScope = mdTokenNil;
}
if (tkResolutionScope == mdTokenNil || TypeFromToken(tkResolutionScope) == mdtTypeDef)
{
return metadaImport_->FindTypeDefByName(szName, tkResolutionScope, token);
}
return metadaEmit_->DefineTypeRefByName(tkResolutionScope, szName, token);
}
HRESULT MemberResolver::GetMemberRefOrDef(
mdToken tkScope, LPCWSTR szName, PCCOR_SIGNATURE pvSigBlob, ULONG cbSigBlob, mdToken* token)
{
if (TypeFromToken(tkScope) == mdtTypeDef)
{
return metadaImport_->FindMember(tkScope, szName, pvSigBlob, cbSigBlob, token);
}
return metadaEmit_->DefineMemberRef(tkScope, szName, pvSigBlob, cbSigBlob, token);
}
} // namespace trace

View File

@ -0,0 +1,43 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef OTEL_CLR_PROFILER_MEMBER_RESOLVER_H_
#define OTEL_CLR_PROFILER_MEMBER_RESOLVER_H_
#include <vector>
#include <corhlpr.h>
#include "com_ptr.h"
#include "module_metadata.h"
namespace trace
{
class MemberResolver
{
private:
ComPtr<IMetaDataImport2> metadaImport_;
ComPtr<IMetaDataEmit2> metadaEmit_;
public:
MemberResolver(const ComPtr<IMetaDataImport2>& import, const ComPtr<IMetaDataEmit2>& emit);
/// <summary>
/// Uses IMetaDataImport::FindTypeDefByName if tkResolutionScope is mdTokenNill, TypeDef or Assembly
/// Uses IMetaDataEmit::DefineTypeRefByName otherwise (if tkResolutionScope is AssemblyRef or TypeRef)
/// </summary>
HRESULT GetTypeRefOrDefByName(mdToken tkResolutionScope, LPCWSTR szName, mdToken* token);
/// <summary>
/// Uses IMetaDataImport::FindMember if tkScope is TypeDef
/// Uses IMetaDataEmit::DefineMemberRef otherwise (if tkScope is TypeRef or TypeSpec)
/// </summary>
HRESULT GetMemberRefOrDef(mdToken tkScope, LPCWSTR szName, PCCOR_SIGNATURE pvSigBlob, ULONG cbSigBlob, mdToken* token);
};
} // namespace trace
#endif // OTEL_CLR_PROFILER_MEMBER_RESOLVER_H_

View File

@ -0,0 +1,288 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef OTEL_CLR_PROFILER_SIGNATURE_BUILDER_H_
#define OTEL_CLR_PROFILER_SIGNATURE_BUILDER_H_
#include <vector>
#include <corhlpr.h>
namespace trace
{
class SignatureData
{
protected:
std::vector<COR_SIGNATURE> blob_;
public:
SignatureData() = default;
SignatureData(std::initializer_list<COR_SIGNATURE> bytes) : blob_(bytes) {}
SignatureData(std::initializer_list<SignatureData> elements)
{
for (auto& inner : elements)
{
blob_.insert(blob_.end(), inner.blob_.begin(), inner.blob_.end());
}
}
PCCOR_SIGNATURE Head() const
{
return blob_.data();
}
ULONG Size() const
{
return static_cast<ULONG>(blob_.size());
}
};
class SignatureBuilder : public virtual SignatureData
{
public:
SignatureBuilder() = default;
SignatureBuilder(std::initializer_list<COR_SIGNATURE> bytes) : SignatureData(bytes) {}
SignatureBuilder(std::initializer_list<SignatureData> elements) : SignatureData(elements) {}
SignatureBuilder& PushRawByte(COR_SIGNATURE byte)
{
blob_.push_back(byte);
return *this;
}
template <class InputIt>
SignatureBuilder& PushRawBytes(InputIt first, InputIt last)
{
blob_.insert(blob_.end(), first, last);
return *this;
}
SignatureBuilder& PushRawBytes(std::initializer_list<COR_SIGNATURE> bytes)
{
blob_.insert(blob_.end(), bytes);
return *this;
}
SignatureBuilder& PushCompressedData(ULONG data)
{
COR_SIGNATURE compressed[sizeof(ULONG)];
ULONG compressedSize = CorSigCompressData(data, compressed);
for (ULONG i = 0; i < compressedSize; i++)
{
PushRawByte(compressed[i]);
}
return *this;
}
SignatureBuilder& PushToken(mdToken token)
{
COR_SIGNATURE compressed[sizeof(mdToken)];
ULONG compressedSize = CorSigCompressToken(token, compressed);
for (ULONG i = 0; i < compressedSize; i++)
{
PushRawByte(compressed[i]);
}
return *this;
}
SignatureBuilder& Push(const SignatureData& inner)
{
blob_.insert(blob_.end(), inner.Head(), inner.Head()+inner.Size());
return *this;
}
enum class MethodCallingConvection : COR_SIGNATURE
{
Static = IMAGE_CEE_CS_CALLCONV_DEFAULT,
Instance = IMAGE_CEE_CS_CALLCONV_HASTHIS
};
enum class TokenTypeMode : COR_SIGNATURE
{
ValueType = ELEMENT_TYPE_VALUETYPE,
Class = ELEMENT_TYPE_CLASS
};
enum class GenericArgMode : COR_SIGNATURE
{
Type = ELEMENT_TYPE_VAR,
Method = ELEMENT_TYPE_MVAR
};
enum class BuiltIn : COR_SIGNATURE
{
Void = ELEMENT_TYPE_VOID,
Boolean = ELEMENT_TYPE_BOOLEAN,
Char = ELEMENT_TYPE_CHAR,
SByte = ELEMENT_TYPE_I1,
Byte = ELEMENT_TYPE_U1,
Int16 = ELEMENT_TYPE_I2,
UInt16 = ELEMENT_TYPE_U2,
Int32 = ELEMENT_TYPE_I4,
UInt32 = ELEMENT_TYPE_U4,
Int64 = ELEMENT_TYPE_I8,
UInt64 = ELEMENT_TYPE_U8,
Float = ELEMENT_TYPE_R4,
Double = ELEMENT_TYPE_R8,
String = ELEMENT_TYPE_STRING,
IntPtr = ELEMENT_TYPE_I,
UintPtr = ELEMENT_TYPE_U,
Object = ELEMENT_TYPE_OBJECT
};
class Type;
class TokenType;
class ValueType;
class Class;
class Array;
class ByRef;
class GenericArgument;
class GenericArgumentOfMethod;
class GenericArgumentOfType;
class GenericInstance;
class Field;
class Method;
class InstanceMethod;
class StaticMethod;
};
class SignatureBuilder::Type : public virtual SignatureData, protected SignatureBuilder
{
public:
Type(BuiltIn builtin)
: SignatureData{static_cast<COR_SIGNATURE>(builtin)}
{
}
protected:
Type() = default;
};
class SignatureBuilder::TokenType : public Type
{
public:
TokenType(TokenTypeMode type, mdToken token)
{
this->PushRawByte(static_cast<COR_SIGNATURE>(type));
this->PushToken(token);
}
};
class SignatureBuilder::ValueType : public TokenType
{
public:
explicit ValueType(mdToken token) : TokenType(TokenTypeMode::ValueType, token) {}
};
class SignatureBuilder::Class : public TokenType
{
public:
explicit Class(mdToken token) : TokenType(TokenTypeMode::Class, token) {}
};
class SignatureBuilder::Array : public Type
{
public:
explicit Array(const Type& type)
{
this->PushRawByte(ELEMENT_TYPE_SZARRAY);
this->Push(type);
}
};
class SignatureBuilder::ByRef : public Type
{
public:
explicit ByRef(const Type& type)
{
this->PushRawByte(ELEMENT_TYPE_BYREF);
this->Push(type);
}
};
class SignatureBuilder::GenericArgument : public Type
{
public:
GenericArgument(GenericArgMode type, UINT num)
{
this->PushRawByte(static_cast<COR_SIGNATURE>(type));
this->PushCompressedData(num);
}
};
class SignatureBuilder::GenericArgumentOfMethod : public GenericArgument
{
public:
explicit GenericArgumentOfMethod(UINT num) : GenericArgument(GenericArgMode::Method, num) {}
};
class SignatureBuilder::GenericArgumentOfType : public GenericArgument
{
public:
explicit GenericArgumentOfType(UINT num) : GenericArgument(GenericArgMode::Type, num) {}
};
class SignatureBuilder::GenericInstance : public Type
{
public:
GenericInstance(const TokenType& open_generic, const std::vector<Type>& generic_args)
{
this->PushRawByte(ELEMENT_TYPE_GENERICINST);
this->Push(open_generic);
this->PushCompressedData(static_cast<ULONG>(generic_args.size()));
for (const auto& arg : generic_args)
{
this->Push(arg);
}
}
};
class SignatureBuilder::Field : public virtual SignatureData, protected SignatureBuilder
{
public:
explicit Field(const Type& field_type)
{
this->PushRawByte(IMAGE_CEE_CS_CALLCONV_FIELD);
this->Push(field_type);
}
};
class SignatureBuilder::Method : public virtual SignatureData, protected SignatureBuilder
{
public:
Method(MethodCallingConvection calling_convection, const Type& return_type, const std::vector<Type>& args)
{
this->PushRawByte(static_cast<COR_SIGNATURE>(calling_convection));
this->PushCompressedData(static_cast<ULONG>(args.size()));
this->Push(return_type);
for (const auto& arg : args)
{
this->Push(arg);
}
}
};
class SignatureBuilder::InstanceMethod : public Method
{
public:
InstanceMethod(const Type& return_type, const std::vector<Type>& args)
: Method(MethodCallingConvection::Instance, return_type, args)
{
}
};
class SignatureBuilder::StaticMethod : public Method
{
public:
StaticMethod(const Type& return_type, const std::vector<Type>& args)
: Method(MethodCallingConvection::Static, return_type, args)
{
}
};
} // namespace trace
#endif // OTEL_CLR_PROFILER_SIGNATURE_BUILDER_H_

View File

@ -17,14 +17,42 @@ public class AspNetTests
Output = output;
}
public enum Gac
{
/// <summary>
/// Use image with OTEL assemblies registered in GAC
/// </summary>
UseGac,
/// <summary>
/// Use image with OTEL assemblies not registered in GAC
/// </summary>
UseLocal
}
public enum AppPoolMode
{
/// <summary>
/// Use image with Classic AppPoolMode
/// </summary>
Classic,
/// <summary>
/// Use image with Integrated AppPoolMode
/// </summary>
Integrated
}
private ITestOutputHelper Output { get; }
[Theory]
[Trait("Category", "EndToEnd")]
[Trait("Containers", "Windows")]
[InlineData("Classic")]
[InlineData("Integrated")]
public async Task SubmitsTraces(string appPoolMode)
[InlineData(AppPoolMode.Classic, Gac.UseGac)]
[InlineData(AppPoolMode.Classic, Gac.UseLocal)]
[InlineData(AppPoolMode.Integrated, Gac.UseGac)]
[InlineData(AppPoolMode.Integrated, Gac.UseLocal)]
public async Task SubmitsTraces(AppPoolMode appPoolMode, Gac useGac)
{
Assert.True(EnvironmentTools.IsWindowsAdministrator(), "This test requires Windows Administrator privileges.");
@ -43,7 +71,7 @@ public class AspNetTests
["OTEL_EXPORTER_OTLP_ENDPOINT"] = collectorUrl
};
var webPort = TcpPortProvider.GetOpenPort();
var imageName = GetTestImageName(appPoolMode);
var imageName = GetTestImageName(appPoolMode, useGac);
await using var container = await IISContainerTestHelper.StartContainerAsync(imageName, webPort, environmentVariables, Output);
await CallTestApplicationEndpoint(webPort);
@ -53,9 +81,11 @@ public class AspNetTests
[Theory]
[Trait("Category", "EndToEnd")]
[Trait("Containers", "Windows")]
[InlineData("Classic")]
[InlineData("Integrated")]
public async Task SubmitTracesCapturesHttpHeaders(string appPoolMode)
[InlineData(AppPoolMode.Classic, Gac.UseGac)]
[InlineData(AppPoolMode.Classic, Gac.UseLocal)]
[InlineData(AppPoolMode.Integrated, Gac.UseGac)]
[InlineData(AppPoolMode.Integrated, Gac.UseLocal)]
public async Task SubmitTracesCapturesHttpHeaders(AppPoolMode appPoolMode, Gac useGac)
{
Assert.True(EnvironmentTools.IsWindowsAdministrator(), "This test requires Windows Administrator privileges.");
@ -67,7 +97,7 @@ public class AspNetTests
using var fwPort = FirewallHelper.OpenWinPort(collector.Port, Output);
collector.Expect("OpenTelemetry.Instrumentation.AspNet.Telemetry", span => // Expect Mvc span
{
if (appPoolMode == "Classic")
if (appPoolMode == AppPoolMode.Classic)
{
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header1")
@ -87,7 +117,7 @@ public class AspNetTests
collector.Expect("OpenTelemetry.Instrumentation.AspNet.Telemetry", span => // Expect WebApi span
{
if (appPoolMode == "Classic")
if (appPoolMode == AppPoolMode.Classic)
{
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header1")
@ -113,7 +143,7 @@ public class AspNetTests
["OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS"] = "Custom-Response-Test-Header1,Custom-Response-Test-Header3"
};
var webPort = TcpPortProvider.GetOpenPort();
var imageName = GetTestImageName(appPoolMode);
var imageName = GetTestImageName(appPoolMode, useGac);
await using var container = await IISContainerTestHelper.StartContainerAsync(imageName, webPort, environmentVariables, Output);
await CallTestApplicationEndpoint(webPort);
@ -181,9 +211,16 @@ public class AspNetTests
collector.AssertExpectations();
}
private static string GetTestImageName(string appPoolMode)
private static string GetTestImageName(AppPoolMode appPoolMode, Gac useGac)
{
return appPoolMode == "Classic" ? "testapplication-aspnet-netframework-classic" : "testapplication-aspnet-netframework-integrated";
return (appPoolMode, useGac) switch
{
(AppPoolMode.Classic, Gac.UseGac) => "testapplication-aspnet-netframework-classic",
(AppPoolMode.Classic, Gac.UseLocal) => "testapplication-aspnet-netframework-classic-nogac",
(AppPoolMode.Integrated, Gac.UseGac) => "testapplication-aspnet-netframework-integrated",
(AppPoolMode.Integrated, Gac.UseLocal) => "testapplication-aspnet-netframework-integrated-nogac",
_ => throw new ArgumentOutOfRangeException()
};
}
private async Task CallTestApplicationEndpoint(int webPort)

View File

@ -0,0 +1,338 @@
#include "pch.h"
#include "../../src/OpenTelemetry.AutoInstrumentation.Native/signature_builder.h"
#include <vector>
using namespace trace;
class SignatureBuilderTest : public ::testing::Test
{
};
TEST_F(SignatureBuilderTest, SignatureBuilderDefaultCtor)
{
SignatureBuilder empty;
EXPECT_EQ(empty.Size(), 0);
}
TEST_F(SignatureBuilderTest, CorSignatureCtor)
{
SignatureBuilder for_test{1, 2, 3, 99};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({1, 2, 3, 99}));
}
TEST_F(SignatureBuilderTest, PushRawByte)
{
SignatureBuilder for_test = SignatureBuilder{}.PushRawByte(64).PushRawByte(50);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({64, 50}));
}
TEST_F(SignatureBuilderTest, PushRawBytes)
{
SignatureBuilder for_test = SignatureBuilder{}.PushRawBytes({64, 98}).PushRawBytes({70, 54});
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({64, 98, 70, 54}));
}
TEST_F(SignatureBuilderTest, PushRawBytesBeginEnd)
{
std::vector<COR_SIGNATURE> source1{23, 98, 37};
std::vector<COR_SIGNATURE> source2{35, 42};
SignatureBuilder for_test =
SignatureBuilder{}.PushRawBytes(source1.begin(), source1.end()).PushRawBytes(source2.begin(), source2.end());
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({23, 98, 37, 35, 42}));
}
TEST_F(SignatureBuilderTest, PushCompressedData)
{
{
SignatureBuilder for_test = SignatureBuilder{}.PushCompressedData(0);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({0}));
}
{
SignatureBuilder for_test = SignatureBuilder{}.PushCompressedData(1);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({1}));
}
{
SignatureBuilder for_test = SignatureBuilder{}.PushCompressedData(0x7F);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({0x7F}));
}
{
SignatureBuilder for_test = SignatureBuilder{}.PushCompressedData(0x80);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({0x80, 0x80}));
}
{
SignatureBuilder for_test = SignatureBuilder{}.PushCompressedData(0x2E57);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({0xAE, 0x57}));
}
{
SignatureBuilder for_test = SignatureBuilder{}.PushCompressedData(0x3FFF);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({0xBF, 0xFF}));
}
{
SignatureBuilder for_test = SignatureBuilder{}.PushCompressedData(0x4000);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({0xC0, 0x00, 0x40, 0x00}));
}
{
SignatureBuilder for_test = SignatureBuilder{}.PushCompressedData(0x1FFFFFFF);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({0xDF, 0xFF, 0xFF, 0xFF}));
}
}
TEST_F(SignatureBuilderTest, PushToken)
{
SignatureBuilder for_test = SignatureBuilder{}.PushToken(TokenFromRid(0x12, mdtTypeRef));
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({0x49}));
}
TEST_F(SignatureBuilderTest, Push)
{
SignatureBuilder source1{23, 98, 37};
SignatureBuilder source2{35, 42};
SignatureBuilder for_test = SignatureBuilder{}.Push(source1).Push(source2);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({23, 98, 37, 35, 42}));
}
TEST_F(SignatureBuilderTest, SignatureBuilderCtor)
{
SignatureBuilder source1{23, 98, 37};
SignatureBuilder source2{35, 42};
SignatureBuilder for_test{source1, source2};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({23, 98, 37, 35, 42}));
}
TEST_F(SignatureBuilderTest, CombinationOfApi)
{
SignatureBuilder source1{23, 98, 37};
SignatureBuilder source2{35, 42};
SignatureBuilder for_test = SignatureBuilder{source1, source2}
.PushRawByte(33)
.PushRawBytes({34, 51})
.PushToken(TokenFromRid(0x12, mdtTypeRef))
.PushCompressedData(0x4000);
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({23, 98, 37, 35, 42, 33, 34, 51, 0x49, 0xC0, 0x00, 0x40, 0x00}));
}
TEST_F(SignatureBuilderTest, Type)
{
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Void};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_VOID}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Boolean};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_BOOLEAN}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Char};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_CHAR}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::SByte};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_I1}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Byte};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_U1}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Int16};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_I2}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::UInt16};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_U2}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Int32};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_I4}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::UInt32};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_U4}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Int64};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_I8}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::UInt64};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_U8}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Float};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_R4}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Double};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_R8}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::String};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_STRING}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::IntPtr};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_I}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::UintPtr};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_U}));
}
{
SignatureBuilder::Type for_test{SignatureBuilder::BuiltIn::Object};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_OBJECT}));
}
}
TEST_F(SignatureBuilderTest, ValueType)
{
SignatureBuilder::ValueType for_test{TokenFromRid(0x12, mdtTypeRef)};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_VALUETYPE, 0x49}));
}
TEST_F(SignatureBuilderTest, Class)
{
SignatureBuilder::Class for_test{TokenFromRid(0x12, mdtTypeRef)};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_CLASS, 0x49}));
}
TEST_F(SignatureBuilderTest, Array)
{
SignatureBuilder::Array for_test{SignatureBuilder::BuiltIn::String};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_SZARRAY, ELEMENT_TYPE_STRING}));
}
TEST_F(SignatureBuilderTest, ByRef)
{
SignatureBuilder::ByRef for_test{SignatureBuilder::BuiltIn::String};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_BYREF, ELEMENT_TYPE_STRING}));
}
TEST_F(SignatureBuilderTest, GenericArgumentOfMethod)
{
SignatureBuilder::GenericArgumentOfMethod for_test{0x80};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_MVAR, 0x80, 0x80}));
}
TEST_F(SignatureBuilderTest, GenericArgumentOfType)
{
SignatureBuilder::GenericArgumentOfType for_test{0x0};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({ELEMENT_TYPE_VAR, 0x00}));
}
TEST_F(SignatureBuilderTest, GenericInstance)
{
SignatureBuilder::GenericInstance for_test{SignatureBuilder::Class{TokenFromRid(0x12, mdtTypeRef)},
{SignatureBuilder::BuiltIn::String, SignatureBuilder::BuiltIn::Double}};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>(
{ELEMENT_TYPE_GENERICINST, ELEMENT_TYPE_CLASS, 0x49, 2, ELEMENT_TYPE_STRING, ELEMENT_TYPE_R8}));
}
TEST_F(SignatureBuilderTest, Field)
{
SignatureBuilder::Field for_test{SignatureBuilder::Class{TokenFromRid(0x12, mdtTypeRef)}};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>({IMAGE_CEE_CS_CALLCONV_FIELD, ELEMENT_TYPE_CLASS, 0x49}));
}
TEST_F(SignatureBuilderTest, InstanceMethod)
{
SignatureBuilder::InstanceMethod for_test{SignatureBuilder::Class{TokenFromRid(0x12, mdtTypeRef)},
{SignatureBuilder::BuiltIn::String, SignatureBuilder::BuiltIn::Double}};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>(
{IMAGE_CEE_CS_CALLCONV_HASTHIS, 2, ELEMENT_TYPE_CLASS, 0x49, ELEMENT_TYPE_STRING, ELEMENT_TYPE_R8}));
}
TEST_F(SignatureBuilderTest, StaticMethod)
{
SignatureBuilder::StaticMethod for_test{SignatureBuilder::Class{TokenFromRid(0x12, mdtTypeRef)},
{SignatureBuilder::BuiltIn::String, SignatureBuilder::BuiltIn::Double}};
EXPECT_EQ(std::vector<COR_SIGNATURE>(for_test.Head(), for_test.Head() + for_test.Size()),
std::vector<COR_SIGNATURE>(
{IMAGE_CEE_CS_CALLCONV_DEFAULT, 2, ELEMENT_TYPE_CLASS, 0x49, ELEMENT_TYPE_STRING, ELEMENT_TYPE_R8}));
}

View File

@ -1,6 +1,6 @@
# escape=`
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2022@sha256:84079c734ab5aec702409ef7967ec47af9468c56ff4046882239cabacda78097 AS integrated
FROM mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-ltsc2022@sha256:84079c734ab5aec702409ef7967ec47af9468c56ff4046882239cabacda78097 AS integrated-base
ARG configuration=Debug
ARG platform=x64
WORKDIR /opentelemetry
@ -8,15 +8,27 @@ COPY bin/tracer.zip .
COPY bin/OpenTelemetry.DotNet.Auto.psm1 .
ENV OTEL_DOTNET_AUTO_INSTALL_DIR=C:\opentelemetry
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
RUN Set-ItemProperty -Path "HKLM:\\SOFTWARE\\Microsoft\\.NETFramework" -Name "LoaderOptimization" -Value 1
RUN Import-Module .\OpenTelemetry.DotNet.Auto.psm1 -Verbose; `
Install-OpenTelemetryCore -LocalPath .\tracer.zip; `
Install-OpenTelemetryCore -LocalPath .\tracer.zip -RegisterAssembliesInGAC $false; `
Register-OpenTelemetryForIIS;
ENV OTEL_DOTNET_AUTO_LOG_DIRECTORY=C:\inetpub\wwwroot\logs `
OTEL_LOG_LEVEL=debug
WORKDIR /inetpub/wwwroot
COPY bin/${configuration}/app.publish .
FROM integrated-base AS integrated-nogac
FROM integrated-nogac AS classic-nogac
RUN Start-IISCommitDelay;`
(Get-IISAppPool -Name DefaultAppPool).ManagedPipelineMode = 'Classic';`
Stop-IISCommitDelay -Commit $True
FROM integrated-base AS integrated
WORKDIR /opentelemetry
RUN Import-Module .\OpenTelemetry.DotNet.Auto.psm1 -Verbose; `
Register-AssembliesInGAC -InstallDir C:\inetpub\wwwroot\otel;
WORKDIR /inetpub/wwwroot
FROM integrated AS classic
RUN Start-IISCommitDelay;`
(Get-IISAppPool -Name DefaultAppPool).ManagedPipelineMode = 'Classic';`

View File

@ -8,7 +8,6 @@ COPY bin/tracer.zip .
COPY bin/OpenTelemetry.DotNet.Auto.psm1 .
ENV OTEL_DOTNET_AUTO_INSTALL_DIR=C:\opentelemetry
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
RUN Set-ItemProperty -Path "HKLM:\\SOFTWARE\\Microsoft\\.NETFramework" -Name "LoaderOptimization" -Value 1
RUN Import-Module .\OpenTelemetry.DotNet.Auto.psm1 -Verbose; `
Install-OpenTelemetryCore -LocalPath .\tracer.zip; `
Register-OpenTelemetryForIIS;

View File

@ -8,7 +8,6 @@ COPY bin/tracer.zip .
COPY bin/OpenTelemetry.DotNet.Auto.psm1 .
ENV OTEL_DOTNET_AUTO_INSTALL_DIR=C:\opentelemetry
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
RUN Set-ItemProperty -Path "HKLM:\\SOFTWARE\\Microsoft\\.NETFramework" -Name "LoaderOptimization" -Value 1
RUN Import-Module .\OpenTelemetry.DotNet.Auto.psm1 -Verbose; `
Install-OpenTelemetryCore -LocalPath .\tracer.zip; `
Register-OpenTelemetryForIIS;