@samitouri / QOSAMI-WSL / commits / fb3d4e7b

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 // -----------------------------------------------------------------------