Skip to content

Commit 76567bf

Browse files
authored
Support loading standalone GC from an absolute path using a new config named DOTNET_GCPath (#101874)
1 parent d01ebfe commit 76567bf

File tree

7 files changed

+181
-85
lines changed

7 files changed

+181
-85
lines changed

src/coreclr/gc/gcconfig.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ class GCConfigStringHolder
137137
INT_CONFIG (GCEnabledInstructionSets, "GCEnabledInstructionSets", NULL, -1, "Specifies whether GC can use AVX2 or AVX512F - 0 for neither, 1 for AVX2, 3 for AVX512F")\
138138
INT_CONFIG (GCConserveMem, "GCConserveMemory", "System.GC.ConserveMemory", 0, "Specifies how hard GC should try to conserve memory - values 0-9") \
139139
INT_CONFIG (GCWriteBarrier, "GCWriteBarrier", NULL, 0, "Specifies whether GC should use more precise but slower write barrier") \
140-
STRING_CONFIG(GCName, "GCName", "System.GC.Name", "Specifies the path of the standalone GC implementation.") \
140+
STRING_CONFIG(GCName, "GCName", "System.GC.Name", "Specifies the name of the standalone GC implementation.") \
141+
STRING_CONFIG(GCPath, "GCPath", "System.GC.Path", "Specifies the path of the standalone GC implementation.") \
141142
INT_CONFIG (GCSpinCountUnit, "GCSpinCountUnit", NULL, 0, "Specifies the spin count unit used by the GC.") \
142143
INT_CONFIG (GCDynamicAdaptationMode, "GCDynamicAdaptationMode", "System.GC.DynamicAdaptationMode", 0, "Enable the GC to dynamically adapt to application sizes.")
143144
// This class is responsible for retreiving configuration information

src/coreclr/inc/clrconfigvalues.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ CONFIG_DWORD_INFO(INTERNAL_GcStressOnDirectCalls, W("GcStressOnDirectCalls"), 0,
284284
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")
285285
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_GCCpuGroup, W("GCCpuGroup"), 0, "Specifies if to enable GC to support CPU groups")
286286
RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCName, W("GCName"), "")
287+
RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCPath, W("GCPath"), "")
287288
/**
288289
* 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.
289290
* 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

src/coreclr/inc/utilcode.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
#define CoreLibSatelliteName_A "System.Private.CoreLib.resources"
5656
#define CoreLibSatelliteNameLen 32
5757

58+
bool ValidateModuleName(LPCWSTR pwzModuleName);
59+
5860
class StringArrayList;
5961

6062
#if !defined(_DEBUG_IMPL) && defined(_DEBUG) && !defined(DACCESS_COMPILE)

src/coreclr/nativeaot/Runtime/clrgc.enabled.cpp

Lines changed: 88 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -58,52 +58,111 @@ HRESULT InitializeStandaloneGC()
5858
return GCHeapUtilities::InitializeStandaloneGC();
5959
}
6060

61+
// Validate that the name used to load the GC is just a simple file name
62+
// and does not contain something that could be used in a non-qualified path.
63+
// For example, using the string "..\..\..\clrgc.dll" we might attempt to
64+
// load a GC from the root of the drive.
65+
//
66+
// The minimal set of characters that we must check for and exclude are:
67+
// On all platforms:
68+
// '/' - (forward slash)
69+
// On Windows:
70+
// '\\' - (backslash)
71+
// ':' - (colon)
72+
//
73+
// Returns false if we find any of these characters in 'pwzModuleName'
74+
// Returns true if we reach the null terminator without encountering
75+
// any of these characters.
76+
//
77+
bool ValidateModuleName(const char* pwzModuleName)
78+
{
79+
const char* pCurChar = pwzModuleName;
80+
char curChar;
81+
do {
82+
curChar = *pCurChar;
83+
if (curChar == '/'
84+
#ifdef TARGET_WINDOWS
85+
|| (curChar == '\\') || (curChar == ':')
86+
#endif
87+
)
88+
{
89+
// Return false if we find any of these character in 'pwzJitName'
90+
return false;
91+
}
92+
pCurChar++;
93+
} while (curChar != 0);
94+
95+
// Return true; we have reached the null terminator
96+
//
97+
return true;
98+
}
99+
61100
HRESULT GCHeapUtilities::InitializeStandaloneGC()
62101
{
63102
char* moduleName;
103+
char* modulePath;
104+
105+
if (!RhConfig::Environment::TryGetStringValue("GCPath", &modulePath))
106+
{
107+
modulePath = nullptr;
108+
}
64109

65110
if (!RhConfig::Environment::TryGetStringValue("GCName", &moduleName))
66111
{
67-
return GCHeapUtilities::InitializeDefaultGC();
112+
moduleName = nullptr;
68113
}
69114

70-
NewArrayHolder<char> moduleNameHolder(moduleName);
71-
HANDLE executableModule = PalGetModuleHandleFromPointer((void*)&PalGetModuleHandleFromPointer);
72-
const TCHAR * executableModulePath = NULL;
73-
PalGetModuleFileName(&executableModulePath, executableModule);
74-
char* convertedExecutableModulePath = PalCopyTCharAsChar(executableModulePath);
75-
if (!convertedExecutableModulePath)
115+
if (!(moduleName || modulePath))
76116
{
77-
return E_OUTOFMEMORY;
117+
return GCHeapUtilities::InitializeDefaultGC();
78118
}
79-
NewArrayHolder<char> convertedExecutableModulePathHolder(convertedExecutableModulePath);
119+
120+
NewArrayHolder<char> moduleNameHolder(moduleName);
121+
if (!modulePath)
80122
{
81-
char* p = convertedExecutableModulePath;
82-
char* q = nullptr;
83-
while (*p != '\0')
123+
if (!ValidateModuleName(moduleName))
124+
{
125+
LOG((LF_GC, LL_FATALERROR, "GC initialization failed to load the Standalone GC library.\n"));
126+
return E_FAIL;
127+
}
128+
129+
HANDLE executableModule = PalGetModuleHandleFromPointer((void*)&PalGetModuleHandleFromPointer);
130+
const TCHAR * executableModulePath = NULL;
131+
PalGetModuleFileName(&executableModulePath, executableModule);
132+
char* convertedExecutableModulePath = PalCopyTCharAsChar(executableModulePath);
133+
if (!convertedExecutableModulePath)
134+
{
135+
return E_OUTOFMEMORY;
136+
}
137+
NewArrayHolder<char> convertedExecutableModulePathHolder(convertedExecutableModulePath);
84138
{
85-
if (*p == DIRECTORY_SEPARATOR_CHAR)
139+
char* p = convertedExecutableModulePath;
140+
char* q = nullptr;
141+
while (*p != '\0')
86142
{
87-
q = p;
143+
if (*p == DIRECTORY_SEPARATOR_CHAR)
144+
{
145+
q = p;
146+
}
147+
p++;
88148
}
89-
p++;
149+
assert(q != nullptr);
150+
q++;
151+
*q = '\0';
90152
}
91-
assert(q != nullptr);
92-
q++;
93-
*q = '\0';
94-
}
95-
size_t folderLength = strlen(convertedExecutableModulePath);
96-
size_t nameLength = strlen(moduleName);
97-
char* moduleFullPath = new (nothrow) char[folderLength + nameLength + 1];
98-
if (!moduleFullPath)
99-
{
100-
return E_OUTOFMEMORY;
153+
size_t folderLength = strlen(convertedExecutableModulePath);
154+
size_t nameLength = strlen(moduleName);
155+
modulePath = new (nothrow) char[folderLength + nameLength + 1];
156+
if (!modulePath)
157+
{
158+
return E_OUTOFMEMORY;
159+
}
160+
strcpy(modulePath, convertedExecutableModulePath);
161+
strcpy(modulePath + folderLength, moduleName);
101162
}
102-
NewArrayHolder<char> moduleFullPathHolder(moduleFullPath);
103-
strcpy(moduleFullPath, convertedExecutableModulePath);
104-
strcpy(moduleFullPath + folderLength, moduleName);
105163

106-
HANDLE hMod = PalLoadLibrary(moduleFullPath);
164+
NewArrayHolder<char> modulePathHolder(modulePath);
165+
HANDLE hMod = PalLoadLibrary(modulePath);
107166

108167
if (!hMod)
109168
{

src/coreclr/utilcode/util.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,45 @@ bool g_arm64_atomics_present = false;
3232

3333
#endif //!DACCESS_COMPILE
3434

35+
// Validate that the name used to load the JIT/GC is just a simple file name
36+
// and does not contain something that could be used in a non-qualified path.
37+
// For example, using the string "..\..\..\myjit.dll" we might attempt to
38+
// load a JIT from the root of the drive.
39+
//
40+
// The minimal set of characters that we must check for and exclude are:
41+
// On all platforms:
42+
// '/' - (forward slash)
43+
// On Windows:
44+
// '\\' - (backslash)
45+
// ':' - (colon)
46+
//
47+
// Returns false if we find any of these characters in 'pwzModuleName'
48+
// Returns true if we reach the null terminator without encountering
49+
// any of these characters.
50+
//
51+
bool ValidateModuleName(LPCWSTR pwzModuleName)
52+
{
53+
LPCWSTR pCurChar = pwzModuleName;
54+
wchar_t curChar;
55+
do {
56+
curChar = *pCurChar;
57+
if (curChar == '/'
58+
#ifdef TARGET_WINDOWS
59+
|| (curChar == '\\') || (curChar == ':')
60+
#endif
61+
)
62+
{
63+
// Return false if we find any of these character in 'pwzJitName'
64+
return false;
65+
}
66+
pCurChar++;
67+
} while (curChar != 0);
68+
69+
// Return true; we have reached the null terminator
70+
//
71+
return true;
72+
}
73+
3574
//*****************************************************************************
3675
// Convert a string of hex digits into a hex value of the specified # of bytes.
3776
//*****************************************************************************

src/coreclr/vm/codeman.cpp

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,45 +1669,6 @@ struct JIT_LOAD_DATA
16691669
// Here's the global data for JIT load and initialization state.
16701670
JIT_LOAD_DATA g_JitLoadData;
16711671

1672-
// Validate that the name used to load the JIT is just a simple file name
1673-
// and does not contain something that could be used in a non-qualified path.
1674-
// For example, using the string "..\..\..\myjit.dll" we might attempt to
1675-
// load a JIT from the root of the drive.
1676-
//
1677-
// The minimal set of characters that we must check for and exclude are:
1678-
// On all platforms:
1679-
// '/' - (forward slash)
1680-
// On Windows:
1681-
// '\\' - (backslash)
1682-
// ':' - (colon)
1683-
//
1684-
// Returns false if we find any of these characters in 'pwzJitName'
1685-
// Returns true if we reach the null terminator without encountering
1686-
// any of these characters.
1687-
//
1688-
static bool ValidateJitName(LPCWSTR pwzJitName)
1689-
{
1690-
LPCWSTR pCurChar = pwzJitName;
1691-
wchar_t curChar;
1692-
do {
1693-
curChar = *pCurChar;
1694-
if (curChar == '/'
1695-
#ifdef TARGET_WINDOWS
1696-
|| (curChar == '\\') || (curChar == ':')
1697-
#endif
1698-
)
1699-
{
1700-
// Return false if we find any of these character in 'pwzJitName'
1701-
return false;
1702-
}
1703-
pCurChar++;
1704-
} while (curChar != 0);
1705-
1706-
// Return true; we have reached the null terminator
1707-
//
1708-
return true;
1709-
}
1710-
17111672
CORINFO_OS getClrVmOs();
17121673

17131674
#define LogJITInitializationError(...) \
@@ -1770,7 +1731,7 @@ static void LoadAndInitializeJIT(LPCWSTR pwzJitName DEBUGARG(LPCWSTR pwzJitPath)
17701731
return;
17711732
}
17721733

1773-
if (ValidateJitName(pwzJitName))
1734+
if (ValidateModuleName(pwzJitName))
17741735
{
17751736
// Load JIT from next to CoreCLR binary
17761737
PathString CoreClrFolderHolder;

src/coreclr/vm/gcheaputilities.cpp

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "configuration.h"
66
#include "gcheaputilities.h"
77
#include "appdomain.hpp"
8+
#include "hostinformation.h"
89

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

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

163163
#ifdef FEATURE_STANDALONE_GC
164-
HMODULE LoadStandaloneGc(LPCWSTR libFileName)
164+
HMODULE LoadStandaloneGc(LPCWSTR libFileName, LPCWSTR libFilePath)
165165
{
166166
LIMITED_METHOD_CONTRACT;
167+
HMODULE result = nullptr;
168+
169+
if (libFilePath)
170+
{
171+
return CLRLoadLibrary(libFilePath);
172+
}
167173

168-
// Look for the standalone GC module next to the clr binary
169-
PathString libPath = GetInternalSystemDirectory();
170-
libPath.Append(libFileName);
174+
if (!ValidateModuleName(libFileName))
175+
{
176+
LOG((LF_GC, LL_INFO100, "Invalid GC name found %s\n", libFileName));
177+
return nullptr;
178+
}
179+
180+
SString appBase;
181+
if (HostInformation::GetProperty("APP_CONTEXT_BASE_DIRECTORY", appBase))
182+
{
183+
PathString libPath = appBase.GetUnicode();
184+
libPath.Append(libFileName);
171185

172-
LOG((LF_GC, LL_INFO100, "Loading standalone GC from path %s\n", libPath.GetUTF8()));
186+
LOG((LF_GC, LL_INFO100, "Loading standalone GC from appBase %s\n", libPath.GetUTF8()));
187+
188+
LPCWSTR libraryName = libPath.GetUnicode();
189+
result = CLRLoadLibrary(libraryName);
190+
}
191+
192+
if (result == nullptr)
193+
{
194+
// Look for the standalone GC module next to the clr binary
195+
PathString libPath = GetInternalSystemDirectory();
196+
libPath.Append(libFileName);
197+
198+
LOG((LF_GC, LL_INFO100, "Loading standalone GC by coreclr %s\n", libPath.GetUTF8()));
199+
200+
LPCWSTR libraryName = libPath.GetUnicode();
201+
result = CLRLoadLibrary(libraryName);
202+
}
173203

174-
LPCWSTR libraryName = libPath.GetUnicode();
175-
return CLRLoadLibrary(libraryName);
204+
return result;
176205
}
177206
#endif // FEATURE_STANDALONE_GC
178207

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

189218
#ifndef FEATURE_STANDALONE_GC
190219
LOG((LF_GC, LL_FATALERROR, "EE not built with the ability to load standalone GCs"));
191220
return E_FAIL;
192221
#else
193-
HMODULE hMod = LoadStandaloneGc(standaloneGcLocation);
222+
HMODULE hMod = LoadStandaloneGc(standaloneGCName, standaloneGCPath);
194223
if (!hMod)
195224
{
196225
HRESULT err = GetLastError();
197226
#ifdef LOGGING
198-
MAKE_UTF8PTR_FROMWIDE(standaloneGcLocationUtf8, standaloneGcLocation);
199-
LOG((LF_GC, LL_FATALERROR, "Load of %s failed\n", standaloneGcLocationUtf8));
227+
LPCWSTR standaloneGCNameLogging = standaloneGCName ? standaloneGCName : W("");
228+
LPCWSTR standaloneGCPathLogging = standaloneGCPath ? standaloneGCPath : W("");
229+
MAKE_UTF8PTR_FROMWIDE(standaloneGCNameUtf8, standaloneGCNameLogging);
230+
MAKE_UTF8PTR_FROMWIDE(standaloneGCPathUtf8, standaloneGCPathLogging);
231+
LOG((LF_GC, LL_FATALERROR, "Load of %s or %s failed\n", standaloneGCNameUtf8, standaloneGCPathUtf8));
200232
#endif // LOGGING
201233
return __HRESULT_FROM_WIN32(err);
202234
}
@@ -344,16 +376,17 @@ HRESULT GCHeapUtilities::LoadAndInitialize()
344376
assert(g_gc_load_status == GC_LOAD_STATUS_BEFORE_START);
345377
g_gc_load_status = GC_LOAD_STATUS_START;
346378

347-
LPCWSTR standaloneGcLocation = Configuration::GetKnobStringValue(W("System.GC.Name"), CLRConfig::EXTERNAL_GCName);
379+
LPCWSTR standaloneGCName = Configuration::GetKnobStringValue(W("System.GC.Name"), CLRConfig::EXTERNAL_GCName);
380+
LPCWSTR standaloneGCPath = Configuration::GetKnobStringValue(W("System.GC.Path"), CLRConfig::EXTERNAL_GCPath);
348381
g_gc_dac_vars.major_version_number = GC_INTERFACE_MAJOR_VERSION;
349382
g_gc_dac_vars.minor_version_number = GC_INTERFACE_MINOR_VERSION;
350-
if (!standaloneGcLocation)
383+
if (!standaloneGCName && !standaloneGCPath)
351384
{
352385
return InitializeDefaultGC();
353386
}
354387
else
355388
{
356-
return LoadAndInitializeGC(standaloneGcLocation);
389+
return LoadAndInitializeGC(standaloneGCName, standaloneGCPath);
357390
}
358391
}
359392

0 commit comments

Comments
 (0)