# 機能設計書 118-Libdl

## 概要

本ドキュメントは、Julia標準ライブラリ `Libdl` モジュールの機能設計を記述する。dlopen / dlsym / dlclose / RTLD_* フラグによる動的ライブラリ操作を提供するモジュールである。

### 本機能の処理概要

**業務上の目的・背景**：Juliaのccall機構は実行時に共有ライブラリのシンボルを呼び出すが、標準的なccallだけでは対応しきれない高度な動的リンク操作が必要な場面がある。Libdlモジュールは、共有ライブラリの明示的なロード（dlopen）、シンボルの検索（dlsym）、ライブラリのアンロード（dlclose）、およびランタイムリンカフラグ（RTLD_*）による詳細な制御を提供する。これにより、プラグインシステムの実装、条件付きライブラリロード、ライブラリの検索・検出などが可能になる。

**機能の利用シーン**：ccall用のライブラリハンドル取得（dlopen）、シンボルの動的解決（dlsym）、ライブラリパスの取得（dlpath）、システム上のライブラリ検索（find_library）、ロード済みライブラリの一覧（dllist）、遅延ライブラリロード（LazyLibrary）、JLLパッケージのライブラリ管理。

**主要な処理内容**：
1. dlopen: 共有ライブラリを動的にロードしハンドルを返す
2. dlsym: ライブラリハンドルからシンボル（関数ポインタ）を検索
3. dlclose: ロード済みライブラリを閉じる
4. find_library: 指定された名前のライブラリをシステムから検索
5. dlpath: ライブラリハンドルまたは名前からフルパスを取得
6. dllist: 現在ロード中の全動的ライブラリを一覧
7. dlext: プラットフォーム固有の拡張子（.so / .dll / .dylib）
8. LazyLibrary: 初回ccall時まで遅延ロードするスレッドセーフなライブラリラッパー
9. LazyLibraryPath / BundledLazyLibraryPath: 遅延評価されるライブラリパス

**関連システム・外部連携**：OSの動的リンカ（ld.so / dyld / LoadLibrary）、JLLパッケージ（BinaryBuilder由来のバイナリアーティファクト）。

**権限による制御**：ライブラリファイルへのアクセス権限に依存。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | REPL | 参照画面 | dlopen/dlsym等の対話的実行 |

## 機能種別

システム連携 / 動的リンク

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| libfile | AbstractString/Symbol | Yes (dlopen時) | ライブラリファイル名またはパス | 有効なライブラリ名/パス |
| flags | Integer | No | RTLD_*フラグのビットOR。デフォルト=RTLD_LAZY\|RTLD_DEEPBIND | 有効なRTLDフラグ |
| handle | Ptr{Cvoid} | Yes (dlsym/dlclose時) | dlopenで取得したライブラリハンドル | NULL以外のポインタ |
| sym | Symbol/AbstractString | Yes (dlsym時) | 検索するシンボル名 | - |
| throw_error | Bool | No | エラー時に例外を投げるか。デフォルト=true | - |
| names | Vector{String} | Yes (find_library時) | 検索するライブラリ名のリスト | - |
| locations | Vector{String} | No | 追加の検索パス | - |

### 入力データソース

- ファイルシステム（ライブラリファイル）
- DL_LOAD_PATH配列（カスタム検索パス）
- OS動的リンカの検索パス

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| handle | Ptr{Cvoid} | dlopen: ライブラリハンドル。失敗時はnothing（throw_error=false） |
| sym_ptr | Ptr{Cvoid} | dlsym: シンボルの関数ポインタ |
| path | String | dlpath: ライブラリのフルパス |
| found_lib | String | find_library: 見つかったライブラリ名。失敗時は空文字列 |
| libs | Vector{String} | dllist: ロード中のライブラリパス一覧 |

### 出力先

- 戻り値としてポインタ/文字列/文字列配列を返却

## 処理フロー

### 処理シーケンス

```
1. dlopen(libfile, flags) 呼び出し
   └─ ccall(:jl_load_dynamic_library, ...) でOSのdlopenを呼び出し
   └─ @executable_path/ プレフィックスをJuliaの実行パスに置換（Julia 1.6+）
   └─ ハンドル（Ptr{Cvoid}）を返却

2. dlsym(handle, sym) 呼び出し
   └─ handle==C_NULLの場合ArgumentError
   └─ ccall(:jl_dlsym, ...) でOSのdlsymを呼び出し
   └─ シンボルのポインタを返却

3. LazyLibrary使用時
   └─ ccallまたはdlopenで初回アクセス
   └─ @lock ll.lock で排他制御
   └─ 依存ライブラリを先にdlopen
   └─ 自身をdlopen
   └─ on_load_callback実行（あれば）
   └─ ハンドルを@atomic :releaseで保存
```

### フローチャート

```mermaid
flowchart TD
    A["dlopen(libfile, flags)"] --> B["ccall(:jl_load_dynamic_library)"]
    B --> C{"成功?"}
    C -->|Yes| D["Ptr{Cvoid}を返却"]
    C -->|No, throw_error=true| E["エラー発生"]
    C -->|No, throw_error=false| F["nothing返却"]

    G["LazyLibrary ccall時"] --> H{"handle==C_NULL?"}
    H -->|Yes| I["@lock ll.lock"]
    I --> J["依存ライブラリをdlopen"]
    J --> K["自身をdlopen"]
    K --> L["on_load_callback実行"]
    L --> M["@atomic :release handle"]
    H -->|No| N["キャッシュされたhandle使用"]
    M --> O["ccall実行"]
    N --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-118-01 | デフォルトフラグ | RTLD_LAZY \| RTLD_DEEPBINDがデフォルト。macOSではRTLD_GLOBALも追加 | dlopen呼び出し時 |
| BR-118-02 | 拡張子自動付与 | dlextの拡張子は省略可能。自動的に追加される | dlopen呼び出し時 |
| BR-118-03 | LazyLibraryスレッドセーフ | LazyLibraryのロードはReentrantLockとatomicで排他制御 | LazyLibrary使用時 |
| BR-118-04 | LazyLibrary依存解決 | dependenciesの全ライブラリが先にロードされる | LazyLibrary初回アクセス時 |
| BR-118-05 | DL_LOAD_PATH優先 | DL_LOAD_PATHのパスがシステムパスより先に検索される | dlopen時 |

### 計算ロジック

特記事項なし。

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ArgumentError | NULLハンドル | dlsymにC_NULLハンドルを渡した場合 | 有効なハンドルを使用 |
| - | ライブラリ未発見 | dlopenで指定ライブラリが見つからない | DL_LOAD_PATHにパスを追加、またはフルパスを指定 |
| - | シンボル未発見 | dlsymで指定シンボルが見つからない | ライブラリ内のシンボル名を確認 |
| TypeError | 不正な引数型 | dlopen(::Any)の場合 | String, Symbol, LazyLibraryのいずれかを使用 |

### リトライ仕様

リトライ不要。

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

該当なし。

## パフォーマンス要件

- dlopen/dlsymはOSの動的リンカを呼び出すため、ミリ秒オーダー
- LazyLibraryは初回ロード後のアクセスではアトミック読み出しのみのため極めて高速

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

- 信頼できないライブラリのロードは任意コード実行のリスクがある
- DL_LOAD_PATHに不正なパスが含まれていないか確認すること
- RTLD_DEEPBINDはシンボル解決の安全性を高める

## 備考

- stdlib/Libdl/src/Libdl.jlはBase.Libc.Libdlからのre-exportのみ
- 実装本体はbase/libdl.jl（534行）に存在
- LazyLibrary/LazyLibraryPath/BundledLazyLibraryPathはJulia 1.11で追加
- jl_libdl_dlopen_funcグローバルを設定してccall()lowering時のdlopen関数を登録

---

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

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

### 推奨読解順序

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

LazyLibrary、LazyLibraryPath、BundledLazyLibraryPath構造体を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | libdl.jl | `base/libdl.jl` | **339-342行目**: LazyLibraryPath構造体 -- piecesタプルで遅延パス構築 |
| 1-2 | libdl.jl | `base/libdl.jl` | **428-459行目**: LazyLibrary構造体 -- path, flags, dependencies, on_load_callback, lock, handleフィールド |

**読解のコツ**: LazyLibraryは`@atomic handle`フィールドでスレッドセーフなロードを実現。OncePerProcessで依存関係リストを管理し、プロセスごとにエフェメラルな依存関係を保持する。

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

dlopen、dlsym、dlcloseがメインAPI。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | libdl.jl | `base/libdl.jl` | **119-125行目**: dlopen実装。ccall(:jl_load_dynamic_library)の呼び出し |
| 2-2 | libdl.jl | `base/libdl.jl` | **59-70行目**: dlsym実装。ccall(:jl_dlsym)でシンボル検索 |
| 2-3 | libdl.jl | `base/libdl.jl` | **167-169行目**: dlclose実装。ccall(:jl_dlclose) |

**主要処理フロー**:
1. **119行目**: `ccall(:jl_load_dynamic_library, Ptr{Cvoid}, (Cstring,UInt32,Cint), s, flags, Cint(throw_error))`
2. **62行目**: `ccall(:jl_dlsym, Cint, (Ptr{Cvoid}, Cstring, Ref{Ptr{Cvoid}}, Cint, Cint), hnd, s, val, ...)`

#### Step 3: LazyLibraryのロードフローを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | libdl.jl | `base/libdl.jl` | **490-529行目**: dlopen(ll::LazyLibrary)の実装。@atomic :acquire → @lock → 依存ロード → dlopen → @atomic :release |

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

```
dlopen(libfile, flags)
    |
    +-- ccall(:jl_load_dynamic_library)      # Cランタイム
        +-- dlopen() [OS]                    # POSIX/Windows

dlsym(handle, sym)
    |
    +-- ccall(:jl_dlsym)                     # Cランタイム
        +-- dlsym() [OS]                     # POSIX/Windows

dlopen(ll::LazyLibrary)
    |
    +-- @atomic :acquire ll.handle           # キャッシュ確認
    +-- @lock ll.lock                        # 排他制御
    +-- dlopen(dep) for dep in dependencies  # 依存解決
    +-- dlopen(string(ll.path), ll.flags)    # 本体ロード
    +-- ll.on_load_callback()                # コールバック
    +-- @atomic :release ll.handle           # キャッシュ保存
```

### データフロー図

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

ライブラリ名   ───▶  dlopen()                  ───▶  Ptr{Cvoid}
                     OS動的リンカ呼び出し              (ハンドル)

ハンドル+名前  ───▶  dlsym()                   ───▶  Ptr{Cvoid}
                     シンボルテーブル検索               (関数ポインタ)

LazyLibrary    ───▶  ccall時に遅延dlopen       ───▶  キャッシュされた
                     依存解決→ロード→キャッシュ        ハンドル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Libdl.jl | `stdlib/Libdl/src/Libdl.jl` | ソース | stdlib版。Base.Libc.Libdlからのre-export |
| libdl.jl | `base/libdl.jl` | ソース | 実装本体。dlopen, dlsym, dlclose, LazyLibrary等 |
| Project.toml | `stdlib/Libdl/Project.toml` | 設定 | パッケージ依存関係定義 |
