人生は勉強ブログ

https://github.com/dooooooooinggggg

Linux Kernelをビルドする際,プリプロセッサの出力を残す

この記事はSFC-RG Advent Calendar 2019の17日目の記事です.

結論

Makefileこの辺KBUILD_CFLAGS += -save-temps=objという行を加えてビルドする.

before

KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
           -fno-strict-aliasing -fno-common -fshort-wchar \
           -Werror-implicit-function-declaration \
           -Wno-format-security \
           -std=gnu89
KBUILD_CPPFLAGS := -D__KERNEL__

after

KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
           -fno-strict-aliasing -fno-common -fshort-wchar \
           -Werror-implicit-function-declaration \
           -Wno-format-security \
           -std=gnu89
KBUILD_CFLAGS += -save-temps=obj
KBUILD_CPPFLAGS := -D__KERNEL__

プリプロセッサ

C言語などでプログラムを書いた時,何も考えない場合は

gcc -o hello hello.c

などのようにしたコンパイルを行う. 少し本格的なプログラムや,実行環境などによって分岐をしたい場合は,マクロを使用することになる.

コンパイルにはいくつかの工程があり,その工程の序盤に,ソースコードに対して前処理を行うプログラムのことをプリプロセッサ(preprocessor)と呼ぶ.

プリプロセッサ - Wikipediaにあるように,

ファイルの読み込み (including) マクロの展開(シンボルを、あらかじめ定義された規則に従って置換する) コンパイル条件によるソースコードの部分的選択 コメントの削除

を行う. これらの工程を行うのがC言語におけるgccプリプロセッサである.

これらの情報が削除できる理由として,例えば,コメントは,開発者のためにあるものであって,コンパイラ的には何の意味もない. 同様に,マクロなどもビルド時には値が決定しているし,includeなどを使ったファイルの読み込みも,開発者が同じコードをいくつも書きたくないからファイルとして分割しているのであって,コンパイラからしたら,全て一つのファイルにまとめておいてほしい.

この,プリプロセッサが行う前処理の結果は,通常一時的なものであって,コンパイル後にファイルが残ることはない. しかし,マクロの挙動を見るなどの目的でプリプロセッサの出力を見たい場合のオプションとして,Eオプションがgccには用意されている.

-E' オプションを使用すると、GCC はプリプロセス以外の処理を行いません。 以下に示すオプションのうちのいくつかは、-E' と同時に使用された時のみ意味をもちます。なぜならば、これらのオプション によって、実際のコンパイルには不適当なプリプロセッサ出力が生成されるためです。

Linux Kernelのマクロを展開したい

一方で,Linux Kernelのような複雑なビルド手順がMakefileにまとまっている.しかし,そのビルド手順は複雑きわまりなく,単にgccを付け足すだけでは厳しいのは明白である.意味がわからない.

カーネルコンフィグを元にマクロを展開しようにも,マクロの数が多すぎて,とても手作業では厳しい.そこで,今回はgccのオプションを使って実現する.

Linux Kernelのビルド手順に関しては,過去記事で触れたが,今回の記事でも簡単に書く.

blog.ishikawa.tech

blog.ishikawa.tech

  1. 何かしらの方法で.configを生成する.
  2. 以下のように,Makefileこの辺KBUILD_CFLAGS += -save-temps=objという行を加える
  3. makeする
KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
           -fno-strict-aliasing -fno-common -fshort-wchar \
           -Werror-implicit-function-declaration \
           -Wno-format-security \
           -std=gnu89
KBUILD_CFLAGS += -save-temps=obj
KBUILD_CPPFLAGS := -D__KERNEL__
make -j9

少し待つと,ビルドが完了するが,ビルドが完了したディレクトリでls -aコマンドなどを叩いてみると,拡張子にiを持ったファイルが生成されているのがわかる.

最後に,vscodeなどを用いて文字列の探索を行う.マシンパワーに頼る.

例えばtask_structの最終的な型を知りたい場合は,struct task_struct {みたいな文字列で検索してみる. すると,sched.hをincludeしている大量の痕跡が見つかる.よく見てみると,マクロも全て展開されている.

以下はMan page of GCCにある説明である.

-save-temps
    通常の ``一時'' 中間ファイルを消去せずに保存します。これらは カレントディレクトリに置かれ、ソースファイルに基づいた名前が付けられます。 従って、`foo.c' を `-c -save-temps' を使用してコンパイルした場合は、 `foo.cpp', `foo.s' が、`foo.o' と同様に生成されます。

まとめ

本当はもっといい方法があるのだと思うが,これしか思いつかなかった.

参考