diff options
Diffstat (limited to '')
-rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/core/file_sys/program_metadata.cpp | 11 | ||||
-rw-r--r-- | src/core/file_sys/program_metadata.h | 6 | ||||
-rw-r--r-- | src/core/hle/kernel/errors.h | 2 | ||||
-rw-r--r-- | src/core/hle/kernel/handle_table.h | 6 | ||||
-rw-r--r-- | src/core/hle/kernel/process.cpp | 80 | ||||
-rw-r--r-- | src/core/hle/kernel/process.h | 58 | ||||
-rw-r--r-- | src/core/hle/kernel/process_capability.cpp | 355 | ||||
-rw-r--r-- | src/core/hle/kernel/process_capability.h | 264 | ||||
-rw-r--r-- | src/core/loader/deconstructed_rom_directory.cpp | 5 | ||||
-rw-r--r-- | src/core/loader/loader.cpp | 4 | ||||
-rw-r--r-- | src/core/loader/loader.h | 2 |
12 files changed, 670 insertions, 125 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 93f5ba3fe..f38271336 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -115,6 +115,8 @@ add_library(core STATIC hle/kernel/object.h hle/kernel/process.cpp hle/kernel/process.h + hle/kernel/process_capability.cpp + hle/kernel/process_capability.h hle/kernel/readable_event.cpp hle/kernel/readable_event.h hle/kernel/resource_limit.cpp diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 8903ed1d3..e90c8c2de 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -40,6 +40,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset)) return Loader::ResultStatus::ErrorBadFileAccessHeader; + aci_kernel_capabilities.resize(aci_header.kac_size / sizeof(u32)); + const u64 read_size = aci_header.kac_size; + const u64 read_offset = npdm_header.aci_offset + aci_header.kac_offset; + if (file->ReadBytes(aci_kernel_capabilities.data(), read_size, read_offset) != read_size) { + return Loader::ResultStatus::ErrorBadKernelCapabilityDescriptors; + } + return Loader::ResultStatus::Success; } @@ -71,6 +78,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const { return aci_file_access.permissions; } +const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const { + return aci_kernel_capabilities; +} + void ProgramMetadata::Print() const { LOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data()); LOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority); diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index e4470d6f0..0033ba347 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <vector> #include "common/bit_field.h" #include "common/common_types.h" #include "common/swap.h" @@ -38,6 +39,8 @@ enum class ProgramFilePermission : u64 { */ class ProgramMetadata { public: + using KernelCapabilityDescriptors = std::vector<u32>; + ProgramMetadata(); ~ProgramMetadata(); @@ -50,6 +53,7 @@ public: u32 GetMainThreadStackSize() const; u64 GetTitleID() const; u64 GetFilesystemPermissions() const; + const KernelCapabilityDescriptors& GetKernelCapabilities() const; void Print() const; @@ -154,6 +158,8 @@ private: FileAccessControl acid_file_access; FileAccessHeader aci_file_access; + + KernelCapabilityDescriptors aci_kernel_capabilities; }; } // namespace FileSys diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index d8240ec6d..d17eb0cb6 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -11,6 +11,7 @@ namespace Kernel { // Confirmed Switch kernel error codes constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7}; +constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14}; constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101}; constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102}; constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105}; @@ -30,6 +31,7 @@ constexpr ResultCode ERR_NOT_FOUND{ErrorModule::Kernel, 121}; constexpr ResultCode ERR_BUSY{ErrorModule::Kernel, 122}; constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE{ErrorModule::Kernel, 123}; constexpr ResultCode ERR_INVALID_STATE{ErrorModule::Kernel, 125}; +constexpr ResultCode ERR_RESERVED_VALUE{ErrorModule::Kernel, 126}; constexpr ResultCode ERR_RESOURCE_LIMIT_EXCEEDED{ErrorModule::Kernel, 132}; } // namespace Kernel diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h index 6b7927fd8..89a3bc740 100644 --- a/src/core/hle/kernel/handle_table.h +++ b/src/core/hle/kernel/handle_table.h @@ -43,6 +43,9 @@ enum KernelHandle : Handle { */ class HandleTable final : NonCopyable { public: + /// This is the maximum limit of handles allowed per process in Horizon + static constexpr std::size_t MAX_COUNT = 1024; + HandleTable(); ~HandleTable(); @@ -91,9 +94,6 @@ public: void Clear(); private: - /// This is the maximum limit of handles allowed per process in Horizon - static constexpr std::size_t MAX_COUNT = 1024; - /// Stores the Object referenced by the handle or null if the slot is empty. std::array<SharedPtr<Object>, MAX_COUNT> objects; diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 5356a4a3f..4f209a979 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -28,13 +28,11 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) { SharedPtr<Process> process(new Process(kernel)); process->name = std::move(name); - process->flags.raw = 0; - process->flags.memory_region.Assign(MemoryRegion::APPLICATION); process->resource_limit = kernel.GetSystemResourceLimit(); process->status = ProcessStatus::Created; process->program_id = 0; process->process_id = kernel.CreateNewProcessID(); - process->svc_access_mask.set(); + process->capabilities.InitializeForMetadatalessProcess(); std::mt19937 rng(Settings::values.rng_seed.value_or(0)); std::uniform_int_distribution<u64> distribution; @@ -64,83 +62,15 @@ ResultCode Process::ClearSignalState() { return RESULT_SUCCESS; } -void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) { +ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) { program_id = metadata.GetTitleID(); ideal_processor = metadata.GetMainThreadCore(); is_64bit_process = metadata.Is64BitProgram(); - vm_manager.Reset(metadata.GetAddressSpaceType()); -} - -void Process::ParseKernelCaps(const u32* kernel_caps, std::size_t len) { - for (std::size_t i = 0; i < len; ++i) { - u32 descriptor = kernel_caps[i]; - u32 type = descriptor >> 20; - - if (descriptor == 0xFFFFFFFF) { - // Unused descriptor entry - continue; - } else if ((type & 0xF00) == 0xE00) { // 0x0FFF - // Allowed interrupts list - LOG_WARNING(Loader, "ExHeader allowed interrupts list ignored"); - } else if ((type & 0xF80) == 0xF00) { // 0x07FF - // Allowed syscalls mask - unsigned int index = ((descriptor >> 24) & 7) * 24; - u32 bits = descriptor & 0xFFFFFF; - - while (bits && index < svc_access_mask.size()) { - svc_access_mask.set(index, bits & 1); - ++index; - bits >>= 1; - } - } else if ((type & 0xFF0) == 0xFE0) { // 0x00FF - // Handle table size - handle_table_size = descriptor & 0x3FF; - } else if ((type & 0xFF8) == 0xFF0) { // 0x007F - // Misc. flags - flags.raw = descriptor & 0xFFFF; - } else if ((type & 0xFFE) == 0xFF8) { // 0x001F - // Mapped memory range - if (i + 1 >= len || ((kernel_caps[i + 1] >> 20) & 0xFFE) != 0xFF8) { - LOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored."); - continue; - } - u32 end_desc = kernel_caps[i + 1]; - ++i; // Skip over the second descriptor on the next iteration - AddressMapping mapping; - mapping.address = descriptor << 12; - VAddr end_address = end_desc << 12; - - if (mapping.address < end_address) { - mapping.size = end_address - mapping.address; - } else { - mapping.size = 0; - } + vm_manager.Reset(metadata.GetAddressSpaceType()); - mapping.read_only = (descriptor & (1 << 20)) != 0; - mapping.unk_flag = (end_desc & (1 << 20)) != 0; - - address_mappings.push_back(mapping); - } else if ((type & 0xFFF) == 0xFFE) { // 0x000F - // Mapped memory page - AddressMapping mapping; - mapping.address = descriptor << 12; - mapping.size = Memory::PAGE_SIZE; - mapping.read_only = false; - mapping.unk_flag = false; - - address_mappings.push_back(mapping); - } else if ((type & 0xFE0) == 0xFC0) { // 0x01FF - // Kernel version - kernel_version = descriptor & 0xFFFF; - - int minor = kernel_version & 0xFF; - int major = (kernel_version >> 8) & 0xFF; - LOG_INFO(Loader, "ExHeader kernel version: {}.{}", major, minor); - } else { - LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x{:08X}", descriptor); - } - } + const auto& caps = metadata.GetKernelCapabilities(); + return capabilities.InitializeForUserProcess(caps.data(), caps.size(), vm_manager); } void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 7da367251..2c0b20f9e 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -11,9 +11,9 @@ #include <string> #include <vector> #include <boost/container/static_vector.hpp> -#include "common/bit_field.h" #include "common/common_types.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/process_capability.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/vm_manager.h" #include "core/hle/kernel/wait_object.h" @@ -42,24 +42,6 @@ enum class MemoryRegion : u16 { BASE = 3, }; -union ProcessFlags { - u16 raw; - - BitField<0, 1, u16> - allow_debug; ///< Allows other processes to attach to and debug this process. - BitField<1, 1, u16> force_debug; ///< Allows this process to attach to processes even if they - /// don't have allow_debug set. - BitField<2, 1, u16> allow_nonalphanum; - BitField<3, 1, u16> shared_page_writable; ///< Shared page is mapped with write permissions. - BitField<4, 1, u16> privileged_priority; ///< Can use priority levels higher than 24. - BitField<5, 1, u16> allow_main_args; - BitField<6, 1, u16> shared_device_mem; - BitField<7, 1, u16> runnable_on_sleep; - BitField<8, 4, MemoryRegion> - memory_region; ///< Default region for memory allocations for this process - BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000). -}; - /** * Indicates the status of a Process instance. * @@ -192,13 +174,13 @@ public: } /// Gets the bitmask of allowed CPUs that this process' threads can run on. - u32 GetAllowedProcessorMask() const { - return allowed_processor_mask; + u64 GetAllowedProcessorMask() const { + return capabilities.GetCoreMask(); } /// Gets the bitmask of allowed thread priorities. - u32 GetAllowedThreadPriorityMask() const { - return allowed_thread_priority_mask; + u64 GetAllowedThreadPriorityMask() const { + return capabilities.GetPriorityMask(); } u32 IsVirtualMemoryEnabled() const { @@ -239,15 +221,12 @@ public: * Loads process-specifics configuration info with metadata provided * by an executable. * - * @param metadata The provided metadata to load process specific info. - */ - void LoadFromMetadata(const FileSys::ProgramMetadata& metadata); - - /** - * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them - * to this process. + * @param metadata The provided metadata to load process specific info from. + * + * @returns RESULT_SUCCESS if all relevant metadata was able to be + * loaded and parsed. Otherwise, an error code is returned. */ - void ParseKernelCaps(const u32* kernel_caps, std::size_t len); + ResultCode LoadFromMetadata(const FileSys::ProgramMetadata& metadata); /** * Applies address space changes and launches the process main thread. @@ -308,22 +287,8 @@ private: /// Resource limit descriptor for this process SharedPtr<ResourceLimit> resource_limit; - /// The process may only call SVCs which have the corresponding bit set. - std::bitset<0x80> svc_access_mask; - /// Maximum size of the handle table for the process. - u32 handle_table_size = 0x200; - /// Special memory ranges mapped into this processes address space. This is used to give - /// processes access to specific I/O regions and device memory. - boost::container::static_vector<AddressMapping, 8> address_mappings; - ProcessFlags flags; - /// Kernel compatibility version for this process - u16 kernel_version = 0; /// The default CPU for this process, threads are scheduled on this cpu by default. u8 ideal_processor = 0; - /// Bitmask of allowed CPUs that this process' threads can run on. TODO(Subv): Actually parse - /// this value from the process header. - u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK; - u32 allowed_thread_priority_mask = 0xFFFFFFFF; u32 is_virtual_address_memory_enabled = 0; /// The Thread Local Storage area is allocated as processes create threads, @@ -333,6 +298,9 @@ private: /// This vector will grow as more pages are allocated for new threads. std::vector<std::bitset<8>> tls_slots; + /// Contains the parsed process capability descriptors. + ProcessCapabilities capabilities; + /// Whether or not this process is AArch64, or AArch32. /// By default, we currently assume this is true, unless otherwise /// specified by metadata provided to the process during loading. diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp new file mode 100644 index 000000000..3a2164b25 --- /dev/null +++ b/src/core/hle/kernel/process_capability.cpp @@ -0,0 +1,355 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/bit_util.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/process_capability.h" +#include "core/hle/kernel/vm_manager.h" + +namespace Kernel { +namespace { + +// clang-format off + +// Shift offsets for kernel capability types. +enum : u32 { + CapabilityOffset_PriorityAndCoreNum = 3, + CapabilityOffset_Syscall = 4, + CapabilityOffset_MapPhysical = 6, + CapabilityOffset_MapIO = 7, + CapabilityOffset_Interrupt = 11, + CapabilityOffset_ProgramType = 13, + CapabilityOffset_KernelVersion = 14, + CapabilityOffset_HandleTableSize = 15, + CapabilityOffset_Debug = 16, +}; + +// Combined mask of all parameters that may be initialized only once. +constexpr u32 InitializeOnceMask = (1U << CapabilityOffset_PriorityAndCoreNum) | + (1U << CapabilityOffset_ProgramType) | + (1U << CapabilityOffset_KernelVersion) | + (1U << CapabilityOffset_HandleTableSize) | + (1U << CapabilityOffset_Debug); + +// Packed kernel version indicating 10.4.0 +constexpr u32 PackedKernelVersion = 0x520000; + +// Indicates possible types of capabilities that can be specified. +enum class CapabilityType : u32 { + Unset = 0U, + PriorityAndCoreNum = (1U << CapabilityOffset_PriorityAndCoreNum) - 1, + Syscall = (1U << CapabilityOffset_Syscall) - 1, + MapPhysical = (1U << CapabilityOffset_MapPhysical) - 1, + MapIO = (1U << CapabilityOffset_MapIO) - 1, + Interrupt = (1U << CapabilityOffset_Interrupt) - 1, + ProgramType = (1U << CapabilityOffset_ProgramType) - 1, + KernelVersion = (1U << CapabilityOffset_KernelVersion) - 1, + HandleTableSize = (1U << CapabilityOffset_HandleTableSize) - 1, + Debug = (1U << CapabilityOffset_Debug) - 1, + Ignorable = 0xFFFFFFFFU, +}; + +// clang-format on + +constexpr CapabilityType GetCapabilityType(u32 value) { + return static_cast<CapabilityType>((~value & (value + 1)) - 1); +} + +u32 GetFlagBitOffset(CapabilityType type) { + const auto value = static_cast<u32>(type); + return static_cast<u32>(Common::BitSize<u32>() - Common::CountLeadingZeroes32(value)); +} + +} // Anonymous namespace + +ResultCode ProcessCapabilities::InitializeForKernelProcess(const u32* capabilities, + std::size_t num_capabilities, + VMManager& vm_manager) { + Clear(); + + // Allow all cores and priorities. + core_mask = 0xF; + priority_mask = 0xFFFFFFFFFFFFFFFF; + kernel_version = PackedKernelVersion; + + return ParseCapabilities(capabilities, num_capabilities, vm_manager); +} + +ResultCode ProcessCapabilities::InitializeForUserProcess(const u32* capabilities, + std::size_t num_capabilities, + VMManager& vm_manager) { + Clear(); + + return ParseCapabilities(capabilities, num_capabilities, vm_manager); +} + +void ProcessCapabilities::InitializeForMetadatalessProcess() { + // Allow all cores and priorities + core_mask = 0xF; + priority_mask = 0xFFFFFFFFFFFFFFFF; + kernel_version = PackedKernelVersion; + + // Allow all system calls and interrupts. + svc_capabilities.set(); + interrupt_capabilities.set(); + + // Allow using the maximum possible amount of handles + handle_table_size = static_cast<u32>(HandleTable::MAX_COUNT); + + // Allow all debugging capabilities. + is_debuggable = true; + can_force_debug = true; +} + +ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities, + std::size_t num_capabilities, + VMManager& vm_manager) { + u32 set_flags = 0; + u32 set_svc_bits = 0; + + for (std::size_t i = 0; i < num_capabilities; ++i) { + const u32 descriptor = capabilities[i]; + const auto type = GetCapabilityType(descriptor); + + if (type == CapabilityType::MapPhysical) { + i++; + + // The MapPhysical type uses two descriptor flags for its parameters. + // If there's only one, then there's a problem. + if (i >= num_capabilities) { + return ERR_INVALID_COMBINATION; + } + + const auto size_flags = capabilities[i]; + if (GetCapabilityType(size_flags) != CapabilityType::MapPhysical) { + return ERR_INVALID_COMBINATION; + } + + const auto result = HandleMapPhysicalFlags(descriptor, size_flags, vm_manager); + if (result.IsError()) { + return result; + } + } else { + const auto result = + ParseSingleFlagCapability(set_flags, set_svc_bits, descriptor, vm_manager); + if (result.IsError()) { + return result; + } + } + } + + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, + u32 flag, VMManager& vm_manager) { + const auto type = GetCapabilityType(flag); + + if (type == CapabilityType::Unset) { + return ERR_INVALID_CAPABILITY_DESCRIPTOR; + } + + // Bail early on ignorable entries, as one would expect, + // ignorable descriptors can be ignored. + if (type == CapabilityType::Ignorable) { + return RESULT_SUCCESS; + } + + // Ensure that the give flag hasn't already been initialized before. + // If it has been, then bail. + const u32 flag_length = GetFlagBitOffset(type); + const u32 set_flag = 1U << flag_length; + if ((set_flag & set_flags & InitializeOnceMask) != 0) { + return ERR_INVALID_COMBINATION; + } + set_flags |= set_flag; + + switch (type) { + case CapabilityType::PriorityAndCoreNum: + return HandlePriorityCoreNumFlags(flag); + case CapabilityType::Syscall: + return HandleSyscallFlags(set_svc_bits, flag); + case CapabilityType::MapIO: + return HandleMapIOFlags(flag, vm_manager); + case CapabilityType::Interrupt: + return HandleInterruptFlags(flag); + case CapabilityType::ProgramType: + return HandleProgramTypeFlags(flag); + case CapabilityType::KernelVersion: + return HandleKernelVersionFlags(flag); + case CapabilityType::HandleTableSize: + return HandleHandleTableFlags(flag); + case CapabilityType::Debug: + return HandleDebugFlags(flag); + default: + break; + } + + return ERR_INVALID_CAPABILITY_DESCRIPTOR; +} + +void ProcessCapabilities::Clear() { + svc_capabilities.reset(); + interrupt_capabilities.reset(); + + core_mask = 0; + priority_mask = 0; + + handle_table_size = 0; + kernel_version = 0; + + program_type = ProgramType::SysModule; + + is_debuggable = false; + can_force_debug = false; +} + +ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) { + if (priority_mask != 0 || core_mask != 0) { + return ERR_INVALID_CAPABILITY_DESCRIPTOR; + } + + const u32 core_num_min = (flags >> 16) & 0xFF; + const u32 core_num_max = (flags >> 24) & 0xFF; + if (core_num_min > core_num_max) { + return ERR_INVALID_COMBINATION; + } + + const u32 priority_min = (flags >> 10) & 0x3F; + const u32 priority_max = (flags >> 4) & 0x3F; + if (priority_min > priority_max) { + return ERR_INVALID_COMBINATION; + } + + // The switch only has 4 usable cores. + if (core_num_max >= 4) { + return ERR_INVALID_PROCESSOR_ID; + } + + const auto make_mask = [](u64 min, u64 max) { + const u64 range = max - min + 1; + const u64 mask = (1ULL << range) - 1; + + return mask << min; + }; + + core_mask = make_mask(core_num_min, core_num_max); + priority_mask = make_mask(priority_min, priority_max); + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags) { + const u32 index = flags >> 29; + const u32 svc_bit = 1U << index; + + // If we've already set this svc before, bail. + if ((set_svc_bits & svc_bit) != 0) { + return ERR_INVALID_COMBINATION; + } + set_svc_bits |= svc_bit; + + const u32 svc_mask = (flags >> 5) & 0xFFFFFF; + for (u32 i = 0; i < 24; ++i) { + const u32 svc_number = index * 24 + i; + + if ((svc_mask & (1U << i)) == 0) { + continue; + } + + if (svc_number >= svc_capabilities.size()) { + return ERR_OUT_OF_RANGE; + } + + svc_capabilities[svc_number] = true; + } + + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::HandleMapPhysicalFlags(u32 flags, u32 size_flags, + VMManager& vm_manager) { + // TODO(Lioncache): Implement once the memory manager can handle this. + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::HandleMapIOFlags(u32 flags, VMManager& vm_manager) { + // TODO(Lioncache): Implement once the memory manager can handle this. + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) { + constexpr u32 interrupt_ignore_value = 0x3FF; + const u32 interrupt0 = (flags >> 12) & 0x3FF; + const u32 interrupt1 = (flags >> 22) & 0x3FF; + + for (u32 interrupt : {interrupt0, interrupt1}) { + if (interrupt == interrupt_ignore_value) { + continue; + } + + // NOTE: + // This should be checking a generic interrupt controller value + // as part of the calculation, however, given we don't currently + // emulate that, it's sufficient to mark every interrupt as defined. + + if (interrupt >= interrupt_capabilities.size()) { + return ERR_OUT_OF_RANGE; + } + + interrupt_capabilities[interrupt] = true; + } + + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::HandleProgramTypeFlags(u32 flags) { + const u32 reserved = flags >> 17; + if (reserved != 0) { + return ERR_RESERVED_VALUE; + } + + program_type = static_cast<ProgramType>((flags >> 14) & 0b111); + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) { + // Yes, the internal member variable is checked in the actual kernel here. + // This might look odd for options that are only allowed to be initialized + // just once, however the kernel has a separate initialization function for + // kernel processes and userland processes. The kernel variant sets this + // member variable ahead of time. + + const u32 major_version = kernel_version >> 19; + + if (major_version != 0 || flags < 0x80000) { + return ERR_INVALID_CAPABILITY_DESCRIPTOR; + } + + kernel_version = flags; + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) { + const u32 reserved = flags >> 26; + if (reserved != 0) { + return ERR_RESERVED_VALUE; + } + + handle_table_size = (flags >> 16) & 0x3FF; + return RESULT_SUCCESS; +} + +ResultCode ProcessCapabilities::HandleDebugFlags(u32 flags) { + const u32 reserved = flags >> 19; + if (reserved != 0) { + return ERR_RESERVED_VALUE; + } + + is_debuggable = (flags & 0x20000) != 0; + can_force_debug = (flags & 0x40000) != 0; + return RESULT_SUCCESS; +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/process_capability.h b/src/core/hle/kernel/process_capability.h new file mode 100644 index 000000000..fbc8812a3 --- /dev/null +++ b/src/core/hle/kernel/process_capability.h @@ -0,0 +1,264 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <bitset> + +#include "common/common_types.h" + +union ResultCode; + +namespace Kernel { + +class VMManager; + +/// The possible types of programs that may be indicated +/// by the program type capability descriptor. +enum class ProgramType { + SysModule, + Application, + Applet, +}; + +/// Handles kernel capability descriptors that are provided by +/// application metadata. These descriptors provide information +/// that alters certain parameters for kernel process instance +/// that will run said application (or applet). +/// +/// Capabilities are a sequence of flag descriptors, that indicate various +/// configurations and constraints for a particular process. +/// +/// Flag types are indicated by a sequence of set low bits. E.g. the +/// types are indicated with the low bits as follows (where x indicates "don't care"): +/// +/// - Priority and core mask : 0bxxxxxxxxxxxx0111 +/// - Allowed service call mask: 0bxxxxxxxxxxx01111 +/// - Map physical memory : 0bxxxxxxxxx0111111 +/// - Map IO memory : 0bxxxxxxxx01111111 +/// - Interrupts : 0bxxxx011111111111 +/// - Application type : 0bxx01111111111111 +/// - Kernel version : 0bx011111111111111 +/// - Handle table size : 0b0111111111111111 +/// - Debugger flags : 0b1111111111111111 +/// +/// These are essentially a bit offset subtracted by 1 to create a mask. +/// e.g. The first entry in the above list is simply bit 3 (value 8 -> 0b1000) +/// subtracted by one (7 -> 0b0111) +/// +/// An example of a bit layout (using the map physical layout): +/// <example> +/// The MapPhysical type indicates a sequence entry pair of: +/// +/// [initial, memory_flags], where: +/// +/// initial: +/// bits: +/// 7-24: Starting page to map memory at. +/// 25 : Indicates if the memory should be mapped as read only. +/// +/// memory_flags: +/// bits: +/// 7-20 : Number of pages to map +/// 21-25: Seems to be reserved (still checked against though) +/// 26 : Whether or not the memory being mapped is IO memory, or physical memory +/// </example> +/// +class ProcessCapabilities { +public: + using InterruptCapabilities = std::bitset<1024>; + using SyscallCapabilities = std::bitset<128>; + + ProcessCapabilities() = default; + ProcessCapabilities(const ProcessCapabilities&) = delete; + ProcessCapabilities(ProcessCapabilities&&) = default; + + ProcessCapabilities& operator=(const ProcessCapabilities&) = delete; + ProcessCapabilities& operator=(ProcessCapabilities&&) = default; + + /// Initializes this process capabilities instance for a kernel process. + /// + /// @param capabilities The capabilities to parse + /// @param num_capabilities The number of capabilities to parse. + /// @param vm_manager The memory manager to use for handling any mapping-related + /// operations (such as mapping IO memory, etc). + /// + /// @returns RESULT_SUCCESS if this capabilities instance was able to be initialized, + /// otherwise, an error code upon failure. + /// + ResultCode InitializeForKernelProcess(const u32* capabilities, std::size_t num_capabilities, + VMManager& vm_manager); + + /// Initializes this process capabilities instance for a userland process. + /// + /// @param capabilities The capabilities to parse. + /// @param num_capabilities The total number of capabilities to parse. + /// @param vm_manager The memory manager to use for handling any mapping-related + /// operations (such as mapping IO memory, etc). + /// + /// @returns RESULT_SUCCESS if this capabilities instance was able to be initialized, + /// otherwise, an error code upon failure. + /// + ResultCode InitializeForUserProcess(const u32* capabilities, std::size_t num_capabilities, + VMManager& vm_manager); + + /// Initializes this process capabilities instance for a process that does not + /// have any metadata to parse. + /// + /// This is necessary, as we allow running raw executables, and the internal + /// kernel process capabilities also determine what CPU cores the process is + /// allowed to run on, and what priorities are allowed for threads. It also + /// determines the max handle table size, what the program type is, whether or + /// not the process can be debugged, or whether it's possible for a process to + /// forcibly debug another process. + /// + /// Given the above, this essentially enables all capabilities across the board + /// for the process. It allows the process to: + /// + /// - Run on any core + /// - Use any thread priority + /// - Use the maximum amount of handles a process is allowed to. + /// - Be debuggable + /// - Forcibly debug other processes. + /// + /// Note that this is not a behavior that the kernel allows a process to do via + /// a single function like this. This is yuzu-specific behavior to handle + /// executables with no capability descriptors whatsoever to derive behavior from. + /// It being yuzu-specific is why this is also not the default behavior and not + /// done by default in the constructor. + /// + void InitializeForMetadatalessProcess(); + + /// Gets the allowable core mask + u64 GetCoreMask() const { + return core_mask; + } + + /// Gets the allowable priority mask + u64 GetPriorityMask() const { + return priority_mask; + } + + /// Gets the SVC access permission bits + const SyscallCapabilities& GetServiceCapabilities() const { + return svc_capabilities; + } + + /// Gets the valid interrupt bits. + const InterruptCapabilities& GetInterruptCapabilities() const { + return interrupt_capabilities; + } + + /// Gets the program type for this process. + ProgramType GetProgramType() const { + return program_type; + } + + /// Gets the number of total allowable handles for the process' handle table. + u32 GetHandleTableSize() const { + return handle_table_size; + } + + /// Gets the kernel version value. + u32 GetKernelVersion() const { + return kernel_version; + } + + /// Whether or not this process can be debugged. + bool IsDebuggable() const { + return is_debuggable; + } + + /// Whether or not this process can forcibly debug another + /// process, even if that process is not considered debuggable. + bool CanForceDebug() const { + return can_force_debug; + } + +private: + /// Attempts to parse a given sequence of capability descriptors. + /// + /// @param capabilities The sequence of capability descriptors to parse. + /// @param num_capabilities The number of descriptors within the given sequence. + /// @param vm_manager The memory manager that will perform any memory + /// mapping if necessary. + /// + /// @return RESULT_SUCCESS if no errors occur, otherwise an error code. + /// + ResultCode ParseCapabilities(const u32* capabilities, std::size_t num_capabilities, + VMManager& vm_manager); + + /// Attempts to parse a capability descriptor that is only represented by a + /// single flag set. + /// + /// @param set_flags Running set of flags that are used to catch + /// flags being initialized more than once when they shouldn't be. + /// @param set_svc_bits Running set of bits representing the allowed supervisor calls mask. + /// @param flag The flag to attempt to parse. + /// @param vm_manager The memory manager that will perform any memory + /// mapping if necessary. + /// + /// @return RESULT_SUCCESS if no errors occurred, otherwise an error code. + /// + ResultCode ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag, + VMManager& vm_manager); + + /// Clears the internal state of this process capability instance. Necessary, + /// to have a sane starting point due to us allowing running executables without + /// configuration metadata. We assume a process is not going to have metadata, + /// and if it turns out that the process does, in fact, have metadata, then + /// we attempt to parse it. Thus, we need this to reset data members back to + /// a good state. + /// + /// DO NOT ever make this a public member function. This isn't an invariant + /// anything external should depend upon (and if anything comes to rely on it, + /// you should immediately be questioning the design of that thing, not this + /// class. If the kernel itself can run without depending on behavior like that, + /// then so can yuzu). + /// + void Clear(); + + /// Handles flags related to the priority and core number capability flags. + ResultCode HandlePriorityCoreNumFlags(u32 flags); + + /// Handles flags related to determining the allowable SVC mask. + ResultCode HandleSyscallFlags(u32& set_svc_bits, u32 flags); + + /// Handles flags related to mapping physical memory pages. + ResultCode HandleMapPhysicalFlags(u32 flags, u32 size_flags, VMManager& vm_manager); + + /// Handles flags related to mapping IO pages. + ResultCode HandleMapIOFlags(u32 flags, VMManager& vm_manager); + + /// Handles flags related to the interrupt capability flags. + ResultCode HandleInterruptFlags(u32 flags); + + /// Handles flags related to the program type. + ResultCode HandleProgramTypeFlags(u32 flags); + + /// Handles flags related to the handle table size. + ResultCode HandleHandleTableFlags(u32 flags); + + /// Handles flags related to the kernel version capability flags. + ResultCode HandleKernelVersionFlags(u32 flags); + + /// Handles flags related to debug-specific capabilities. + ResultCode HandleDebugFlags(u32 flags); + + SyscallCapabilities svc_capabilities; + InterruptCapabilities interrupt_capabilities; + + u64 core_mask = 0; + u64 priority_mask = 0; + + u32 handle_table_size = 0; + u32 kernel_version = 0; + + ProgramType program_type = ProgramType::SysModule; + + bool is_debuggable = false; + bool can_force_debug = false; +}; + +} // namespace Kernel diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index ac04d72d7..07aa7a1cd 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -129,7 +129,10 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process) return ResultStatus::Error32BitISA; } - process.LoadFromMetadata(metadata); + if (process.LoadFromMetadata(metadata).IsError()) { + return ResultStatus::ErrorUnableToParseKernelMetadata; + } + const FileSys::PatchManager pm(metadata.GetTitleID()); // Load NSO modules diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 9cd0b0ccd..d8cc30959 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 60> RESULT_MESSAGES{ +constexpr std::array<const char*, 62> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -103,6 +103,7 @@ constexpr std::array<const char*, 60> RESULT_MESSAGES{ "The NPDM has a bad ACI header,", "The NPDM file has a bad file access control.", "The NPDM has a bad file access header.", + "The NPDM has bad kernel capability descriptors.", "The PFS/HFS partition has a bad header.", "The PFS/HFS partition has incorrect size as determined by the header.", "The NCA file has a bad header.", @@ -125,6 +126,7 @@ constexpr std::array<const char*, 60> RESULT_MESSAGES{ "The file could not be found or does not exist.", "The game is missing a program metadata file (main.npdm).", "The game uses the currently-unimplemented 32-bit architecture.", + "Unable to completely parse the kernel metadata when loading the emulated process", "The RomFS could not be found.", "The ELF file has incorrect size as determined by the header.", "There was a general error loading the NRO into emulated memory.", diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index cfd67adc0..30eacd803 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -71,6 +71,7 @@ enum class ResultStatus : u16 { ErrorBadACIHeader, ErrorBadFileAccessControl, ErrorBadFileAccessHeader, + ErrorBadKernelCapabilityDescriptors, ErrorBadPFSHeader, ErrorIncorrectPFSFileSize, ErrorBadNCAHeader, @@ -93,6 +94,7 @@ enum class ResultStatus : u16 { ErrorNullFile, ErrorMissingNPDM, Error32BitISA, + ErrorUnableToParseKernelMetadata, ErrorNoRomFS, ErrorIncorrectELFFileSize, ErrorLoadingNRO, |