简介
当线程遇到故障条件(例如分段错误)时,执行 处于暂停状态,并且线程进入异常处理阶段。具有 都会收到通知,并有机会 检查或更正使用情况
此功能常用于调试程序或崩溃记录器,它们需要 有机会在线程崩溃之前与其交互。 适用于只想跟踪任务生命周期,而不需要 拦截崩溃,signals 可能是更好的选择。
基础知识
通过在用户空间上创建异常渠道,
zx_task_create_exception_channel()
执行任务(线程、进程或作业)
系统调用。创建的标识名是标准的 Zircon
channel,但以只读方式创建,因此只能使用
接收异常消息。
发生异常时,该线程会暂停,并显示包含
zx_exception_info_t
,并向相应渠道发送异常句柄。通过
异常的生命周期会绑定到此异常句柄的生命周期,因此
当接收器处理完毕后,系统会继续关闭此异常句柄
例外情况。此异常句柄是不可复制的,也就是说,在任何
则此异常只有一个处理程序。
默认情况下,关闭异常句柄会使线程保持暂停状态,并发送
将异常传递给下一个处理程序。如果接收方已更正例外情况
并希望线程继续执行,则可以更改异常
在以下时间之前,通过zx_object_set_property()
的状态更改为ZX_EXCEPTION_STATE_HANDLED
正在关闭。
异常句柄
异常句柄的行为类似于挂起令牌,具体方法是保留线程, 处于暂停状态,直到他们关闭为止。此外,异常句柄还包含一些函数, 以帮助接收方处理异常:
- 使用
ZX_PROP_EXCEPTION_STATE
设置行为的zx_object_set_property()
手柄关闭时 zx_exception_get_thread()
,用于获取异常线程的句柄zx_exception_get_process()
,用于获取异常流程的句柄 (仅限流程或作业例外渠道)
从异常中检索到的任务句柄与任务具有相同的权限
最初传递到 zx_task_create_exception_channel()
中。
示例
这个简单的示例创建了一个异常渠道和读取异常的循环 直到任务关闭。
void ExceptionHandlerLoop(zx_handle_t task) {
// Create the exception channel.
uint32_t options = 0;
zx_handle_t channel;
zx_status_t status = zx_task_create_exception_channel(task, options,
&channel);
// ... check status ...
while (true) {
// Wait until we get ZX_CHANNEL_READABLE (exception) or
// ZX_CHANNEL_PEER_CLOSED (task terminated).
zx_signals_t signals = 0;
status = zx_object_wait_one(channel,
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
ZX_TIME_INFINITE, &signals);
// ... check status ...
if (signals & ZX_CHANNEL_READABLE) {
// Read the exception info and handle from the channel.
zx_exception_info_t info;
zx_handle_t exception;
status = zx_channel_read(channel, 0, &info, &exception, sizeof(info), 1,
nullptr, nullptr);
// ... check status ...
// Send the exception out to some other function for processing, which
// returns true if the exception has been handled and we can resume the
// thread, or false to pass the exception to the next handler.
bool handled = process_exception(info, exception);
if (handled) {
uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
status = zx_object_set_property(exception, ZX_PROP_EXCEPTION_STATE,
&state, sizeof(state));
// ... check status ...
}
// Close the exception to finish handling.
zx_handle_close(exception);
} else {
// We got ZX_CHANNEL_PEER_CLOSED, the task has terminated.
zx_handle_close(channel);
return;
}
}
}
异常类型
概括来讲,存在两种类型的例外情况:架构和合成。 架构异常包括分段错误(例如,解引用 NULL 指针)或执行未定义的指令。合成异常包括 消息串开始/停止通知等 违规行为。
架构和政策例外情况被视为致命,会导致 它们就会被终止仅限调试程序的异常 - 线程 启动/停止和流程启动 - 仅供参考,并会继续执行 正常情况下,即使线程未显式恢复也是如此。例外情况包括 旨在让调试程序有机会正确响应这些生命周期事件, 因为相应的线程将会暂停,直到异常恢复为止。
异常类型是在
<zircon/syscalls/exception.h>
。
例外情况渠道类型
异常渠道具有不同的特征,具体取决于任务类型和
ZX_EXCEPTION_CHANNEL_DEBUGGER
标志是否会传递给
zx_task_create_exception_channel()
。下表总结了
各种渠道类型之间的区别:
频道类型 | get_thread |
get_process |
建筑与政策例外情况 | 线程启动/停止异常 | 进程启动异常 |
---|---|---|---|---|---|
线程 | X | X | |||
流程 | X | X | X | ||
进程调试程序 | X | X | X | X | |
作业 | X | X | X | ||
作业调试程序 | X | X | X |
渠道类型还决定了例外渠道的显示顺序 有机会处理异常:
- 进程调试程序
- 会话串
- 进程
- 进程调试程序(可选,如果异常为
'second-chance'
) - 作业(父级作业 -> 祖父级作业 -> 等)
如果没有剩余的异常通道可供尝试,内核会终止
该过程就像调用了 zx_task_kill()
一样。进程的返回代码
由 ZX_TASK_RETCODE_EXCEPTION_KILL
异常终止,且可
使用 ZX_INFO_PROCESS
通过 zx_object_get_info()
获取。
每个任务仅支持每种类型有一个异常渠道,例如 给定一个进程并附加了调试异常通道,试图创建 第二个调试异常渠道将失败,但会创建一个非调试渠道 会成功。
ZX_EXCP_PROCESS_STARTING
和作业调试程序渠道
ZX_EXCP_PROCESS_STARTING
的行为与其他异常不同。时间是
仅发送到作业调试程序异常渠道,并始终发送到所有找到的
处理程序,基本上假定为 ZX_EXCEPTION_STATE_TRY_NEXT
,而不考虑
实际处理程序行为这也是唯一一个由内核定义的异常,
作业调试程序通道接收,使它们成为检测
新进程作业调试程序也可能会收到 ZX_EXCP_USER
异常,这
可以使用 zx_thread_raise_exception()
系统调用引发。
由于作业调试程序通道被视为“只读”,因此多个作业调试程序
可在以下位置创建频道(最多 ZX_EXCEPTION_CHANNEL_JOB_DEBUGGER_MAX_COUNT
个)
单个作业。在一个作业中创建多个调试渠道时,
ZX_EXCP_PROCESS_STARTING
事件将依序发送到所有渠道,其中
较晚创建的渠道先于后期创建的渠道收到通知。
用户定义的异常
zx_thread_raise_exception()
系统调用可用于引发用户定义的
异常。这些异常的类型为 ZX_EXCP_USER
,
值在 synth_code
和 synth_data
字段中提供,
zx_exception_context_t
。目前,可以传递用户定义的异常。
仅限作业调试程序异常渠道。
先使用进程调试程序,稍后再试
在 Zircon 中,首先尝试进程调试程序异常通道。有用 至少出于以下几个原因:
- 允许“修正并继续”调试,例如如果某个线程出现分段错误, 调试程序用户可以修复分段错误并恢复线程,而无需执行任何 非调试程序频道。
- 确保调试程序断点直接发送到调试程序,而无需 其他处理程序必须显式传递它们。
如果异常已设置 ZX_EXCEPTION_STRATEGY_SECOND_CHANCE 并仍然存在 尝试进程异常通道后未处理,进程调试程序 将有第二次机会。它的有用之处在于 进程会监听自己的异常并使用该异常 以确保正常运行;在本示例中,它可以使用调试程序 并在更正失败时进行检查
与任务暂停的互动
异常和线程挂起是分开处理的。 换言之,线程可以同时处于异常中,也可以被挂起。 如果线程在等待响应时挂起,就会发生这种情况 从异常处理程序中获取。线程会保持暂停状态,直到恢复为止 :
zx_handle_close(exception);
zx_handle_close(suspend_token);
顺序无关紧要。
与终止任务的交互
zx_task_kill()
会停止对任务的任何异常处理。如果在调用
线程(或其父进程/作业)处于异常状态时:
- 线程将停止等待当前的异常处理程序
- 其他异常处理程序不会再收到该异常
zx_exception_get_thread()
和zx_exception_get_process()
(位于 未完成的异常句柄将继续提供有效的任务句柄- 用于设置异常状态的
zx_object_set_property()
仍会返回ZX_OK
,不过状态不会产生任何影响,因为线程不再 在处理程序上执行阻塞操作
此外,已终止的线程仍会发送 ZX_EXCP_THREAD_EXITING
异常(如果已注册进程调试处理程序),但如上所述
等待处理程序的响应。
虽然 zx_task_kill()
通常是异步的,这意味着线程可以
在系统调用返回时还没有完成终止,它会同步执行
停止异常处理,以便在返回后关闭异常句柄
不会恢复线程或将异常传递给另一个处理程序。
信号
信号是观察状态变化的核心 Zircon 机制 内核对象(通道变为可读,进程终止,事件 变为有信号状态,等等)。
与异常不同,信号不需要异常处理程序的响应。 另一方面,信号将发送给正在等待线程 处理,而不是发送到可能会绑定到的异常渠道 线程的进程
Zircon 中的一种常见模式是让消息循环等待
一个或多个对象,并在它们传入时对其进行处理。纳入例外情况
处理转换为此模式,使用 zx_object_wait_async()
等待
ZX_CHANNEL_READABLE
(以及可选的 ZX_CHANNEL_PEER_CLOSED
)
例外渠道:
zx_handle_t port;
zx_status_t status = zx_port_create(0, &port);
// ... check status ...
// Start waiting on relevant signals on the exception channel.
status = zx_object_wait_async(exception_channel, port, kMyExceptionKey,
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0);
// ... check status ...
// ... add other objects to |port| with wait_async() ...
while (1) {
zx_port_packet_t packet;
status = zx_port_wait(port, ZX_TIME_INFINITE, &packet);
// ... check status ...
if (packet.key == kMyExceptionKey) {
if (packet.signal.observed & ZX_CHANNEL_READABLE) {
// ... extract exception from |exception_channel| and process it ...
// wait_async() is one-shot so we need to reload it to continue
// receiving signals.
status = zx_object_wait_async(
exception_channel, port, kMyExceptionKey,
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0);
// ... check status ...
} else {
// Got ZX_CHANNEL_PEER_CLOSED, task has terminated.
zx_handle_close(exception_channel);
}
} else {
// ... handle other objects added to |port| ...
}
}
与 Posix(和 Linux)对比
下表显示了 针对异常和异常情况的 Zircon 和 Posix/Linux 处理程序通常都是如此。
Zircon | Posix/Linux |
---|---|
异常/信号 | 信号 |
ZXEXCP* | SIG* |
zx_task_create_exception_channel() | ptrace(ATTACH,DETACH) |
zx_task_suspend() | kill(SIGSTOP),ptrace(KILL(SIGSTOP)) |
zx_handle_close(suspend_token) | kill(SIGCONT)、ptrace(CONT) |
zx_handle_close(exception) | kill(SIGCONT)、ptrace(CONT) |
zx_task_kill() | kill(SIGKILL) |
zx_thread_raise_exception() | 终止(SIGUSR1) |
不适用 | kill(everything_else) |
待定 | signal()/sigaction() |
zx_port_wait() | wait*() |
各种 | 来自 sys/wait.h 的 W*() 宏 |
zx_exception_info_t | siginfo_t |
zx_exception_context_t | siginfo_t |
zx_thread_read_state() | ptrace(GETREGS,GETREGSET) |
zx_thread_write_state() | ptrace(SETREGS,SETREGSET) |
zx_process_read_memory() | ptrace(PEEKTEXT) |
zx_process_write_memory() | ptrace(POKETEXT) |
Zircon 没有 SIGINT
、SIGQUIT
、SIGTERM
等非严重异步信号
SIGUSR1
、SIGUSR2
等。
与 Posix 的另一个显著区别是,在 Zircon 中,线程不能 因为 Zircon 异常处理是同步处理, 操作,而不是由 由内核执行
示例
如需查看使用异常的 Zircon 代码,您可以查看更多示例,包括:
src/bringup/bin/pwrbtn-monitor/crashsvc
:系统级崩溃处理程序system/utest/exception
:异常单元测试system/utest/debugger
:与调试程序相关的功能单元测试
另请参阅
zx_task_create_exception_channel()
zx_exception_get_thread()
zx_exception_get_process()
zx_object_set_property()
zx_port_wait()