
【免费下载链接】EglantineVERY experimental Nintendo Switch 2 emulator项目地址https://gitcode.com/gh_mirrors/eg/Eglantine点击查看免费下载本文以仓库内的 HOS Kernel 子系统文档 为主线讲清 Nintendo SwitchHOS内核在 Eglantine 模拟器中是如何被以 HLE 方式重建的svc #imm指令如何进入 C 处理函数、32/36/39 位地址空间如何区分、每个进程持有哪九类内核结构以及内存红黑树、句柄表、线程本地页与调度器在多核模拟中的实际取舍。读完之后你应当能够在源码中定位任意一条 SVC 的实现入口并理解新增或修补一条 SVC 所需的完整链路。一、文档给出的事实基线HOS 是一个微内核原文档开篇给出了几条后续所有实现都依赖的前提HOS 内核是微内核所有服务与程序都运行在用户态用户态之间的主要通信方式是HIPC文档中明确说明不展开其余内核原语大多以svc #imm系统调用的形式存在内核同时支持32 位与 64 位程序并可为被创建的进程使用32、36、39 位地址空间网络栈大部分派生自 FreeBSD 的网络栈。接着文档划定了模拟器的能力边界模拟器实现了与 HOS 内核本身相关的大部分 SVC而这里所说的HOS 内核严格指系统调用的处理机制及其副作用例如页表记账Eglantine并不做 supervisor-levelEL1 及以上级别的仿真也无法加载一个自定义的底层内核。这一点与仓库的整体设计取向一致设计概览 说明项目采用 HLEHigh-Level Emulation即模拟可见的 I/O 接口而非逐个子系统精确仿真。由此得到一个重要的阅读视角阅读src/core/hle/kernel/时不应该把它当成一颗真实的内核在启动而应理解成一组被系统调用触发、并维护内核对象状态的回调集合。二、svc #imm如何被分发到处理函数文档中大多数 NX 程序最终都会执行一条svc并根据其立即数被分发到某个具体的系统调用处理器这句话对应代码里的三个层次。2.1 统一入口Kernel::Svc::Call入口位于 src/core/hle/kernel/svc.cpp。它做四件事取当前进程与当前物理核把 8 个通用参数保存到std::arrayuint64_t, 8 args打印一条LOG_TRACE随后按立即数分发通过SvcId(imm)的switch转到具体的SvcWrap_*包装函数处理完成后把结果写回寄存器上下文。参数搬运依赖架构差异处理src/core/hle/kernel/svc.cpp 中提供了GetArg32/SetArg32/GetArg64/SetArg64与一个能安全处理源与目标宽度不同的ConvertT模板并用大量static_assert锁死各枚举与结构的尺寸例如Handle、Result、MemoryState均为 4 字节lp64::LastThreadContext为 32 字节、ilp32::LastThreadContext为 16 字节。这些断言是 ABI 正确性的第一道防线。2.2 32 位与 64 位程序的两套派发表Call根据process.Is64Bit()决定进入Call64还是Call32两个函数各自维护一份switchvoid Call(Core::System system, u32 imm) { auto kernel system.Kernel(); auto process GetCurrentProcess(kernel); std::arrayuint64_t, 8 args; kernel.CurrentPhysicalCore().SaveSvcArguments(process, args); // ... if (imm 0x80) { // Switch 2 专用svcSetGpuMemoryAttribute } else if (process.Is64Bit()) { Call64(system, imm, args); } else { Call32(system, imm, args); } kernel.CurrentPhysicalCore().LoadSvcArguments(process, args); }32 位路径使用SvcWrap_*64From32形式的包装器这类包装器负责把 32 位程序的参数/返回值投影到统一的 64 位槽位上再调用共享实现。这让大部分 SVC 的实现只要写一份而由包装层承担位宽适配正是svc_generator.py生成代码的核心思路。Is64Bit()的实现很轻量读取创建进程时记录的标志位见 src/core/hle/kernel/k_process.hbool Is64Bit() const { return True(m_flags Svc::CreateProcessFlag::Is64Bit); }需要注意32 位/64 位不等于地址空间宽度Is64Bit只是CreateProcessFlag的最低比特地址空间宽度由另一个字段决定见第四节。2.3 未实现的 SVC 会直接触发断言两个派发表的default分支都是default: UNREACHABLE_MSG(Unhandled SVC {:#x}!, imm);也就是说Eglantine 对内核接口覆盖不全的态度是显式失败而不是静默返回成功。这一行为对排查游戏在某条指令处崩溃很有价值日志里出现Unhandled SVC 0x..就意味着对应系统调用尚未实现。原文档也提醒过usb:u、pcie这类功能位于专门服务中目前尚未被模拟——这类缺失通常不会表现为 SVC 未实现而是表现为服务端返回错误或桩实现。2.4 代码是自动生成的tools/svc_generator.pysvc.cpp与svc.h顶部都有明确注释This file is automatically generated using svc_generator.py. DO NOT MODIFY IT MANUALLY。生成器 tools/svc_generator.py 内维护了一张 SVC 表例如SVCS [ [0x01, Result SetHeapSize(Address* out_address, Size size);], [0x02, Result SetMemoryPermission(Address address, Size size, MemoryPermission perm);], [0x03, Result SetMemoryAttribute(Address address, Size size, uint32_t mask, uint32_t attr);], [0x06, Result QueryMemory(Address out_memory_info, PageInfo* out_page_info, Address address);], [0x08, Result CreateThread(Handle* out_handle, ThreadFunc func, Address arg, Address stack_bottom, int32_t priority, int32_t core_id);], ... [0x45, Result CreateEvent(Handle* out_write_handle, Handle* out_read_handle);], ]生成器内部实现了 ARM 过程调用标准意义上的寄存器分配器reg_alloc、add_single、add再由emit_gather/emit_scatter生成从寄存器数组取值、把结果写回寄存器数组的代码。这解释了为什么svc.cpp里会出现大量逐参数搬运的样板代码它是被机器生成的 ABI 胶水层而不是手写的业务逻辑。实用结论要新增或修改一条 SVC应该改生成器的 SVC 表和对应的手写实现src/core/hle/kernel/svc/下再重新生成而不是直接编辑svc.cpp。2.5 用环境变量跟踪 SVC 时序Call中还有一段调试设施通过环境变量EDEN_NX2_SVC_TRACE控制EDEN_NX2_SVC_TRACE1记录每一条 SVC 及其入参与返回值EDEN_NX2_SVC_TRACEfirst:last只在第first到last个 present帧之间记录用于帧级性能剖析。日志会带上线程 ID例如svc {:#04x} in t{} ...与svc {:#04x} out t{} ...。对于某帧里发生了什么这类问题这比全量LOG_TRACE更可用因为后者噪声极大。三、SVC 版本号与哪些 SVC 允许被调用HOS 对可调用 SVC 集合做了版本化与权限化的双重约束仓库在 src/core/hle/kernel/svc_version.h 与 src/core/hle/kernel/svc_types.h 中如实建模概念代码位置取值/说明SVC 主版本 ↔ SDK 主版本ConvertToSvcMajorVersionsvc sdk 4最低支持版本RequiredKernelMajorVersion/MinorVersion3.0Nintendo 不再支持低于 3.0 的程序当前支持的最高版本SupportedKernelMajorVersion/MinorVersionConvertToSvcMajorVersion(22)、ConvertToSvcMinorVersion(2)版本编码EncodeKernelVersionminor占 bit 0..3major占 bit 4..16SVC 总数上限NumSupervisorCalls0xC0即 192允许访问集合SvcAccessFlagSetstd::bitsetNumSupervisorCallsSvcAccessFlagSet是关键SVC 的可调用性不是实现了就能调而是由进程的 capabilities 位图决定这与微内核 最小权限的模型一致。调试时若出现权限类错误应优先检查进程 capability 而不是 SVC 实现本身。四、地址空间32 / 36 / 39 位如何落地文档提到可为被创建的进程使用 32、36 和 39 位地址空间。选择方式在CreateProcessFlag中src/core/hle/kernel/svc_types.henum class CreateProcessFlag : u32 { Is64Bit (1 0), AddressSpaceShift 1, AddressSpaceMask (7 AddressSpaceShift), AddressSpace32Bit (0 AddressSpaceShift), AddressSpace64BitDeprecated (1 AddressSpaceShift), AddressSpace32BitWithoutAlias (2 AddressSpaceShift), AddressSpace64Bit (3 AddressSpaceShift), EnableDebug (1 4), EnableAslr (1 5), IsApplication (1 6), DeprecatedUseSecureMemory (1 7), PoolPartitionShift 7, // ... };位宽的映射在 src/core/hle/kernel/k_page_table_base.hstatic constexpr size_t GetAddressSpaceWidth(Svc::CreateProcessFlag as_type) { switch (static_castSvc::CreateProcessFlag(as_type Svc::CreateProcessFlag::AddressSpaceMask)) { case Svc::CreateProcessFlag::AddressSpace64Bit: return 39; case Svc::CreateProcessFlag::AddressSpace64BitDeprecated: return 36; case Svc::CreateProcessFlag::AddressSpace32Bit: case Svc::CreateProcessFlag::AddressSpace32BitWithoutAlias: return 32; default: UNREACHABLE(); } }各宽度的区域布局是一张静态表src/core/hle/kernel/k_address_space_info.cpp区域类型为MapSmall / MapLarge / Map39Bit / Heap / Stack / Alias位宽区域起始地址大小32MapSmall2 MiB1 GiB − 2 MiB32MapLarge1 GiB4 GiB − 1 GiB32Alias无效动态1 GiB32Heap无效动态1 GiB36MapSmall128 MiB2 GiB − 128 MiB36MapLarge2 GiB64 GiB − 2 GiB36Heap无效8 GiB36Alias无效6 GiB39Map39Bit128 MiB512 GiB − 128 MiB启用HAS_NCE时为 256 GiB − 128 MiB39MapSmall无效64 GiB39Heap无效8 GiB39Alias无效64 GiB39Stack无效2 GiB两个值得注意的实现细节NCE 下地址空间被压缩。源码注释说明启用 NCE原生代码执行时因为内存限制使用 38 位地址空间safely truncate ASLR region表中Map39Bit大小相应从 512 GiB 降为 256 GiB。AddressSpace32BitWithoutAlias会把 Alias 区并入 Heap 区。InitializeForProcess中有对应逻辑若该标志被设置则heap_region_size alias_region_size; alias_region_size 0;并且进程的最大可用内存随之从GetHeapRegionSize()变为GetHeapRegionSize() GetAliasRegionSize()见 src/core/hle/kernel/k_process.cpp 中按AddressSpaceMask分支的m_max_process_memory计算。五、进程持有的九类结构文档清单与源码对照原文档列出了每个进程都要维护的结构Name13 字符64-bit ID一个句柄表Exclusive monitorThreadsHeld locksThread local pages每个进程一张页表对到 src/core/hle/kernel/k_process.h 的成员上可以逐条验证文档条目源码成员说明Name13 字符std::arraychar, 13 m_name初始化时memcpy进程名并补\0见KProcess::Initialize64-bit IDu64 m_process_id初始进程 ID 区间1..0x50用户进程从0x51起InitialProcessIdMin/Max、ProcessIdMin/Max句柄表KHandleTable m_handle_tableMaxTableSize 1024线性 ID 空闲链表分配Exclusive monitorstd::unique_ptrCore::ExclusiveMonitor m_exclusive_monitor按 CPU 后端Dynarmic/NCE分别绑定到各核ThreadsThreadList m_thread_listIntrusiveListMemberTraitsKThread::m_process_list_nodeHeld locksKLightLock m_state_lock、KLightLock m_list_lock、KAddressArbiter m_address_arbiter另有KConditionVariable m_cond_var用于等待/同步Thread local pagesTLPTree m_fully_used_tlp_tree、TLPTree m_partially_used_tlp_tree两棵红黑树分别管理已用满和部分使用的 TLPPage tableKProcessPageTable m_page_table内部组合KPageTable继承自KPageTableBase补充几点源码层面的细节这些是原文档没有展开、但对理解行为很关键的进程名在结构中是 13 字节数组而Svc::CreateProcessParameter::name是 12 字节初始化时会把第 13 字节写成0m_name[sizeof(params.name)] 0;。句柄表上限 1024并且伪句柄不占表项CurrentThread 0xFFFF8000、CurrentProcess 0xFFFF8001InvalidHandle 0src/core/hle/kernel/svc_common.h。判断函数IsPseudoHandle是查表前必须经过的分支。线程本地页的粒度Svc::ThreadLocalRegionSize 0x200512 字节而页面PageSize 1 124 KiB因此每页可容纳RegionsPerPage PageSize / ThreadLocalRegionSize个区域src/core/hle/kernel/k_thread_local_page.h。IsAllUsed()/IsAllFree()决定该页挂在已满还是部分使用的红黑树上——这正是文档中用树结构跟踪进程空间的具体出处之一。进程还额外持有KResourceLimit* m_resource_limit、KSystemResource* m_system_resource、m_exception_thread以及一组统计计数器m_num_supervisor_calls、m_num_ipc_messages等它们支撑svcGetProcessInfo一类查询。六、内存记账FreeBSD 风格侵入式红黑树文档的关键论断是很多系统调用都与内存交互被模拟的内核有一套内部树状结构借自 FreeBSD 的 intrusive red-black tree用于跟踪和查找新增或移除的映射。这条描述在代码中完全可验证通用实现位于 src/common/intrusive_red_black_tree.h其节点内部使用freebsd::RBEntryIntrusiveRedBlackTreeNode根为freebsd::RBHeadIntrusiveRedBlackTreeNode并有Enum节点尺寸的static_assert保证布局稳定。进程地址空间用KMemoryBlockManager维护src/core/hle/kernel/k_memory_block_manager.hclass KMemoryBlockManager final { public: using MemoryBlockTree Common::IntrusiveRedBlackTreeBaseTraitsKMemoryBlock::TreeTypeKMemoryBlock; using MemoryBlockLockFunction void (KMemoryBlock::*)(KMemoryPermission new_perm, bool left, bool right); // ... };每个KMemoryBlock描述一段连续且属性一致的地址区间。修改映射时内核会切断受影响的块、修改属性、再尝试与左右相邻块合并——这是KMemoryBlockDisableMergeAttributeDeviceLeft、IpcLeft、Locked、DeviceRight等存在的目的某些状态下一旦切开就不允许再合并否则会丢失 IPC 锁定等语义。KMemoryBlockManagerUpdateAllocator固定MaxBlocks 2意味着单次操作最多新增两个块超出则返回OutOfResource。七、内存状态、属性与权限三位一体的描述模型svcQueryMemory返回给用户态的信息本质上是这些字段的用户可见投影。源码把它们拆成三层src/core/hle/kernel/k_memory_block.h状态Svc::MemoryStateFree0x00、Io0x01、Static0x02、Code0x03、Normal0x05、Shared0x06、Alias0x07、Stack0x0B、ThreadLocal0x0C、Transferred0x0D、Insecure0x17等 24 种内核侧用KMemoryState在同一低 8 位之外叠加 23 个能力标志例如FlagCanUseIpc、FlagCanAlias、FlagCanTransfer、FlagCanChangeAttribute、FlagLinearMapped。源码用一长串static_assert固定这些组合值如KMemoryState::Code 0x04DC7E03。属性Svc::MemoryAttribute包含Locked(10)、IpcLocked(11)、DeviceShared(12)、Uncached(13)、PermissionLocked(14)以及 Switch 2 新增的GpuAccessible(15)。权限Svc::MemoryPermission为Read1、Write2、Execute4另有DontCare(128)内核侧KMemoryPermission把用户权限左移KernelShift3得到内核权限位并用NotMapped表示未映射。svcQueryMemory填充的Svc::MemoryInfo结构src/core/hle/kernel/svc_types.h为struct MemoryInfo { u64 base_address; u64 size; MemoryState state; MemoryAttribute attribute; MemoryPermission permission; u32 ipc_count; u32 device_count; u32 padding; };即 8 8 4 4 4 4 4 4 40 字节。转换由KMemoryInfo::GetSvcMemoryInfo()完成它会用KMemoryState::Mask、KMemoryAttribute::UserMask、KMemoryPermission::UserMask把内部标志位裁剪成用户态可见的值——这也是排查为什么 QueryMemory 看不到某个属性时的第一处检查点。八、以SetHeapSize与MapMemory为例看 SVC 的副作用文档强调系统调用会带来页表记账等副作用具体形态可以看两条典型实现。8.1svcSetHeapSize0x01实现位于 src/core/hle/kernel/svc/svc_physical_memory.cppResult SetHeapSize(Core::System system, u64* out_address, u64 size) { LOG_TRACE(Kernel_SVC, called, heap_size{:#x}, size); R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize); R_UNLESS(size MainMemorySizeMax, ResultInvalidSize); KProcessAddress address{}; R_TRY(GetCurrentProcess(system.Kernel()) .GetPageTable() .SetHeapSize(std::addressof(address), size)); *out_address GetInteger(address); R_SUCCEED(); }关键约束有两条2 MiB 对齐HeapSizeAlignment 2_MiB见svc_common.h以及不得超过主机内存上限MainMemorySizeMax。进入KPageTableBase::SetHeapSize后还会依次检查size heap_region_end - heap_region_start、size m_max_heap_size缩小堆时要求尾部区间的状态为KMemoryState::Normal且权限为UserReadWrite、属性为None否则拒绝收缩。也就是说堆既能扩也能缩但收缩受尾部必须干净的约束这与游戏动态释放堆内存的行为直接相关。8.2svcMapMemory/svcUnmapMemory的共用校验两者共用MapUnmapMemorySanityCheckssrc/core/hle/kernel/svc/svc_memory.cpp错误码与检查顺序如下检查失败返回目标地址 4 KiB 对齐ResultInvalidAddress源地址 4 KiB 对齐ResultInvalidSizesize ! 0ResultInvalidSizesize4 KiB 对齐ResultInvalidSizedst size不溢出ResultInvalidCurrentMemorysrc size不溢出ResultInvalidCurrentMemorysrc落在本进程地址空间内ResultInvalidCurrentMemory注意第 2 项源地址未对齐返回的是ResultInvalidSize而非InvalidAddress——这类看起来不一致的错误码在 HOS 上是真实存在的代码保持了原样。写兼容性测试或复现问题时不要顺手修正否则会与真实固件行为分叉。九、线程、IPC 与内核对用户态服务只是通道9.1CreateThread与核心亲和性折叠src/core/hle/kernel/svc/svc_thread.cpp 顶部定义了三个小工具constexpr bool IsValidVirtualCoreId(int32_t core_id) { return (0 core_id core_id static_castint32_t(Core::Hardware::NUM_CPU_CORES)); } constexpr int32_t FoldExtraCoreId(int32_t core_id) { constexpr auto num_cores static_castint32_t(Core::Hardware::NUM_CPU_CORES); return core_id num_cores core_id 2 * num_cores ? core_id - num_cores : core_id; } constexpr u64 FoldExtraCoreMask(u64 mask) { constexpr u64 low (1ULL Core::Hardware::NUM_CPU_CORES) - 1; return (mask low) | ((mask Core::Hardware::NUM_CPU_CORES) low); }注释说明Switch 2 硬件有八个核而模拟器只模拟应用最多能拿到的六个因此超出范围的 core id 与亲和掩码会被折回低编号核而不是直接报错。CreateThread的调用顺序是先用IdealCoreUseProcessValue替换默认 magic再FoldExtraCoreId然后才校验IsValidVirtualCoreId、(1 core_id) process.GetCoreMask()、优先级范围与process.CheckThreadPriority(priority)。svcSetThreadCoreMask0x0F复用同一套折叠逻辑先按真实进程掩码做合法性校验ResultInvalidCoreId/ResultInvalidCombination再折叠到被模拟的核上最后由thread-SetCoreMask(...)落地。这个先按真实语义校验、再按模拟规模折叠的顺序很重要否则会掩盖本应报错的调用。9.2SendSyncRequest只是把请求转交给会话对象src/core/hle/kernel/svc/svc_ipc.cpp 中Result SendSyncRequestImpl(KernelCore kernel, uintptr_t message, size_t buffer_size, Handle session_handle) { KScopedAutoObject session GetCurrentProcess(kernel).GetHandleTable().GetObjectKClientSession(kernel, session_handle); R_UNLESS(session.IsNotNull(), ResultInvalidHandle); KScopedAutoObject parent {kernel, session-GetParent()}; ASSERT(parent.IsNotNull()); R_RETURN(session-SendSyncRequest(kernel, message, buffer_size)); }这与文档的判断一致内核只负责句柄解析与生命周期保活KScopedAutoObject就是引用计数的 RAII 包装实际的 HIPC 报文格式与语义由会话对象及上层服务实现。SendSyncRequestWithUserBuffer额外要求缓冲区 4 KiB 对齐、长度非零且对齐、message size不溢出并通过page_table.LockForIpcUserBuffer(...)给用户缓冲区打上 IPC 锁定失败路径上用ON_RESULT_FAILURE保证解锁避免地址空间出现永久 IPC 锁定的泄漏。ReplyAndReceive则是反向路径如果指定了reply_target先KServerSession::SendReply(...)然后进入接收等待失败时会把*out_index置为-1。超时以HardwareTimer().GetTick()换算这是内核时间而非主机时间。十、调度文档中的 4 核与当前源码的 6 核原文档这一段是全文最容易与当前源码产生偏差的地方需要分开陈述。10.1 文档陈述模拟器主动把自身限制为只使用 4 个线程以模拟 4 个核因为大多数现有应用从更多核数中获益不大反而可能因额外竞争而受损。这等价于每个进程约 4 个ArmInterface槽位这些槽位会被重定向到该核上最后运行的线程的最后一个pc随后运行它并在返回halt 或中断时重新调度。文档同时坦承调度器并非 100% 忠实例如原机是协作式而非抢占式且有较大的时序方差因为宿主系统的时序差异极大。10.2 当前源码src/core/hardware_properties.h 现在是// Switch 1 applications get cores 0..3. Switch 2 applications ask for 0..5 (title 4s npdm), and // its job system starts a worker per core, so emulate six: with four, cores 0 and 1 each ran two // of its workers. Switch 1 titles leave cores 4 and 5 idle. constexpr u32 NUM_CPU_CORES 6; // Number of CPU Cores - sync with dynarmic exclusive_monitor.h也就是说4 核是文档写作时的状态为支持按核起 worker 的 Switch 2 标题核数已提升到 6而 Switch 1 标题会让核 4、5 空闲。VirtualToPhysicalCoreMap与ConvertVirtualCoreMaskToPhysical()负责虚拟核到物理核的映射KProcess中的m_arm_interfaces、m_running_threads、m_pinned_threads都是按NUM_CPU_CORES开的数组CpuManager::Initialize()也按num_cores多核时为NUM_CPU_CORES单核时退化为 1启动宿主线程。10.3 调度器的真实结构每个物理核一个KScheduler初始化时绑定一个main thread与一个idle threadKernelCore内的InitializePhysicalCores()见 src/core/hle/kernel/kernel.cpp全局调度上下文GlobalSchedulerContext持有MAX_THREADS 256的线程列表、一个KSchedulerPriorityQueue优先级范围HighestThreadPriority 0到LowestThreadPriority 63以及调度器锁KScheduler提供Schedule / RescheduleCurrentCore / PreemptSingleCore / RequestScheduleOnInterrupt等入口IsIdle()通过比较m_current_thread与m_idle_thread判断KThread::IdleThreadPriority Svc::LowestThreadPriority 1即空闲线程优先级比任何用户线程都低。文档描述的重定向到该核最后运行线程的pc、运行、返回后重新调度在源码中对应KScheduler::Unload / Reload与被注释掉部分功能的ScheduleImpl路径——KScheduler::Initialize里还能看到被注释的SetSchedulerUpdateNeeded与中断处理注册代码src/core/hle/kernel/k_scheduler.cpp这也从侧面印证了文档调度器尚不完整/不完全忠实的说法。十一、Switch 2 扩展svc 0x80与GpuAccessible原文档没有涉及 Switch 2 的新增系统调用但当前代码里有一个直接在Call内联处理的特例值得作为如何扩展 SVC的示范if (imm 0x80) { // Switch 2 only: svcSetGpuMemoryAttribute(address, size, mask, attr). const u64 address GetArg64(args, 0); const u64 size GetArg64(args, 1); const u32 mask static_castu32(GetArg64(args, 2)); const u32 attr static_castu32(GetArg64(args, 3)); constexpr u32 Supported static_castu32(Svc::MemoryAttribute::GpuAccessible); // 对齐、长度、mask/attr 组合、区间归属检查然后 result page_table.SetMemoryAttribute(address, size, static_castKMemoryAttribute(mask), static_castKMemoryAttribute(attr)); }注释说明了动机nn::gpu::sdk与nn::gs在把内存注册给 GPU 之前会置上GPU 可访问属性0x20销毁时清除NVN 可以通过QueryMemory看到这一位。实现上复用了svcSetMemoryAttribute的检查顺序但把Supported收窄到只剩GpuAccessible地址未对齐返回ResultInvalidAddresssize为 0 或未对齐返回ResultInvalidSize(attr ~mask) ! 0或包含不支持的位返回ResultInvalidCombination越界或溢出返回ResultInvalidCurrentMemory。在KMemoryAttribute中对应GpuAccessible static_castu8(Svc::MemoryAttribute::GpuAccessible)并且它被放进了SetMask与Uncached、PermissionLocked并列意味着这一位是可被SetMemoryAttribute路径修改的而Locked、IpcLocked、DeviceShared只能由内核在特定操作中隐式设置。十二、调试与排查路线结合文档与源码可以把内核相关问题的定位压缩成一张检查清单先看 SVC 是否实现Unhandled SVC 0x..来自两个Call32/Call64的default分支确认后到 tools/svc_generator.py 的 SVC 表中查是否登记再看src/core/hle/kernel/svc/是否有对应.cpp。怀疑权限而非实现核对SvcAccessFlagSetNumSupervisorCalls 0xC0与进程 capabilitiesResultInvalidCoreId、ResultInvalidCombination这类错误多半来自 capability/掩码校验而非算法错误。内存类错误看Result语义InvalidAddress通常是地址未 4 KiB 对齐InvalidSize可能是长度问题也可能是源地址未对齐MapUnmapMemorySanityChecks的历史行为InvalidCurrentMemory多为区间越界或溢出OutOfMemory常见于堆上限与m_max_heap_size。时序问题用EDEN_NX2_SVC_TRACE需要唯一帧时用first:last形式避免全量日志淹没现场。跨进程/跨服务问题落到会话对象SVC 层只看得到KClientSession/KServerSession报文内容需要往 HIPC 与服务实现方向追。多核相关行为注意核数变化核对NUM_CPU_CORES与VirtualToPhysicalCoreMap文档所述的 4 核模型与当前 6 核模型会影响线程被放在哪个核上的直觉判断。十三、能力边界再确认回到文档最后几句这里的三条限制至今仍然成立且能直接在代码中找到依据没有 supervisor-level 仿真KernelCore只维护内核对象进程、线程、句柄表、页表、资源限制、中断/定时器不存在完整的 EL1 异常向量与 MMU 硬件模型地址空间是被记账的软件结构物理内存以MainMemoryAddress 0x80000000的固定映射为基准src/core/hle/kernel/board/nintendo/nx/k_memory_layout.h。专有设备不在内核中模拟usb:u、pcie等位于服务层文档明确指出尚未被模拟内核侧只提供了CreateDeviceAddressSpace、MapDeviceAddressSpaceByForce/Aligned、CreateIoPool、CreateIoRegion这类抽象原语见 src/core/hle/kernel/svc/svc_device_address_space.cpp 与 src/core/hle/kernel/svc/svc_io_pool.cpp真实设备语义由上层决定。调度不忠实且有时序方差KScheduler的注释化代码、PreemptSingleCore与中断调度入口表明其仍在演进文档所说原机是协作式而非抢占式与 HOS 实际的协作调度模型一致而模拟器在宿主抢占式线程之上运行抖动无法消除。十四、继续深入的文件索引目标建议入口SVC 分发与 ABI 胶水src/core/hle/kernel/svc.cpp、src/core/hle/kernel/svc.h各 SVC 的具体实现src/core/hle/kernel/svc/按功能拆成svc_memory.cpp、svc_thread.cpp、svc_ipc.cpp、svc_process.cpp等 38 个文件SVC 生成器tools/svc_generator.pySVC 类型与常量src/core/hle/kernel/svc_types.h、src/core/hle/kernel/svc_common.h、src/core/hle/kernel/svc_version.h进程与线程对象src/core/hle/kernel/k_process.h、src/core/hle/kernel/k_thread.h地址空间布局src/core/hle/kernel/k_address_space_info.cpp、src/core/hle/kernel/k_page_table_base.h内存块与红黑树src/core/hle/kernel/k_memory_block.h、src/core/hle/kernel/k_memory_block_manager.h、src/common/intrusive_red_black_tree.h调度器src/core/hle/kernel/k_scheduler.cpp、src/core/hle/kernel/global_scheduler_context.h核数与核映射src/core/hardware_properties.h、src/core/cpu_manager.cpp内核初始化src/core/hle/kernel/kernel.cpp、src/core/hle/kernel/kernel.hHLE 总体设计取向docs/DesignOverview.md阅读时建议始终带着本文反复出现的那条主线Eglantine 并不运行一个内核而是在svc #imm到达时用一套忠实度逐步逼近的内核对象模型去回应它文档描述的是这套模型的意图源码记录的是它当前的真实形态两者之间的差异最典型的是 4 核与 6 核正是排查行为偏差时最有价值的线索。赞分享【免费下载链接】EglantineVERY experimental Nintendo Switch 2 emulator项目地址https://gitcode.com/gh_mirrors/eg/Eglantine点击查看免费下载相关推荐Linux内核引导过程第六部分内核地址空间随机化KASLRLinux内核引导过程第六部分内核地址空间随机化KASLR 前言 本文是深入解析Linux内核引导过程的第六部分我们将重点探讨内核地址空间随机化Ker文档教程操作系统知识库Linux内核引导过程第六部分内核地址空间随机化详解Linux内核引导过程第六部分内核地址空间随机化详解 前言 本文是深入解析Linux内核引导过程的第六部分我们将重点探讨内核地址空间随机化 KASLR 的实文档教程操作系统知识库ctf-wiki 内核 Pwn 专题KASLR 内核地址空间随机化原理与绕过实战指南ctf wiki 内核 Pwn 专题KASLR 内核地址空间随机化原理与绕过实战指南 导读 KASLRKernel Address Space Layout网络安全文档知识库教程上一篇Comark流式渲染原理auto-close 如何治愈 LLM 半截 Markdown告别闪烁与跳变下一篇fullstack-agent社区与贡献指南Discord交流、issue署名机制与AGPL-3.0许可免费商用规则创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考