Skip to content

Support loading standalone GC from an absolute path using a new config named DOTNET_GCPath #101874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/coreclr/gc/gcconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ class GCConfigStringHolder
INT_CONFIG (GCEnabledInstructionSets, "GCEnabledInstructionSets", NULL, -1, "Specifies whether GC can use AVX2 or AVX512F - 0 for neither, 1 for AVX2, 3 for AVX512F")\
INT_CONFIG (GCConserveMem, "GCConserveMemory", "System.GC.ConserveMemory", 0, "Specifies how hard GC should try to conserve memory - values 0-9") \
INT_CONFIG (GCWriteBarrier, "GCWriteBarrier", NULL, 0, "Specifies whether GC should use more precise but slower write barrier") \
STRING_CONFIG(GCName, "GCName", "System.GC.Name", "Specifies the path of the standalone GC implementation.") \
STRING_CONFIG(GCName, "GCName", "System.GC.Name", "Specifies the name of the standalone GC implementation.") \
STRING_CONFIG(GCPath, "GCPath", "System.GC.Path", "Specifies the path of the standalone GC implementation.") \
INT_CONFIG (GCSpinCountUnit, "GCSpinCountUnit", NULL, 0, "Specifies the spin count unit used by the GC.") \
INT_CONFIG (GCDynamicAdaptationMode, "GCDynamicAdaptationMode", "System.GC.DynamicAdaptationMode", 0, "Enable the GC to dynamically adapt to application sizes.")
// This class is responsible for retreiving configuration information
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ CONFIG_DWORD_INFO(INTERNAL_GcStressOnDirectCalls, W("GcStressOnDirectCalls"), 0,
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_HeapVerify, W("HeapVerify"), 0, "When set verifies the integrity of the managed heap on entry and exit of each GC")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_GCCpuGroup, W("GCCpuGroup"), 0, "Specifies if to enable GC to support CPU groups")
RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCName, W("GCName"), "")
RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCPath, W("GCPath"), "")
/**
* This flag allows us to force the runtime to use global allocation context on Windows x86/amd64 instead of thread allocation context just for testing purpose.
* The flag is unsafe for a subtle reason. Although the access to the g_global_alloc_context is protected under a lock. The implementation of
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/inc/utilcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
#define CoreLibSatelliteName_A "System.Private.CoreLib.resources"
#define CoreLibSatelliteNameLen 32

bool ValidateModuleName(LPCWSTR pwzModuleName);

class StringArrayList;

#if !defined(_DEBUG_IMPL) && defined(_DEBUG) && !defined(DACCESS_COMPILE)
Expand Down
117 changes: 88 additions & 29 deletions src/coreclr/nativeaot/Runtime/clrgc.enabled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,52 +58,111 @@ HRESULT InitializeStandaloneGC()
return GCHeapUtilities::InitializeStandaloneGC();
}

// Validate that the name used to load the GC is just a simple file name
// and does not contain something that could be used in a non-qualified path.
// For example, using the string "..\..\..\clrgc.dll" we might attempt to
// load a GC from the root of the drive.
//
// The minimal set of characters that we must check for and exclude are:
// On all platforms:
// '/' - (forward slash)
// On Windows:
// '\\' - (backslash)
// ':' - (colon)
//
// Returns false if we find any of these characters in 'pwzModuleName'
// Returns true if we reach the null terminator without encountering
// any of these characters.
//
bool ValidateModuleName(const char* pwzModuleName)
{
const char* pCurChar = pwzModuleName;
char curChar;
do {
curChar = *pCurChar;
if (curChar == '/'
#ifdef TARGET_WINDOWS
|| (curChar == '\\') || (curChar == ':')
#endif
)
{
// Return false if we find any of these character in 'pwzJitName'
return false;
}
pCurChar++;
} while (curChar != 0);

// Return true; we have reached the null terminator
//
return true;
}

HRESULT GCHeapUtilities::InitializeStandaloneGC()
{
char* moduleName;
char* modulePath;

if (!RhConfig::Environment::TryGetStringValue("GCPath", &modulePath))
{
modulePath = nullptr;
}

if (!RhConfig::Environment::TryGetStringValue("GCName", &moduleName))
{
return GCHeapUtilities::InitializeDefaultGC();
moduleName = nullptr;
}

NewArrayHolder<char> moduleNameHolder(moduleName);
HANDLE executableModule = PalGetModuleHandleFromPointer((void*)&PalGetModuleHandleFromPointer);
const TCHAR * executableModulePath = NULL;
PalGetModuleFileName(&executableModulePath, executableModule);
char* convertedExecutableModulePath = PalCopyTCharAsChar(executableModulePath);
if (!convertedExecutableModulePath)
if (!(moduleName || modulePath))
{
return E_OUTOFMEMORY;
return GCHeapUtilities::InitializeDefaultGC();
}
NewArrayHolder<char> convertedExecutableModulePathHolder(convertedExecutableModulePath);

NewArrayHolder<char> moduleNameHolder(moduleName);
if (!modulePath)
{
char* p = convertedExecutableModulePath;
char* q = nullptr;
while (*p != '\0')
if (!ValidateModuleName(moduleName))
{
LOG((LF_GC, LL_FATALERROR, "GC initialization failed to load the Standalone GC library.\n"));
return E_FAIL;
}

HANDLE executableModule = PalGetModuleHandleFromPointer((void*)&PalGetModuleHandleFromPointer);
const TCHAR * executableModulePath = NULL;
PalGetModuleFileName(&executableModulePath, executableModule);
char* convertedExecutableModulePath = PalCopyTCharAsChar(executableModulePath);
if (!convertedExecutableModulePath)
{
return E_OUTOFMEMORY;
}
NewArrayHolder<char> convertedExecutableModulePathHolder(convertedExecutableModulePath);
{
if (*p == DIRECTORY_SEPARATOR_CHAR)
char* p = convertedExecutableModulePath;
char* q = nullptr;
while (*p != '\0')
{
q = p;
if (*p == DIRECTORY_SEPARATOR_CHAR)
{
q = p;
}
p++;
}
p++;
assert(q != nullptr);
q++;
*q = '\0';
}
assert(q != nullptr);
q++;
*q = '\0';
}
size_t folderLength = strlen(convertedExecutableModulePath);
size_t nameLength = strlen(moduleName);
char* moduleFullPath = new (nothrow) char[folderLength + nameLength + 1];
if (!moduleFullPath)
{
return E_OUTOFMEMORY;
size_t folderLength = strlen(convertedExecutableModulePath);
size_t nameLength = strlen(moduleName);
modulePath = new (nothrow) char[folderLength + nameLength + 1];
if (!modulePath)
{
return E_OUTOFMEMORY;
}
strcpy(modulePath, convertedExecutableModulePath);
strcpy(modulePath + folderLength, moduleName);
}
NewArrayHolder<char> moduleFullPathHolder(moduleFullPath);
strcpy(moduleFullPath, convertedExecutableModulePath);
strcpy(moduleFullPath + folderLength, moduleName);

HANDLE hMod = PalLoadLibrary(moduleFullPath);
NewArrayHolder<char> modulePathHolder(modulePath);
HANDLE hMod = PalLoadLibrary(modulePath);

if (!hMod)
{
Expand Down
39 changes: 39 additions & 0 deletions src/coreclr/utilcode/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,45 @@ bool g_arm64_atomics_present = false;

#endif //!DACCESS_COMPILE

// Validate that the name used to load the JIT/GC is just a simple file name
// and does not contain something that could be used in a non-qualified path.
// For example, using the string "..\..\..\myjit.dll" we might attempt to
// load a JIT from the root of the drive.
//
// The minimal set of characters that we must check for and exclude are:
// On all platforms:
// '/' - (forward slash)
// On Windows:
// '\\' - (backslash)
// ':' - (colon)
//
// Returns false if we find any of these characters in 'pwzModuleName'
// Returns true if we reach the null terminator without encountering
// any of these characters.
//
bool ValidateModuleName(LPCWSTR pwzModuleName)
{
LPCWSTR pCurChar = pwzModuleName;
wchar_t curChar;
do {
curChar = *pCurChar;
if (curChar == '/'
#ifdef TARGET_WINDOWS
|| (curChar == '\\') || (curChar == ':')
#endif
)
{
// Return false if we find any of these character in 'pwzJitName'
return false;
}
pCurChar++;
} while (curChar != 0);

// Return true; we have reached the null terminator
//
return true;
}

//*****************************************************************************
// Convert a string of hex digits into a hex value of the specified # of bytes.
//*****************************************************************************
Expand Down
41 changes: 1 addition & 40 deletions src/coreclr/vm/codeman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1669,45 +1669,6 @@ struct JIT_LOAD_DATA
// Here's the global data for JIT load and initialization state.
JIT_LOAD_DATA g_JitLoadData;

// Validate that the name used to load the JIT is just a simple file name
// and does not contain something that could be used in a non-qualified path.
// For example, using the string "..\..\..\myjit.dll" we might attempt to
// load a JIT from the root of the drive.
//
// The minimal set of characters that we must check for and exclude are:
// On all platforms:
// '/' - (forward slash)
// On Windows:
// '\\' - (backslash)
// ':' - (colon)
//
// Returns false if we find any of these characters in 'pwzJitName'
// Returns true if we reach the null terminator without encountering
// any of these characters.
//
static bool ValidateJitName(LPCWSTR pwzJitName)
{
LPCWSTR pCurChar = pwzJitName;
wchar_t curChar;
do {
curChar = *pCurChar;
if (curChar == '/'
#ifdef TARGET_WINDOWS
|| (curChar == '\\') || (curChar == ':')
#endif
)
{
// Return false if we find any of these character in 'pwzJitName'
return false;
}
pCurChar++;
} while (curChar != 0);

// Return true; we have reached the null terminator
//
return true;
}

CORINFO_OS getClrVmOs();

#define LogJITInitializationError(...) \
Expand Down Expand Up @@ -1770,7 +1731,7 @@ static void LoadAndInitializeJIT(LPCWSTR pwzJitName DEBUGARG(LPCWSTR pwzJitPath)
return;
}

if (ValidateJitName(pwzJitName))
if (ValidateModuleName(pwzJitName))
{
// Load JIT from next to CoreCLR binary
PathString CoreClrFolderHolder;
Expand Down
63 changes: 48 additions & 15 deletions src/coreclr/vm/gcheaputilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "configuration.h"
#include "gcheaputilities.h"
#include "appdomain.hpp"
#include "hostinformation.h"

#include "../gc/env/gcenv.ee.h"
#include "../gc/env/gctoeeinterface.standalone.inl"
Expand Down Expand Up @@ -85,7 +86,6 @@ PTR_VOID GCHeapUtilities::GetGCModuleBase()

namespace
{

// This block of code contains all of the state necessary to handle incoming
// EtwCallbacks before the GC has been initialized. This is a tricky problem
// because EtwCallbacks can appear at any time, even when we are just about
Expand Down Expand Up @@ -161,18 +161,47 @@ void StashKeywordAndLevel(bool isPublicProvider, GCEventKeyword keywords, GCEven
}

#ifdef FEATURE_STANDALONE_GC
HMODULE LoadStandaloneGc(LPCWSTR libFileName)
HMODULE LoadStandaloneGc(LPCWSTR libFileName, LPCWSTR libFilePath)
{
LIMITED_METHOD_CONTRACT;
HMODULE result = nullptr;

if (libFilePath)
{
return CLRLoadLibrary(libFilePath);
}

// Look for the standalone GC module next to the clr binary
PathString libPath = GetInternalSystemDirectory();
libPath.Append(libFileName);
if (!ValidateModuleName(libFileName))
{
LOG((LF_GC, LL_INFO100, "Invalid GC name found %s\n", libFileName));
return nullptr;
}

SString appBase;
if (HostInformation::GetProperty("APP_CONTEXT_BASE_DIRECTORY", appBase))
{
PathString libPath = appBase.GetUnicode();
libPath.Append(libFileName);

LOG((LF_GC, LL_INFO100, "Loading standalone GC from path %s\n", libPath.GetUTF8()));
LOG((LF_GC, LL_INFO100, "Loading standalone GC from appBase %s\n", libPath.GetUTF8()));

LPCWSTR libraryName = libPath.GetUnicode();
result = CLRLoadLibrary(libraryName);
}

if (result == nullptr)
{
// Look for the standalone GC module next to the clr binary
PathString libPath = GetInternalSystemDirectory();
libPath.Append(libFileName);

LOG((LF_GC, LL_INFO100, "Loading standalone GC by coreclr %s\n", libPath.GetUTF8()));

LPCWSTR libraryName = libPath.GetUnicode();
result = CLRLoadLibrary(libraryName);
}

LPCWSTR libraryName = libPath.GetUnicode();
return CLRLoadLibrary(libraryName);
return result;
}
#endif // FEATURE_STANDALONE_GC

Expand All @@ -182,21 +211,24 @@ HMODULE LoadStandaloneGc(LPCWSTR libFileName)
//
// See Documentation/design-docs/standalone-gc-loading.md for details
// on the loading protocol in use here.
HRESULT LoadAndInitializeGC(LPCWSTR standaloneGcLocation)
HRESULT LoadAndInitializeGC(LPCWSTR standaloneGCName, LPCWSTR standaloneGCPath)
{
LIMITED_METHOD_CONTRACT;

#ifndef FEATURE_STANDALONE_GC
LOG((LF_GC, LL_FATALERROR, "EE not built with the ability to load standalone GCs"));
return E_FAIL;
#else
HMODULE hMod = LoadStandaloneGc(standaloneGcLocation);
HMODULE hMod = LoadStandaloneGc(standaloneGCName, standaloneGCPath);
if (!hMod)
{
HRESULT err = GetLastError();
#ifdef LOGGING
MAKE_UTF8PTR_FROMWIDE(standaloneGcLocationUtf8, standaloneGcLocation);
LOG((LF_GC, LL_FATALERROR, "Load of %s failed\n", standaloneGcLocationUtf8));
LPCWSTR standaloneGCNameLogging = standaloneGCName ? standaloneGCName : W("");
LPCWSTR standaloneGCPathLogging = standaloneGCPath ? standaloneGCPath : W("");
MAKE_UTF8PTR_FROMWIDE(standaloneGCNameUtf8, standaloneGCNameLogging);
MAKE_UTF8PTR_FROMWIDE(standaloneGCPathUtf8, standaloneGCPathLogging);
LOG((LF_GC, LL_FATALERROR, "Load of %s or %s failed\n", standaloneGCNameUtf8, standaloneGCPathUtf8));
#endif // LOGGING
return __HRESULT_FROM_WIN32(err);
}
Expand Down Expand Up @@ -344,16 +376,17 @@ HRESULT GCHeapUtilities::LoadAndInitialize()
assert(g_gc_load_status == GC_LOAD_STATUS_BEFORE_START);
g_gc_load_status = GC_LOAD_STATUS_START;

LPCWSTR standaloneGcLocation = Configuration::GetKnobStringValue(W("System.GC.Name"), CLRConfig::EXTERNAL_GCName);
LPCWSTR standaloneGCName = Configuration::GetKnobStringValue(W("System.GC.Name"), CLRConfig::EXTERNAL_GCName);
LPCWSTR standaloneGCPath = Configuration::GetKnobStringValue(W("System.GC.Path"), CLRConfig::EXTERNAL_GCPath);
g_gc_dac_vars.major_version_number = GC_INTERFACE_MAJOR_VERSION;
g_gc_dac_vars.minor_version_number = GC_INTERFACE_MINOR_VERSION;
if (!standaloneGcLocation)
if (!standaloneGCName && !standaloneGCPath)
{
return InitializeDefaultGC();
}
else
{
return LoadAndInitializeGC(standaloneGcLocation);
return LoadAndInitializeGC(standaloneGCName, standaloneGCPath);
}
}

Expand Down
Loading