/* * Copyright (c) 2018-2020 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "pm_process_manager.hpp" #include "pm_resource_manager.hpp" #include "pm_process_info.hpp" namespace ams::pm::impl { namespace { /* Types. */ enum HookType { HookType_ProgramId = (1 << 0), HookType_Application = (1 << 1), }; struct LaunchProcessArgs { os::ProcessId *out_process_id; ncm::ProgramLocation location; u32 flags; }; #define GET_FLAG_MASK(flag) (hos_version >= hos::Version_5_0_0 ? static_cast(LaunchFlags_##flag) : static_cast(LaunchFlagsDeprecated_##flag)) inline bool ShouldSignalOnExit(u32 launch_flags) { const auto hos_version = hos::GetVersion(); return launch_flags & GET_FLAG_MASK(SignalOnExit); } inline bool ShouldSignalOnStart(u32 launch_flags) { const auto hos_version = hos::GetVersion(); if (hos_version < hos::Version_2_0_0) { return false; } return launch_flags & GET_FLAG_MASK(SignalOnStart); } inline bool ShouldSignalOnException(u32 launch_flags) { const auto hos_version = hos::GetVersion(); return launch_flags & GET_FLAG_MASK(SignalOnException); } inline bool ShouldSignalOnDebugEvent(u32 launch_flags) { const auto hos_version = hos::GetVersion(); return launch_flags & GET_FLAG_MASK(SignalOnDebugEvent); } inline bool ShouldStartSuspended(u32 launch_flags) { const auto hos_version = hos::GetVersion(); return launch_flags & GET_FLAG_MASK(StartSuspended); } inline bool ShouldDisableAslr(u32 launch_flags) { const auto hos_version = hos::GetVersion(); return launch_flags & GET_FLAG_MASK(DisableAslr); } #undef GET_FLAG_MASK template class ProcessInfoAllocator { NON_COPYABLE(ProcessInfoAllocator); NON_MOVEABLE(ProcessInfoAllocator); static_assert(MaxProcessInfos >= 0x40, "MaxProcessInfos is too small."); private: TYPED_STORAGE(ProcessInfo) process_info_storages[MaxProcessInfos]; bool process_info_allocated[MaxProcessInfos]; os::Mutex lock; private: constexpr inline size_t GetProcessInfoIndex(ProcessInfo *process_info) const { return process_info - GetPointer(this->process_info_storages[0]); } public: constexpr ProcessInfoAllocator() : lock(false) { std::memset(this->process_info_storages, 0, sizeof(this->process_info_storages)); std::memset(this->process_info_allocated, 0, sizeof(this->process_info_allocated)); } void *AllocateProcessInfoStorage() { std::scoped_lock lk(this->lock); for (size_t i = 0; i < MaxProcessInfos; i++) { if (!this->process_info_allocated[i]) { this->process_info_allocated[i] = true; std::memset(&this->process_info_storages[i], 0, sizeof(this->process_info_storages[i])); return GetPointer(this->process_info_storages[i]); } } return nullptr; } void FreeProcessInfo(ProcessInfo *process_info) { std::scoped_lock lk(this->lock); const size_t index = this->GetProcessInfoIndex(process_info); AMS_ABORT_UNLESS(index < MaxProcessInfos); AMS_ABORT_UNLESS(this->process_info_allocated[index]); process_info->~ProcessInfo(); this->process_info_allocated[index] = false; } }; /* Process Tracking globals. */ void ProcessTrackingMain(void *arg); os::ThreadType g_process_track_thread; alignas(os::ThreadStackAlignment) u8 g_process_track_thread_stack[16_KB]; /* Process lists. */ ProcessList g_process_list; ProcessList g_dead_process_list; /* Process Info Allocation. */ /* Note: The kernel slabheap is size 0x50 -- we allow slightly larger to account for the dead process list. */ constexpr size_t MaxProcessCount = 0x60; ProcessInfoAllocator g_process_info_allocator; /* Global events. */ os::SystemEventType g_process_event; os::SystemEventType g_hook_to_create_process_event; os::SystemEventType g_hook_to_create_application_process_event; os::SystemEventType g_boot_finished_event; /* Process Launch synchronization globals. */ os::Mutex g_launch_program_lock(false); os::Event g_process_launch_start_event(os::EventClearMode_AutoClear); os::Event g_process_launch_finish_event(os::EventClearMode_AutoClear); Result g_process_launch_result = ResultSuccess(); LaunchProcessArgs g_process_launch_args = {}; /* Hook globals. */ std::atomic g_program_id_hook; std::atomic g_application_hook; /* Forward declarations. */ Result LaunchProcess(os::WaitableManagerType &waitable_manager, const LaunchProcessArgs &args); void OnProcessSignaled(ProcessListAccessor &list, ProcessInfo *process_info); /* Helpers. */ void ProcessTrackingMain(void *arg) { /* This is the main loop of the process tracking thread. */ /* Setup waitable manager. */ os::WaitableManagerType process_waitable_manager; os::WaitableHolderType start_event_holder; os::InitializeWaitableManager(std::addressof(process_waitable_manager)); os::InitializeWaitableHolder(std::addressof(start_event_holder), g_process_launch_start_event.GetBase()); os::LinkWaitableHolder(std::addressof(process_waitable_manager), std::addressof(start_event_holder)); while (true) { auto signaled_holder = os::WaitAny(std::addressof(process_waitable_manager)); if (signaled_holder == &start_event_holder) { /* Launch start event signaled. */ /* TryWait will clear signaled, preventing duplicate notifications. */ if (g_process_launch_start_event.TryWait()) { g_process_launch_result = LaunchProcess(process_waitable_manager, g_process_launch_args); g_process_launch_finish_event.Signal(); } } else { /* Some process was signaled. */ ProcessListAccessor list(g_process_list); OnProcessSignaled(list, reinterpret_cast(os::GetWaitableHolderUserData(signaled_holder))); } } } inline u32 GetLoaderCreateProcessFlags(u32 launch_flags) { u32 ldr_flags = 0; if (ShouldSignalOnException(launch_flags) || (hos::GetVersion() >= hos::Version_2_0_0 && !ShouldStartSuspended(launch_flags))) { ldr_flags |= ldr::CreateProcessFlag_EnableDebug; } if (ShouldDisableAslr(launch_flags)) { ldr_flags |= ldr::CreateProcessFlag_DisableAslr; } return ldr_flags; } bool HasApplicationProcess() { ProcessListAccessor list(g_process_list); for (auto &process : *list) { if (process.IsApplication()) { return true; } } return false; } Result StartProcess(ProcessInfo *process_info, const ldr::ProgramInfo *program_info) { R_TRY(svcStartProcess(process_info->GetHandle(), program_info->main_thread_priority, program_info->default_cpu_id, program_info->main_thread_stack_size)); process_info->SetState(svc::ProcessState_Running); return ResultSuccess(); } void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info) { /* Remove the process from the list. */ list->Remove(process_info); /* Delete the process. */ g_process_info_allocator.FreeProcessInfo(process_info); } Result LaunchProcess(os::WaitableManagerType &waitable_manager, const LaunchProcessArgs &args) { /* Get Program Info. */ ldr::ProgramInfo program_info; cfg::OverrideStatus override_status; R_TRY(ldr::pm::AtmosphereGetProgramInfo(&program_info, &override_status, args.location)); const bool is_application = (program_info.flags & ldr::ProgramInfoFlag_ApplicationTypeMask) == ldr::ProgramInfoFlag_Application; const bool allow_debug = (program_info.flags & ldr::ProgramInfoFlag_AllowDebug) || hos::GetVersion() < hos::Version_2_0_0; /* Ensure we only try to run one application. */ R_UNLESS(!is_application || !HasApplicationProcess(), pm::ResultApplicationRunning()); /* Fix the program location to use the right program id. */ const ncm::ProgramLocation location = ncm::ProgramLocation::Make(program_info.program_id, static_cast(args.location.storage_id)); /* Pin the program with loader. */ ldr::PinId pin_id; R_TRY(ldr::pm::AtmospherePinProgram(&pin_id, location, override_status)); /* Ensure resources are available. */ resource::WaitResourceAvailable(&program_info); /* Actually create the process. */ Handle process_handle; { auto pin_guard = SCOPE_GUARD { ldr::pm::UnpinProgram(pin_id); }; R_TRY(ldr::pm::CreateProcess(&process_handle, pin_id, GetLoaderCreateProcessFlags(args.flags), resource::GetResourceLimitHandle(&program_info))); pin_guard.Cancel(); } /* Get the process id. */ os::ProcessId process_id = os::GetProcessId(process_handle); /* Make new process info. */ void *process_info_storage = g_process_info_allocator.AllocateProcessInfoStorage(); AMS_ABORT_UNLESS(process_info_storage != nullptr); ProcessInfo *process_info = new (process_info_storage) ProcessInfo(process_handle, process_id, pin_id, location, override_status); /* Link new process info. */ { ProcessListAccessor list(g_process_list); list->push_back(*process_info); process_info->LinkToWaitableManager(waitable_manager); } /* Prevent resource leakage if register fails. */ auto cleanup_guard = SCOPE_GUARD { ProcessListAccessor list(g_process_list); process_info->Cleanup(); CleanupProcessInfo(list, process_info); }; const u8 *acid_sac = program_info.ac_buffer; const u8 *aci_sac = acid_sac + program_info.acid_sac_size; const u8 *acid_fac = aci_sac + program_info.aci_sac_size; const u8 *aci_fah = acid_fac + program_info.acid_fac_size; /* Register with FS and SM. */ R_TRY(fsprRegisterProgram(static_cast(process_id), static_cast(location.program_id), static_cast(location.storage_id), aci_fah, program_info.aci_fah_size, acid_fac, program_info.acid_fac_size)); R_TRY(sm::manager::RegisterProcess(process_id, location.program_id, override_status, acid_sac, program_info.acid_sac_size, aci_sac, program_info.aci_sac_size)); /* Set flags. */ if (is_application) { process_info->SetApplication(); } if (ShouldSignalOnStart(args.flags) && allow_debug) { process_info->SetSignalOnStart(); } if (ShouldSignalOnExit(args.flags)) { process_info->SetSignalOnExit(); } if (ShouldSignalOnDebugEvent(args.flags) && allow_debug) { process_info->SetSignalOnDebugEvent(); } /* Process hooks/signaling. */ if (location.program_id == g_program_id_hook) { os::SignalSystemEvent(std::addressof(g_hook_to_create_process_event)); g_program_id_hook = ncm::InvalidProgramId; } else if (is_application && g_application_hook) { os::SignalSystemEvent(std::addressof(g_hook_to_create_application_process_event)); g_application_hook = false; } else if (!ShouldStartSuspended(args.flags)) { R_TRY(StartProcess(process_info, &program_info)); } /* We succeeded, so we can cancel our cleanup. */ cleanup_guard.Cancel(); *args.out_process_id = process_id; return ResultSuccess(); } void OnProcessSignaled(ProcessListAccessor &list, ProcessInfo *process_info) { /* Reset the process's signal. */ svcResetSignal(process_info->GetHandle()); /* Update the process's state. */ const svc::ProcessState old_state = process_info->GetState(); { s64 tmp = 0; R_ABORT_UNLESS(svc::GetProcessInfo(&tmp, process_info->GetHandle(), svc::ProcessInfoType_ProcessState)); process_info->SetState(static_cast(tmp)); } const svc::ProcessState new_state = process_info->GetState(); /* If we're transitioning away from crashed, clear waiting attached. */ if (old_state == svc::ProcessState_Crashed && new_state != svc::ProcessState_Crashed) { process_info->ClearExceptionWaitingAttach(); } switch (new_state) { case svc::ProcessState_Created: case svc::ProcessState_CreatedAttached: case svc::ProcessState_Terminating: break; case svc::ProcessState_Running: if (process_info->ShouldSignalOnDebugEvent()) { process_info->ClearSuspended(); process_info->SetSuspendedStateChanged(); os::SignalSystemEvent(std::addressof(g_process_event)); } else if (hos::GetVersion() >= hos::Version_2_0_0 && process_info->ShouldSignalOnStart()) { process_info->SetStartedStateChanged(); process_info->ClearSignalOnStart(); os::SignalSystemEvent(std::addressof(g_process_event)); } break; case svc::ProcessState_Crashed: process_info->SetExceptionOccurred(); os::SignalSystemEvent(std::addressof(g_process_event)); break; case svc::ProcessState_RunningAttached: if (process_info->ShouldSignalOnDebugEvent()) { process_info->ClearSuspended(); process_info->SetSuspendedStateChanged(); os::SignalSystemEvent(std::addressof(g_process_event)); } break; case svc::ProcessState_Terminated: /* Free process resources, unlink from waitable manager. */ process_info->Cleanup(); if (hos::GetVersion() < hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) { os::SignalSystemEvent(std::addressof(g_process_event)); } else { /* Handle the case where we need to keep the process alive some time longer. */ if (hos::GetVersion() >= hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) { /* Remove from the living list. */ list->Remove(process_info); /* Add the process to the list of dead processes. */ { ProcessListAccessor dead_list(g_dead_process_list); dead_list->push_back(*process_info); } /* Signal. */ os::SignalSystemEvent(std::addressof(g_process_event)); } else { /* Actually delete process. */ CleanupProcessInfo(list, process_info); } } break; case svc::ProcessState_DebugBreak: if (process_info->ShouldSignalOnDebugEvent()) { process_info->SetSuspended(); process_info->SetSuspendedStateChanged(); os::SignalSystemEvent(std::addressof(g_process_event)); } break; } } } /* Initialization. */ Result InitializeProcessManager() { /* Create events. */ R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_process_event), os::EventClearMode_AutoClear, true)); R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_process_event), os::EventClearMode_AutoClear, true)); R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_application_process_event), os::EventClearMode_AutoClear, true)); R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_boot_finished_event), os::EventClearMode_AutoClear, true)); /* Initialize resource limits. */ R_TRY(resource::InitializeResourceManager()); /* Create thread. */ R_ABORT_UNLESS(os::CreateThread(std::addressof(g_process_track_thread), ProcessTrackingMain, nullptr, g_process_track_thread_stack, sizeof(g_process_track_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(pm, ProcessTrack))); os::SetThreadNamePointer(std::addressof(g_process_track_thread), AMS_GET_SYSTEM_THREAD_NAME(pm, ProcessTrack)); /* Start thread. */ os::StartThread(std::addressof(g_process_track_thread)); return ResultSuccess(); } /* Process Management. */ Result LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 flags) { /* Ensure we only try to launch one program at a time. */ std::scoped_lock lk(g_launch_program_lock); /* Set global arguments, signal, wait. */ g_process_launch_args = { .out_process_id = out_process_id, .location = loc, .flags = flags, }; g_process_launch_start_event.Signal(); g_process_launch_finish_event.Wait(); return g_process_launch_result; } Result StartProcess(os::ProcessId process_id) { ProcessListAccessor list(g_process_list); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); R_UNLESS(!process_info->HasStarted(), pm::ResultAlreadyStarted()); ldr::ProgramInfo program_info; R_TRY(ldr::pm::GetProgramInfo(&program_info, process_info->GetProgramLocation())); return StartProcess(process_info, &program_info); } Result TerminateProcess(os::ProcessId process_id) { ProcessListAccessor list(g_process_list); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); return svcTerminateProcess(process_info->GetHandle()); } Result TerminateProgram(ncm::ProgramId program_id) { ProcessListAccessor list(g_process_list); auto process_info = list->Find(program_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); return svcTerminateProcess(process_info->GetHandle()); } Result GetProcessEventHandle(Handle *out) { *out = os::GetReadableHandleOfSystemEvent(std::addressof(g_process_event)); return ResultSuccess(); } Result GetProcessEventInfo(ProcessEventInfo *out) { /* Check for event from current process. */ { ProcessListAccessor list(g_process_list); for (auto &process : *list) { if (process.HasStarted() && process.HasStartedStateChanged()) { process.ClearStartedStateChanged(); out->event = GetProcessEventValue(ProcessEvent::Started); out->process_id = process.GetProcessId(); return ResultSuccess(); } if (process.HasSuspendedStateChanged()) { process.ClearSuspendedStateChanged(); if (process.IsSuspended()) { out->event = GetProcessEventValue(ProcessEvent::DebugBreak); } else { out->event = GetProcessEventValue(ProcessEvent::DebugRunning); } out->process_id = process.GetProcessId(); return ResultSuccess(); } if (process.HasExceptionOccurred()) { process.ClearExceptionOccurred(); out->event = GetProcessEventValue(ProcessEvent::Exception); out->process_id = process.GetProcessId(); return ResultSuccess(); } if (hos::GetVersion() < hos::Version_5_0_0 && process.ShouldSignalOnExit() && process.HasTerminated()) { out->event = GetProcessEventValue(ProcessEvent::Exited); out->process_id = process.GetProcessId(); return ResultSuccess(); } } } /* Check for event from exited process. */ if (hos::GetVersion() >= hos::Version_5_0_0) { ProcessListAccessor dead_list(g_dead_process_list); if (!dead_list->empty()) { auto &process_info = dead_list->front(); out->event = GetProcessEventValue(ProcessEvent::Exited); out->process_id = process_info.GetProcessId(); CleanupProcessInfo(dead_list, &process_info); return ResultSuccess(); } } out->process_id = os::ProcessId{}; out->event = GetProcessEventValue(ProcessEvent::None); return ResultSuccess(); } Result CleanupProcess(os::ProcessId process_id) { ProcessListAccessor list(g_process_list); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); R_UNLESS(process_info->HasTerminated(), pm::ResultNotTerminated()); CleanupProcessInfo(list, process_info); return ResultSuccess(); } Result ClearExceptionOccurred(os::ProcessId process_id) { ProcessListAccessor list(g_process_list); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); process_info->ClearExceptionOccurred(); return ResultSuccess(); } /* Information Getters. */ Result GetModuleIdList(u32 *out_count, u8 *out_buf, size_t max_out_count, u64 unused) { /* This function was always stubbed... */ *out_count = 0; return ResultSuccess(); } Result GetExceptionProcessIdList(u32 *out_count, os::ProcessId *out_process_ids, size_t max_out_count) { ProcessListAccessor list(g_process_list); size_t count = 0; for (auto &process : *list) { if (process.HasExceptionWaitingAttach()) { out_process_ids[count++] = process.GetProcessId(); } } *out_count = static_cast(count); return ResultSuccess(); } Result GetProcessId(os::ProcessId *out, ncm::ProgramId program_id) { ProcessListAccessor list(g_process_list); auto process_info = list->Find(program_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); *out = process_info->GetProcessId(); return ResultSuccess(); } Result GetProgramId(ncm::ProgramId *out, os::ProcessId process_id) { ProcessListAccessor list(g_process_list); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); *out = process_info->GetProgramLocation().program_id; return ResultSuccess(); } Result GetApplicationProcessId(os::ProcessId *out_process_id) { ProcessListAccessor list(g_process_list); for (auto &process : *list) { if (process.IsApplication()) { *out_process_id = process.GetProcessId(); return ResultSuccess(); } } return pm::ResultProcessNotFound(); } Result AtmosphereGetProcessInfo(Handle *out_process_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) { ProcessListAccessor list(g_process_list); auto process_info = list->Find(process_id); R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); *out_process_handle = process_info->GetHandle(); *out_loc = process_info->GetProgramLocation(); *out_status = process_info->GetOverrideStatus(); return ResultSuccess(); } /* Hook API. */ Result HookToCreateProcess(Handle *out_hook, ncm::ProgramId program_id) { *out_hook = INVALID_HANDLE; { ncm::ProgramId old_value = ncm::InvalidProgramId; R_UNLESS(g_program_id_hook.compare_exchange_strong(old_value, program_id), pm::ResultDebugHookInUse()); } *out_hook = os::GetReadableHandleOfSystemEvent(std::addressof(g_hook_to_create_process_event)); return ResultSuccess(); } Result HookToCreateApplicationProcess(Handle *out_hook) { *out_hook = INVALID_HANDLE; { bool old_value = false; R_UNLESS(g_application_hook.compare_exchange_strong(old_value, true), pm::ResultDebugHookInUse()); } *out_hook = os::GetReadableHandleOfSystemEvent(std::addressof(g_hook_to_create_application_process_event)); return ResultSuccess(); } Result ClearHook(u32 which) { if (which & HookType_ProgramId) { g_program_id_hook = ncm::InvalidProgramId; } if (which & HookType_Application) { g_application_hook = false; } return ResultSuccess(); } /* Boot API. */ Result NotifyBootFinished() { static bool g_has_boot_finished = false; if (!g_has_boot_finished) { /* Set program verification disabled, if we should. */ /* NOTE: Nintendo does not check the result of this. */ if (spl::IsDisabledProgramVerification()) { if (hos::GetVersion() >= hos::Version_10_0_0) { ldr::pm::SetEnabledProgramVerification(false); } else { fsprSetEnabledProgramVerification(false); } } boot2::LaunchPreSdCardBootProgramsAndBoot2(); g_has_boot_finished = true; os::SignalSystemEvent(std::addressof(g_boot_finished_event)); } return ResultSuccess(); } Result GetBootFinishedEventHandle(Handle *out) { /* In 8.0.0, Nintendo added this command, which signals that the boot sysmodule has finished. */ /* Nintendo only signals it in safe mode FIRM, and this function aborts on normal FIRM. */ /* We will signal it always, but only allow this function to succeed on safe mode. */ AMS_ABORT_UNLESS(spl::IsRecoveryBoot()); *out = os::GetReadableHandleOfSystemEvent(std::addressof(g_boot_finished_event)); return ResultSuccess(); } /* Resource Limit API. */ Result BoostSystemMemoryResourceLimit(u64 boost_size) { return resource::BoostSystemMemoryResourceLimit(boost_size); } Result BoostApplicationThreadResourceLimit() { return resource::BoostApplicationThreadResourceLimit(); } Result AtmosphereGetCurrentLimitInfo(s64 *out_cur_val, s64 *out_lim_val, u32 group, u32 resource) { return resource::GetResourceLimitValues(out_cur_val, out_lim_val, static_cast(group), static_cast(resource)); } }