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

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

Zircon 允许在作业中使用多个调试异常通道。

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

摘要

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

设计初衷

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

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

不过,目前的实现方式有两个缺点。

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

利益相关方

协调员:cpu@google.com

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

咨询人:johngro@google.com

社交化:此想法已在 Fuchsia 的内核演变工作组中讨论过。

设计

我们提议 Zircon 允许在一个作业上有多个调试作业异常通道。

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

不过,“调试作业”在这里具有独特之处,因为它是一个仅限通知的渠道:它可以接收的唯一异常类型是 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;
};

这种方法存在以下问题:

  • 它仅适用于根作业。调试程序需要针对根作业和非根作业提供两种逻辑。
  • 在子作业上创建的异常通道可以阻止事件传播到根作业。
  • 引入一个包含 syscall 提供的类似函数的 FIDL API 会令人困惑。

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

我们还可以让 debug_agent 支持多个客户端,以解决今天的问题,即 debug_agent 变为单例。

问题如下:

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