Skip to content

Commit 2ac6609

Browse files
Force callstacks to only run on untainted threads (#7918)
Co-authored-by: Wiktor Kopec <[email protected]>
1 parent c3eca56 commit 2ac6609

File tree

6 files changed

+94
-12
lines changed

6 files changed

+94
-12
lines changed

Diff for: src/Profilers/MonitorProfiler/Communication/CommandServer.cpp

+45-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ CommandServer::CommandServer(const std::shared_ptr<ILogger>& logger, ICorProfile
1616
HRESULT CommandServer::Start(
1717
const std::string& path,
1818
std::function<HRESULT(const IpcMessage& message)> callback,
19-
std::function<HRESULT(const IpcMessage& message)> validateMessageCallback)
19+
std::function<HRESULT(const IpcMessage& message)> validateMessageCallback,
20+
std::function<HRESULT(unsigned short commandSet, bool& unmanagedOnly)> unmanagedOnlyCallback)
2021
{
2122
if (_shutdown.load())
2223
{
@@ -35,10 +36,12 @@ HRESULT CommandServer::Start(
3536

3637
_callback = callback;
3738
_validateMessageCallback = validateMessageCallback;
39+
_unmanagedOnlyCallback = unmanagedOnlyCallback;
3840

3941
IfFailLogRet_(_logger, _server.Bind(path));
4042
_listeningThread = std::thread(&CommandServer::ListeningThread, this);
4143
_clientThread = std::thread(&CommandServer::ClientProcessingThread, this);
44+
_unmanagedOnlyThread = std::thread(&CommandServer::UnmanagedOnlyProcessingThread, this);
4245
return S_OK;
4346
}
4447

@@ -48,10 +51,12 @@ void CommandServer::Shutdown()
4851
if (_shutdown.compare_exchange_strong(shutdown, true))
4952
{
5053
_clientQueue.Complete();
54+
_unmanagedOnlyQueue.Complete();
5155
_server.Shutdown();
5256

5357
_listeningThread.join();
5458
_clientThread.join();
59+
_unmanagedOnlyThread.join();
5560
}
5661
}
5762

@@ -110,7 +115,15 @@ void CommandServer::ListeningThread()
110115

111116
if (doEnqueueMessage)
112117
{
113-
_clientQueue.Enqueue(message);
118+
bool unmanagedOnly = false;
119+
if (SUCCEEDED(_unmanagedOnlyCallback(message.CommandSet, unmanagedOnly)) && unmanagedOnly)
120+
{
121+
_unmanagedOnlyQueue.Enqueue(message);
122+
}
123+
else
124+
{
125+
_clientQueue.Enqueue(message);
126+
}
114127
}
115128
}
116129
}
@@ -134,6 +147,36 @@ void CommandServer::ClientProcessingThread()
134147
//We are complete, discard all messages
135148
break;
136149
}
150+
151+
// DispatchMessage in the callback serializes all callbacks.
152+
hr = _callback(message);
153+
if (hr != S_OK)
154+
{
155+
_logger->Log(LogLevel::Warning, _LS("IpcMessage callback failed: 0x%08x"), hr);
156+
}
157+
}
158+
}
159+
160+
void CommandServer::UnmanagedOnlyProcessingThread()
161+
{
162+
HRESULT hr = _profilerInfo->InitializeCurrentThread();
163+
164+
if (FAILED(hr))
165+
{
166+
_logger->Log(LogLevel::Error, _LS("Unable to initialize thread: 0x%08x"), hr);
167+
return;
168+
}
169+
170+
while (true)
171+
{
172+
IpcMessage message;
173+
hr = _unmanagedOnlyQueue.BlockingDequeue(message);
174+
if (hr != S_OK)
175+
{
176+
// We are complete, discard all messages
177+
break;
178+
}
179+
137180
hr = _callback(message);
138181
if (hr != S_OK)
139182
{

Diff for: src/Profilers/MonitorProfiler/Communication/CommandServer.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,34 @@ class CommandServer final
2222
HRESULT Start(
2323
const std::string& path,
2424
std::function<HRESULT (const IpcMessage& message)> callback,
25-
std::function<HRESULT (const IpcMessage& message)> validateMessageCallback);
25+
std::function<HRESULT (const IpcMessage& message)> validateMessageCallback,
26+
std::function<HRESULT (unsigned short commandSet, bool& unmanagedOnly)> unmanagedOnlyCallback);
2627
void Shutdown();
2728

2829
private:
2930
void ListeningThread();
3031
void ClientProcessingThread();
32+
void UnmanagedOnlyProcessingThread();
3133

3234
std::atomic_bool _shutdown;
3335

3436
std::function<HRESULT(const IpcMessage& message)> _callback;
3537
std::function<HRESULT(const IpcMessage& message)> _validateMessageCallback;
38+
std::function<HRESULT(unsigned short commandSet, bool& unmanagedOnly)> _unmanagedOnlyCallback;
3639

3740
IpcCommServer _server;
3841

42+
// We allocates two queues and two threads to process messages.
43+
// UnmanagedOnlyQueue is dedicated to ICorProfiler api calls that cannot be called on threads that have previously invoked managed code, such as StackSnapshot.
44+
// Other command sets such as StartupHook call managed code and therefore interfere with StackSnapshot calls.
3945
BlockingQueue<IpcMessage> _clientQueue;
46+
BlockingQueue<IpcMessage> _unmanagedOnlyQueue;
47+
4048
std::shared_ptr<ILogger> _logger;
4149

4250
std::thread _listeningThread;
4351
std::thread _clientThread;
52+
std::thread _unmanagedOnlyThread;
4453

4554
ComPtr<ICorProfilerInfo12> _profilerInfo;
4655
};

Diff for: src/Profilers/MonitorProfiler/Communication/MessageCallbackManager.cpp

+17-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ bool MessageCallbackManager::TryRegister(unsigned short commandSet, ManagedMessa
1616
return TryRegister(commandSet, [pCallback](const IpcMessage& message)-> HRESULT
1717
{
1818
return pCallback(message.Command, message.Payload.data(), message.Payload.size());
19-
});
19+
}, false);
2020
}
2121

22-
bool MessageCallbackManager::TryRegister(unsigned short commandSet, std::function<HRESULT (const IpcMessage& message)> callback)
22+
bool MessageCallbackManager::TryRegister(unsigned short commandSet, std::function<HRESULT (const IpcMessage& message)> callback, bool unmanagedOnly)
2323
{
2424
std::lock_guard<std::mutex> dispatchLock(m_dispatchMutex);
2525
std::lock_guard<std::mutex> lookupLock(m_lookupMutex);
@@ -30,7 +30,7 @@ bool MessageCallbackManager::TryRegister(unsigned short commandSet, std::functio
3030
return false;
3131
}
3232

33-
m_callbacks[commandSet] = callback;
33+
m_callbacks[commandSet] = CallbackInfo(unmanagedOnly, callback);
3434
return true;
3535
}
3636

@@ -61,9 +61,22 @@ bool MessageCallbackManager::TryGetCallback(unsigned short commandSet, std::func
6161
auto const& it = m_callbacks.find(commandSet);
6262
if (it != m_callbacks.end())
6363
{
64-
callback = it->second;
64+
callback = it->second.Callback;
6565
return true;
6666
}
6767

6868
return false;
6969
}
70+
71+
HRESULT MessageCallbackManager::UnmanagedOnly(unsigned short commandSet, bool& unmanagedOnly)
72+
{
73+
std::lock_guard<std::mutex> lookupLock(m_lookupMutex);
74+
75+
auto const& it = m_callbacks.find(commandSet);
76+
if (it != m_callbacks.end())
77+
{
78+
unmanagedOnly = it->second.UnmanagedOnly;
79+
return S_OK;
80+
}
81+
return E_FAIL;
82+
}

Diff for: src/Profilers/MonitorProfiler/Communication/MessageCallbackManager.h

+18-2
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,33 @@
1212

1313
typedef HRESULT (STDMETHODCALLTYPE *ManagedMessageCallback)(UINT16, const BYTE*, UINT64);
1414

15+
struct CallbackInfo
16+
{
17+
CallbackInfo() = default;
18+
CallbackInfo(bool unmanagedOnly, std::function<HRESULT (const IpcMessage& message)> callback)
19+
: UnmanagedOnly(unmanagedOnly), Callback(callback)
20+
{
21+
}
22+
23+
bool UnmanagedOnly = false;
24+
std::function<HRESULT (const IpcMessage& message)> Callback;
25+
};
26+
1527
class MessageCallbackManager
1628
{
1729
public:
1830
HRESULT DispatchMessage(const IpcMessage& message);
1931
bool IsRegistered(unsigned short commandSet);
20-
bool TryRegister(unsigned short commandSet, std::function<HRESULT (const IpcMessage& message)> callback);
32+
33+
// Some callbacks, such as the profiler, must run on their own thread due to ICorProfiler API restrictions.
34+
// Setting unmanagedOnly to true will queue the work from the command set to a separate thread.
35+
bool TryRegister(unsigned short commandSet, std::function<HRESULT (const IpcMessage& message)> callback, bool unmanagedOnly);
2136
bool TryRegister(unsigned short commandSet, ManagedMessageCallback pCallback);
2237
void Unregister(unsigned short commandSet);
38+
HRESULT UnmanagedOnly(unsigned short commandSet, bool& unmanagedOnly);
2339
private:
2440
bool TryGetCallback(unsigned short commandSet, std::function<HRESULT (const IpcMessage& message)>& callback);
25-
std::unordered_map<unsigned short, std::function<HRESULT (const IpcMessage& message)>> m_callbacks;
41+
std::unordered_map<unsigned short, CallbackInfo> m_callbacks;
2642
//
2743
// Ideally we would use a single std::shared_mutex instead, but we are targeting C++11 without
2844
// an easy way to upgrade to C++17 at this time, so we use two separate mutexes instead to

Diff for: src/Profilers/MonitorProfiler/MainProfiler/MainProfiler.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ HRESULT MainProfiler::InitializeCommandServer()
241241
_commandServer = std::unique_ptr<CommandServer>(new CommandServer(m_pLogger, m_pCorProfilerInfo));
242242
tstring socketPath = sharedPath + separator + instanceId + _T(".sock");
243243

244-
if (!g_MessageCallbacks.TryRegister(static_cast<unsigned short>(CommandSet::Profiler), [this](const IpcMessage& message)-> HRESULT { return this->ProfilerCommandSetCallback(message); }))
244+
if (!g_MessageCallbacks.TryRegister(static_cast<unsigned short>(CommandSet::Profiler), [this](const IpcMessage& message)-> HRESULT { return this->ProfilerCommandSetCallback(message); }, true))
245245
{
246246
m_pLogger->Log(LogLevel::Error, _LS("Unable to register Profiler CommandSet callback."));
247247
return E_FAIL;
@@ -250,7 +250,8 @@ HRESULT MainProfiler::InitializeCommandServer()
250250
hr = _commandServer->Start(
251251
to_string(socketPath),
252252
[this](const IpcMessage& message)-> HRESULT { return this->MessageCallback(message); },
253-
[this](const IpcMessage& message)-> HRESULT { return this->ValidateMessage(message); });
253+
[this](const IpcMessage& message)-> HRESULT { return this->ValidateMessage(message); },
254+
[](unsigned short commandSet, bool& unmanagedOnly)-> HRESULT { return g_MessageCallbacks.UnmanagedOnly(commandSet, unmanagedOnly);});
254255
if (FAILED(hr))
255256
{
256257
g_MessageCallbacks.Unregister(static_cast<unsigned short>(CommandSet::Profiler));

Diff for: src/Profilers/MutatingMonitorProfiler/ProbeInstrumentation/ProbeInstrumentation.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ HRESULT ProbeInstrumentation::InitBackgroundService()
6565
m_probeManagementThread = thread(&ProbeInstrumentation::WorkerThread, this);
6666
//
6767
// Create a dedicated thread for managed callbacks.
68-
// Performing the callbacks will taint the calling thread preventing it
68+
// Performing the callbacks will prevent the calling thread
6969
// from using certain ICorProfiler APIs marked as unsafe.
7070
// Those functions will fail with CORPROF_E_UNSUPPORTED_CALL_SEQUENCE.
7171
//

0 commit comments

Comments
 (0)