- initrdをカーネルが直接解釈して実行するプログラム単位として定義し、Linuxを一種のインタプリタとして再解釈する
kexec、base64、cpioを使って自分自身を再起動する再帰型Linuxディストリビューションを構成し、initrdが自らを再実行する
/initスクリプトが自身のcpioイメージを出力するようにすると、Quine形式の自己複製initrdが成立する
- ELF実行構造と
ld.so、binfmt_miscを通じて、インタプリタ階層がカーネルまで続く構造を説明する
kexecやQEMUを活用すると、Linux上で別のLinuxを末尾再帰的に実行でき、カーネル・仮想化・インタプリタの境界を実験的に拡張する
rkx.gzのリバースエンジニアリングと自己再帰型initrd構造
curl https://astrid.tech/rkx.gz | gunzip | sudo sh コマンドは、20MBサイズのbase64エンコードされたシェルスクリプトをダウンロードして実行する
- スクリプトはroot権限を確認し、
kexec、base64、cpioの存在を検査する
- base64データをデコードして
rというcpioアーカイブを作成し、その中からkというカーネルイメージを抽出する
kexecを使ってkをカーネルに、rをラムディスクとしてロードして実行する
r.cpioの内部には/bin、/init、kファイルが含まれ、kはLinux 6.18.18カーネルイメージ、/initはシェルスクリプト形式である
/initは/procをマウントし、/rに現在のファイルシステムをcpioで束ねた後、kexecで/kと/rを再実行する
- 結果として自分自身を継続的に再起動する再帰型Linuxディストリビューションになる
Linuxカーネルをインタプリタとみなす視点
- initrdは単なるブート用ラムディスクではなく、Linuxカーネルが解釈して実行するプログラムとみなせる
curl | shやpython3 script.pyのように、initrdもカーネルによって実行される入力プログラムの形である
- したがってLinuxカーネルはinitrdを解釈するインタプリタとして機能する
- この構造は**末尾再帰最適化(tail-call optimization)**に似ている
kexecは以前のカーネルを上書きせず、新しいメモリ空間にロードして実行する
- 各カーネルは以前の状態を保持せず、新しい「スタックフレーム」に置き換えられる
Quineとinitrdの自己複製
- Quineは自分自身を出力するプログラムを意味する
/initスクリプトが最後にcat /rを実行するようにすると、自身と同一のcpioを出力する
- この場合、Linux initrdインタプリタのQuineが成立する
- すべてのファイルはRAM上の
tmpfsに存在するため、実際のディスクI/Oは発生しない
ELF、ld.so、そしてインタプリタの階層
- ELF実行ファイルはヘッダに**インタプリタ(ld-linux-x86-64.so.2)**のパスを含む
- 実行時、カーネルは
ld.soを先に実行し、ld.soがELFの動的ライブラリを読み込んだ後にプログラムを実行する
- したがってELFも一種のインタプリタ言語とみなせる
/bin/shはld.soによって解釈され、ld.soはカーネルが直接解釈する
ld.soは静的リンクされたELFなので、カーネルが直接実行できる
- これによって**インタプリタ階層の基底ケース(base case)**が成立する
binfmt_miscによるCPIO実行
binfmt_miscを使うと、特定のマジックバイトを持つファイルを指定したインタプリタで実行できる
- QEMUを使ってCPIOをinitrdとして実行するスクリプトをインタプリタとして登録できる
- QEMUは指定されたカーネルとinitrdを使って仮想マシンを起動する
- 結果としてCPIOファイルのインタプリタはQEMUが起動するLinuxカーネルになる
再帰的インタプリタと「最も奇妙なループ」
- QEMUベースのインタプリタは、新しいLinux環境のスタックフレームを生成する
- Linux上で別のLinuxを実行する構造であり、メモリの限界まで入れ子にできる
kexecベースのインタプリタに置き換えると、末尾呼び出し最適化された再帰型Linux実行が可能になる
/initでbinfmt_miscを登録し、/rを実行するように構成すると
自分自身を実行するinitrdが完成する
/rはCPIOフォーマットの次のinitプロセスであり、実行時に再び自分自身を解釈する
結論
- initrdは単なるブートツールではなく、Linuxカーネルが解釈するプログラム単位である
kexecとbinfmt_miscを使えば、Linux自体をインタプリタのように再帰的に実行できる
- この構造はカーネル、仮想化、インタプリタ、自己複製プログラムの境界を取り払う実験的な概念である
- 関連ソースコードはGitHubリポジトリ ifd3f/rekexec で公開されている
2件のコメント
無知だと大胆になるというけれど……こういう文章は控えてほしいです。
Hacker Newsの意見
この記事を読みながら、あまりに多くの誤解のせいでつらかった
cpioアーカイブはファイルシステムではない。筆者はinitramfsを使っているが、これはtmpfsベースだ。Linuxはcpioをtmpfsに展開できる。ファイルとディレクトリのアーカイブは、それ自体がプログラムではない
何かが似て見えるからといって同じとは限らない。バイナリプログラムはCPU上で実行され、もしインタプリタがあるなら、それはハードウェア環境の中に隠れている。これはカーネルの範囲外だ
シェルスクリプトを実行するには、そのスクリプトを解釈するシェルが必要だ。筆者はこの部分を省略して、カーネルとシェルプログラムを混同している
Linuxはinitramfsやramdiskなしでもコンパイルでき、それでもファイルシステム上のユーザーランドを実行できる
「Linux initrd interpreter」という表現は本当に不適切な説明だ
すべてのOSはカーネル権限で動く機械語インタプリタの役割をしているのでは?
この記事は「Linuxはインタプリタだ」というメンタルモデルとして見るなら問題ないが、文字どおりに受け取ると間違っている
CPU命令レベルでの解釈ではなく、カーネルがELF、shebangスクリプト、initramfsのような実行形式を調整する役割だと見れば、より妥当だ。混乱は「インタプリタ」の二つの意味が混ざっていることに由来するようだ
重要なのは比喩が正しいかどうかではなく、「実行」という概念がどれほど環境依存かを示している点だ
「すべてはインタプリタだ?」
TuringのTheta Combinator
シリーズの以前の記事で、筆者はContaboのオブジェクトストレージを使いたくなかったので、自分でVPSイメージを作ったと言っていた
月1.50ドルを節約するために50時間使うことと、25万ドルをトークンに使う極端さの間には、ちょうどよいバランスがあると思う。
インフラ費用を賄えないのなら、技術力よりも社会的要因が問題なのかもしれない。Doomをcurlで動かすことに執着するのは生産的ではないと感じる
man ld.soを見ると、ELFの.interpセクションに保存された動的リンカが実行されると明記されている。セクション名そのものが興味深いLinuxはプログラム可能なインターフェースとして非常に有用だ。Windowsでも可能だが、Linuxのほうが向いていると感じる
GUIはWindowsのほうがよいと思うが、GNOMEやKDEも使いづらい。だからfluxbox、icewm、ときにはxfceやmate-desktopを使っている。最近はシンプルで速い環境を好む。作業の大半はコマンドラインとコード編集でこなしている