ソースの読み方
ようこそいらっしゃいました。ここからが本編。Linux Kernel の森の中へようこそ。Linux のソースコードの量は膨大であると書いたが、なぜこれほど膨大なのか。まず、CPUの違いによるアセンブリ言語の違いが上げられる。いくつかのCPU に対応したOS を作るとなると、最終的にCPU にとって個別の処理が必要な部分も出てくる。そのようなソースコードがあるため、使わないソースファイルも多々存在する。必ずしも全てコンパイルするわけではない。
そして、モジュール(拡張機能) やドライバの割合もかなり多い。特にドライバは世界中で量産され、OS はその中でも良く使われるものをあらかじめ持っておいて使うので、その量がかなり多くなっている。(最近世界標準規格が生まれつつある。Web カメラに関しては共通規格のUVC が生まれ徐々に広がっている。詳細は本誌「WebCam を作ってみた」にて。)
量が多い多いと言っても分からないので、先ほどLinux のコンパイルに5 時間かかったAtom を例にとると、モジュールやドライバの多くをコンパイルしない最小構成の設定を行った上でコンパイルすると、15 分で終了する場合もある。本当の核の部分はそれほど大きくはない(動かなかったけど)。
そう考えると、私たちがいじる部分は、かなり限定的ということになる。それを知った上で、少しソースコードを読み、改造してコンパイルを行う。
システムコールを探せ
システムコールを探してみる。この課題はセキュリティ&プログラミングキャンプの事前課題で出され、多くの学生が頭を悩ませた部分である。
システムコールとは、Linux における権限を越えた命令の実行を代行する命令のことである。例えばファイルの読み書きは、実はユーザー権限では許されておらず、Kernel が厳重に守っている。しかしそれではまともにプログラムも実行できないので、システムコールという形でKernel にその作業を代行してもらうことで、セキュリティ等を高めている。
今回はsymlink(ショートカットを作るシステムコール) を探してみる。ソースファイルの検索はコマンドが用意されているとは思うが、今回はglobal を使ってみる。
//すでにlinux-2.6(クロスリファレンスと作ったところ) にいるとする。
//global コマンドで、main という関数を検索する。
$ global main
たくさんのファイル名が出てきただろう。これらのファイルの中に、main() 関数が書かれているという意味である。
では、さっそくsymlink の場所を調べる。システムコールも関数として定義されているので検索できるはずである。
//global コマンドで、symlink という関数を検索する。
$ global symlink
何かエラーが返されただろうか。このエラーは検索したオブジェクト(関数や定数等) が見つからなかったという意味である。そんな馬鹿な!検索して見つからないってどういうことだ!と思うだろう。実は別の名前なのかとも疑いたくなる。
こんな装備で森の中に迷い込んでしまっては、探索も進まない。ここで秘密兵器である、vi とglobal を連携させてsymlink の捜索を行う。
vim+Gtags
ここでsymlink が見つからなかった理由を書いておく。ずばり、クロスリファレンスを作った時に、シンボルが作成されなかったということである。global で関数検索を行うと、シンボルを検索するので、無い場合にはエラーとなる。ここでsymlink は特殊な書き方をされているということが何となく分かる。
さて、まずはvi を起動する。(恐らくvim だけど。) すでにプラグインで拡張しているので、global コマンドと同等のことが行える。まずはmain() 関数を検索してみる。
起動時にはコマンドモードなので、:Gtags main というコマンドを実行する。いきなり上下の2 画面モードとなり、上にソースファイル。下にファイル一覧が出てきたはずだ。このように、vim のGtags を使えばファイル一覧とソースの中身を見たりジャンプすることが可能だ。global コマンドよりははるかに捜索効率が上がる。ここでいつものように:q コマンドで脱出すれば、ファイル一覧のみとなり、上下で移動してエンターキーを押すと、そのファイルのmain() 関数が記述されている部分を読むことができる。
主に使うことになるvi のコマンドを以下にまとめる。
コマンドコマンド説明
:Gtags シンボル関数やマクロなどのシンボルを検索する。
:Gtags -g 文字列文字列を検索する。
:GtagsCursor 現在カーソル位置にある文字列の定義場所にジャンプする。
/文字列vi で表示している文中から文字列を検索しジャンプする。
これらのコマンドを駆使して、今からsymlink を見つけ出す。
symlink を探せ
まずは無難に:Gtags symlink でシンボル検索を行う。見つからない。ここで全文検索を行う。:Gtags -g symlink のコマンドを実行してしばらく待つと、結果が返ってくる。
結果は次のような形式で返ってくるので、非常に分かりやすい。
ファイルパス|行数|その行の記述
大量のファイルが出てきただろう。これでsymlink の使われている場所、宣言されている場所が全て分かる。だが少し待とう。上から全部調べればいつか定義にたどりつくだろうが、量がおかしい。システムコールとなれば、他のソースでも多々使われる。ここから探し出すのは困難である。
そこで、システムコールは恐らくC 言語で書かれているだろうという予測の元、アセンブリ言語で書かれたファイルを無視することも考えられる。それでも量が多い。宣言らしきものや#define がたくさん見えることだろう。
ここで、少し落ち着こう。ここからはLinux やパソコンに対する基本知識を総動員する必要がある。簡単なところから考えてみると、ファイルへのショートカットを作るのだから、symlink という関数は恐らくファイル関連の場所に集められているはずだ。その考えからls してディレクトリ構造を調べると、fs というディレクトリが見つかるだろう。この中にはファイルシステム絡みのソースコードが入っているので、そこに移動してみる。
ここにも大量のファイルがあるので、良く考える。ショートカットを作ると言うことは、ファイルへの別名をつけるとも考えられる。ファイルシステムについて記述したext3 とかfat 等も怪しいが、ファイルシステムごとに作っていたら大変である。別名というところからファイル名を斜め読みすると、namei.c というファイルが見つかるはずだ。少し覗いてみる。
vi でファイルを開き、/symlink で検索してみる。いくつか候補が見つかるので、n キーを押して次に進む。
しばらく進むと、vfs_symlink 等の似たような関数の宣言が出てくる。これが本体か?でも引数にディレクトリのようなものがあるので違うだろう。もう少しn を押して次に進むと、少し英語の読める人なら「あれ?」と感じる部分が出てくる。
SYSCALL_DEFINE2(symlink,const char __user *,oldname,const char __user *,newname)
SYSCALL……システムコールのこと?第一引数はsymlinkだ。第二引数はconst char__user *で型宣言だとしたら、第三引数はoldname。古い名前という変数名。じゃぁ次は同じ文字列型で、新しい名前という変数名だ。どう見てもsymlink のシステムコールを呼び出すのに必要な変数名が書かれている。
ここでようやく、symlink の正体にたどりついた。
SYSCALL_DEFINE2(symlink,const char __user *,oldname,const char __
user *,newname)
{ return sys_symlinkat(oldname, AT_FDCWD, newname);
}
これが、symlink システムコールのソースコードである。sys_symlinkat も実はシステムコールなので普通に探すと見つからない。
さて、ここで分かったことがある。システムコールは、SYSCALL_DEFINE[引数の数] というマクロで定義されていた。だからクロスリファレンス作成時に、シンボルとして登録されなかったのである(関数定義には見えないため)。
これはLinux 独特の書き方で、少し前からシステムコールはこのようなマクロで定義するようになったらしい。この前提知識を使えば、vi でGtags -g SYSCALL_DEFINE で全文検索を行った後、vi の検索でシステムコール名を探せば、それなりに早く見つけることができる。
他にも独特な書き方として、条件分岐後goto 文を使って後ろの方に飛ばしていくつかの処理を視覚的に見やすく書いている部分があったりする(goto 文はスパゲッティコードになるので使わないと言う人も多くいるが、比較的この書き方は多用されている)。
システムログへの書き込み
ここで、システムログ(dmesg コマンドで読める) にsymlink システムコールが呼ばれたら通知する改良をKernel に施す。
ここで一度バージョン管理ソフトであるGit を使って、作業の差分をとるようにする。
//すでにlinux-2.6 にいるとする。
//チェックアウト。ここで作業を分岐させる。名前はmydebug だがある程度好きにすることができる。
$ git checkout mydebug
ここから作業を行うことになる。
ここで一つ注意したいのが、C 言語で使えた命令文が使えない可能性が多々あることを意識することである。例えば、メモリ領域を確保するmalloc() 関数は存在せず、その代わりとなるLinux Kernel 内部でのみ使えるメモリ確保の命令(kmalloc とか) があったりする。
さて、システムログに関してだが、これに関してもファイルをわざわざ開くなどの必要はなく、専用の関数が用意されている。
printk( ログレベル"整形文", 代入する変数など);
ログレベル以外は普通のprintf() 関数と同じものである。ここのログレベルは省略可能なもので、ここの内容によってはLinux を停止させることも可能である。
ログレベルマクロ意味
0 KERN EMERG システムが使用不能
1 KERN ALERT 直ちに対処が必要
2 KERN CRIT 致命的な状態
3 KERN ERR エラー状態
4 KERN WARNING 警告状態
5 KERN NOTICE 通常状態だが大事な情報
6 KERN INFO 通知
7 KERN DEBUG デバッグレベル情報
この中で、例えば警告状態等のログレベルにすると、Fedora のデスクトップにポップアップウィンドウが出て、警告文を出力するようになる。このようにこの値を読み取って視覚的に訴える処置などが施される場合がある。
これを使って以下のように改造する。
SYSCALL_DEFINE2(symlink,const char __user *,oldname,const char __user *,newname)
{ printk(KERN_DEBUG "symlink( %s, %s )Y=n",oldname,newname);
return sys_symlinkat(oldname, AT_FDCWD, newname);
}
この改良を施してKernel をコンパイルすると、symlink が呼ばれるたびに、元のファイル名と新しいファイル名の情報をシステムログに出力するようになった。ログレベルも値が大きいので、警告が出るようなこともない。
これができたら、ひとまずKernel ハック第一歩である。
ここまでの作業をパッチファイル(変更点を記したファイル) として出力してみる。
//変更に名前をつけて更新する。これがパッチファイルの名前となる。スペースは-に置換される。
$ git commit -m "symlink systemlog write"
//master(本体) との差分をとる。
$ git format-patch -o p1 master..mydebug
2 つ目のコマンドで、p1 というディレクトリを作り、その中にパッチファイルを出力するようにしている。これによりp1 の中に変更箇所の詰まったいくつかのパッチファイルが吐かれる。今回はファイルを1 ついじっただけなので、1 つのファイルが存在しているはずである。
今回の命名はかなり下手な例である。ちゃんとした修正を行う場合は、どの個所を変更したかが分かるような名前の付け方にする必要がある。