# 帳票設計書 5-バックトレース出力

## 概要

例外発生時のコールスタック情報をstderrにトレース形式で出力する帳票設計書である。ファイル名・行番号・メソッド名を含むバックトレース情報を、最も新しい呼び出しから順に表示する。

### 本帳票の処理概要

本帳票は、mrubyで例外が発生した際に、例外に紐づくコールスタック情報を人間が読める形式で標準エラー出力(stderr)に出力する機能である。各スタックフレームのファイル名、行番号、メソッド名を表示し、例外メッセージと共にトレース情報を提供する。

**業務上の目的・背景**：mrubyアプリケーション実行時にエラーが発生した場合、問題の原因箇所を迅速に特定するために必要な情報を提供する。Rubyプログラマにとって馴染みのあるバックトレース形式で出力することで、デバッグ効率を向上させる。

**帳票の利用シーン**：mrubyスクリプト実行時に未捕捉の例外が発生した場合に自動的に出力される。mrubyコマンドでスクリプトを実行した際のランタイムエラー表示で使用される。

**主要な出力内容**：
1. トレースヘッダー（"trace (most recent call last):"）
2. 各スタックフレームの位置情報（`[番号] ファイル名:行番号:in メソッド名`）
3. 最上位フレームの位置情報（`ファイル名:行番号: `）
4. 例外メッセージ（例外クラス名と詳細メッセージ）

**帳票の出力タイミング**：未捕捉の例外が発生し、mrb_print_backtrace関数が呼び出された際に出力される。

**帳票の利用者**：mrubyアプリケーション開発者、運用監視担当者。

## 帳票種別

テキスト出力（stderrへのバックトレースダンプ）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | CLIコマンド (mruby) | `mruby script.rb` | 例外発生時に自動出力 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（stderr） |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | N/A（標準エラー出力） |
| 出力方法 | fputs / fprintf / fwrite（stderr） |
| 文字コード | UTF-8 / ASCII |

### PDF固有設定

N/A

### Excel固有設定

N/A

## 帳票レイアウト

### レイアウト概要

バックトレースは「最も新しい呼び出しが最後」の逆順で出力される。最上位フレーム（インデックス0）は例外メッセージの前に配置される。

```
trace (most recent call last):
	[N] file.rb:10:in method_c
	[N-1] file.rb:5:in method_b
	[1] file.rb:1:in method_a
file.rb:15: エラーメッセージ (ExceptionClass)
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | トレースヘッダー | バックトレースの開始行 | 固定文字列 | `trace (most recent call last):\n` |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | インデックス | スタックフレーム番号 | ループカウンタ(n-1→1) | `\t[%d] ` | 可変 |
| 2 | ファイル名 | ソースファイル名 | `mrb_debug_get_position()` → filename | テキスト | 可変 |
| 3 | 行番号 | ソースコードの行番号 | `mrb_debug_get_position()` → lineno | `:%d` | 可変 |
| 4 | メソッド名 | 呼び出し元メソッド名 | `entry->method_id` → `mrb_sym_name()` | `:in メソッド名` | 可変 |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 最上位フレーム | インデックス0のフレーム位置 | decode_location() | `ファイル名:行番号: ` |
| 2 | 例外メッセージ | 例外の詳細情報 | `mrb_exc_get_output()` | テキスト |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| 例外発生 | mrb->excがNULLでない | Yes |
| 例外型チェック | mrb->exc->ttがMRB_TT_EXCEPTION | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | スタックフレームインデックス | 降順（最新の呼び出しが最後に表示） |

### 改ページ条件

N/A

## データベース参照仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| mrb_backtrace_location (構造体) | バックトレース位置情報 | RException->backtrace |
| mrb_irep_debug_info (構造体) | ファイル名・行番号の取得 | irep->debug_info |
| mrb_callinfo (構造体) | コールスタック情報 | mrb->c->cibase |

### テーブル別参照項目詳細

#### mrb_backtrace_location

| 参照項目（フィールド名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| irep | IREP参照（ファイル名・行番号取得用） | 常時 | NULLの場合は"(unknown):0" |
| idx | iseq内オフセット | irep != NULL | debug_get_positionで使用 |
| method_id | メソッド名 | method_id != 0 | mrb_sym_nameで名前取得 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| pcオフセット | `ci->pc[-1] - irep->iseq` | なし | pc[-1]で実行済み命令を指す |
| スタックフレーム数 | `mrb->c->ci - mrb->c->cibase + 1` | なし | - |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[例外発生] --> B[mrb_keep_backtrace呼び出し]
    B --> C[packed_backtrace: コールスタックをパック]
    C --> D[pack_backtrace: 各フレームの位置情報を収集]
    D --> E[store_backtrace: 例外オブジェクトに保存]
    E --> F[mrb_print_backtrace呼び出し]
    F --> G{mrb->excが有効?}
    G -->|No| H[何も出力せず終了]
    G -->|Yes| I[print_backtrace呼び出し]
    I --> J{バックトレースあり?}
    J -->|Yes| K[trace ヘッダー出力]
    K --> L[フレームn-1→1を逆順出力]
    L --> M[フレーム0を出力]
    M --> N[例外メッセージ出力]
    J -->|No| O[UNKNOWN_LOCATION出力]
    O --> N
    N --> P[完了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 例外なし | mrb->excがNULL | 出力なし | 正常（例外なし） |
| 例外型不正 | mrb->exc->tt != MRB_TT_EXCEPTION | 出力なし | - |
| バックトレースなし | backtrace==NULL, n==0 | `(unknown):0: {例外メッセージ}` | デバッグ情報なしでコンパイルされた可能性 |
| IREP情報なし | entry->irep==NULL | `(unknown):0` | C関数からの例外等 |
| メモリ不足 | mrb->nomem_err | `Out of memory (NoMemoryError)` | メモリ確保不可時の特別処理 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 通常数十フレーム |
| 目標出力時間 | ミリ秒単位 |
| 同時出力数上限 | 1 |

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

- バックトレースにはファイルパス、行番号、メソッド名が含まれるため、内部構造が露出する
- 本番環境ではバックトレースの出力を抑制するか、ログに制限することを推奨
- MRB_NO_STDIOが定義されている場合、mrb_print_backtraceは空関数となる

## 備考

- バックトレースは「packed backtrace」形式で例外オブジェクトに格納され、出力時にデコードされる
- pack_backtrace時にIREPのrefcntをインクリメントし、参照を保持する
- refcntがMAX_IREP_REFCNT(UINT16_MAX)の場合はIREP参照を放棄する
- mrb_get_backtrace関数はRubyレベルでバックトレースをArray形式で取得可能
- nomem_err（NoMemoryError）の場合は特別処理で固定メッセージを出力する

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | backtrace.c | `src/backtrace.c` | `mrb_backtrace_location`構造体: irep, idx, method_idフィールド |
| 1-2 | backtrace.c | `src/backtrace.c` | 定数定義（20-22行目）: MAX_IREP_REFCNT, UNKNOWN_LINENO, UNKNOWN_LOCATION |

**読解のコツ**: バックトレースは「パック→保存→出力時にデコード」の2段階処理を採る。pack_backtraceでコールスタックからlocationを収集し、decode_locationで文字列に変換する。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | backtrace.c | `src/backtrace.c` | `mrb_print_backtrace`（281-288行目）: 公開API。mrb->excの検証後にprint_backtraceを呼び出し |
| 2-2 | backtrace.c | `src/backtrace.c` | `mrb_keep_backtrace`（127-137行目）: 例外発生時にバックトレースをパックして保存 |

**主要処理フロー**:
1. **283行目**: mrb->excの存在チェックとMRB_TT_EXCEPTIONの型チェック
2. **287行目**: print_backtrace呼び出し（excオブジェクトとbacktraceを渡す）

#### Step 3: バックトレースのパック処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | backtrace.c | `src/backtrace.c` | `packed_backtrace`（100-116行目）: RBacktraceオブジェクト生成、メモリ確保 |
| 3-2 | backtrace.c | `src/backtrace.c` | `pack_backtrace`（52-97行目）: コールスタック走査、各フレームのlocation構築 |
| 3-3 | backtrace.c | `src/backtrace.c` | `copy_backtrace`（25-39行目）: IREP参照カウント管理付きコピー |

**主要処理フロー**:
- **64行目**: Rubyプロシージャの場合、irep->debug_infoからファイル名・行番号取得
- **70-71行目**: `ci->pc[-1]`で実行済み命令のアドレスを計算
- **73-91行目**: C関数の場合、呼び出し元のRubyフレームを遡って位置情報を取得

#### Step 4: バックトレースの出力処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | backtrace.c | `src/backtrace.c` | `print_backtrace`（223-273行目）: 出力メイン処理 |
| 4-2 | backtrace.c | `src/backtrace.c` | `decode_location`（140-171行目）: 位置情報を文字列に変換 |

**主要処理フロー**:
- **243行目**: "trace (most recent call last):\n"をstderrに出力
- **244-252行目**: フレームn-1→1を逆順で出力（"\t[%d] "形式）
- **253-258行目**: フレーム0を出力（例外メッセージの前に配置）
- **264-272行目**: 例外メッセージ出力（nomem_errは特別処理）

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

```
mrb_print_backtrace() [backtrace.c:281]
    │
    └─ print_backtrace() [backtrace.c:223]
           │
           ├─ decode_location() [backtrace.c:140]  ← 各フレームの文字列化
           │      ├─ mrb_debug_get_position() ← ファイル名・行番号取得
           │      ├─ mrb_format() ← "file:line" 文字列生成
           │      └─ mrb_sym_name() ← メソッド名取得
           │
           ├─ fputs() ← "trace (most recent call last):"
           ├─ fprintf() ← "\t[%d] " フレーム番号
           ├─ fwrite() ← フレーム位置文字列
           └─ mrb_exc_get_output() ← 例外メッセージ

mrb_keep_backtrace() [backtrace.c:127]  ← 例外発生時に呼ばれる
    │
    └─ packed_backtrace() [backtrace.c:100]
           │
           ├─ MRB_OBJ_ALLOC() ← RBacktrace生成
           └─ pack_backtrace() [backtrace.c:52]
                  └─ copy_backtrace() [backtrace.c:25]
                         └─ mrb_irep_incref() ← IREP参照カウント++
```

### データフロー図

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

コールスタック ────▶ pack_backtrace() ────▶ RBacktrace (packed)
(mrb->c->cibase)     │
                     ├─ 各ci→irep/pc/mid
                     └─ location構築

RBacktrace ─────────▶ print_backtrace() ──────▶ stderr
(packed)               │
                       ├─ decode_location()
                       │   ├─ file:line 文字列
                       │   └─ :in method_name
                       ├─ トレースヘッダー
                       ├─ 各フレーム逆順出力
                       └─ 例外メッセージ出力
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| backtrace.c | `src/backtrace.c` | ソース | バックトレース処理のコア実装 |
| debug.h | `include/mruby/debug.h` | ヘッダー | mrb_debug_get_position, mrb_debug_get_line関数宣言 |
| proc.h | `include/mruby/proc.h` | ヘッダー | RProc構造体、MRB_PROC_CFUNC_Pマクロ定義 |
| error.h | `include/mruby/error.h` | ヘッダー | RException構造体、mrb_exc_get_output関数宣言 |
| variable.h | `include/mruby/variable.h` | ヘッダー | 変数操作関連 |
