Easyboot プラグイン
===================

デフォルトでは、[Easyboot](https://gitlab.com/bztsrc/easyboot) はブート パーティションから ELF および PE 形式の Multiboot2 準拠カーネルを起動します。
カーネルが異なるファイル形式、異なるブート プロトコルを使用している場合、またはカーネルがブート パーティション上にない場合は、ブート パーティションにプラグインが必要です。
これらは [src/plugins](../../src/plugins) ディレクトリにあります。

[[_TOC_]]

インストール
------------

プラグインをインストールするには、menu.cfg ファイルの横にある `easyboot` サブディレクトリの下の `(indir)` パラメータで指定されたディレクトリにプラグインをコピーするだけです。

例えば：

```
ブートパーツ
|-- easyboot
|   |-- linux_x86.plg
|   |-- minix_x86.plg
|   `-- menu.cfg
|-- EFI
|   `-- BOOT
|-- kernel
`-- initrd
$ easyboot ブートパーツ ディスク.img
```

コンパイル
----------

*ELF がこのタスクに適していないことは最初から明らかでした。 ELF は肥大化しすぎて複雑すぎます。 そのため、当初は struct exec (従来の UNIX a.out 形式)
を使用するつもりでしたが、残念ながら最新のツールチェーンではこれを作成できなくなりました。 そのため、プラグイン用に独自の形式と独自のリンカーを作成することにしました。*

プラグインのソースは、標準の ANSI C クロスコンパイラを使用して ELF オブジェクト ファイルにコンパイルできますが、最終的なバイナリを作成するには [plgld](../../src/misc/plgld.c)
リンカーを使用する必要があります。これはアーキテクチャに依存しないクロスリンカーであり、プラグインがどのマシン コード用にコンパイルされたかに関係なく機能します。
最終的な .plg は、生成元の .o ELF のほんの一部にすぎません。

### プラグインAPI

プラグインの C ソースには、`src/loader.h` ヘッダー ファイルと、`EASY_PLUGIN` 行が含まれている必要があります。これには、プラグインのタイプを示す 1 つのパラメーターと、
それに続く識別子の一致指定があります。後者は、ローダーによって、特定のプラグインをいつ使用するかを決定するために使用されます。

例えば：

```c
#include "../loader.h"

/* Linuxカーネルを識別するマジックバイト */
EASY_PLUGIN(PLG_T_KERNEL) {
 /*オフセット サイズ  一致タイプ   マジックバイト */
    { 0x1fe,     2, PLG_M_CONST, { 0xAA, 0x55, 0, 0 } },
    { 0x202,     4, PLG_M_CONST, { 'H', 'd', 'r', 'S' } }
};

/* エントリポイント、プロトタイプはプラグインのタイプによって定義されます */
PLG_API void _start(uint8_t *buf, uint64_t size);
{
    /* Linuxカーネルの環境を準備する */
}
```

プラグインはいくつかの変数と関数を使用できます。これらはすべてヘッダー ファイルで定義され、実行時にリンクされます。

```c
uint32_t verbose;
```
詳細レベル。プラグインは、エラー メッセージを除き、この値が 0 以外の場合にのみ出力を印刷できます。値が大きいほど、印刷される詳細が多くなります。

```c
uint64_t file_size;
```
開かれたファイルの合計サイズ (以下の `open` および `loadfile` を参照)。

```c
uint8_t *root_buf;
```
ファイルシステムプラグインが初期化されると、パーティションの最初の 128k が含まれます (スーパーブロックも含まれる可能性があります)。
後でファイルシステムプラグインはこの 128k バッファを任意の目的 (FAT キャッシュ、inode キャッシュなど) で再利用できます。

```c
uint8_t *tags_buf;
```
Multiboot2 タグが含まれます。カーネル プラグインはこれを解析して、ブート マネージャーが提供するデータをカーネルが期待する形式に変換できます。このポインターはバッファーの先頭を指します。

```c
uint8_t *tags_ptr;
```
このポインターは、Multiboot2 タグ バッファーの末尾を指します。タグ プラグインは、ここに新しいタグを追加し、このポインターを調整する場合があります。

```c
uint8_t *rsdp_ptr;
```
RSDP ACPI ポインターを指します。

```c
uint8_t *dsdt_ptr;
```
DSDT (または GUDT、FDT) ハードウェア記述バイナリ オブジェクトを指します。

```c
efi_system_table_t *ST;
```
UEFI マシンでは EFI システム テーブルを指します。それ以外の場合は `NULL` です。

```c
void memset(void *dst, uint8_t c, uint32_t n);
void memcpy(void *dst, const void *src, uint32_t n);
int  memcmp(const void *s1, const void *s2, uint32_t n);
```
必須のメモリ関数 (直接呼び出しがない場合でも、C コンパイラはこれらの呼び出しを生成する場合があります)。

```c
void *alloc(uint32_t num);
```
`num` ページ (4k) のメモリを割り当てます。プラグインは多くのメモリを割り当ててはならず、最小限のメモリ フットプリントを目指す必要があります。

```c
void free(void *buf, uint32_t num);
```
以前に割り当てられた `num` ページのメモリを解放します。

```c
void printf(char *fmt, ...);
```
フォーマットされた文字列をブート コンソールに出力します。

```c
uint64_t pb_init(uint64_t size);
```
進行状況バーを開始します。`size` はそれが表す合計サイズです。1 ピクセルに相当するバイト数を返します。

```c
void pb_draw(uint64_t curr);
```
現在の値のプログレスバーを描画します。`curr` は 0 から合計サイズまでの範囲でなければなりません。

```c
void pb_fini(void);
```
プログレスバーを閉じて、画面上の場所をクリアします。

```c
void loadsec(uint64_t sec, void *dst);
```
ファイル システム プラグインによって使用され、ディスクからメモリにセクターをロードします。`sec` は、ルート パーティションを基準としたセクター番号です。

```c
void sethooks(void *o, void *r, void *c);
```
ファイル システム プラグインによって使用され、ルート パーティションのファイル システムの open / read / close 関数のフックを設定します。

```c
int open(char *fn);
```
ルート (またはブート) パーティション上のファイルを読み取り用に開き、成功すると 1 を返します。一度に開くことができるファイルは 1 つだけです。
事前に `sethooks` 呼び出しが行われていない場合は、ブート パーティションで動作します。

```c
uint64_t read(uint64_t offs, uint64_t size, void *buf);
```
開かれたファイルのシーク位置 `offs` からデータをメモリに読み取り、実際に読み取られたバイト数を返します。

```c
void close(void);
```
開いたファイルを閉じます。

```c
uint8_t *loadfile(char *path);
```
ルート (またはブート) パーティションからファイル全体を新しく割り当てられたメモリ バッファーにロードし、プラグインが見つかった場合は透過的に解凍します。
サイズは `file_size` で返されます。

```c
int loadseg(uint32_t offs, uint32_t filesz, uint64_t vaddr, uint32_t memsz);
```
カーネル バッファからセグメントをロードします。これは、メモリ `vaddr` が使用可能かどうかを確認し、上位半分の場合はセグメントをマップします。
`offs` はファイル オフセットなので、カーネル バッファを基準とします。`memsz` が `filesz` より大きい場合、その差はゼロで埋められます。

```c
void _start(void);
```
ファイル システム プラグイン (`PLG_T_FS`) のエントリ ポイント。`root_buf` 内のスーパーブロックを解析し、`sethooks` を呼び出す必要があります。
エラーの場合は、フックを設定せずにそのまま返されます。

```c
void _start(uint8_t *buf, uint64_t size);
```
カーネル プラグインのエントリ ポイント (`PLG_T_KERNEL`)。メモリ内のカーネル イメージを受け取り、セグメントを再配置し、適切な環境を設定して制御を転送します。
エラーがない場合、戻りません。

```c
uint8_t *_start(uint8_t *buf);
```
解凍プラグインのエントリ ポイント (`PLG_T_DECOMP`)。圧縮されたバッファ (およびそのサイズは `file_size` に設定されます) を受け取り、
圧縮解除されたデータを含む割り当てられた新しいバッファを返す必要があります (また、新しいバッファのサイズも `file_size` に設定します)。
古いバッファを解放する必要があります (注意: `file_size` はバイト単位ですが、free() はページ単位のサイズを期待します)。エラーが発生した場合、
`file_size` は変更してはならず、変更されていない元のバッファを返す必要があります。

```c
void _start(void);
```
タグ プラグインのエントリ ポイント (`PLG_T_TAG`)。`tags_ptr` に新しいタグを追加し、そのポインタを新しい 8 バイトの位置に調整する場合があります。

### ローカル関数

プラグインはローカル関数を使用できますが、CLang のバグのため、これらは `static` として宣言する必要があります。(このバグは、コマンドラインで "-fno-plt"
フラグが渡された場合でも、CLang がこれらの関数の PLT レコードを生成することです。`static` を使用すると、この問題を回避できます)。

低レベルファイルフォーマット仕様
--------------------------------

C 以外の言語 (たとえばアセンブリ) でプラグインを作成したい場合のために、ファイル形式の低レベルの説明を次に示します。

これは a.out 形式と非常によく似ています。ファイルは固定サイズのヘッダーと、それに続くさまざまな長さのセクションで構成されています。セクション ヘッダーはなく、
各セクションのデータは、次の順序で前のセクションのデータに直接続きます：

```
(ヘッダ)
(識別子一致レコード)
(再配置レコード)
(マシンコード)
(読み取り専用データ)
(初期化された読み取り/書き込み可能データ)
```

最初の実際のセクションであるマシン コードには、アライメントが含まれます。他のすべてのセクションでは、前のセクションのサイズにパディングが追加されます。

ヒント: プラグインを単一の引数として `plgld` に渡すと、`readelf -a` または `objdump -xd` に似た出力でファイル内のセクションがダンプされます。

### ヘッダ

すべての数値は、アーキテクチャに関係なく、リトルエンディアン形式です。

| オフセット | サイズ | 説明                                                       |
|--------:|------:|----------------------------------------------------------------|
|       0 |     4 | マジックバイト `EPLG`                                          |
|       4 |     4 | ファイルの合計サイズ                                           |
|       8 |     4 | ファイルの読み込み時に必要なメモリの合計                       |
|      12 |     4 | コードセクションのサイズ                                       |
|      16 |     4 | 読み取り専用データセクションのサイズ                           |
|      20 |     4 | プラグインのエントリポイント                                   |
|      24 |     2 | アーキテクチャコード（ELFと同じ）                              |
|      26 |     2 | 移転記録の数                                                   |
|      28 |     1 | 識別子一致レコードの数                                         |
|      29 |     1 | 最も多く参照された GOT エントリ                                |
|      30 |     1 | ファイル形式のリビジョン (現時点では 0)                        |
|      31 |     1 | プラグインのタイプ (1=ファイルシステム、2=カーネル、3=デコンプレッサー、4=タグ) |

アーキテクチャ コードは ELF ヘッダーと同じです。たとえば、62 = x86_64、183 = Aarch64、243 = RISC-V などです。

プラグインのタイプはエントリ ポイントのプロトタイプを指定し、ABI は常に SysV になります。

### 識別子一致セクション

このセクションには、ヘッダーの「識別子一致レコードの数」フィールドで指定された数と同じ数の次のレコードが含まれます。

| オフセット | サイズ | 説明                                                 |
|--------:|------:|----------------------------------------------------------|
|       0 |     2 | オフセット                                               |
|       2 |     1 | サイズ                                                   |
|       3 |     1 | タイプ                                                   |
|       4 |     4 | 一致するマジックバイト                                   |

まず、オブジェクトの先頭がバッファにロードされます。アキュムレータがセットアップされ、最初は 0 になります。これらのレコードのオフセットは常にこのアキュムレータを基準とし、
バッファ内のそのバイトをアドレス指定します。

タイプ フィールドは、オフセットの解釈方法を示します。1 の場合、オフセットとアキュムレータが値として使用されます。2 の場合、オフセットで 8 ビット バイト値が取得され、
3 は 16 ビット ワード値が取得され、4 は 32 ビット ダブルワード値が取得されることを意味します。5 は 8 ビット バイト値が取得され、それにアキュムレータが加算されることを意味し、
6 は 16 ビット ワード値が取得され、それにアキュムレータが加算されることを意味し、7 は同じですが 32 ビット値です。8 は、アキュムレータ番目のバイトからバッファーの末尾までのマジック バイトをオフセット ステップで検索し、
見つかった場合は一致するオフセットを値として返します。

サイズがゼロの場合、アキュムレータはその値に設定されます。サイズがゼロでない場合、そのバイト数が指定されたマジック バイトと一致するかどうかがチェックされます。

たとえば、PE 実行ファイルが NOP 命令で始まるかどうかを確認するには、次のようにします：

```c
EASY_PLUGIN(PLG_T_KERNEL) {
 /*オフセット サイズ  一致タイプ   マジックバイト */
    { 0,         2, PLG_M_CONST, { 'M', 'Z', 0, 0 } },      /* マジックバイトをチェックする */
    { 60,        0, PLG_M_DWORD, { 0, 0, 0, 0 } },          /* PEヘッダーのアキュムレータへのオフセットを取得する */
    { 0,         4, PLG_M_CONST, { 'P', 'E', 0, 0 } },      /* マジックバイトをチェックする */
    { 40,        1, PLG_M_DWORD, { 0x90, 0, 0, 0 } }        /* エントリポイントでNOP命令をチェックする */
};
```

### 移転セクション

このセクションには、ヘッダーの「再配置レコードの数」フィールドで指定された数と同じ数の次のレコードが含まれます。

| オフセット | サイズ | 説明                                                 |
|--------:|------:|----------------------------------------------------------|
|       0 |     4 | オフセット                                               |
|       4 |     4 | 移転タイプ                                               |

型内のビットの意味:

| から | まで | 説明                                                         |
|--------:|------:|----------------------------------------------------------|
|       0 |     7 | シンボル (0 - 255)                                       |
|       8 |     8 | PC相対アドレス指定                                       |
|       9 |     9 | GOT 相対間接アドレッシング                               |
|      10 |    13 | 即時マスクインデックス (0 - 15)                          |
|      14 |    19 | スタートビット (0 - 63)                                  |
|      20 |    25 | 終了ビット (0 - 63)                                      |
|      26 |    31 | 否定アドレスフラグビット位置 (0 - 63)                    |

オフセット フィールドはプラグインのヘッダー内のマジックに相対的であり、再配置を実行する必要があるメモリ内の整数を選択します。

シンボルは、使用するアドレスを示します。0 は、プラグインがメモリにロードされた BASE アドレス、つまり、メモリ内のヘッダーのマジックのアドレスを意味します。
他の値は、ローダーまたは別のプラグインで定義された GOT から外部シンボル アドレスを選択します。plgld.c のソースの `plg_got` 配列を見て、
どの値がどのシンボルに対応するかを確認してください。GOT 相対ビットが 1 の場合、シンボルの実際のアドレスではなく、シンボルの GOT エントリのアドレスが使用されます。

PC 相対ビットが 1 の場合、最初にオフセットがアドレスから減算されます (命令ポインタ相対アドレッシング モード)。

即値マスク インデックスは、命令内のアドレスを格納するビットを示します。これが 0 の場合、アーキテクチャに関係なく、アドレスはそのままオフセットに書き込まれます。
x86_64 の場合、インデックス 0 のみが許可されます。ARM Aarch64 の場合: 0 = そのまま、1 = 0x07ffffe0 (左に 5 ビットシフト)、2 = 0x07fffc00 (左に 10 ビットシフト)、
3 = 0x60ffffe0 (ADR/ADRP 命令では即値がシフトされ、2 つのビットグループに分割されます)。将来のアーキテクチャでは、より多くの異なる即値ビットマスクが定義される可能性があります。

イミディエイトマスクを使用して、メモリから end - start + 1 ビットが取得され、符号付き拡張されます。この値はアドレスに追加されます (addend、内部参照の場合は内部シンボルのアドレスもここでエンコードされます)。

否定されたアドレス フラグ ビットが 0 でなく、アドレスが正の場合、そのビットはクリアされます。アドレスが負の場合、そのビットが設定され、アドレスが否定されます。

最後に、開始ビットと終了ビットは、選択した整数に書き込むアドレスの部分を選択します。これにより、再配置のサイズも定義され、この範囲外のビットと即時マスクの一部ではないビットは変更されません。

### コードセクション

このセクションには、ヘッダーで指定されたアーキテクチャのマシン命令が含まれており、コード サイズ フィールドに示されているバイト数と同じバイト数になります。

### 読み取り専用データセクション

これはオプションのセクションであり、欠落している可能性があります。ヘッダーの読み取り専用セクション サイズ フィールドに示されている長さと同じです。すべての定数変数はこのセクションに配置されます。

### 初期化されたデータセクション

これはオプションのセクションであり、欠落している可能性があります。コード セクション (またはオプションの読み取り専用データ セクション) の後にファイルにまだバイトが残っている場合、
それらのバイトはすべてデータ セクションと見なされます。変数がゼロ以外の値で初期化されている場合は、このセクションに配置されます。

### BSSセクション

これはオプションのセクションであり、欠落している可能性があります。このセクションはファイルに保存されることはありません。メモリ内のサイズ フィールドがヘッダーのファイル サイズ フィールドよりも大きい場合、
その差はメモリ内でゼロで埋められます。変数が初期化されていないか、ゼロとして初期化されている場合は、このセクションに配置されます。
