Linux Kernel smatch

静的解析ツールsmatchでLinuxカーネルの関数コール情報を取得するまでの作業を記事にしました。

環境

  • windows11
  • Docker
    • Ubuntu12.4

Docker

install

Ubuntu:12.4を起動

#powershell
docker run -it --name old-gcc-env ubuntu:12.04 /bin/bash
  • コマンド捕捉
    • docker run: imageからコンテナを作成して実行
    • -it: powershellからコンテナ内のUbuntuのターミナルへ入力するためのオプション
    • –name old-gcc-env: コンテナに名前を付ける
    • ubuntu:12.04: イメージ
    • /bin/bash: コンテナ起動時にbashを起動

停止後、再度実行する場合

#powershell
docker start -i -a old-gcc-env
  • コマンド捕捉
    • -i: コンテナへの標準入力を有効にする
    • -a: コンテナからの標準出力を有効にする
    • old-gcc-env: 実行するコンテナの名前
    • ubuntu:12.04: イメージ
    • /bin/bash: コンテナ起動時にbashを起動

コンテナのファイルをホスト(windows)にコピーする

#powershell
#xxxのフォルダをpowerhellのカレントフォルダにコピー
#ファイルでもOK
docker cp old-gcc-env:root/xxx .

ホスト(windows)のファイルをコンテナにコピーする

#powershell
#ホストのxxxファイルをコンテナのyyyフォルダにコピー
docker cp ./xxx old-gcc-env:yyy

apt参照先を変更

# sh
# 参照先を old-releases.ubuntu.com に書き換え
sed -i 's/archive.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list
sed -i 's/security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list

# アップデートを実行
apt-get update
  • コマンド捕捉
    • sed: 文字列編集コマンド
    • -i: 表示をオフ
    • s/A/B/g: sは置換、Aは検索文字列、Bは置き換える文字列、gは行内の一致

Linux Kernelのビルドに必要なツール

以下の公式ドキュメントを参考にしてツールをインストールしていますが、この記事では使用していないツールもあります。

https://docs.kernel.org/process/changes.html#current-minimal-requirements

#sh
apt-get install -y gcc
apt-get install -y make
apt-get install -y binutils
apt-get install -y wget
apt-get install -y openssl
apt-get install -y tar
apt-get install libncurses5-dev

作業ディレクトリの作成

#sh
mkdir ~/workspace_my_linux && cd ~/workspace_my_linux

Linux Kernel

自作のLinuxに組み込むカーネルを設定してビルドします。

Linux Kernelのソースをダウンロード

以下のサイトにダウンロードできるバージョン一覧があります。

https://cdn.kernel.org/pub/linux/kernel/

この記事ではv3.0.5をダウンロードします。

#sh
cd ~/workspace_my_linux
wget https://cdn.kernel.org/pub/linux/kernel/v3.0/linux-3.0.5.tar.xz

Linux Kernelのソースを解凍

#sh
tar -xvf linux-3.0.5.tar.xz

最小設定のコンフィグファイルを作成

#sh
cd linux-3.0.5
make allnoconfig

allnoconfigはLinux Kernelのmakefileにターゲットとして定義されています。 https://docs.kernel.org/kbuild/kconfig.html

Linux Kernelの設定

#sh
make menuconfig 

menuconfigで以下を設定します。

  • General Setup
    • kernel compression mode: Gzip
    • Default hostname: My_Linux
    • Initial RAM filesystem and RAM disk (initramfs/initrd) support: Yes
    • Configure standard kernel features (expert users): yes
  • Processor type and features
    • Build a relocatable kernel: No
  • Executable file formats
    • Kernel support for ELF binaries: Yes
  • Device Driver
    • Generic Driver Options
      • Maintain a devtmpfs filesystem to mount at /dev: Yes
      • Automount devtmpfs at /dev, after the kernel mounted the rootfs: yes
    • Character devices
      • Serial Drivers
        • 8250/16550 and compatible serial support: Yes
        • Console on 8250/16550 and compatible serial port: Yes
  • File systems
    • Pseudo filesystems
      • /proc file system support: Yes
      • /sysfs file system support: Yes

セーブしてコンフィグメニューから抜けます。 .configが更新されます。

Linux Kernelをビルド

#sh
make

ビルドができたらsmatchによる静的を実行できる準備が完了です。

smatch

Linuxカーネルで使用される静的解析ツールです。 https://github.com/error27/smatch/blob/master/Documentation/smatch.rst

smatchをclone

#sh
cd ~
apt-get install -y git sqlite3 libsqlite3-dev libtry-tiny-perl libdbd-sqlite3-perl libssl-dev
GIT_SSL_NO_VERIFY=true git clone https://github.com/error27/smatch.git
cd smatch
# 記載時の最新コミット
git checkout 2614ff1a041f0bff0a647f84df7fa4427a84c015

smatchの修正

古い環境で最新のsmatchを使用します。

しかし、そのままだとエラーが発生して解析できません。

以下の対応を行います。

smatchパッチ

修正を以下のgitのパッチにまとめています。

smatch_ubuntu1204.patch

Makefileを修正

121行目付近のcflagsにC99のオプションを追加する。

diff --git a/Makefile b/Makefile
index f5e4929..ad107bc 100644
--- a/Makefile
+++ b/Makefile
@@ -121,7 +121,7 @@ all:
 # common flags/options/...

 cflags = -fno-strict-aliasing
-cflags += -Wall -Wwrite-strings -Wno-switch -Wno-psabi
+cflags += -Wall -Wwrite-strings -Wno-switch -Wno-psabi -std=gnu99

修正しない場合に発生するエラー

smatch_kernel_host_data.c: In function 'get_host_data_fn_param':
smatch_kernel_host_data.c:157:2: error: 'for' loop initial declarations are only allowed in C99 mode
builtin.cを修正

279,709行目付近のclrsbをコメントアウトする。

  • Geminiによる解説 clrsb(Count Leading Redundant Sign Bits)は、符号ビットの冗長性をカウントする特殊な関数です。Linux カーネル 3.0.5 の解析において、この特定の組み込み関数が Smatch でサポートされていなくても、解析全体に大きな支障が出ることはまずありません。
diff --git a/builtin.c b/builtin.c
index 9e8fa59..4d08199 100644
--- a/builtin.c
+++ b/builtin.c
@@ -279,7 +279,7 @@ static struct symbol_op name##_op = {                               \

 EXPAND_FINDBIT(clz);
 EXPAND_FINDBIT(ctz);
-EXPAND_FINDBIT(clrsb);
+//EXPAND_FINDBIT(clrsb);
 EXPAND_FINDBIT(ffs);
 EXPAND_FINDBIT(parity);
 EXPAND_FINDBIT(popcount);
@@ -709,9 +709,9 @@ static const struct builtin_fn builtins_common[] = {
        { "__builtin_bswap64", &ullong_ctype, 0, { &ullong_ctype }, .op = &bswap_op },
        { "__builtin_bzero", &void_ctype, 0, { &ptr_ctype, size_t_ctype }},
        { "__builtin_calloc", &ptr_ctype, 0, { size_t_ctype, size_t_ctype }},
-       { "__builtin_clrsb", &int_ctype, 0, { &int_ctype }, .op = &clrsb_op },
-       { "__builtin_clrsbl", &int_ctype, 0, { &long_ctype }, .op = &clrsb_op },
-       { "__builtin_clrsbll", &int_ctype, 0, { &llong_ctype }, .op = &clrsb_op },
+//     { "__builtin_clrsb", &int_ctype, 0, { &int_ctype }, .op = &clrsb_op },
+//     { "__builtin_clrsbl", &int_ctype, 0, { &long_ctype }, .op = &clrsb_op },
+//     { "__builtin_clrsbll", &int_ctype, 0, { &llong_ctype }, .op = &clrsb_op },
        { "__builtin_clz", &int_ctype, 0, { &int_ctype }, .op = &clz_op },
        { "__builtin_clzl", &int_ctype, 0, { &long_ctype }, .op = &clz_op },
        { "__builtin_clzll", &int_ctype, 0, { &llong_ctype }, .op = &clz_op },

修正しない場合に発生するエラー

libsparse.a(builtin.o): In function `expand_clrsb':
/root/smatch/builtin.c:282: undefined reference to `__builtin_clrsb'
/root/smatch/builtin.c:282: undefined reference to `__builtin_clrsbll'
collect2: ld returned 1 exit status
make: *** [compile] Error 1
解析プログラムのsqlインサート文を修正

smatch_db.cなどで解析結果をsqliteに出力するためのsql文をsql_insert関数で作成する。 しかし、出力パラメータの一部が’‘で囲めておらず、sqliteでエラーとなる。

以下の3ファイルを修正する。

  • smatch_db.c
  • smatch_mtag.c
  • smatch_units.c

3ファイルとも以下のように「0x%llx」を「’0x%llx’」に修正する。

diff --git a/smatch_db.c b/smatch_db.c
index e1f7e32..c9e6826 100644
--- a/smatch_db.c
+++ b/smatch_db.c
@@ -296,7 +296,7 @@ void sql_insert_return_states(int return_id, const char *return_ranges,
        else
                id = __fn_mtag;

-       sql_insert(return_states, "0x%llx, '%s', %llu, %d, '%s', %d, %d, %d, '%s', '%s'",
+       sql_insert(return_states, "'0x%llx', '%s', %llu, %d, '%s', %d, %d, %d, '%s', '%s'",
                   get_base_file_id(), get_function(), id, return_id,
                   return_ranges, is_local(cur_func_sym), type, param, key, value);
 }

修正コマンドの例

#sh
cd /root/smatch
sed -i "s/0x%llx/'0x%llx'/g" ./smatch_db.c
sed -i "s/0x%llx/'0x%llx'/g" ./smatch_mtag.c
sed -i "s/0x%llx/'0x%llx'/g" ./smatch_units.c

修正しない場合に発生するエラー

# "0x340ee0837437c564"はファイルのハッシュ値で様々な値が大量に出力される。
# smatch.h sql_insert_helperで出力されるエラーログ。
SQL error #2: unrecognized token: "0x340ee0837437c564"
fixup_kernel.shを修正

sql文で#によるコメントを/**/に変更する。

古いsqliteでは#によるコメントがエラーとなるため。

diff --git a/smatch_data/db/fixup_kernel.sh b/smatch_data/db/fixup_kernel.sh
index f76af0e..e45632b 100755
--- a/smatch_data/db/fixup_kernel.sh
+++ b/smatch_data/db/fixup_kernel.sh
@@ -77,9 +77,9 @@ delete from caller_info where function = '(struct inet6_protocol)->handler' and
 delete from caller_info where function = 'do_dentry_open param 2' and type = 8017;
 delete from caller_info where function = 'do_dentry_open param 2' and type = 9018;
 delete from caller_info where function = 'param_array param 7' and type = 9018;
-# this is just too complicated for Smatch.  See how snd_ctl_find_id() is called.
+/* this is just too complicated for Smatch.  See how snd_ctl_find_id() is called. */
 delete from caller_info where function = 'snd_ctl_notify_one' and type = 8017;
-#temporary.  Just to fix recursion
+/* temporary.  Just to fix recursion */
 delete from caller_info where caller = 'ecryptfs_mkdir' and type = 8017;
 delete from caller_info where caller = 'rpm_suspend' and type = 8017;
 delete from return_states where function = 'rpm_resume' and type = 8017;

修正しない場合に発生するエラー

root@7d78c3b283b7:~/workspace_my_linux/linux-3.0.5# ~/smatch/smatch_scripts/build_kernel_data.sh
2147483646
off
off
Error: near line 68: unrecognized token: "#"
Error: near line 70: near "#temporary": syntax error
Hm... Not working.  Make sure you have all the sqlite3 packages
And the sqlite3 libraries for Perl and Python

smatchをビルド

make

smatchで解析

#sh
cd ~/workspace_my_linux/linux-3.0.5
~/smatch/smatch_scripts/build_kernel_data.sh

デバッグ出力を有効にしたい場合

#sh
~/smatch/smatch_scripts/build_kernel_data.sh --debug

smatch解析結果をホスト(windows)にコピー

# powershell
# powerhsellを起動したフォルダにsmatch解析結果をコピー
docker cp old-gcc-env:root/workspace_my_linux/linux-3.0.5/smatch_db.sqlite . 
docker cp old-gcc-env:root/workspace_my_linux/linux-3.0.5/smatch_warns.txt.caller_info .
  • smatch_db.sqlite 解析結果が記録されたsqliteデータベース。
  • smatch_warns.txt.caller_info 関数コールのSQLインサート情報。smatch_db.sqliteのcaller_infoテーブルではファイル名がハッシュ化されており、smatch_warns.txt.caller_infoでハッシュとファイル名を確認する必要がある。

smatch_db.sqliteをcsv出力

vscodeで以下のエクステンションをインストールする。 https://marketplace.visualstudio.com/items?itemName=alexcvzz.vscode-sqlite

インストールが完了したら、コマンドパレットで「>SQlite: open Database」を実行してsmatch_db.sqliteを開く。 sqliteでDBを開く

vscodeのEXPLORERに開いたDBが表示される。 DB

「▶: Show Table」を実行するとテーブルが表示される。 以下は「caller_info」テーブルを表示。 赤枠からcsv出力できる。 caller_info

  • file: ファイルのハッシュ。ファイル名はsmatch_warns.txt.caller_infoで確認する。
  • caller: 呼び出し元の関数
  • function: callerが呼び出している関数

出力した解析結果です。

解析結果の正しさはコードと付き合わせて確認が必要ですが、grepしながら関数コールを辿るより楽になると思います。