# 機能設計書 83-クラッシュハンドラ

## 概要

本ドキュメントは、serve-dの予期しないクラッシュ時にバックトレース情報を収集・出力する機能について、その処理内容・データ構造・動作条件を記述した機能設計書である。

### 本機能の処理概要

本機能は、serve-dプロセスがSIGSEGV（セグメンテーション違反）やSIGABRT（異常終了）等の致命的シグナルを受信した際に、バックトレース（スタックトレース）情報を標準エラー出力に書き出してから終了する仕組みを提供する。これにより、クラッシュ原因の診断に必要な情報を確保する。本機能はLinuxおよびmacOS環境でのみ有効であり、muslベースのLinuxやWindowsでは無効化される。

**業務上の目的・背景**：serve-dはLSPサーバーとしてバックグラウンドで動作するため、予期しないクラッシュが発生した場合に原因特定が困難になりやすい。特にセグメンテーション違反やメモリ不正アクセス等のネイティブレベルのエラーは、通常のD言語例外処理では捕捉できない。本機能により、クラッシュ時にスタックトレースを自動収集することで、開発者がバグ報告・デバッグを効率的に行えるようにする。

**機能の利用シーン**：
- serve-dがSIGSEGV（セグメンテーション違反）を受信した場合
- serve-dがSIGABRT（abort呼び出し）を受信した場合
- serve-dがSIGILL（不正命令実行）を受信した場合
- serve-dがSIGTRAP（デバッガトラップ）を受信した場合
- その他の致命的シグナル（SIGALRM, SIGINT, SIGKILL, SIGPIPE）を受信した場合

**主要な処理内容**：
1. プラットフォーム判定: コンパイル時にBacktraceHandler定数で機能の有効/無効を決定する（Linux非musl版とmacOSで有効）
2. モジュール初期化時（`shared static this`）に`registerErrorHandlers`関数を呼び出し、致命的シグナルにバックトレースハンドラを登録する
3. シグナル受信時にバックトレースハンドラ（`backtrace_handler`）が呼び出され、Cランタイムの`backtrace`関数でスタックフレームを取得する
4. `backtrace_symbols_fd`でスタックフレームのシンボル情報を標準エラー出力（fd=2）に書き出す
5. シグナル番号の負値をexit codeとしてプロセスを終了する

**関連システム・外部連携**：
- POSIX Cランタイムライブラリ（`backtrace`, `backtrace_symbols_fd`関数）
- POSIXシグナル処理機構（`signal`関数）
- 標準エラー出力（stderr, fd=2）

**権限による制御**：特になし。シグナルハンドラはプロセスレベルで登録され、全てのスレッド・ファイバーに適用される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | コンポーネント読み込み失敗通知 | 参照画面 | コンポーネント読み込み失敗はバックエンドのonBindFailハンドラで処理されるが、クラッシュレベルのエラーは本機能で捕捉される |

## 機能種別

エラーハンドリング / クラッシュダンプ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| sig | int | Yes | 受信したPOSIXシグナル番号（backtrace_handlerのパラメータ） | SIGABRT, SIGALRM, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGSEGV, SIGTRAPのいずれか |

### 入力データソース

- OSカーネルからのPOSIXシグナル配信
- Cランタイムの`backtrace`関数によるスタックフレーム情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| シグナル番号 | int | `"Error: signal %d:\n"` 形式でstderrに出力 |
| バックトレース情報 | void*[] -> シンボル文字列 | スタックフレームのアドレスからシンボル名に変換されたバックトレース（最大100フレーム） |

### 出力先

- 標準エラー出力（stderr, ファイルディスクリプタ2）
- プロセスの終了コード（`-sig`、シグナル番号の負値）

## 処理フロー

### 処理シーケンス

```
1. コンパイル時プラットフォーム判定
   └─ BacktraceHandler定数の評価
   └─ CRuntime_Musl: false
   └─ linux: true
   └─ OSX: true
   └─ その他: false
2. モジュール初期化（shared static this）
   └─ BacktraceHandler==trueの場合のみ実行
   └─ registerErrorHandlers()を呼び出し
3. シグナルハンドラ登録（registerErrorHandlers）
   └─ SIGABRT, SIGALRM, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGSEGV, SIGTRAPの8シグナルに対して
   └─ backtrace_handlerを登録
4. シグナル受信時（backtrace_handler）
   └─ backtrace()でスタックフレーム取得（最大100フレーム）
   └─ fprintf(stderr, "Error: signal %d:\n", sig) でシグナル番号出力
   └─ backtrace_symbols_fd()でシンボル情報をstderrに出力
   └─ fflush(stderr) でバッファをフラッシュ
   └─ exit(-sig) でプロセス終了
```

### フローチャート

```mermaid
flowchart TD
    A[コンパイル時] --> B{BacktraceHandler?}
    B -->|true: Linux非musl / macOS| C[shared static this 実行]
    B -->|false: musl / Windows等| D[機能無効 - 何もしない]
    C --> E[registerErrorHandlers呼び出し]
    E --> F["signal(SIGABRT, &backtrace_handler)<br>signal(SIGALRM, &backtrace_handler)<br>signal(SIGILL, &backtrace_handler)<br>signal(SIGINT, &backtrace_handler)<br>signal(SIGKILL, &backtrace_handler)<br>signal(SIGPIPE, &backtrace_handler)<br>signal(SIGSEGV, &backtrace_handler)<br>signal(SIGTRAP, &backtrace_handler)"]
    F --> G[通常動作中...]
    G --> H{致命的シグナル受信}
    H --> I[backtrace_handler実行]
    I --> J["backtrace(buffer, 100)<br>スタックフレーム取得"]
    J --> K["fprintf(stderr, 'Error: signal %d', sig)"]
    K --> L["backtrace_symbols_fd(buffer, size, 2)<br>シンボル情報出力"]
    L --> M["fflush(stderr)"]
    M --> N["exit(-sig)<br>プロセス終了"]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-83-01 | プラットフォーム制限 | バックトレースハンドラはLinux（非musl）およびmacOSでのみ有効。それ以外のプラットフォームではコンパイルされない | コンパイル時 |
| BR-83-02 | 自動登録 | シグナルハンドラはモジュール初期化時（shared static this）に自動登録される。明示的な呼び出しは不要 | モジュールロード時 |
| BR-83-03 | @nogc @nothrow制約 | シグナルハンドラはGCアロケーション禁止・例外スロー禁止の制約下で動作する | シグナルハンドラ実行時 |
| BR-83-04 | 最大フレーム数 | バックトレースのスタックフレーム数は最大100に制限される | バックトレース取得時 |
| BR-83-05 | 終了コード | プロセスはシグナル番号の負値（`-sig`）で終了する | シグナルハンドラ完了時 |

### 計算ロジック

特になし。

## データベース操作仕様

### 操作別データベース影響一覧

該当なし。本機能はデータベースを使用しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| -SIGSEGV (-11) | セグメンテーション違反 | 不正なメモリアクセス | バックトレースをstderrに出力して終了 |
| -SIGABRT (-6) | 異常終了 | abort()呼び出しまたはアサーション失敗 | バックトレースをstderrに出力して終了 |
| -SIGILL (-4) | 不正命令 | CPUが実行不可能な命令を検出 | バックトレースをstderrに出力して終了 |
| -SIGTRAP (-5) | デバッガトラップ | ブレークポイントまたはトラップ命令 | バックトレースをstderrに出力して終了 |
| -SIGPIPE (-13) | パイプ破断 | 書き込み先のパイプが切断 | バックトレースをstderrに出力して終了 |
| -SIGALRM (-14) | アラーム | タイマーシグナル | バックトレースをstderrに出力して終了 |
| -SIGINT (-2) | 割り込み | Ctrl+C等のユーザー割り込み | バックトレースをstderrに出力して終了 |

### リトライ仕様

該当なし。致命的シグナル受信後はプロセスを終了するため、リトライは行わない。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- シグナルハンドラは`@nogc`（GCアロケーション禁止）および`@nothrow`（例外スロー禁止）として宣言されており、シグナルハンドラコンテキストで安全に実行可能
- `backtrace`および`backtrace_symbols_fd`はCランタイムのシステムコールであり、シグナルハンドラ内でのasync-signal-safe性については注意が必要（POSIXの厳密な保証外だが、実用上問題は少ない）
- シグナルハンドラ登録（`registerErrorHandlers`）はモジュール初期化時に1回のみ実行される

## セキュリティ考慮事項

- バックトレース情報にはメモリアドレスと関数シンボル名が含まれるため、プロセスの内部構造に関する情報が標準エラー出力に書き出される。ただし、serve-dはローカルで動作するLSPサーバーであり、stderrは通常エディタのログとして記録されるため、情報漏洩リスクは低い
- シグナルハンドラ内でexit()を呼び出すため、デッドロック等の異常状態でもプロセスが確実に終了する

## 備考

- `BacktraceHandler`はコンパイル時定数であり、`version`ブロックにより以下のように判定される:
  - `CRuntime_Musl`（Alpine Linux等のmusl libc環境）: `false` - muslではbacktrace APIが提供されないため無効
  - `linux`: `true` - glibc環境のLinuxで有効
  - `OSX`: `true` - macOSで有効
  - その他（Windows等）: `false` - 対応するAPIがないため無効
- `backtrace`と`backtrace_symbols_fd`はCの外部関数として`extern(C)`で宣言されており、D言語ランタイムを経由せずに直接Cランタイムを呼び出す
- SIGKILLはOSレベルでは捕捉不可能なシグナルだが、Dの`signal`関数はPOSIXの`signal(2)`をラップしており、登録自体はエラーにならないが実際にはハンドラは呼ばれない
- ユニットテストビルド時のみ、`registerErrorHandlers`内で "Registered backtrace signal handlers" メッセージがstderrに出力される（42-44行目）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: プラットフォーム判定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | crash_handler.d | `source/crash_handler.d` | `BacktraceHandler`コンパイル時定数の定義（3-10行目）。versionブロックによるプラットフォーム分岐を理解する |

**読解のコツ**: D言語の`version`ブロックはC/C++の`#ifdef`に相当するコンパイル時条件分岐である。`enum BacktraceHandler = true/false;`はコンパイル時定数として定義され、後続の`static if (BacktraceHandler)`で機能全体のコンパイル有無が制御される。`CRuntime_Musl`が最初にチェックされるのは、muslベースのLinuxでは`linux`版も成立するため、先にmuslを除外する必要があるからである。

#### Step 2: Cランタイム外部関数宣言を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | crash_handler.d | `source/crash_handler.d` | `backtrace`と`backtrace_symbols_fd`の外部関数宣言（14-15行目） |

**主要処理フロー**:
- **14行目**: `extern (C) int backtrace(void** buffer, int size)` - 現在のスタックフレームのアドレスをbufferに格納し、取得したフレーム数を返却する
- **15行目**: `extern (C) void backtrace_symbols_fd(const(void*)* buffer, int size, int fd)` - スタックフレームアドレスをシンボル名に変換し、指定ファイルディスクリプタに書き出す

**読解のコツ**: `extern (C)`はD言語からCランタイムの関数を呼び出すための外部関数宣言である。`nothrow @nogc @system`の属性はシグナルハンドラコンテキストでの安全な呼び出しを保証する。

#### Step 3: シグナルハンドラの処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | crash_handler.d | `source/crash_handler.d` | `backtrace_handler`関数（17-29行目）。シグナル受信時の処理フロー |

**主要処理フロー**:
- **22行目**: `void*[100] buffer;` - スタックフレームアドレス用の固定長バッファ（スタック上に確保、GCアロケーションなし）
- **23行目**: `auto size = backtrace(buffer.ptr, cast(int) buffer.length);` - 現在のスタックフレームを取得
- **25行目**: `fprintf(stderr, "Error: signal %d:\n", sig);` - 受信シグナル番号をstderrに出力
- **26行目**: `backtrace_symbols_fd(buffer.ptr, size, 2);` - fd=2（stderr）にバックトレースのシンボル情報を出力
- **27行目**: `fflush(stderr);` - stderrバッファをフラッシュして出力を確定
- **28行目**: `exit(-sig);` - シグナル番号の負値を終了コードとしてプロセス終了

#### Step 4: シグナルハンドラ登録を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | crash_handler.d | `source/crash_handler.d` | `registerErrorHandlers`関数（31-45行目）とモジュール初期化（47-50行目） |

**主要処理フロー**:
- **37-40行目**: `static foreach`で8つのシグナル（SIGABRT, SIGALRM, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGSEGV, SIGTRAP）に対して`backtrace_handler`を登録
- **42-44行目**: `version(unittest)`ブロック内でのみ登録完了メッセージを出力
- **47-50行目**: `shared static this()`でモジュール初期化時に`registerErrorHandlers()`を自動呼び出し

**読解のコツ**: `static foreach`はD言語のコンパイル時ループで、各シグナルに対して個別の`signal`呼び出しコードをコンパイル時に展開する。`shared static this()`はモジュールがロードされた時点で自動的に実行される初期化関数であり、明示的な呼び出しは不要。

### プログラム呼び出し階層図

```
[モジュールロード時]
    |
    +-- shared static this()                 [crash_handler.d 47-50行目]
            |
            +-- registerErrorHandlers()      [crash_handler.d 31-45行目]
                    |
                    +-- signal(SIGABRT, &backtrace_handler)
                    +-- signal(SIGALRM, &backtrace_handler)
                    +-- signal(SIGILL, &backtrace_handler)
                    +-- signal(SIGINT, &backtrace_handler)
                    +-- signal(SIGKILL, &backtrace_handler)
                    +-- signal(SIGPIPE, &backtrace_handler)
                    +-- signal(SIGSEGV, &backtrace_handler)
                    +-- signal(SIGTRAP, &backtrace_handler)

[シグナル受信時]
    |
    +-- backtrace_handler(sig)               [crash_handler.d 17-29行目]
            |
            +-- backtrace(buffer, 100)       [Cランタイム: スタックフレーム取得]
            +-- fprintf(stderr, ...)         [Cランタイム: シグナル番号出力]
            +-- backtrace_symbols_fd(...)    [Cランタイム: シンボル情報出力]
            +-- fflush(stderr)               [Cランタイム: バッファフラッシュ]
            +-- exit(-sig)                   [Cランタイム: プロセス終了]
```

### データフロー図

```
[入力]                          [処理]                            [出力]

POSIXシグナル ──────────▶ backtrace_handler(sig)
(SIGSEGV, SIGABRT等)           │
                               ├─▶ backtrace()
                               │    スタックフレーム取得 ──▶ void*[100] buffer
                               │
                               ├─▶ fprintf(stderr) ──────▶ "Error: signal N:\n"
                               │                              (stderr出力)
                               │
                               └─▶ backtrace_symbols_fd() ▶ シンボル情報
                                                              (stderr出力)
                                        │
                                        v
                                   exit(-sig) ──────────▶ 終了コード
                                                          (OSに返却)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| crash_handler.d | `source/crash_handler.d` | ソース | クラッシュハンドラ本体（プラットフォーム判定・シグナルハンドラ登録・バックトレース取得・出力、全51行） |
| app.d | `source/app.d` | ソース | serve-dのエントリーポイント。crash_handler.dはモジュールとして自動ロードされるため直接参照はないが、同一バイナリに含まれる |
| extension.d | `source/served/extension.d` | ソース | onBindFailハンドラ（1358行目）でコンポーネント読み込み失敗を処理。本機能とは直接の呼び出し関係はないが、エラーハンドリングの別レイヤーとして関連 |
