例外狀況處理

簡介

當執行緒遇到錯誤狀況,例如分段錯誤時 且執行緒會進入例外狀況處理。包含 註冊收到這些例外狀況的通知,並有機會 檢查或修正條件

這項功能經常被偵錯工具或當機記錄器使用 有機會與執行緒互動,以免發生其他當機情況。 適用於只想追蹤任務生命週期的應用程式, 攔截當機,信號可能較適合您。

基本概念

藉由在 透過 zx_task_create_exception_channel() 執行任務 (執行緒、程序或工作) 系統呼叫。建立的帳號代碼是標準的 Zircon channel,但以唯讀方式建立,因此只能使用 用於接收例外狀況訊息

發生例外狀況時,執行緒就會暫停,而訊息會包含 zx_exception_info_t 和例外狀況帳號代碼會傳送至該管道。 例外狀況的生命週期是這個例外狀況控制代碼的生命週期,所以 接收器處理完畢後,關閉這個例外狀況處理常式就會繼續執行 例外狀況。這個例外狀況帳號代碼不可複製,也就是說,任何 這個例外狀況只有一個處理常式。

根據預設,關閉例外狀況控點會讓執行緒暫停並傳送 下一個處理常式除外如果接收方已修正例外狀況 且希望執行緒改為繼續執行,則可以變更例外狀況 狀態透過 zx_object_set_property() 傳送給 ZX_EXCEPTION_STATE_HANDLED 關閉中。

例外狀況帳號代碼

例外狀況處理的方式類似於暫停權杖,也就是保留執行緒 直至關閉為止此外,例外狀況控點具備 ,以便接收端處理例外狀況:

從例外狀況擷取的工作控點具備與工作相同的權限 原本傳遞至 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

管道類型也會決定例外狀況管道的發生順序 有機會處理例外狀況:

  1. 程序偵錯工具
  2. 執行緒
  3. 程序
  4. 程序偵錯工具 (如果例外狀況為 'second-chance',則可選擇是否使用)
  5. 工作 (父項工作 -> 祖父母工作 -> 等)

如果沒有嘗試剩餘的例外狀況管道,核心會終止 處理過程就好像呼叫 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() syscall 引發。

系統會將工作偵錯工具管道視為「唯讀」,因此多個工作偵錯工具 頻道 (最多 ZX_EXCEPTION_CHANNEL_JOB_DEBUGGER_MAX_COUNT 個) 可在以下位置建立: 一項工作如果某個工作建立了多個偵錯管道, ZX_EXCP_PROCESS_STARTING事件將依序傳送至所有管道, 並通知先前建立的頻道。

使用者定義的例外狀況

zx_thread_raise_exception() 系統呼叫可用於提出使用者定義的 例外狀況。這些例外狀況的類型為 ZX_EXCP_USER,亦即使用者定義 產生在以下兩個欄位的 synth_codesynth_data 欄位中的值 zx_exception_context_t。目前,可以傳送使用者定義的例外狀況 使用於工作偵錯工具的例外狀況管道。

先處理偵錯工具,可能之後再處理

在 Zircon 中,系統會先嘗試執行程序偵錯工具例外狀況管道。這很實用 至少由下列幾個原因造成:

  • 允許「修正並繼續」偵錯,例如如果執行緒發生分段錯誤 偵錯工具使用者可以修正隔離錯誤,並在不使用任何指令的情況下恢復執行緒 非偵錯工具管道看到例外狀況。
  • 確保偵錯工具的中斷點直接傳送至偵錯工具,而沒有 就必須明確傳遞這些物件

如果例外狀況已設定 ZX_EXCEPTION_STRATEGY_second_CHANCE 並維持 嘗試執行程序例外狀況管道後未處理的情況,那麼程序偵錯工具 則有更多機會這項技術的功用為 程序會監聽自身的例外狀況 確保正常運作的資訊在這個例子中 會在修正失敗事件時執行檢查。

與工作停權互動

例外狀況和執行緒停權會分別處理。 換句話說,執行緒可能屬於例外狀況,並會暫停。 如果執行緒在等候回應時遭到停權,就可能會發生這種情況 來自例外狀況處理常式執行緒在恢復之前會保持暫停狀態 例外狀況和停權狀態:

zx_handle_close(exception);
zx_handle_close(suspend_token);

順序沒有任何影響。

與工作終止互動

zx_task_kill() 會停止工作上的任何例外狀況處理。如果應用程式在 2014 年 執行緒 (或其父項程序/工作) 遭到例外狀況時:

  • 執行緒將會停止等候目前的例外狀況處理常式
  • 再也不會收到例外狀況
  • zx_exception_get_thread()zx_exception_get_process(): 未完成的例外狀況帳號代碼會繼續提供有效的工作控制代碼
  • 用來設定例外狀況狀態的 zx_object_set_property(),仍會傳回 ZX_OK,但狀態不會產生任何影響,因為執行緒不再 封鎖處理常式

此外,終止的執行緒仍會傳送 ZX_EXCP_THREAD_EXITING 例外狀況 (註冊了程序偵錯處理常式),但如上所述,則不會 等待處理常式回應。

雖然 zx_task_kill() 通常為非同步性質,但這樣即使執行緒 未在 Syscall 傳回時完成終止,則會同步執行 停止例外狀況處理,在傳回後關閉例外狀況控點 不會繼續執行執行緒,也不會將例外狀況傳遞至其他處理常式。

訊號

信號是觀察狀態變更的核心 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 沒有重大的非同步信號,例如 SIGINTSIGQUITSIGTERMSIGUSR1SIGUSR2 等。

與 Posix 的另一個重大差異 在於 Zircon 中的執行緒無法 因為 Zircon 例外狀況處理是同步的 作業是由使用者空間驅動,而不是由 與核心部分相同

範例

這裡可進一步查看使用例外狀況的 Zircon 程式碼,包括:

  • src/bringup/bin/pwrbtn-monitor/crashsvc:系統層級的當機處理常式
  • system/utest/exception:例外狀況單元測試
  • system/utest/debugger:偵錯工具相關功能單元測試

另請參閱