Add crash dump callback (#40674)
Kevin Vega committed
Jun 10, 2026 at 11:15 UTC
fb3d4e7b2e9aa5aea8cc51418014125839ef1ec2
16 files changed
+469
-9
msipackage/package.wix.in
+8
@@ -314,6 +314,14 @@
314
</RegistryKey>
315
</RegistryKey>
316
317
+ <!-- ICrashDumpCallback-->
318
+ <RegistryKey Root="HKCR" Key="Interface\{8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801}">
319
+ <RegistryValue Value="ICrashDumpCallback" Type="string" />
320
+ <RegistryKey Key="ProxyStubClsid32">
321
+ <RegistryValue Value="{4EA0C6DD-E9FF-48E7-994E-13A31D10DC60}" Type="string" />
322
+ </RegistryKey>
323
+ </RegistryKey>
324
+
325
<!-- IProgressCallback-->
326
<RegistryKey Root="HKCR" Key="Interface\{5038842F-53DB-4F30-A6D0-A41B02C94AC1}">
327
<RegistryValue Value="IProgressCallback" Type="string" />
src/windows/WslcSDK/CMakeLists.txt
+2
@@ -2,6 +2,7 @@ set(SOURCES
2
IOCallback.cpp
3
ProgressCallback.cpp
4
TerminationCallback.cpp
5
+ CrashDumpCallback.cpp
6
wslcsdk.cpp
7
WslcsdkPrivate.cpp
8
)
@@ -10,6 +11,7 @@ set(HEADERS
11
IOCallback.h
12
ProgressCallback.h
13
TerminationCallback.h
14
+ CrashDumpCallback.h
15
wslcsdk.h
16
WslcsdkPrivate.h
17
)
src/windows/WslcSDK/CrashDumpCallback.cpp
new
+40
@@ -0,0 +1,40 @@
1
+/*++
2
+
3
+Copyright (c) Microsoft. All rights reserved.
4
+
5
+Module Name:
6
+
7
+ CrashDumpCallback.cpp
8
+
9
+Abstract:
10
+
11
+ Implementation of a type that implements ICrashDumpCallback.
12
+
13
+--*/
14
+#include "precomp.h"
15
+#include "CrashDumpCallback.h"
16
+
17
+CrashDumpCallback::CrashDumpCallback(WslcSessionCrashDumpCallback callback, PVOID context) :
18
+ m_callback(callback), m_context(context)
19
+{
20
+}
21
+
22
+HRESULT STDMETHODCALLTYPE CrashDumpCallback::OnCrashDump(
23
+ _In_ LPCWSTR DumpPath, _In_opt_ LPCSTR ProcessName, _In_ ULONGLONG Pid, _In_ ULONG Signal, _In_ ULONGLONG Timestamp)
24
+try
25
+{
26
+ if (m_callback)
27
+ {
28
+ WslcSessionCrashDumpInfo info{};
29
+ info.dumpPath = DumpPath;
30
+ info.processName = ProcessName ? ProcessName : "";
31
+ info.pid = Pid;
32
+ info.signal = Signal;
33
+ info.timestamp = Timestamp;
34
+
35
+ m_callback(&info, m_context);
36
+ }
37
+
38
+ return S_OK;
39
+}
40
+CATCH_RETURN();
src/windows/WslcSDK/CrashDumpCallback.h
new
+31
@@ -0,0 +1,31 @@
1
+/*++
2
+
3
+Copyright (c) Microsoft. All rights reserved.
4
+
5
+Module Name:
6
+
7
+ CrashDumpCallback.h
8
+
9
+Abstract:
10
+
11
+ Header for a type that implements ICrashDumpCallback. Bridges the COM
12
+ ICrashDumpCallback interface back to the C-style SDK callback registered
13
+ via WslcRegisterSessionCrashDumpCallback.
14
+
15
+--*/
16
+#pragma once
17
+#include "wslc.h"
18
+#include "wslcsdkprivate.h"
19
+#include <winrt/base.h>
20
+
21
+struct CrashDumpCallback : public winrt::implements<CrashDumpCallback, ICrashDumpCallback>
22
+{
23
+ CrashDumpCallback(WslcSessionCrashDumpCallback callback, PVOID context);
24
+
25
+ // ICrashDumpCallback
26
+ HRESULT STDMETHODCALLTYPE OnCrashDump(_In_ LPCWSTR DumpPath, _In_opt_ LPCSTR ProcessName, _In_ ULONGLONG Pid, _In_ ULONG Signal, _In_ ULONGLONG Timestamp) override;
27
+
28
+private:
29
+ WslcSessionCrashDumpCallback m_callback = nullptr;
30
+ PVOID m_context = nullptr;
31
+};
src/windows/WslcSDK/WslcsdkPrivate.cpp
+5
@@ -40,6 +40,11 @@ WslcSessionImpl* GetInternalType(WslcSession handle)
40
return reinterpret_cast<WslcSessionImpl*>(handle);
41
}
42
43
+WslcCrashDumpSubscriptionImpl* GetInternalType(WslcCrashDumpSubscription handle)
44
+{
45
+ return reinterpret_cast<WslcCrashDumpSubscriptionImpl*>(handle);
46
+}
47
+
48
WslcContainerImpl* GetInternalType(WslcContainer handle)
49
{
50
return reinterpret_cast<WslcContainerImpl*>(handle);
src/windows/WslcSDK/WslcsdkPrivate.h
+10
@@ -112,6 +112,16 @@ struct WslcSessionImpl
112
113
WslcSessionImpl* GetInternalType(WslcSession handle);
114
115
+// Backs a WslcCrashDumpSubscription handle. Keeps the COM shim alive and holds the service-side
116
+// subscription whose release unregisters the callback.
117
+struct WslcCrashDumpSubscriptionImpl
118
+{
119
+ wil::com_ptr<ICrashDumpCallback> callback;
120
+ wil::com_ptr<IUnknown> subscription;
121
+};
122
+
123
+WslcCrashDumpSubscriptionImpl* GetInternalType(WslcCrashDumpSubscription handle);
124
+
125
struct WslcContainerImpl
126
{
127
wil::com_ptr<IWSLCContainer> container;
src/windows/WslcSDK/wslcsdk.cpp
+46
-2
@@ -18,6 +18,7 @@ Abstract:
18
#include "Defaults.h"
19
#include "ProgressCallback.h"
20
#include "TerminationCallback.h"
21
+#include "CrashDumpCallback.h"
22
#include "Localization.h"
23
#include "WslInstall.h"
24
#include "wslutil.h"
@@ -600,13 +601,56 @@ try
601
}
602
CATCH_RETURN();
603
604
+STDAPI WslcRegisterSessionCrashDumpCallback(
605
+ _In_ WslcSession session,
606
+ _In_ WslcSessionCrashDumpCallback crashDumpCallback,
607
+ _In_opt_ PVOID crashDumpContext,
608
+ _Out_ WslcCrashDumpSubscription* subscription,
609
+ _Outptr_opt_result_z_ PWSTR* errorMessage)
610
+try
611
+{
612
+ RETURN_HR_IF_NULL(E_POINTER, subscription);
613
+ *subscription = nullptr;
614
+ RETURN_HR_IF_NULL(E_INVALIDARG, crashDumpCallback);
615
+
616
+ ErrorInfoWrapper errorInfoWrapper{errorMessage};
617
+ auto internalSession = CheckAndGetInternalType(session);
618
+ RETURN_HR_IF_NULL(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), internalSession->session);
619
+
620
+ auto result = std::make_unique<WslcCrashDumpSubscriptionImpl>();
621
+ auto callback = winrt::make_self<CrashDumpCallback>(crashDumpCallback, crashDumpContext);
622
+ result->callback = callback.get();
623
+
624
+ if (SUCCEEDED(errorInfoWrapper.CaptureResult(
625
+ internalSession->session->RegisterCrashDumpCallback(result->callback.get(), &result->subscription))))
626
+ {
627
+ *subscription = reinterpret_cast<WslcCrashDumpSubscription>(result.release());
628
+ }
629
+
630
+ return errorInfoWrapper;
631
+}
632
+CATCH_RETURN();
633
+
634
+STDAPI WslcReleaseCrashDumpSubscription(_In_ WslcCrashDumpSubscription subscription)
635
+try
636
+{
637
+ auto internalType = CheckAndGetInternalTypeUniquePointer(subscription);
638
+
639
+ // Release the service-side subscription first so it unregisters cleanly, then drop the shim.
640
+ internalType->subscription.reset();
641
+ internalType->callback.reset();
642
+
643
+ return S_OK;
644
+}
645
+CATCH_RETURN();
646
+
647
STDAPI WslcReleaseSession(_In_ WslcSession session)
648
try
649
{
650
auto internalType = CheckAndGetInternalTypeUniquePointer(session);
651
608
- // Intentionally destroy session before termination callback in the event that
609
- // the termination callback ends up being invoked by session destruction.
652
+ // Drop the session before the termination callback, in case session destruction triggers
653
+ // the termination callback.
654
internalType->session.reset();
655
internalType->terminationCallback.reset();
656
src/windows/WslcSDK/wslcsdk.def
+3
@@ -22,6 +22,9 @@ WslcSetSessionSettingsMemory
22
WslcSetSessionSettingsTimeout
23
WslcSetSessionSettingsVhd
24
25
+WslcRegisterSessionCrashDumpCallback
26
+WslcReleaseCrashDumpSubscription
27
+
28
WslcTerminateSession
29
WslcSessionAuthenticate
30
WslcPullSessionImage
src/windows/WslcSDK/wslcsdk.h
+28
@@ -125,6 +125,21 @@ typedef enum WslcSessionTerminationReason
125
126
typedef __callback void(CALLBACK* WslcSessionTerminationCallback)(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context);
127
128
+typedef struct WslcSessionCrashDumpInfo
129
+{
130
+ _Field_z_ PCWSTR dumpPath;
131
+ _Field_z_ PCSTR processName;
132
+ uint64_t pid;
133
+ uint32_t signal;
134
+ uint64_t timestamp;
135
+} WslcSessionCrashDumpInfo;
136
+
137
+typedef __callback void(CALLBACK* WslcSessionCrashDumpCallback)(_In_ const WslcSessionCrashDumpInfo* info, _In_opt_ PVOID context);
138
+
139
+// Opaque handle returned by WslcRegisterSessionCrashDumpCallback. Holding it keeps the crash dump
140
+// registration alive; pass it to WslcReleaseCrashDumpSubscription to unsubscribe.
141
+DECLARE_HANDLE(WslcCrashDumpSubscription);
142
+
143
STDAPI WslcInitSessionSettings(_In_ PCWSTR name, _In_ PCWSTR storagePath, _Out_ WslcSessionSettings* sessionSettings);
144
145
STDAPI WslcCreateSession(_In_ WslcSessionSettings* sessionSettings, _Out_ WslcSession* session, _Outptr_opt_result_z_ PWSTR* errorMessage);
@@ -145,6 +160,19 @@ STDAPI WslcSetSessionSettingsTerminationCallback(
160
STDAPI WslcTerminateSession(_In_ WslcSession session);
161
STDAPI WslcReleaseSession(_In_ WslcSession session);
162
163
+// Registers a callback invoked when a Linux process crash dump is written for the session.
164
+// Works for any caller holding a live session. The returned subscription keeps the registration
165
+// alive; release it with WslcReleaseCrashDumpSubscription to unsubscribe. Multiple subscriptions
166
+// can be registered against the same session.
167
+STDAPI WslcRegisterSessionCrashDumpCallback(
168
+ _In_ WslcSession session,
169
+ _In_ WslcSessionCrashDumpCallback crashDumpCallback,
170
+ _In_opt_ PVOID crashDumpContext,
171
+ _Out_ WslcCrashDumpSubscription* subscription,
172
+ _Outptr_opt_result_z_ PWSTR* errorMessage);
173
+
174
+STDAPI WslcReleaseCrashDumpSubscription(_In_ WslcCrashDumpSubscription subscription);
175
+
176
// CONTAINER DEFINITIONS
177
178
typedef enum WslcPortProtocol
src/windows/service/inc/wslc.idl
+17
@@ -109,6 +109,21 @@ interface ITerminationCallback : IUnknown
109
HRESULT OnTermination(WSLCVirtualMachineTerminationReason Reason, LPCWSTR Details);
110
};
111
112
+[
113
+ uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801),
114
+ pointer_default(unique),
115
+ object
116
+]
117
+interface ICrashDumpCallback : IUnknown
118
+{
119
+ HRESULT OnCrashDump(
120
+ [in, string] LPCWSTR DumpPath,
121
+ [in, unique, string] LPCSTR ProcessName,
122
+ [in] ULONGLONG Pid,
123
+ [in] ULONG Signal,
124
+ [in] ULONGLONG Timestamp);
125
+};
126
+
127
[
128
uuid(5038842F-53DB-4F30-A6D0-A41B02C94AC1),
129
pointer_default(unique),
@@ -810,6 +825,8 @@ interface IWSLCSession : IUnknown
825
HRESULT DeleteNetwork([in] LPCSTR Name);
826
HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count);
827
HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output);
828
+
829
+ HRESULT RegisterCrashDumpCallback([in] ICrashDumpCallback* Callback, [out] IUnknown** Subscription);
830
}
831
832
//
src/windows/wslcsession/WSLCSession.cpp
+94
-2
@@ -160,6 +160,38 @@ std::string GenerateContainerName(int retry)
160
161
namespace wsl::windows::service::wslc {
162
163
+// COM object returned by WSLCSession::RegisterCrashDumpCallback. Holds a strong reference to the
164
+// owning session so the session COM facade cannot be destroyed before all subscriptions are
165
+// released. When the last reference is dropped, the destructor removes the matching entry from
166
+// the session's callback list. Safe to release in any order with respect to the session pointer
167
+// that the client also holds.
168
+//
169
+// The subscription is returned to callers as a bare IUnknown -- the type is an opaque lifetime
170
+// handle with no methods of its own, so there is no need for a dedicated COM interface.
171
+class CrashDumpSubscription
172
+ : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IUnknown, IFastRundown>
173
+{
174
+public:
175
+ HRESULT RuntimeClassInitialize(Microsoft::WRL::ComPtr<WSLCSession> Session, WSLCSession::CrashDumpCallbackList::iterator It)
176
+ {
177
+ m_session = std::move(Session);
178
+ m_iterator = It;
179
+ return S_OK;
180
+ }
181
+
182
+ ~CrashDumpSubscription()
183
+ {
184
+ if (m_session)
185
+ {
186
+ m_session->RemoveCrashDumpCallback(m_iterator);
187
+ }
188
+ }
189
+
190
+private:
191
+ Microsoft::WRL::ComPtr<WSLCSession> m_session;
192
+ WSLCSession::CrashDumpCallbackList::iterator m_iterator{};
193
+};
194
+
195
UserHandle::UserHandle(WSLCSession& Session, HANDLE handle) : m_session(&Session), m_handle(handle)
196
{
197
WI_ASSERT(!!m_handle);
@@ -294,8 +326,13 @@ try
326
TraceLoggingValue(m_displayName.c_str(), "DisplayName"),
327
TraceLoggingValue(m_creatorProcessName.c_str(), "CreatorProcess"));
328
297
- // Create the VM.
298
- m_virtualMachine.emplace(Vm, Settings, m_sessionTerminatingEvent.get());
329
+ // Create the VM. The VM produces crash events; the session multiplexes them out to any
330
+ // registered ICrashDumpCallback subscribers via OnCrashDumpWritten.
331
+ m_virtualMachine.emplace(
332
+ Vm,
333
+ Settings,
334
+ m_sessionTerminatingEvent.get(),
335
+ std::bind(&WSLCSession::OnCrashDumpWritten, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
336
337
// Make sure that everything is destroyed correctly if an exception is thrown.
338
auto errorCleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { LOG_IF_FAILED(Terminate()); });
@@ -2625,6 +2662,61 @@ try
2662
}
2663
CATCH_RETURN();
2664
2665
+HRESULT WSLCSession::RegisterCrashDumpCallback(_In_ ICrashDumpCallback* Callback, _Out_ IUnknown** Subscription)
2666
+try
2667
+{
2668
+ RETURN_HR_IF(E_POINTER, Callback == nullptr || Subscription == nullptr);
2669
+ *Subscription = nullptr;
2670
+
2671
+ CrashDumpCallbackList::iterator it;
2672
+ {
2673
+ auto lock = m_crashDumpLock.lock_exclusive();
2674
+ it = m_crashDumpCallbacks.emplace(m_crashDumpCallbacks.end(), Callback);
2675
+ }
2676
+
2677
+ // Roll back the registration if creating the subscription object fails so we don't leak it.
2678
+ auto removeOnFailure = wil::scope_exit([&]() { RemoveCrashDumpCallback(it); });
2679
+
2680
+ // The subscription holds a strong reference to this session, which guarantees that
2681
+ // RemoveCrashDumpCallback is safe to call from the subscription destructor regardless of the
2682
+ // order in which the client releases its session and subscription pointers.
2683
+ Microsoft::WRL::ComPtr<CrashDumpSubscription> subscription;
2684
+ RETURN_IF_FAILED(Microsoft::WRL::MakeAndInitialize<CrashDumpSubscription>(&subscription, Microsoft::WRL::ComPtr<WSLCSession>{this}, it));
2685
+
2686
+ RETURN_IF_FAILED(subscription.CopyTo(Subscription));
2687
+
2688
+ removeOnFailure.release();
2689
+ return S_OK;
2690
+}
2691
+CATCH_RETURN();
2692
+
2693
+void WSLCSession::RemoveCrashDumpCallback(CrashDumpCallbackList::iterator It) noexcept
2694
+{
2695
+ auto lock = m_crashDumpLock.lock_exclusive();
2696
+ m_crashDumpCallbacks.erase(It);
2697
+}
2698
+
2699
+void WSLCSession::OnCrashDumpWritten(const std::wstring& DumpPath, const std::string& ProcessName, ULONGLONG Pid, ULONG Signal, ULONGLONG Timestamp)
2700
+try
2701
+{
2702
+ // Snapshot the callback list under the lock so that cross-process callback invocations don't
2703
+ // hold m_crashDumpLock (and can't deadlock with Register/Remove on the same thread that the
2704
+ // callback might in turn use).
2705
+ std::vector<wil::com_ptr<ICrashDumpCallback>> snapshot;
2706
+ {
2707
+ auto lock = m_crashDumpLock.lock_shared();
2708
+ snapshot.assign(m_crashDumpCallbacks.begin(), m_crashDumpCallbacks.end());
2709
+ }
2710
+
2711
+ auto comCall = RegisterUserCOMCallback();
2712
+
2713
+ for (const auto& callback : snapshot)
2714
+ {
2715
+ LOG_IF_FAILED(callback->OnCrashDump(DumpPath.c_str(), ProcessName.c_str(), Pid, Signal, Timestamp));
2716
+ }
2717
+}
2718
+CATCH_LOG();
2719
+
2720
HRESULT WSLCSession::MountWindowsFolder(LPCWSTR WindowsPath, LPCSTR LinuxPath, BOOL ReadOnly)
2721
try
2722
{
src/windows/wslcsession/WSLCSession.h
+22
@@ -22,6 +22,7 @@ Abstract:
22
#include "DockerEventTracker.h"
23
#include "DockerHTTPClient.h"
24
#include "IORelay.h"
25
+#include <list>
26
#include <unordered_map>
27
28
namespace wsl::windows::service::wslc {
@@ -83,6 +84,10 @@ public:
84
// Used by the COM server host to signal process exit.
85
void SetDestructionCallback(std::function<void()>&& callback);
86
87
+ // Type of m_crashDumpCallbacks. Exposed so CrashDumpSubscription can hold an iterator into
88
+ // it as an O(1) registration handle.
89
+ using CrashDumpCallbackList = std::list<wil::com_ptr<ICrashDumpCallback>>;
90
+
91
// IWSLCSession - initialization methods
92
IFACEMETHOD(GetProcessHandle)(_Out_ HANDLE* ProcessHandle) override;
93
IFACEMETHOD(Initialize)(
@@ -173,6 +178,12 @@ public:
178
179
IFACEMETHOD(Terminate()) override;
180
181
+ IFACEMETHOD(RegisterCrashDumpCallback)(_In_ ICrashDumpCallback* Callback, _Out_ IUnknown** Subscription) override;
182
+
183
+ // Called by CrashDumpSubscription when its last reference is released. The iterator must
184
+ // have been returned by RegisterCrashDumpCallback against this session.
185
+ void RemoveCrashDumpCallback(CrashDumpCallbackList::iterator It) noexcept;
186
+
187
// ISupportErrorInfo
188
IFACEMETHOD(InterfaceSupportsErrorInfo)(_In_ REFIID riid) override;
189
@@ -217,6 +228,8 @@ private:
228
std::string InspectImageLockHeld(const std::string& Id);
229
void OnContainerDeleted(const WSLCContainerImpl* Container);
230
231
+ void OnCrashDumpWritten(const std::wstring& DumpPath, const std::string& ProcessName, ULONGLONG Pid, ULONG Signal, ULONGLONG Timestamp);
232
+
233
_Requires_shared_lock_held_(m_lock)
234
void OnImageCreated(const std::string& ImageNameOrId) noexcept;
235
@@ -278,6 +291,15 @@ private:
291
std::recursive_mutex m_userCOMCallbacksLock;
292
__guarded_by(m_userCOMCallbacksLock) std::map<DWORD, int> m_userCOMCallbackThreads;
293
294
+ // Callbacks registered via RegisterCrashDumpCallback. std::list gives stable iterators that
295
+ // survive insertions and unrelated erasures, so each CrashDumpSubscription stashes its own
296
+ // iterator and uses it as an O(1) removal handle when the last reference is released.
297
+ // The session's lifetime extends past Terminate() (the COM object outlives the VM), so this
298
+ // list may outlive m_virtualMachine; that's fine because dispatch only runs while the VM
299
+ // thread is alive.
300
+ mutable wil::srwlock m_crashDumpLock;
301
+ _Guarded_by_(m_crashDumpLock) CrashDumpCallbackList m_crashDumpCallbacks;
302
+
303
// Used for testing only.
304
std::mutex m_allocatedPortsLock;
305
__guarded_by(m_allocatedPortsLock) std::map<uint16_t, std::pair<std::shared_ptr<VmPortAllocation>, size_t>> m_allocatedPorts;
src/windows/wslcsession/WSLCVirtualMachine.cpp
+21
-4
@@ -248,12 +248,14 @@ VMPortMapping& VMPortMapping::operator=(VMPortMapping&& Other)
248
return *this;
249
}
250
251
-WSLCVirtualMachine::WSLCVirtualMachine(_In_ IWSLCVirtualMachine* Vm, _In_ const WSLCSessionInitSettings* Settings, _In_ HANDLE SessionTerminatingEvent) :
251
+WSLCVirtualMachine::WSLCVirtualMachine(
252
+ _In_ IWSLCVirtualMachine* Vm, _In_ const WSLCSessionInitSettings* Settings, _In_ HANDLE SessionTerminatingEvent, _In_ TOnCrashDump&& OnCrashDump) :
253
m_vm(Vm),
254
m_featureFlags(static_cast<WSLCFeatureFlags>(Settings->FeatureFlags)),
255
m_networkingMode(Settings->NetworkingMode),
256
m_bootTimeoutMs(Settings->BootTimeoutMs),
257
m_rootVhdType(Settings->RootVhdTypeOverride ? Settings->RootVhdTypeOverride : "ext4"),
258
+ m_onCrashDump(std::move(OnCrashDump)),
259
m_sessionTerminatingEvent(SessionTerminatingEvent)
260
{
261
// N.B. The constructor should not run any operation that could throw, so the destructor runs even if the VM fails to boot.
@@ -1249,6 +1251,8 @@ void WSLCVirtualMachine::CollectCrashDumps(wil::unique_socket&& listenSocket)
1251
// No impersonation needed - the session process already runs as the user.
1252
wslutil::SetThreadDescription(L"CrashDumpCollection");
1253
1254
+ const auto coInit = wil::CoInitializeEx(COINIT_MULTITHREADED);
1255
+
1256
const auto crashDumpFolder = filesystem::GetTempFolderPath(GetCurrentProcessToken()) / L"wslc-crashes";
1257
1258
while (!m_vmTerminatingEvent.is_signaled())
@@ -1275,10 +1279,14 @@ void WSLCVirtualMachine::CollectCrashDumps(wil::unique_socket&& listenSocket)
1279
const auto bufferSize = responseSpan.size_bytes() - offsetof(LX_PROCESS_CRASH, Buffer);
1280
const std::string process(message.Buffer, strnlen(message.Buffer, bufferSize));
1281
1282
+ const auto crashPid = message.Pid;
1283
+ const auto crashSignal = message.Signal;
1284
+ const auto crashTimestamp = message.Timestamp;
1285
+
1286
constexpr auto dumpExtension = ".dmp";
1287
constexpr auto dumpPrefix = "wsl-crash";
1288
1281
- auto filename = std::format("{}-{}-{}-{}-{}{}", dumpPrefix, message.Timestamp, message.Pid, process, message.Signal, dumpExtension);
1289
+ auto filename = std::format("{}-{}-{}-{}-{}{}", dumpPrefix, crashTimestamp, crashPid, process, crashSignal, dumpExtension);
1290
1291
std::replace_if(
1292
filename.begin(),
@@ -1291,8 +1299,8 @@ void WSLCVirtualMachine::CollectCrashDumps(wil::unique_socket&& listenSocket)
1299
WSL_LOG(
1300
"WSLCLinuxCrash",
1301
TraceLoggingValue(fullPath.c_str(), "FullPath"),
1294
- TraceLoggingValue(message.Pid, "Pid"),
1295
- TraceLoggingValue(message.Signal, "Signal"),
1302
+ TraceLoggingValue(crashPid, "Pid"),
1303
+ TraceLoggingValue(crashSignal, "Signal"),
1304
TraceLoggingValue(process.c_str(), "process"));
1305
1306
filesystem::EnsureDirectory(crashDumpFolder.c_str());
@@ -1316,6 +1324,15 @@ void WSLCVirtualMachine::CollectCrashDumps(wil::unique_socket&& listenSocket)
1324
1325
transaction.SendResultMessage<std::int32_t>(0);
1326
relay::InterruptableRelay(reinterpret_cast<HANDLE>(channel.Socket()), file.get(), nullptr);
1327
+
1328
+ file.reset();
1329
+
1330
+ // Notify the session that a crash dump has been fully written. The session fans out
1331
+ // to any registered ICrashDumpCallback subscribers. Failures are caller-handled.
1332
+ if (m_onCrashDump)
1333
+ {
1334
+ m_onCrashDump(fullPath.wstring(), process, crashPid, crashSignal, crashTimestamp);
1335
+ }
1336
}
1337
CATCH_LOG()
1338
}
src/windows/wslcsession/WSLCVirtualMachine.h
+11
-1
@@ -121,7 +121,13 @@ public:
121
122
using TPrepareCommandLine = std::function<void(const std::vector<ConnectedSocket>&)>;
123
124
- WSLCVirtualMachine(_In_ IWSLCVirtualMachine* Vm, _In_ const WSLCSessionInitSettings* Settings, _In_ HANDLE SessionTerminatingEvent);
124
+ // Invoked when a Linux process crash dump has been written to disk. The arguments mirror
125
+ // ICrashDumpCallback::OnCrashDump. The VM owns producing crash events; the session owns
126
+ // fanning them out to any registered COM callbacks.
127
+ using TOnCrashDump =
128
+ std::function<void(const std::wstring& DumpPath, const std::string& ProcessName, ULONGLONG Pid, ULONG Signal, ULONGLONG Timestamp)>;
129
+
130
+ WSLCVirtualMachine(_In_ IWSLCVirtualMachine* Vm, _In_ const WSLCSessionInitSettings* Settings, _In_ HANDLE SessionTerminatingEvent, _In_ TOnCrashDump&& OnCrashDump);
131
~WSLCVirtualMachine();
132
133
void Initialize();
@@ -223,6 +229,10 @@ private:
229
230
std::string m_rootVhdType;
231
232
+ // Invoked by the crash dump collection thread after a crash dump is fully written.
233
+ // Supplied by the session, which fans out to any registered ICrashDumpCallback subscribers.
234
+ TOnCrashDump m_onCrashDump;
235
+
236
std::thread m_processExitThread;
237
std::thread m_crashDumpThread;
238
test/windows/WSLCTests.cpp
+68
@@ -2918,6 +2918,74 @@ class WSLCTests
2918
VERIFY_ARE_NOT_EQUAL(details, L"");
2919
}
2920
2921
+ WSLC_TEST_METHOD(CrashDumpCallback)
2922
+ {
2923
+ struct Invocation
2924
+ {
2925
+ std::wstring DumpPath;
2926
+ std::string ProcessName;
2927
+ ULONGLONG Pid;
2928
+ ULONG Signal;
2929
+ ULONGLONG Timestamp;
2930
+ };
2931
+
2932
+ class DECLSPEC_UUID("8C5A7B14-9D26-4FAE-AB31-7E5BC23F4802") CallbackInstance
2933
+ : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, ICrashDumpCallback, IFastRundown, Microsoft::WRL::FtmBase>
2934
+ {
2935
+ public:
2936
+ CallbackInstance(std::promise<Invocation>& promise, wil::unique_event& release) :
2937
+ m_promise(promise), m_release(release)
2938
+ {
2939
+ }
2940
+
2941
+ HRESULT OnCrashDump(LPCWSTR DumpPath, LPCSTR ProcessName, ULONGLONG Pid, ULONG Signal, ULONGLONG Timestamp) override
2942
+ {
2943
+ m_promise.set_value(Invocation{
2944
+ DumpPath ? std::wstring{DumpPath} : std::wstring{}, ProcessName ? std::string{ProcessName} : std::string{}, Pid, Signal, Timestamp});
2945
+
2946
+ // Block until the test has finished probing, so anything the test verifies is observed mid-callback.
2947
+ m_release.wait();
2948
+ return S_OK;
2949
+ }
2950
+
2951
+ private:
2952
+ std::promise<Invocation>& m_promise;
2953
+ wil::unique_event& m_release;
2954
+ };
2955
+
2956
+ std::promise<Invocation> promise;
2957
+ wil::unique_event release{wil::EventOptions::ManualReset};
2958
+ auto callback = Microsoft::WRL::Make<CallbackInstance>(promise, release);
2959
+ auto releaseCallback = wil::scope_exit([&]() { release.SetEvent(); });
2960
+
2961
+ WSLCSessionSettings sessionSettings = GetDefaultSessionSettings(L"crash-dump-callback-test");
2962
+ auto session = CreateSession(sessionSettings);
2963
+
2964
+ // Register the callback through IWSLCSession::RegisterCrashDumpCallback. Holding the
2965
+ // returned subscription keeps the registration alive; releasing it auto-unregisters.
2966
+ wil::com_ptr<IUnknown> subscription;
2967
+ VERIFY_SUCCEEDED(session->RegisterCrashDumpCallback(callback.Get(), &subscription));
2968
+
2969
+ // Trigger a Linux process crash. The shell exits with 128 + SIGSEGV.
2970
+ ExpectCommandResult(session.get(), {"/bin/sh", "-c", "kill -SEGV $$"}, 128 + WSLCSignalSIGSEGV);
2971
+
2972
+ auto future = promise.get_future();
2973
+ VERIFY_ARE_EQUAL(future.wait_for(std::chrono::seconds(60)), std::future_status::ready);
2974
+
2975
+ auto invocation = future.get();
2976
+ VERIFY_IS_FALSE(invocation.DumpPath.empty());
2977
+ VERIFY_IS_TRUE(invocation.ProcessName.find("sh") != std::string::npos);
2978
+ VERIFY_ARE_EQUAL(invocation.Signal, static_cast<ULONG>(WSLCSignalSIGSEGV));
2979
+ VERIFY_IS_GREATER_THAN(invocation.Pid, 0ull);
2980
+ VERIFY_IS_GREATER_THAN(invocation.Timestamp, 0ull);
2981
+
2982
+ // The dump file should be readable and non-empty.
2983
+ wil::unique_hfile dumpFile{CreateFileW(
2984
+ invocation.DumpPath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)};
2985
+ VERIFY_IS_TRUE(dumpFile.is_valid());
2986
+ VERIFY_IS_GREATER_THAN(std::filesystem::file_size(invocation.DumpPath), 0ull);
2987
+ }
2988
+
2989
WSLC_TEST_METHOD(BuildImageStuckCallbackCancellation)
2990
{
2991
SKIP_TEST_SERVER();
test/windows/WslcSdkTests.cpp
+63
@@ -17,6 +17,7 @@ Abstract:
17
#include "wslcsdk.h"
18
#include "WslcsdkPrivate.h"
19
#include "WSLCContainerLauncher.h"
20
+#include "WSLCProcessLauncher.h"
21
#include "wslc_schema.h"
22
#include <optional>
23
@@ -64,6 +65,9 @@ void CloseProcess(WslcProcess process)
65
66
using UniqueProcess = wil::unique_any<WslcProcess, decltype(CloseProcess), CloseProcess>;
67
68
+using UniqueCrashDumpSubscription =
69
+ wil::unique_any<WslcCrashDumpSubscription, decltype(&WslcReleaseCrashDumpSubscription), WslcReleaseCrashDumpSubscription>;
70
+
71
struct ProcessOutput
72
{
73
std::string stdoutOutput;
@@ -319,6 +323,65 @@ class WslcSdkTests
323
VERIFY_ARE_EQUAL(future.get(), WSLC_SESSION_TERMINATION_REASON_SHUTDOWN);
324
}
325
326
+ WSLC_TEST_METHOD(CrashDumpCallback)
327
+ {
328
+ struct Invocation
329
+ {
330
+ std::wstring DumpPath;
331
+ std::string ProcessName;
332
+ uint64_t Pid;
333
+ uint32_t Signal;
334
+ uint64_t Timestamp;
335
+ };
336
+
337
+ std::promise<Invocation> promise;
338
+
339
+ auto callback = [](const WslcSessionCrashDumpInfo* info, PVOID context) {
340
+ auto* p = static_cast<std::promise<Invocation>*>(context);
341
+ p->set_value(Invocation{
342
+ info->dumpPath ? std::wstring{info->dumpPath} : std::wstring{},
343
+ info->processName ? std::string{info->processName} : std::string{},
344
+ info->pid,
345
+ info->signal,
346
+ info->timestamp});
347
+ };
348
+
349
+ std::filesystem::path extraStorage = m_storagePath / "wslc-crash-callback-storage";
350
+ auto cleanupStorage = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
351
+ std::error_code ec;
352
+ std::filesystem::remove_all(extraStorage, ec);
353
+ });
354
+
355
+ WslcSessionSettings sessionSettings;
356
+ VERIFY_SUCCEEDED(WslcInitSessionSettings(L"wslc-crashcb-test", extraStorage.c_str(), &sessionSettings));
357
+ VERIFY_SUCCEEDED(WslcSetSessionSettingsTimeout(&sessionSettings, 30 * 1000));
358
+
359
+ UniqueSession session;
360
+ VERIFY_SUCCEEDED(WslcCreateSession(&sessionSettings, &session, nullptr));
361
+
362
+ UniqueCrashDumpSubscription subscription;
363
+ VERIFY_SUCCEEDED(WslcRegisterSessionCrashDumpCallback(session.get(), callback, &promise, &subscription, nullptr));
364
+
365
+ auto& comSession = *reinterpret_cast<WslcSessionImpl*>(session.get())->session;
366
+
367
+ wsl::windows::common::WSLCProcessLauncher launcher{"/bin/sh", {"/bin/sh", "-c", "kill -SEGV $$"}};
368
+ auto process = launcher.Launch(comSession);
369
+ auto result = process.WaitAndCaptureOutput();
370
+ VERIFY_ARE_EQUAL(result.Code, 128 + WSLCSignalSIGSEGV);
371
+
372
+ auto future = promise.get_future();
373
+ VERIFY_ARE_EQUAL(future.wait_for(std::chrono::seconds(60)), std::future_status::ready);
374
+
375
+ auto invocation = future.get();
376
+ VERIFY_IS_FALSE(invocation.DumpPath.empty());
377
+ VERIFY_IS_TRUE(std::filesystem::exists(invocation.DumpPath));
378
+ VERIFY_IS_GREATER_THAN(std::filesystem::file_size(invocation.DumpPath), 0ull);
379
+ VERIFY_IS_TRUE(invocation.ProcessName.find("sh") != std::string::npos);
380
+ VERIFY_ARE_EQUAL(invocation.Signal, static_cast<uint32_t>(WSLCSignalSIGSEGV));
381
+ VERIFY_IS_GREATER_THAN(invocation.Pid, 0ull);
382
+ VERIFY_IS_GREATER_THAN(invocation.Timestamp, 0ull);
383
+ }
384
+
385
// -----------------------------------------------------------------------
386
// Image tests
387
// -----------------------------------------------------------------------