SharpEmu 实时调试服务器(Live Debug Server)完全指南:架构、JSON-Lines 线协议与客户端接入
2026/9/18 9:40:40 网站建设 项目流程

SharpEmu 实时调试服务器(Live Debug Server)完全指南:架构、JSON-Lines 线协议与客户端接入

【免费下载链接】sharpemuAn experimental PlayStation 5 emulator for Windows, Linux and macOS.项目地址: https://gitcode.com/GitHub_Trending/sh/sharpemu

导读

SharpEmu 是一个面向 Windows、Linux 与 macOS 的实验性 PlayStation 5 模拟器,其 CPU 核心直接原生执行 guest x86-64 代码。为了让外部进程能够实时检视并控制正在运行的 guest,SharpEmu 内置了一个通过 TCP 暴露的实时调试服务器(Live Debug Server):服务器运行在模拟器进程内部,配套的SharpEmu.DebugClient独立可执行程序是它的一个客户端,而其行分隔 JSON 线协议足够简单,可以用nc、脚本或任何自定义工具直接驱动。本文以仓库中的 docs/debugger-server.md 为骨架,结合SharpEmu.DebuggerSharpEmu.CoreSharpEmu.CLI等源码实现,完整讲解调试服务器的分层架构、执行模型、启用方式、线协议规范、协议替换与嵌入方式,并给出可直接复制的实战操作步骤。

读完本文你将掌握:如何用--debug-server启动调试服务器并配合 stop-at-entry 抢占断点窗口、如何使用SharpEmu.DebugClient交互式或脚本化驱动目标、如何用无依赖的 Python 浏览器前端进行图形化调试,以及如何基于DebuggerServerHost在自己的代码中嵌入调试服务器。

Layering:四层装配与单向依赖

调试功能被刻意拆分为四个程序集,依赖方向非常明确——Core 保持对调试器零感知,只发布一个接缝(seam)

程序集角色
SharpEmu.Core定义调度器接缝ICpuDebugHook/ICpuDebugFrame(命名空间SharpEmu.Core.Cpu.Debugging),以及CpuExecutionOptions.DebugHook注入槽。Core不引用调试器。
SharpEmu.Debugger调试器本体:实现接缝的DebuggerSessionBreakpointStore、TCP 版DebuggerServer、可插拔的IDebugProtocol(含 JSON-lines 实现),以及一站式装配类DebuggerServerHost
SharpEmu.CLI解析--debug-server参数,构建DebuggerServerHost,把它的Hook交给SharpEmuRuntimeOptions.DebugHook,并管理其生命周期。
SharpEmu.DebugClient独立客户端可执行程序,只依赖 BCL(.NET 基类库)。

在 src/SharpEmu.Core/Cpu/CpuExecutionOptions.cs 中可以看到这个接缝的形态:DebugHook是一个可空的ICpuDebugHook属性,默认null且不带来任何运行时开销;而 src/SharpEmu.Core/Cpu/Debugging/ICpuDebugHook.cs 定义了三个回调:

  • OnFrameEnter(ICpuDebugFrame frame)—— 原生后端开始执行某一帧之前调用;
  • OnFrameExit(ICpuDebugFrame frame, OrbisGen2Result result)—— 帧执行完毕(无论正常返回还是出错)之后调用;
  • OnStall(ICpuDebugFrame frame, CpuStallInfo info)—— 后端在运行帧中检测到执行停滞(如互斥锁自旋循环)时调用。

接口文档还特别强调:实现必须是线程安全的,因为帧可能由专用模拟线程派发,而调试服务器在自己的线程上服务客户端。这样一来,调试器可以独立演进而不触碰 CPU 核心——任何想观察执行的组件只需要实现ICpuDebugHook并通过 options 注入即可。

执行模型:帧边界上的停与走

CpuDispatcher会为进程入口点以及每个模块初始化器(module initializer)开启一个全新的帧(frame)。当DebugHook被挂载后,它会在这些边界被通知:

  • OnFrameEnter(frame):在原生后端运行帧之前调用。DebuggerSession在此决定是否停止——判断依据依次是暂停请求、入口地址断点、单步请求、以及 stop-at-entry。若要停止,它会在调用内部把模拟线程停放在一道闸门(gate)上;此时帧保持存活,客户端可以读写寄存器与内存。continue/step会释放这道闸门。
  • OnFrameExit(frame, result):帧完成之后调用。

在 src/SharpEmu.Debugger/Session/DebuggerSession.cs 的OnFrameEnter实现中可以看到这一机制的全貌:ResolveStopReason依次检查_pausePending(返回DebugStopReason.Pause)、_stepPending(返回Step)、Breakpoints.FindExecuteHit(frame.EntryPoint)(返回Breakpoint),最后是_options.StopAtEntry && firstFrame(返回EntryPoint);一旦确定要停止,就把状态置为Paused、抓取寄存器快照构造DebugStopEvent,然后调用_resumeGate.Wait()阻塞模拟线程,直到客户端Continue()/StepFrame()调用_resumeGate.Set()释放。

这里的关键设计是:暂停停放的是唯一拥有 guest 上下文的线程,因此寄存器与内存访问器只有在会话报告Paused时才会被服务;否则一律返回"not paused",客户端永远不会观察到撕裂(torn)状态。DebuggerSession中的所有访问器(TryGetRegistersTrySetRegisterTryReadMemoryTryWriteMemoryTryReadXmm)都先经过IsPausedWithFrame检查,而 src/SharpEmu.Debugger/Server/DebuggerServer.cs 中每个连接共享同一个IDebuggerSession,因此多个客户端(例如一个 UI 加一个脚本探针)观察到的是一致的视图。

目前"活的"与"仅在表面层"的能力

  • Live(已可用):attach/握手、运行状态跟踪、寄存器读写、内存读写、断点管理、帧入口执行断点、暂停、帧级单步、继续,以及停止/恢复/终止事件。
  • 仅表面层(armed,随后端钩子增长而激活):逐指令单步与数据监视点(readwatch/writewatch/accesswatch)。协议动词与类型已经存在,客户端和工具现在就可以先编写好。

会话级开关:DebuggerSessionOptions

从 src/SharpEmu.Debugger/Session/DebuggerSessionOptions.cs 可以看到三个默认全部为true的行为开关:

选项默认值含义
StopAtEntrytrue在观察到的第一帧暂停,方便客户端在 guest 运行前挂断点,等价于多数调试器的"stop at entry"行为。
BreakOnFaulttrue帧以非 OK 结果结束(CPU trap、内存故障或未实现路径)时暂停,让客户端在帧被销毁前检查故障后的寄存器/内存状态,停止原因报告为Fault
BreakOnStalltrue后端检测到执行停滞(互斥锁自旋/livelock)时暂停,停止原因报告为Stall

BreakOnFault的实现在DebuggerSession.OnFrameExit中:当result != OrbisGen2Result.ORBIS_GEN2_OK且状态未终止时,同样停放模拟线程,并通过BuildFaultStop附带 16 字节 opcode 预览(ReadOpcodePreview)作为结构化证据。

启用服务器:CLI 一行启动

在模拟器 CLI 中通过--debug-server参数启用:

SharpEmu --debug-server "/path/to/eboot.bin" # 127.0.0.1:5714 SharpEmu --debug-server=0.0.0.0:5714 "/path/to/eboot.bin"

绑定地址默认是回环地址(loopback);可路由地址必须显式给出。由于 stop-at-entry 是默认行为,guest 会在第一个帧处停住,直到有客户端连接并发起continue——这给了你一个"在任意 guest 代码运行之前设置断点"的窗口。

从 src/SharpEmu.CLI/Program.cs 的实现可以看到命令行到服务器的完整接线:TryGetDebugServerOptions解析参数并复用DebuggerServerOptions.TryParseEndpoint(位于 src/SharpEmu.Debugger/Server/DebuggerServerOptions.cs),它支持host:port、裸port、裸 host 三种写法,端口校验范围是 1–65535,localhost会被当作回环处理;然后创建DebuggerServerHost、调用Start(),最后通过runtimeOptions with { DebugHook = debugHost.Hook }把钩子注入运行时。运行结束后在finally块中依次调用debugHost.NotifyRunCompleted()DisposeAsync()

DebuggerServerOptions还暴露了三个可配置项:BindAddress(默认IPAddress.Loopback)、Port(默认常量5714)、MaxClients(默认4,超出的连接在 accept 队列中等待)。

浏览器前端:零依赖的 Python 调试 UI

仓库还附带一个无外部依赖的 Python 浏览器前端(tools/SharpEmu.DebuggerFrontend/),它只使用 Python 标准库,无需安装任何包、无需 JavaScript 构建步骤。它可以选并启动一个eboot.bin、自动 attach 到其调试器,并提供了执行控制、寄存器、内存检视、断点管理、进程输出和实时协议活动流:

./tools/SharpEmu.DebuggerFrontend/run.sh

默认连接127.0.0.1:5714并打开http://127.0.0.1:8765/。完整配置与测试选项见 tools/SharpEmu.DebuggerFrontend/README.md,常用参数包括:

--debug-host HOST Debug server host (default 127.0.0.1) --debug-port PORT Debug server port (default 5714) --listen ADDRESS Web UI bind address (default 127.0.0.1) --ui-port PORT Web UI port; 0 chooses a free port (default 8765) --no-connect Do not connect to SharpEmu automatically --no-browser Do not open a browser automatically --verbose Print HTTP request logs

前端功能亮点包括:连接受控与目标状态显示、本地模拟器启动 + 自动 attach + 进程停止、前台进程实时输出、continue/pause/frame-step 控制(含键盘快捷键)、寄存器检视与编辑、Hex/ASCII 内存读写、断点与监视点创建/切换/删除、停止原因/帧/结果/opcode/故障详情,以及基于证据的停滞诊断(给出可能原因、排序后的修复建议与针对性检查)。需要提醒的是:HTTP 服务默认绑定回环且无认证,只有在可信网络上才应使用非回环的--listen地址;在 Linux 上"Browse"按钮依赖zenitykdialog,也可以手动输入完整路径。

线协议(json-lines/1):双向 JSON Lines

协议是每行一个 JSON 对象,UTF-8 编码,以\n结尾,双向如此。协议名称为json-lines/1(见 src/SharpEmu.Debugger/Protocol/JsonLineDebugProtocol.cs)。

请求(Requests)

请求由command字符串加命令专属字段组成。数字字段接受 JSON 数字或0x前缀的十六进制字符串。完整动词表如下:

command字段回复data
ping
status(别名infostatebreakpointslastStop?
statestate
registers(别名regsregisters(rax..r15, rip, rflags, fs_base, gs_base)
set-registerregistervalue
read-memoryaddresslength(≤ 65536)addresslengthbytes(十六进制)
write-memoryaddressbytes(十六进制)written
list-breakpoints(别名breakpointsbreakpoints[]
add-breakpoint(别名breakaddresskind?length?breakpoint
remove-breakpoint(别名delete-breakpointid
enable-breakpointidenabled?(默认 true)
continue(别名contc
step(别名s
pause

从 src/SharpEmu.Debugger/Protocol/DebugCommandDispatcher.cs 的Dispatch实现可以看到,命令语义唯一地集中在这个DebugCommandDispatcher中,与线格式无关,因此可以被所有连接共享。几个实现细节值得注意:

  • read-memorylength被钳制在 1 到MaxMemoryChunk = 64 * 1024(65536)之间,超出直接报错;
  • write-memorybytes必须是偶数长度的十六进制字符串,且同样受 65536 字节上限约束;
  • add-breakpointkind缺省为execute,可选readwatch/writewatch/accesswatchlength缺省为 1;
  • set-register支持riprflags与 16 个通用寄存器;fs_base/gs_base由 TLS 设置逻辑拥有,在会话层是只读的(TrySetRegister中对它们的分支返回false);
  • enable-breakpointenabled字段缺省为true,即不带该字段的调用等价于启用。

DebuggerServer的每个连接都由DebuggerClientConnection承载,走_protocolFactory()创建的协议实例,连接关闭时自动从连接字典移除并释放(见 src/SharpEmu.Debugger/Server/DebuggerServer.cs)。

回复(Replies)

成功与失败回复都遵循同一信封结构,ok布尔值加上command,成功后带data,失败后带error

{"ok":true,"command":"registers","data":{ "registers": { "rax":"0x…", … } }} {"ok":false,"command":"read-memory","error":"Target is not paused."}

JsonLineDebugProtocol.WriteResponseAsync会逐字段序列化okcommanddataerror,每行写入后立即 Flush,保证客户端能及时收到(src/SharpEmu.Debugger/Protocol/JsonLineDebugProtocol.cs)。协议对畸形请求的处理也值得一提:解析失败时不会直接断开客户端,而是构造一个command$parse-errorParseErrorCommand常量)的合成请求交给分发器,让它回一个带错误信息的失败回复。

事件(Events,主动推送)

服务器会主动推送以下事件:

{"event":"hello","protocol":"json-lines/1","state":"Paused"} {"event":"stopped","reason":"Breakpoint","address":"0x…","frameKind":"ProcessEntry","frameLabel":"eboot.bin","registers":{…},"breakpoint":{…}} {"event":"resumed"} {"event":"terminated"}

stopped事件的reason取值集合为:EntryPointBreakpointWatchpointStepPauseFaultStall。其中Fault停止还会附带resultopcodeBytes(前 16 字节 opcode 的十六进制预览),Stall停止则附带完整的结构化证据。

Stall 停滞事件的结构化证据

停滞停止除了人类可读的细节之外,还带有结构化证据。例如 import-loop 证据会标识出 NID、解析到的 HLE 导出、重复的 guest 返回点、派发计数以及前两个 ABI 参数:

{ "event": "stopped", "reason": "Stall", "stall": { "kind": "ImportLoop", "nid": "9UK1vLZQft4", "instructionPointer": "0x0000000801CE2418", "dispatchIndex": 40667904, "argument0": "0x0000000812345000", "argument1": "0x0000000000000000", "resolved": true, "library": "libKernel", "function": "scePthreadMutexLock" } }

Python 前端正是利用这段证据来解释可能的失败类别、并对具体的检查/修复进行排序;它的诊断被刻意标注为启发式(heuristic)——用于定位责任 HLE/调度路径,但并不能替代完整跟踪。该事件字段在 src/SharpEmu.Debugger/Protocol/DebugCommandDispatcher.cs 的DescribeStop中被逐字段映射为协议载荷。

使用SharpEmu.DebugClient:从命令行驱动目标

SharpEmu.DebugClient是一个独立、小体积的命令行客户端,它不依赖任何模拟器程序集——直接通过 TCP 讲服务器的 JSON-lines 协议,因此你完全可以用nc、脚本或自研工具替代它。它的构建方式:

dotnet build src/SharpEmu.DebugClient/SharpEmu.DebugClient.csproj

快速上手

  1. 启用调试服务器启动模拟器。默认监听127.0.0.1:5714,在 stop-at-entry 开启时,guest 会在第一个帧处停住直到你继续:

    SharpEmu --debug-server "/path/to/game/eboot.bin" # 或显式指定端点: SharpEmu --debug-server=127.0.0.1:5714 "/path/to/game/eboot.bin"
  2. 另开一个终端,attach 客户端:

    SharpEmu.DebugClient # 默认 127.0.0.1:5714 SharpEmu.DebugClient 127.0.0.1:5714 # 显式端点
  3. 驱动目标:

    status regs break 0x00000008801234a0 continue mem 0x00000008802000000 64

调用方式

SharpEmu.DebugClient [host:port] [--exec "<command>"]... [--quiet]
选项含义
host:port服务器端点。默认127.0.0.1:5714localhost亦可。
--exec, -e非交互式地运行一条命令后退出。可重复使用。
--quiet抑制连接横幅。
--help, -h显示用法与命令列表。

可脚本化的非交互示例:

SharpEmu.DebugClient --exec "break 0x8801234a0" --exec "continue"

命令一览

地址和值接受十进制或0x前缀十六进制。寄存器与内存命令只在目标处于Paused状态时才会成功

命令服务器动词说明
status|infostatus目标状态加最近一次停止。
statestate仅运行状态(Running/Paused/…)。
regs|registersregisters转储整数寄存器。
setreg <reg> <value>set-register设置riprflags或某个通用寄存器。
mem <addr> <len>|read <addr> <len>read-memory以十六进制读取 guest 内存。
write <addr> <hex>write-memory用十六进制字符串写 guest 内存。
break <addr> [kind] [len]|b …add-breakpoint添加断点。kindexecute(默认)、readwatchwritewatchaccesswatch
bp|breakpointslist-breakpoints列出断点。
del <id>|rm <id>remove-breakpoint删除断点。
enable <id>/disable <id>enable-breakpoint切换断点启用状态。
continue|ccontinue恢复暂停的目标。
step|sstep恢复并在下一个帧边界停止。
pausepause请求运行中的目标在下一个边界停止。
pingping往返存活检查。
raw <json>(透传)发送一条字面 JSON 请求。
help|?显示命令列表(本地)。
quit|exit断开并退出(本地)。

输出语义

客户端按到达顺序打印两类行:

  • reply>—— 你发出的命令的响应(ok,以及dataerror);
  • event>—— 主动通知:连接时的hello、命中断点/入口/单步/暂停时的stopped、继续时的resumed、运行结束时的terminated

因为回复和事件共享同一条流,客户端打印的是它收到的一切,而不是把回复与请求配对——stopped事件可能夹在你的一条命令和它的回复之间到达。状态栏明确标注:"infrastructure" 阶段停止在帧边界(进程入口与每个模块初始化器)投递;逐指令单步与数据监视点属于协议表面层,待 CPU 后端长出对应钩子后激活。

替换协议:把 GDB stub 或其他封装插进去

DebuggerServer的构造函数接受一个Func<IDebugProtocol>协议工厂,默认工厂返回JsonLineDebugProtocol(见 src/SharpEmu.Debugger/Server/DebuggerServer.cs)。这意味着一个 GDB remote serial stub(或任何其他成帧方式)都可以直接替换进来,无需改动会话或命令语义——因为命令语义集中在DebugCommandDispatcher,协议层只负责行的读取与写入。

JsonLineDebugProtocol实现了IDebugProtocol的三个成员:Name(协议名json-lines/1)、ReadRequestAsync(逐行读请求,空行跳过、坏行转成$parse-error合成请求)、WriteResponseAsync/WriteEventAsync(把响应/事件序列化并立即 Flush)。

嵌入服务器:在自己的代码里三行接线

如果你不想走 CLI,也可以在宿主代码中直接构造DebuggerServerHost,一条调用链完成"建会话、起网络、注入运行时":

using SharpEmu.Debugger; using SharpEmu.Core.Runtime; await using var host = new DebuggerServerHost(); host.Start(); var options = new SharpEmuRuntimeOptions { DebugHook = host.Hook }; using var runtime = SharpEmuRuntime.CreateDefault(options); var result = runtime.Run(ebootPath); host.NotifyRunCompleted();

从 src/SharpEmu.Debugger/DebuggerServerHost.cs 可以看到这个"一站式"类的内部结构:它同时拥有一个DebuggerSession和一个DebuggerServerHook属性把会话自身(DebuggerSession实现了ICpuDebugHook)暴露给运行时;Start()委托给服务器开始接受客户端;NotifyRunCompleted()调用会话的NotifyTerminated(),释放任何被停放的模拟线程并把目标标记为终止——这样任何已连接的客户端都会收到通知,且 guest 线程绝不会被卡在调试器里(DisposeAsync也会先NotifyTerminated再关闭服务器)。

完整调用链速览

把上面的内容串起来,一次典型的调试会话沿如下路径流动:

  1. SharpEmu.CLI解析--debug-server,通过DebuggerServerOptions.TryParseEndpoint得到绑定配置;
  2. 构造DebuggerServerHost→ 内部创建DebuggerSession(含BreakpointStore)与DebuggerServer(含默认JsonLineDebugProtocol工厂),Start()启动 TCP 监听;
  3. DebuggerServerHost.Hook通过SharpEmuRuntimeOptions.DebugHook注入运行时,最终落到CpuExecutionOptions.DebugHook
  4. CpuDispatcher在每个帧边界回调ICpuDebugHookOnFrameEnter/OnFrameExit/OnStall),DebuggerSession决定是否停放模拟线程;
  5. 客户端(SharpEmu.DebugClient/ Python 前端 / 自研工具)连上 TCP 端口,DebuggerClientConnection用协议对象读写 JSON-lines 消息,DebugCommandDispatcher把动词翻译成会话操作;
  6. 会话在Paused窗口内为客户端服务寄存器/内存访问;continue/step释放_resumeGate,guest 继续执行;运行结束或宿主调用NotifyRunCompleted后,所有客户端收到terminated

如需深入了解客户端实现细节,可阅读 src/SharpEmu.DebugClient/DEVELOPER_READ.md;调试器各组件(断点存储、会话、协议、服务器)的源码均在 src/SharpEmu.Debugger 目录下,其对应测试可参考 tests/SharpEmu.Libs.Tests 中的调试相关用例。

【免费下载链接】sharpemuAn experimental PlayStation 5 emulator for Windows, Linux and macOS.项目地址: https://gitcode.com/GitHub_Trending/sh/sharpemu

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询