mirror of
https://github.com/microsoft/WSL.git
synced 2025-12-10 16:30:30 -06:00
Merge
This commit is contained in:
commit
f737a8edd2
@ -22,6 +22,12 @@ Abstract:
|
||||
Json[#Value] = (Object).Value.value(); \
|
||||
}
|
||||
|
||||
#define ASSIGN_IF_PRESENT(Json, Object, Value) \
|
||||
if (Json.contains(#Value)) \
|
||||
{ \
|
||||
(Object).Value = Json.at(#Value).get_to((Object).Value); \
|
||||
}
|
||||
|
||||
namespace wsl::windows::common::hcs {
|
||||
|
||||
enum class ModifyRequestType
|
||||
@ -439,6 +445,19 @@ struct Scsi
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Scsi, Attachments);
|
||||
};
|
||||
|
||||
struct DebugOptions
|
||||
{
|
||||
std::optional<std::wstring> BugcheckSavedStateFileName;
|
||||
std::optional<std::wstring> ShutdownOrResetSavedStateFileName;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const DebugOptions& d)
|
||||
{
|
||||
j = nlohmann::json::object();
|
||||
OMIT_IF_EMPTY(j, d, BugcheckSavedStateFileName);
|
||||
OMIT_IF_EMPTY(j, d, ShutdownOrResetSavedStateFileName);
|
||||
}
|
||||
|
||||
struct Devices
|
||||
{
|
||||
std::optional<VirtioSerial> VirtioSerial;
|
||||
@ -467,8 +486,9 @@ struct VirtualMachine
|
||||
Chipset Chipset;
|
||||
Topology ComputeTopology;
|
||||
Devices Devices;
|
||||
DebugOptions DebugOptions;
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(VirtualMachine, StopOnReset, Chipset, ComputeTopology, Devices);
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(VirtualMachine, StopOnReset, Chipset, ComputeTopology, Devices, DebugOptions);
|
||||
};
|
||||
|
||||
struct ComputeSystem
|
||||
@ -481,13 +501,117 @@ struct ComputeSystem
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(ComputeSystem, Owner, ShouldTerminateOnLastHandleClosed, SchemaVersion, VirtualMachine)
|
||||
};
|
||||
|
||||
struct GuestErrorSaveReport
|
||||
{
|
||||
std::optional<std::wstring> SaveStateFile;
|
||||
std::optional<long> Status;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const GuestErrorSaveReport& g)
|
||||
{
|
||||
j = nlohmann::json::object();
|
||||
OMIT_IF_EMPTY(j, g, SaveStateFile);
|
||||
OMIT_IF_EMPTY(j, g, Status);
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, GuestErrorSaveReport& r)
|
||||
{
|
||||
ASSIGN_IF_PRESENT(j, r, SaveStateFile);
|
||||
ASSIGN_IF_PRESENT(j, r, Status);
|
||||
}
|
||||
|
||||
struct CrashReport
|
||||
{
|
||||
std::wstring CrashLog;
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(CrashReport, CrashLog);
|
||||
std::optional<GuestErrorSaveReport> GuestCrashSaveInfo;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const CrashReport& c)
|
||||
{
|
||||
j = nlohmann::json::object();
|
||||
j.at("CrashLog") = c.CrashLog;
|
||||
OMIT_IF_EMPTY(j, c, GuestCrashSaveInfo);
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, CrashReport& c)
|
||||
{
|
||||
ASSIGN_IF_PRESENT(j, c, CrashLog);
|
||||
ASSIGN_IF_PRESENT(j, c, GuestCrashSaveInfo);
|
||||
}
|
||||
|
||||
enum class NotificationType
|
||||
{
|
||||
None,
|
||||
GracefulExit,
|
||||
ForcedExit,
|
||||
UnexpectedExit,
|
||||
Unknown
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(
|
||||
NotificationType,
|
||||
{
|
||||
{NotificationType::None, "None"},
|
||||
{NotificationType::GracefulExit, "GracefulExit"},
|
||||
{NotificationType::ForcedExit, "ForcedExit"},
|
||||
{NotificationType::UnexpectedExit, "UnexpectedExit"},
|
||||
{NotificationType::Unknown, "Unknown"},
|
||||
})
|
||||
|
||||
struct GuestCrashAttribution
|
||||
{
|
||||
std::optional<std::vector<uint64_t>> CrashParameters;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const GuestCrashAttribution& g)
|
||||
{
|
||||
j = nlohmann::json::object();
|
||||
OMIT_IF_EMPTY(j, g, CrashParameters)
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, GuestCrashAttribution& g)
|
||||
{
|
||||
ASSIGN_IF_PRESENT(j, g, CrashParameters);
|
||||
}
|
||||
|
||||
// Attribution record (trimmed to GuestCrash only for now)
|
||||
struct AttributionRecord
|
||||
{
|
||||
std::optional<GuestCrashAttribution> GuestCrash;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const AttributionRecord& a)
|
||||
{
|
||||
j = nlohmann::json::object();
|
||||
OMIT_IF_EMPTY(j, a, GuestCrash)
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, AttributionRecord& a)
|
||||
{
|
||||
ASSIGN_IF_PRESENT(j, a, GuestCrash);
|
||||
}
|
||||
|
||||
struct SystemExitStatus
|
||||
{
|
||||
int32_t Status;
|
||||
std::optional<NotificationType> ExitType;
|
||||
std::optional<std::vector<AttributionRecord>> Attribution;
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const SystemExitStatus& s)
|
||||
{
|
||||
j = nlohmann::json{{"Status", s.Status}};
|
||||
OMIT_IF_EMPTY(j, s, ExitType);
|
||||
OMIT_IF_EMPTY(j, s, Attribution);
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, SystemExitStatus& s)
|
||||
{
|
||||
s.Status = j.at("Status").get<int32_t>();
|
||||
ASSIGN_IF_PRESENT(j, s, ExitType);
|
||||
ASSIGN_IF_PRESENT(j, s, Attribution);
|
||||
}
|
||||
|
||||
} // namespace wsl::windows::common::hcs
|
||||
|
||||
#undef OMIT_IF_EMPTY
|
||||
@ -13,6 +13,8 @@ Abstract:
|
||||
--*/
|
||||
|
||||
#include "WSLAVirtualMachine.h"
|
||||
#include <format>
|
||||
#include <filesystem>
|
||||
#include "hcs_schema.h"
|
||||
#include "NatNetworking.h"
|
||||
#include "WSLAUserSession.h"
|
||||
@ -24,11 +26,19 @@ using helpers::WindowsVersion;
|
||||
using wsl::windows::service::wsla::WSLAProcess;
|
||||
using wsl::windows::service::wsla::WSLAVirtualMachine;
|
||||
|
||||
constexpr auto MAX_VM_CRASH_FILES = 3;
|
||||
constexpr auto SAVED_STATE_FILE_EXTENSION = L".vmrs";
|
||||
constexpr auto SAVED_STATE_FILE_PREFIX = L"saved-state-";
|
||||
|
||||
WSLAVirtualMachine::WSLAVirtualMachine(const VIRTUAL_MACHINE_SETTINGS& Settings, PSID UserSid, WSLAUserSessionImpl* Session) :
|
||||
m_settings(Settings), m_userSid(UserSid), m_userSession(Session)
|
||||
{
|
||||
THROW_IF_FAILED(CoCreateGuid(&m_vmId));
|
||||
|
||||
m_vmIdString = wsl::shared::string::GuidToString<wchar_t>(m_vmId, wsl::shared::string::GuidToStringFlags::Uppercase);
|
||||
m_userToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
|
||||
m_crashDumpFolder = GetCrashDumpFolder();
|
||||
|
||||
if (Settings.EnableDebugShell)
|
||||
{
|
||||
m_debugShellPipe = wsl::windows::common::wslutil::GetDebugShellPipeName(m_userSid) + m_settings.DisplayName;
|
||||
@ -103,6 +113,17 @@ WSLAVirtualMachine::~WSLAVirtualMachine()
|
||||
CATCH_LOG()
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// If the VM did not crash, the saved state file should be empty, so we can remove it.
|
||||
if (!m_vmSavedStateFile.empty() && !m_vmSavedStateCaptured)
|
||||
{
|
||||
WI_ASSERT(std::filesystem::is_empty(m_vmSavedStateFile));
|
||||
std::filesystem::remove(m_vmSavedStateFile);
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
if (m_processExitThread.joinable())
|
||||
{
|
||||
m_processExitThread.join();
|
||||
@ -122,7 +143,8 @@ void WSLAVirtualMachine::Start()
|
||||
systemSettings.Owner = L"WSL";
|
||||
systemSettings.ShouldTerminateOnLastHandleClosed = true;
|
||||
systemSettings.SchemaVersion.Major = 2;
|
||||
systemSettings.SchemaVersion.Minor = 3;
|
||||
systemSettings.SchemaVersion.Minor = 7;
|
||||
|
||||
hcs::VirtualMachine vmSettings{};
|
||||
vmSettings.StopOnReset = true;
|
||||
vmSettings.Chipset.UseUtc = true;
|
||||
@ -267,12 +289,20 @@ void WSLAVirtualMachine::Start()
|
||||
hvSocketConfig.HvSocketConfig.DefaultConnectSecurityDescriptor = securityDescriptor;
|
||||
vmSettings.Devices.HvSocket = std::move(hvSocketConfig);
|
||||
|
||||
CreateVmSavedStateFile();
|
||||
WI_ASSERT(!m_vmSavedStateFile.empty());
|
||||
|
||||
// Prepare debug options: create saved state (.vmrs) file and grant vmwp access.
|
||||
hcs::DebugOptions debugOptions{};
|
||||
debugOptions.BugcheckSavedStateFileName = m_vmSavedStateFile;
|
||||
|
||||
vmSettings.DebugOptions = std::move(debugOptions);
|
||||
|
||||
systemSettings.VirtualMachine = std::move(vmSettings);
|
||||
auto json = wsl::shared::ToJsonW(systemSettings);
|
||||
|
||||
WSL_LOG("CreateWSLAVirtualMachine", TraceLoggingValue(json.c_str(), "json"));
|
||||
|
||||
m_vmIdString = wsl::shared::string::GuidToString<wchar_t>(m_vmId, wsl::shared::string::GuidToStringFlags::Uppercase);
|
||||
m_computeSystem = hcs::CreateComputeSystem(m_vmIdString.c_str(), json.c_str());
|
||||
|
||||
auto runtimeId = wsl::windows::common::hcs::GetRuntimeId(m_computeSystem.get());
|
||||
@ -471,12 +501,18 @@ void WSLAVirtualMachine::ConfigureNetworking()
|
||||
}
|
||||
|
||||
void CALLBACK WSLAVirtualMachine::s_OnExit(_In_ HCS_EVENT* Event, _In_opt_ void* Context)
|
||||
try
|
||||
{
|
||||
if (Event->Type == HcsEventSystemExited || Event->Type == HcsEventSystemCrashInitiated || Event->Type == HcsEventSystemCrashReport)
|
||||
if (Event->Type == HcsEventSystemExited)
|
||||
{
|
||||
reinterpret_cast<WSLAVirtualMachine*>(Context)->OnExit(Event);
|
||||
}
|
||||
if (Event->Type == HcsEventSystemCrashInitiated || Event->Type == HcsEventSystemCrashReport)
|
||||
{
|
||||
reinterpret_cast<WSLAVirtualMachine*>(Context)->OnCrash(Event);
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
void WSLAVirtualMachine::OnExit(_In_ const HCS_EVENT* Event)
|
||||
{
|
||||
@ -485,24 +521,59 @@ void WSLAVirtualMachine::OnExit(_In_ const HCS_EVENT* Event)
|
||||
|
||||
m_vmExitEvent.SetEvent();
|
||||
|
||||
std::lock_guard lock(m_lock);
|
||||
const auto exitStatus = wsl::shared::FromJson<wsl::windows::common::hcs::SystemExitStatus>(Event->EventData);
|
||||
|
||||
WslVirtualMachineTerminationReason reason = WslVirtualMachineTerminationReasonUnknown;
|
||||
|
||||
if (exitStatus.ExitType.has_value())
|
||||
{
|
||||
switch (exitStatus.ExitType.value())
|
||||
{
|
||||
case hcs::NotificationType::ForcedExit:
|
||||
case hcs::NotificationType::GracefulExit:
|
||||
reason = WslVirtualMachineTerminationReasonShutdown;
|
||||
break;
|
||||
case hcs::NotificationType::UnexpectedExit:
|
||||
reason = WslVirtualMachineTerminationReasonCrashed;
|
||||
break;
|
||||
default:
|
||||
reason = WslVirtualMachineTerminationReasonUnknown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_terminationCallback)
|
||||
{
|
||||
// TODO: parse json and give a better error.
|
||||
WslVirtualMachineTerminationReason reason = WslVirtualMachineTerminationReasonUnknown;
|
||||
if (Event->Type == HcsEventSystemExited)
|
||||
{
|
||||
reason = WslVirtualMachineTerminationReasonShutdown;
|
||||
}
|
||||
else if (Event->Type == HcsEventSystemCrashInitiated || Event->Type == HcsEventSystemCrashReport)
|
||||
{
|
||||
reason = WslVirtualMachineTerminationReasonCrashed;
|
||||
}
|
||||
|
||||
LOG_IF_FAILED(m_terminationCallback->OnTermination(static_cast<ULONG>(reason), Event->EventData));
|
||||
}
|
||||
}
|
||||
|
||||
void WSLAVirtualMachine::OnCrash(_In_ const HCS_EVENT* Event)
|
||||
{
|
||||
WSL_LOG(
|
||||
"WSLAGuestCrash",
|
||||
TraceLoggingValue(Event->EventData, "details"),
|
||||
TraceLoggingValue(static_cast<int>(Event->Type), "type"));
|
||||
|
||||
if (m_crashLogCaptured && m_vmSavedStateCaptured)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto crashReport = wsl::shared::FromJson<wsl::windows::common::hcs::CrashReport>(Event->EventData);
|
||||
|
||||
if (crashReport.GuestCrashSaveInfo.has_value() && crashReport.GuestCrashSaveInfo->SaveStateFile.has_value())
|
||||
{
|
||||
m_vmSavedStateCaptured = true;
|
||||
EnforceVmSavedStateFileLimit();
|
||||
}
|
||||
|
||||
if (!m_crashLogCaptured && !crashReport.CrashLog.empty())
|
||||
{
|
||||
WriteCrashLog(crashReport.CrashLog);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ULONG, std::string> WSLAVirtualMachine::AttachDisk(_In_ PCWSTR Path, _In_ BOOL ReadOnly)
|
||||
{
|
||||
ULONG Lun{};
|
||||
@ -1176,10 +1247,74 @@ try
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
std::filesystem::path WSLAVirtualMachine::GetCrashDumpFolder()
|
||||
{
|
||||
auto tempPath = wsl::windows::common::filesystem::GetTempFolderPath(m_userToken.get());
|
||||
return tempPath / L"wsla-crashes";
|
||||
}
|
||||
|
||||
void WSLAVirtualMachine::CreateVmSavedStateFile()
|
||||
{
|
||||
auto runAsUser = wil::impersonate_token(m_userToken.get());
|
||||
|
||||
const auto filename = std::format(L"{}{}-{}{}", SAVED_STATE_FILE_PREFIX, std::time(nullptr), m_vmIdString, SAVED_STATE_FILE_EXTENSION);
|
||||
|
||||
auto savedStateFile = m_crashDumpFolder / filename;
|
||||
|
||||
wsl::windows::common::filesystem::EnsureDirectory(m_crashDumpFolder.c_str());
|
||||
|
||||
wil::unique_handle file{CreateFileW(savedStateFile.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, nullptr)};
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
|
||||
hcs::GrantVmAccess(m_vmIdString.c_str(), savedStateFile.c_str());
|
||||
m_vmSavedStateFile = savedStateFile;
|
||||
}
|
||||
|
||||
void wsl::windows::service::wsla::WSLAVirtualMachine::EnforceVmSavedStateFileLimit()
|
||||
{
|
||||
auto pred = [](const auto& e) {
|
||||
return WI_IsFlagSet(GetFileAttributes(e.path().c_str()), FILE_ATTRIBUTE_TEMPORARY) && e.path().has_extension() &&
|
||||
e.path().extension() == SAVED_STATE_FILE_EXTENSION && e.path().has_filename() &&
|
||||
e.path().filename().wstring().find(SAVED_STATE_FILE_PREFIX) == 0 && e.file_size() > 0;
|
||||
};
|
||||
|
||||
wsl::windows::common::wslutil::EnforceFileLimit(m_crashDumpFolder.c_str(), MAX_VM_CRASH_FILES + 1, pred);
|
||||
}
|
||||
|
||||
void WSLAVirtualMachine::WriteCrashLog(const std::wstring& crashLog)
|
||||
{
|
||||
auto runAsUser = wil::impersonate_token(m_userToken.get());
|
||||
|
||||
constexpr auto c_extension = L".txt";
|
||||
constexpr auto c_prefix = L"kernel-panic-";
|
||||
const auto filename = std::format(L"{}{}-{}{}", c_prefix, std::time(nullptr), m_vmIdString, c_extension);
|
||||
auto filePath = m_crashDumpFolder / filename;
|
||||
|
||||
WI_ASSERT(std::filesystem::exists(m_crashDumpFolder));
|
||||
WI_ASSERT(std::filesystem::is_directory(m_crashDumpFolder));
|
||||
|
||||
auto pred = [&c_extension, &c_prefix](const auto& e) {
|
||||
return WI_IsFlagSet(GetFileAttributes(e.path().c_str()), FILE_ATTRIBUTE_TEMPORARY) && e.path().has_extension() &&
|
||||
e.path().extension() == c_extension && e.path().has_filename() && e.path().filename().wstring().find(c_prefix) == 0;
|
||||
};
|
||||
|
||||
wsl::windows::common::wslutil::EnforceFileLimit(m_crashDumpFolder.c_str(), MAX_VM_CRASH_FILES, pred);
|
||||
|
||||
{
|
||||
std::wofstream outputFile(filePath.wstring());
|
||||
THROW_HR_IF(E_UNEXPECTED, !outputFile.is_open());
|
||||
|
||||
outputFile << crashLog;
|
||||
THROW_HR_IF(E_UNEXPECTED, outputFile.fail());
|
||||
}
|
||||
|
||||
THROW_IF_WIN32_BOOL_FALSE(SetFileAttributesW(filePath.c_str(), FILE_ATTRIBUTE_TEMPORARY));
|
||||
m_crashLogCaptured = true;
|
||||
}
|
||||
|
||||
void WSLAVirtualMachine::OnProcessReleased(int Pid)
|
||||
{
|
||||
std::lock_guard lock{m_lock};
|
||||
|
||||
auto erased = std::erase_if(m_trackedProcesses, [Pid](const auto* e) { return e->GetPid() == Pid; });
|
||||
WI_VERIFY(erased <= 1);
|
||||
}
|
||||
@ -70,6 +70,7 @@ private:
|
||||
void ConfigureNetworking();
|
||||
void ConfigureMounts();
|
||||
void OnExit(_In_ const HCS_EVENT* Event);
|
||||
void OnCrash(_In_ const HCS_EVENT* Event);
|
||||
|
||||
std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> Fork(enum WSLA_FORK::ForkType Type);
|
||||
std::tuple<int32_t, int32_t, wsl::shared::SocketChannel> Fork(wsl::shared::SocketChannel& Channel, enum WSLA_FORK::ForkType Type);
|
||||
@ -79,6 +80,11 @@ private:
|
||||
static void OpenLinuxFile(wsl::shared::SocketChannel& Channel, const char* Path, uint32_t Flags, int32_t Fd);
|
||||
void LaunchPortRelay();
|
||||
|
||||
std::filesystem::path GetCrashDumpFolder();
|
||||
void CreateVmSavedStateFile();
|
||||
void EnforceVmSavedStateFileLimit();
|
||||
void WriteCrashLog(const std::wstring& crashLog);
|
||||
|
||||
Microsoft::WRL::ComPtr<WSLAProcess> CreateLinuxProcessImpl(
|
||||
_In_ const WSLA_PROCESS_OPTIONS& Options, int* Errno = nullptr, const TPrepareCommandLine& PrepareCommandLine = [](const auto&) {});
|
||||
|
||||
@ -102,10 +108,17 @@ private:
|
||||
int m_coldDiscardShiftSize{};
|
||||
bool m_running = false;
|
||||
PSID m_userSid{};
|
||||
wil::unique_handle m_userToken;
|
||||
std::wstring m_debugShellPipe;
|
||||
std::vector<WSLAProcess*> m_trackedProcesses;
|
||||
|
||||
wsl::windows::common::hcs::unique_hcs_system m_computeSystem;
|
||||
|
||||
std::filesystem::path m_vmSavedStateFile;
|
||||
std::filesystem::path m_crashDumpFolder;
|
||||
bool m_vmSavedStateCaptured = false;
|
||||
bool m_crashLogCaptured = false;
|
||||
|
||||
std::shared_ptr<DmesgCollector> m_dmesgCollector;
|
||||
wil::unique_event m_vmExitEvent{wil::EventOptions::ManualReset};
|
||||
wil::unique_event m_vmTerminatingEvent{wil::EventOptions::ManualReset};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user