- 游戏开发
- 图形学
【免费下载链接】xenia
Xbox 360 Emulator Research Project
本篇技术指南围绕 docs/kernel.md 展开,系统讲解 Xenia(Xbox 360 模拟器研究项目)如何在宿主进程中实现客体内核 API:从模块加载时的导入替换、syscall 桥接,到两种内核导出编写约定(已废弃的SHIM_CALL与基于模板的新式约定),再到 xboxkrnl.exe 与 xam.xex 两大内核模块的组织结构。读完本文,你将掌握 Xenia 内核层的调用链原理、参数传递 ABI,以及如何阅读和编写内核导出实现。
背景:guest 与 host 之间的内核边界
Xbox 360 上的游戏是面向 PowerPC(Xenon CPU)编译的二进制程序,运行时会调用由系统内核提供的系统服务(如音频、输入、内存、线程、文件系统等)。Xenia 并不在模拟器中运行真实的 xboxkrnl.exe,而是采用"宿主原生函数"方案:Xenia 将所有内核 API 以宿主(host)平台上的原生 C++ 函数实现(见 docs/kernel.md 的 "Kernel shims" 一节)。
这意味着每次游戏发起内核调用,都必须跨越 guest(PowerPC 虚拟地址空间)与 host(x86-64 原生代码)之间的边界。这一边界正是"kernel shims"要解决的问题,完整的 guest→host 调用转换细节在 cpu 文档 的 ABI 一节有进一步说明(x64 后端通过 thunk 过渡宿主上下文,寄存器布局见 x64_stack_layout.h)。
Kernel Shims:syscall 替换与导出链接
模块加载时的三步处理
当游戏模块(xex/exe)被加载时,loader 会对导入表执行如下处理(docs/kernel.md):
- 找出所有内核导入:扫描模块的导入描述,定位所有指向内核模块(如 xboxkrnl.exe、xam.xex)的函数引用;
- 以 syscall 指令替换:在每个内核导入的位置放入一条 PowerPC
sc(system call)指令; - 查找内核导出并链接:根据导入的模块名与序号(ordinal),在 Xenia 已注册的导出表中找到对应的导出实现,并将该导出"链接"到每个导入点上。
JIT 对 syscall 的处理
当 JIT(即时编译器)翻译 guest 代码时,一旦遇到sc指令,就会生成一段调用宿主函数的指令序列。源码层面可以看到这条路径:
- 在 ppc_emit_control.cc 中,
InstrEmit_sc对SC.LEV == 0的系统调用调用f.builtins()->syscall_handler; - 该内置函数在 ppc_frontend.cc 中通过
processor_->DefineBuiltin("SyscallHandler", SyscallHandler, ...)注册; - 真正的
SyscallHandler读取ppc_context->r[0]作为系统调用号(见 ppc_frontend.cc),再分发到对应的导出实现。
由此形成完整的调用链:guest 代码执行sc→ JIT 生成调用 SyscallHandler 的序列 → SyscallHandler 按系统调用号分发 → 命中 Xenia 的原生导出实现。
两种导出定义方式
目前定义内核导出有两种方式(原文以 xboxkrnl_audio.cc 为例):
// 方式一:SHIM_CALL 约定(已废弃) SHIM_CALL XAudioGetSpeakerConfig_shim(PPCContext* ppc_context, KernelState* kernel_state) // 方式二:模板化约定(现行) dword_result_t XAudioGetSpeakerConfig(lpdword_t config_ptr)SHIM_CALL 约定(Deprecated)
SHIM_CALL约定已废弃,但它能更直白地展示调用机制内部:
ppc_context是 guest 的 PowerPC 上下文(xe::cpu::ppc::PPCContext),保存 guest 的全部寄存器;- 函数参数从 r3...r10 寄存器获取(对应宏
SHIM_GET_ARG_32等,见 shim_utils.h),超过 8 个的附加参数从栈上加载; - 返回值(如有)存入 r3(宏
SHIM_SET_RETURN_32:ppc_context->r[3] = (uint64_t)((int32_t)v))。
参数的实际读取逻辑在 shim_utils.h:前 8 个参数(index ≤ 7)直接取ppc_context->r[3 + index],其余参数通过栈指针r[1] + 0x54 + (index - 8) * 8结合内存翻译(SHIM_MEM_32等宏完成 guest 地址到 host 地址的转换与字节序交换)来读取。
模板化约定(现行)
新式约定在语义上等价,但用 C++ 模板自动化了"加载参数、设置返回值"的过程(见 shim_utils.h 的RegisterExport)。其核心机制:
- 每个参数类型都派生自
shim::Param,在构造时通过Param::Init从ppc_context中完成取值:整数/指针参数从r[3 + ordinal]取,浮点参数从f[1 + float_ordinal]取(shim_utils.h); - 返回值类型派生自
shim::Result,在调用完成后调用result.Store(ppc_context)将结果写回r[3](shim_utils.h); RegisterExport生成一个Trampoline,负责计数、按标签决定是否打印调用日志(PrintKernelCall),再转发到用户实现的*_entry函数。
配套的宏DECLARE_XBOXKRNL_EXPORT1/2/3/4、DECLARE_XAM_EXPORT1/2等(shim_utils.h)把"导出序号 + 分类 + 标签"打包进注册调用,声明了模块名、分类(如kAudio)以及行为标签(如kImplemented、kStub、kHighFrequency)。
一个完整的内核导出示例
以音频模块为例(xboxkrnl_audio.cc):
dword_result_t XAudioGetSpeakerConfig_entry(lpdword_t config_ptr) { *config_ptr = 0x00010001; return X_ERROR_SUCCESS; } DECLARE_XBOXKRNL_EXPORT1(XAudioGetSpeakerConfig, kAudio, kImplemented);其中lpdword_t是指向 guest 内存中 32 位值(大端序xe::be<uint32_t>)的指针参数类型(shim_utils.h 中定义了一整套类型别名:dword_t、qword_t、float_t、lpvoid_t、lpdword_t、lpstring_t等)。写入*config_ptr时,PrimitivePointerParam已经通过TranslateVirtual完成了 guest 地址到 host 地址的转换。
一个更复杂的例子XAudioRegisterRenderDriverClient_entry(xboxkrnl_audio.cc)展示了如何与模拟器的子系统交互:它读取回调指针与参数,调用audio_system->RegisterClient(...)注册客户端,并把返回的索引拼装成 guest 侧的驱动句柄0x41550000 | index——这个0x4155魔数是 guest 与 host 约定的"音频驱动"标识,后续XAudioSubmitRenderDriverFrame_entry等接口会通过driver_ptr.guest_address() & 0xFFFF0000 == 0x41550000断言校验后取回索引(xboxkrnl_audio.cc),最终把采样帧交给audio_system->SubmitFrame(...)。
导出标签(ExportTag)
从源码结构看,每个导出都携带一组标签位,用于描述其实现状态与调用特性(定义在 export_resolver.h):
| 标签 | 含义 |
|---|---|
kImplemented | 导出已有某种形式实现,可被使用 |
kStub | 导出是桩实现,可能不正确 |
kSketchy | 已知可能引发问题或实现不完整 |
kHighFrequency | 被极其频繁地调用 |
kImportant | 重要导出,应始终记录日志 |
kBlocking | 导出会阻塞调用线程 |
kLog/kLogResult | 每次调用 / 每次结果都记录日志 |
这些标签在运行时驱动日志与性能行为:例如 shim_utils.h 中的 Trampoline 会判断kLog标签,且高频调用(kHighFrequency)只有在 cvarlog_high_frequency_kernel_calls开启时才记录,避免日志洪泛。导出还带有分类(ExportCategory,如kAudio、kInput、kThreading、kVideo、kFileSystem、kNetworking等,见 export_resolver.h)。
Kernel Modules:两大内核模块的实现
Xenia 实现了两个 Xbox 内核模块:xboxkrnl.exe与xam.xex(见 docs/kernel.md 的 "Kernel Modules" 一节)。
xboxkrnl.exe —— Xbox 内核
定义于 src/xenia/kernel/xboxkrnl 目录。
这是 NT 内核的一个"微调版本":大部分 API 与 MSDN 或其他公开资料上能查到的 NT 内核 API 等价,因此可以参考 NT 内核文档理解其语义。源文件按 API 功能分组组织,从目录结构可以清晰看到分组方式:
- 音频:xboxkrnl_audio.cc、xboxkrnl_audio_xma.cc
- 加密:xboxkrnl_crypt.cc调试:xboxkrnl_debug.cc错误处理:xboxkrnl_error.cc
- 硬件抽象层:xboxkrnl_hal.cc输入:xboxkrnl_hid.ccIO:xboxkrnl_io.cc、xboxkrnl_io_info.cc
- 内存:xboxkrnl_memory.cc杂项:xboxkrnl_misc.cc对象管理器:xboxkrnl_ob.cc
- 运行时库:xboxkrnl_rtl.cc、xboxkrnl_rtl.h字符串:xboxkrnl_strings.cc
- 线程:xboxkrnl_threading.cc、xboxkrnl_threading.h摄像头:xboxkrnl_usbcam.cc
- 视频:xboxkrnl_video.cc、xboxkrnl_video.h系统配置:xboxkrnl_xconfig.cc
模块的注册逻辑在 xboxkrnl_module.cc:
- 构造函数调用
RegisterExportTable(export_resolver_),并通过xboxkrnl_module_export_groups.inc批量调用各分组的Register##n##Exports完成所有导出注册(xboxkrnl_module.cc); - 导出表是一个 4096 槽位的数组,按 ordinal 索引(xboxkrnl_module.cc),静态表(xboxkrnl_table.inc)与动态注册的导出合并后整体注册进
ExportResolver(xboxkrnl_module.cc)。
除了函数导出,该模块还通过SetVariableMapping向 guest 暴露若干"内核变量"(xboxkrnl_module.cc),例如:
XboxHardwareInfo:16 字节硬件信息结构,flags 置为0x20、CPU 数量置为6(0x06),游戏中常检查 bit 26 判断是否带硬盘;KeTimeStampBundle:24 字节时间戳包,由 1ms 周期的HighResolutionTimer持续刷新 guest 运行毫秒数;XboxKrnlVersion:伪造的2.FFFF.FFFF.80.00版本号;ExLoadedCommandLine:通过 cvarcl附加的 guest 命令行参数。
模块还定义了若干命令行开关(xboxkrnl_module.cc):--cl(附加 guest 命令行)、--kernel_debug_monitor(启用调试监视器)、--kernel_cert_monitor(启用证书监视器)、--kernel_pix(启用 PIX 支持)。
xam.xex —— Xbox 辅助方法
定义于 src/xenia/kernel/xam 目录。
该模块实现 Xbox 特有的功能,即那些不属于 NT 内核语义、而属于 Xbox 平台层的能力。从目录与源文件可以归纳出它的功能覆盖面:
- 应用与应用管理:app_manager.cc、apps/、xam_msg.cc、xam_task.cc、xam_ui.cc
- 内容与存储:content_manager.cc、xam_content.cc、xam_content_aggregate.cc、xam_content_device.cc
- 用户与个人资料:user_profile.cc、xam_user.cc、xam_avatar.cc
- 网络与语音:xam_net.cc、xam_voice.cc、xam_party.cc
- 系统信息与本地化:xam_info.cc、xam_locale.cc、xam_enum.cc
- 输入与通知:xam_input.cc、xam_notify.cc、xam_nui.cc
- 视频:xam_video.cc
注册流程与 xboxkrnl 一致:xam_module.cc 在构造时调用RegisterExportTable,并通过xam_module_export_groups.inc批量注册各分组导出,最终以"xam.xex"为模块名注册进ExportResolver(xam_module.cc)。xam 中还维护了一个全局原子计数器xam_dialogs_shown_,xeXamIsUIActive()据此判断系统 UI 对话框是否处于活动状态(xam_module.cc)。
导出解析机制(ExportResolver)
所有内核导出的注册与查询都经由xe::cpu::ExportResolver(export_resolver.h):
RegisterTable(module_name, exports):注册某个模块(如"xboxkrnl.exe"、"xam.xex")的导出表,表内同时维护"按 ordinal"与"按名称"两种索引,供加载器与调试器查询;GetExportByOrdinal(module_name, ordinal):按模块名 + 序号解析导出,这是 loader 把 syscall 链接到宿主实现时的核心查询路径;SetFunctionMapping/SetVariableMapping:前者为函数导出设置 trampoline(JIT 生成的 guest→host 桥接入口),后者为变量导出设置 guest 内存中的地址。
每个Export条目(export_resolver.h)包含 ordinal、类型(函数/变量)、96 字节名称、标签位,以及函数数据(trampoline 指针与调用计数call_count)。从ExportTag的位布局注释(ll...... cccccccc ........ ..bihssi)可以看出,标签高 16 位承载分类(CategoryShift = 16),低 6 位承载行为标签,这正是宏DECLARE_XBOXKRNL_EXPORT1把kAudio等分类与行为标签打包组合的实现基础。
小结
Xenia 的内核层设计可以概括为一条清晰的边界策略:
- 全部内核 API 宿主化:不仿真真实内核二进制,而是用宿主原生 C++ 函数实现(docs/kernel.md);
- syscall 桥接:模块加载时以
sc指令替换内核导入并链接到已注册导出,JIT 翻译sc时调用SyscallHandler分发(ppc_frontend.cc、ppc_emit_control.cc); - 两套导出编写约定:
SHIM_CALL(已废弃,直观展示 r3~r10 参数传递与栈参数)与模板化约定(自动参数加载/返回值写回,见 shim_utils.h); - 两大内核模块:xboxkrnl.exe 覆盖与 NT 内核等价的系统服务,xam.xex 覆盖 Xbox 平台特有功能,均按功能分组组织在 src/xenia/kernel/xboxkrnl 与 src/xenia/kernel/xam 下,由
ExportResolver统一管理导出表与变量映射。
对想要深入 Xenia 内核层或为其增加内核 API 支持的开发者而言,阅读 shim_utils.h 理解参数/返回值机制、参考 xboxkrnl_audio.cc 学习导出编写范式、再对照 xboxkrnl_module.cc 与 xam_module.cc 了解模块注册流程,是一条完整的学习路径。
- 游戏开发
- 图形学
【免费下载链接】xenia
Xbox 360 Emulator Research Project
相关推荐
Fill 3D部署指南:从开发到生产的完整部署流程
Fill 3D部署指南:从开发到生产的完整部署流程 想要将AI驱动的3D虚拟置景技术应用到你的项目中吗?🚀 Fill 3D是一个强大的生成式AI工具,能够为2
Xenia Canary深度解析:解锁Xbox 360游戏模拟的五大核心技术
Xenia Canary深度解析:解锁Xbox 360游戏模拟的五大核心技术 Xenia Canary是一款功能强大的Xbox 360游戏模拟器,它通过先进的技
虚拟化图形学KTransformers kt-kernel模块详解:高性能推理内核实现
KTransformers kt kernel模块是一个专为大语言模型 LLM 推理优化的高性能计算内核,支持Intel AMX、AVX等先进指令集,实现了CP
人工智能大模型推理引擎本地部署微调
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考