Skip to content

Commit

Permalink
Support loading standalone GC from an absolute path using a new confi…
Browse files Browse the repository at this point in the history
…g named DOTNET_GCPath (#101874)
  • Loading branch information
cshung committed May 9, 2024
1 parent d01ebfe commit 76567bf
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 85 deletions.
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

0 comments on commit 76567bf

Please sign in to comment.