LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Linuxカーネルのビルドの一部最適化を無効化する

関連記事

概要

最近のLinuxカーネルでは、デフォルトで最適化オプションが付与されている。

そのため、kgdbやその他のデバッグツールを利用する際に、不都合が生じることがあり、デバッグの間だけが無効化したいことがある。

そこで、本記事では、Makefileを書き換える方法と関数にattributeを付与する方法により最適化を無効化して、kgdbで変数の値を確認する方法をまとめた。

はじめに

前回、VirtualBox仮想マシン同士でkgdbでデバッグできるようにした。

leavatail.hatenablog.com

kgdbを使えば、指定した関数内の変数を参照したり、ステップ実行でコードフローを追っていくことができるのでソースコードを読むときにも役立つ。

しかし、Linuxカーネルはビルド時にデフォルトで最適化オプションを付与しているので、kgdbでソースコードリーディングをするにはいくつかの問題点がある。 そこで、kgdbでも比較的簡単にソースコードリーディングができるようにいくつかの施策をする。

現状の問題点

gdbを使用した人ならわかるが、最適化オプションを付与したオブジェクトファイルをgdbにかけると、以下の問題がある。

  • 一部の変数がと表示され参照できなくなる
  • ステップ実行とソースコードが対応しなくなる

<optimized out>されてしまう変数の実例

そこで、ソースコードリーディングをしている間はカーネルのビルド時に最適化オプションを無効にしたい(-O0)のだが、どうやらLinuxカーネル-O0コンパイルできないらしい。

The kernel will not run with -O0, sorry, just live with the build optimization levels that is currently used and you should be fine.

Yes, it doesn't work :) How to compile Linux kernel with -O0 flag

そのため、別の方法で変数などを確認する必要がある。

ファイル単位で最適化を無効化する

ファイル単位で無効化する場合は、該当するMakefileに下記の内容を追記することでできる。

CFLAGS_(オブジェクトファイル名) = コンパイルオプション

例えば、fs/fat/dir.cの最適化のみ無効にする場合、fs/fat/Makefileを下記のように修正する。

# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Linux fat filesystem support.
#

obj-$(CONFIG_FAT_FS) += fat.o
obj-$(CONFIG_VFAT_FS) += vfat.o
obj-$(CONFIG_MSDOS_FS) += msdos.o

CFALGS_dir.o = -O0   # 最適化を無効化するために追加した行

fat-y := cache.o dir.o fatent.o file.o inode.o misc.o nfs.o
vfat-y := namei_vfat.o
msdos-y := namei_msdos.o

Makefileを修正後、再ビルドをして対象マシンに再インストールする。

ファイル単位で最適化を無効にした例

関数単位で最適化を無効化する

関数単位で無効化する場合は、該当するソースコードを修正する必要がある。 最適化を無効にしたい関数に対して、下記の修正をすればよい。

型  __attribute__((optimize("コンパイルオプション"))) 関数名(引数)

例えば、ext4_readpages()の最適化のみ無効にする場合、下記のように記述する。

// 3365:
static int __attribute__((optimize("O0")))
ext4_readpages(struct file *file, struct address_space *mapping,
        struct list_head *pages, unsigned nr_pages)

ソースコードを修正後、再ビルドをして対象マシンに再インストールする。

関数単位で最適化を無効にした例

おわりに

kgdbでカーネルソースコードを読むために必要最低限、変数の値やフローを追うための準備をした。 まずファイル単位で最適化オプションを無効化する方法は、再ビルドが必要になるが、容易に解決できるためオススメである。

変更履歴

  • 2019/11/10: 記事公開
  • 2022/06/05: デザイン修正

参考

補足

kgdbで特定の変数を参照したいだけであれば、ビルド最適化を必ずしも無効化する必要はない

マシン命令に変換してレジスタを表示する

関数に対応するマシン命令とレジスタにより、特定の変数を確認することはできる。

これは、デバッグ時にも有効な手段で、指定範囲をマシン命令や、アセンブリコードを出力することでフローを追っていくことができる。

注意

ここで紹介する方法はx86_64カーネルのケースである。

その他のアーキテクチャの場合には、レジスタ規則が異なるため注意。

stackoverflow.com

アセンブリコードの出力にはdisassembleコマンド、命令単位で実行するにはsiコマンドを実行していく。 その他のコマンドも含めてgdbの使い方をまとめてあるサイトはたくさん見つかるので一度目を通しておくことを推奨する。

例えば、fat_parse_short()をマシン命令単位の実行フローを見てみる。

アセンブリコードを出力

また、レジスタから引数のunsigned char *nameを参照する。

すべてのレジスタの値を表示

ここに関しては、プロセッサ毎のレジスタ規則を把握している必要がある。*1

レジスタを参照する

これらの結果から、fat_parse_short()ではAというファイルのエントリをパースしていたようだ。 実際に、対象マシンの方の実行結果を見てみても同様のことがわかる。

vagrant@ubuntu-bionic:~$ ls /mnt
A

*1:x86_64の場合、第3引数はrdxレジスタに格納されると思うのですが、詳しい人いたら教えてください