RFC-0178:每个作业有多个调试异常通道

RFC-0178:每个作业多个调试异常渠道
状态已接受
区域
  • 内核
说明

Zircon 允许作业具有多个调试异常渠道。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-06-08
审核日期(年-月-日)2022-07-12

摘要

此 RFC 建议更改 zx_task_create_exception_channel,以允许在一个作业中创建最多 32 个调试异常渠道。

设计初衷

Fuchsia 上的调试器依赖于在作业上创建调试异常渠道来监控进程启动,以便捕获感兴趣的进程并主动附加到该进程。调试异常渠道通常在系统的根作业上创建,以便调试器可以监控所有进程。

当进程启动时,内核将遍历作业层次结构,寻找要通知的调试作业异常渠道。此遍历从新进程的包含作业开始,并向上遍历层次结构,直到找到调试作业异常渠道或到达根作业。

不过,目前的实现存在两个缺点。

  • 对于给定的作业,最多只能有一个调试异常渠道,这意味着最多只能有一个调试器监控作业层次结构的任何给定子树。
  • 系统只会通知第一个找到的调试异常渠道,也就是说,子作业上的调试器会阻止父作业上的调试器观察进程启动。

利益相关方

Facilitator: cpu@google.com

审核者:brettw@google.com、maniscalco@google.com

咨询对象:johngro@google.com

社会化:该想法已在 Fuchsia 的内核演进工作组中讨论过。

设计

我们建议 Zircon 允许一个作业上有多个调试作业异常渠道。

Zircon 中有 5 种类型的异常渠道:调试作业、作业、调试进程、进程和线程。每个属性最多只能在相应对象上创建一次。设置此限制是为了避免出现棘手的情况,例如,当多个异常处理程序为 ZX_PROP_EXCEPTION_STATE 设置不同的值时。

不过,“debug job”在这里很独特,因为它是一个仅限通知的渠道:它唯一可以接收的异常类型是 ZX_EXCP_PROCESS_STARTING,其中 ZX_PROP_EXCEPTION_STATE 会被忽略。因此,您可以在一个作业上允许使用多个调试异常渠道,而无需担心出现不一致的情况。

如果在一个作业上创建了多个调试作业异常渠道,则 ZX_EXCP_PROCESS_STARTING 事件将按顺序发送到所有渠道,从而允许多个监听器检查进程。只能有一个调试器附加到给定进程的限制不会改变,因为“调试进程”异常通道仍然由第一个调试器独占,除第一个调试器之外的所有调试器都会收到 ZX_ERR_ALREADY_BOUND

此外,我们还建议修改 ZX_EXCP_PROCESS_STARTING 事件的传播,以便即使在子作业上创建了异常渠道,一个事件也能一直传播到根。作业层次结构中较低级别的调试作业异常渠道将比更高级别的渠道更早收到通知。在任何给定级别,较早创建的渠道会比稍后创建的渠道更早收到通知。只有在所有异常渠道都收到通知且异常对象都已关闭后,进程才能继续启动。

请注意,任何异常消息接收器都有可能通过不关闭异常对象的句柄来无限期地阻止进程启动。不过,它们无法完全阻止进程启动。

实现

系统调用不会有任何 API 或 ABI 变化。zx_task_create_exception_channel 的现有行为将发生变化,允许创建最多 N 个渠道,而不是在创建第一个渠道后返回 ZX_ERR_ALREADY_BOUND

性能

性能可能会下降,因为更多监听器可能会阻止进程启动。不过,调试异常渠道不应在生产环境中使用,也不应由长时间运行的程序持有。它仅适用于调试器或类似的调试工具,因此影响应极小。

一般来说,调试器应在执行必要的设置后立即关闭异常句柄。

安全注意事项

为避免针对内核的 DoS 攻击,我们应限制在单个作业上创建的调试作业异常渠道的最大数量。该限制应足够大,以允许运行任意合理数量的调试器,例如 32。

测试

我们将向 //zircon/system/utest/debugger 添加新的测试用例,以涵盖此功能。

文档

描述异常处理zx_task_create_exception_channel 的文档将更新以反映此变更。

缺点、替代方案和未知因素

替代方案:进程的用户空间委托在根作业上启动

此方法可避免对内核进行更改。相反,用户空间委托将持有根作业调试通道,并提供一个 FIDL 接口供调试器订阅进程启动。委托可以是组件管理器本身(目前提供 fuchsia.kernel.RootJob),也可以是独立程序。

协议将如下所示

@discoverable
protocol RootJob {
    /// Hanging get pattern
    GetProcessStartingEvent() -> (resource struct {
        process zx.handle:PROCESS;
        continue zx.handle:EVENTPAIR;  // dropping this eventpair will propagate
                                       // the event to the next listener.
    }) error zx.status;
};

此方法的缺点是

  • 它仅适用于根作业。调试器需要针对根作业和非根作业提供两种逻辑。
  • 在子作业上创建的异常渠道可以阻止事件传播到根。
  • 引入具有与系统调用类似提供的功能的 FIDL API 会造成混淆。

替代方案:debug_agent 支持多个客户端

我们还可以使 debug_agent 支持多个客户端,以解决当前的问题,即 debug_agent 成为单例。

问题包括

  • 需要完成更多工作。
  • debug_agent 将成为 Fuchsia 的指定调试器。其他调试器必须与 debug_agent 通信,而 debug_agent 会公开一个庞大且不稳定的接口。