Androidスマートフォン用のLinuxカーネルイメージはあるが、適切なソースコードまたはカーネルヘッダーファイルがないとします。 カーネルが(幸いなことに)モジュールのロードをサポートしており、このカーネル用のモジュールを構築したいとします。 ソースから新しいカーネルをコンパイルして、それだけで終わらない理由はいくつかあります(たとえば、組み立てられたカーネルはLCDやタッチスクリーンなどの重要なデバイスをサポートしません)。 LinuxカーネルのABIが絶えず変化し、ソースファイルとヘッダーファイルが不足しているため、最終的に停止状態にあると思われるかもしれません。
実際、他のヘッダーファイル(使用しているカーネルイメージのビルドに使用したものではなく-約)を使用してカーネルモジュールをコンパイルすると、モジュールはどれだけエラーが発生して起動できなくなります。ヘッダーファイルは必要なものとは異なります。 彼は悪い署名、悪いバージョン、その他について不平を言うかもしれません。
しかし、これについてはさらに詳しく説明します。
カーネル構成
最初のステップは、可能な限りカーネルイメージに最も近いカーネルソースを見つけることです。 明らかに、正しい構成を取得することは、モジュールアセンブリプロセス全体の中で最も難しい部分です。
/proc/version
から読み取ることができるカーネルバージョン番号から始めます。 私のように、Androidデバイス用のモジュールを構築している場合、Code Aurora、Cyanogen、またはAndroid(デバイスに最も近いもの)のAndroidカーネルを試してください。 私の場合、それはmsm-3.0カーネルでした。 カーネルイメージのバージョンとまったく同じソースバージョンを探す必要がないことに注意してください。 バージョンのわずかな違いは、ほとんど障害にならないでしょう。 カーネルソースバージョン3.0.21を使用しましたが、既存のカーネルイメージのバージョンは3.0.8でした。 ただし、3.0.xカーネルイメージがある場合は、カーネル3.1ソースを使用しないでください。
所有しているカーネルイメージが
/proc/config.gz
ファイルを提供するのに十分な場合、これで開始できます。それ以外の場合は、デフォルト構成で開始を試みることができますが、この場合は非常に注意する必要があります(デフォルトの構成を使用する詳細については掘り下げませんが、幸運にもこれに頼らないほど幸運だったので、正しい構成が非常に重要である理由についての詳細があります)。
PATH環境変数のパスの1つで
arm-eabi-gcc
使用可能であり、ターミナルがカーネルソースファイルのあるフォルダーで開いていると仮定すると、カーネルの構成とヘッダーファイルとスクリプトのインストールを開始できます。
$ mkdir build $ gunzip config.gz > build/.config # , , .config $ make silentoldconfig prepare headers_install scripts ARCH=arm CROSS_COMPILE=arm-eabi- O=build KERNELRELEASE=`adb shell uname -r`
silentoldconfig
アセンブリは、特定のオプションを有効にするかどうかを尋ねる可能性が最も高くなります。 デフォルトを選択できますが、これは機能しない場合があります。
KERNELRELEASE
で別のものを使用できますが、これはモジュールのロード元のカーネルのバージョンと正確に一致する必要があります。
簡単なモジュールを書く
空のモジュールを作成するには、ソースと
Makefile
2つのファイルを作成する必要があります。 次のコードを
hello.c
ファイルの別のディレクトリに配置します。
#include <linux/module.h> /* Needed by all modules */ #include <linux/kernel.h> /* Needed for KERN_INFO */ #include <linux/init.h> /* Needed for the macros */ static int __init hello_start(void) { printk(KERN_INFO "Hello world\n"); return 0; } static void __exit hello_end(void) { printk(KERN_INFO "Goodbye world\n"); } module_init(hello_start); module_exit(hello_end);
同じディレクトリの
Makefile
に次のテキストを配置します。
obj-m = hello.o
モジュールの組み立ては非常に簡単ですが、この段階では、結果のモジュールは起動できません。
モジュールの組み立て
典型的なカーネルビルドでは、カーネルビルドシステムが
hello.mod.c
ファイルを作成します。このファイルの内容により、さまざまな問題が発生する可能性があります。
MODULE_INFO(vermagic, VERMAGIC_STRING);
VERMAGIC_STRING
値
VERMAGIC_STRING
、カーネルビルドシステムによって
include/generated/utsrelease.h
れた
include/generated/utsrelease.h
ある
VERMAGIC_STRING
マクロによって決定され
include/generated/utsrelease.h
。 デフォルトでは、この値はカーネルのバージョンとgitリポジトリのステータスによって決定されます。 これは、カーネルを構成するときに
KERNELRELEASE
設定するもの
KERNELRELEASE
。
VERMAGIC_STRING
がカーネルバージョンと一致しない場合、モジュールをロードすると
dmesg
にこの種のメッセージが表示されます。
hello: version magic '3.0.21-perf-ge728813-00399-gd5fa0c9' should be '3.0.8-perf'
さらに、ここにはモジュールの構造の定義もあります。
struct module __this_module __attribute__((section(".gnu.linkonce.this_module"))) = { .name = KBUILD_MODNAME, .init = init_module, #ifdef CONFIG_MODULE_UNLOAD .exit = cleanup_module, #endif .arch = MODULE_ARCH_INIT, };
この定義自体は無害に見えますが、
include/linux/module.h
定義されている
struct module
構造に
include/linux/module.h
不快な驚きがあります。
struct module { (...) #ifdef CONFIG_UNUSED_SYMBOLS (...) #endif (...) /* Startup function. */ int (*init)(void); (...) #ifdef CONFIG_GENERIC_BUG (...) #endif #ifdef CONFIG_KALLSYMS (...) #endif (...) (... plenty more ifdefs ...) #ifdef CONFIG_MODULE_UNLOAD (...) /* Destruction function. */ void (*exit)(void); (...) #endif (...) }
これは、
init
ポインターを正しい場所に配置するために、カーネルイメージが使用するものに従って
CONFIG_UNUSED_SYMBOLS
定義する必要があることを意味します。 出口ポインターは
CONFIG_GENERIC_BUG
、
CONFIG_KALLSYMS
、
CONFIG_TRACEPOINTS
、
CONFIG_JUMP_LABEL
、
CONFIG_TRACING
、
CONFIG_TRACING
、
CONFIG_EVENT_TRACING
、
CONFIG_MODULE_UNLOAD
です。
カーネルの構築に使用したのとまったく同じヘッダーファイルを通常使用することになっている理由を理解し始めますか?
次に、キャラクターバージョンの定義:
static const struct modversion_info ____versions[] __used __attribute__((section("__versions"))) = { { 0xsomehex, "module_layout" }, { 0xsomehex, "__aeabi_unwind_cpp_pr0" }, { 0xsomehex, "printk" }, };
これらの定義は、ヘッダーファイルに従って生成される
Module.symvers
ファイルから取得されます。
そのような各エントリは、モジュールに必要な文字と、その文字に必要な署名を表します。 最初の文字
module_layout
は、
struct module
外観に依存します。つまり、前述の構成オプションが有効になっていることに依存します。 2番目の
__aeabi_unwind_cpp_pr0
は、ABI ARM固有の関数であり、最後は
printk
関数の呼び出しです。
各文字のシグネチャは、この関数のカーネルコードとカーネルの構築に使用されたコンパイラに応じて異なる場合があります。 つまり、特定のカーネルのモジュールだけでなく、ソースからカーネルをコンパイルし、たとえば
printk
関数を変更した後、互換性のある方法でカーネルを再構築すると、最初にビルドされたモジュールは新しいカーネルでロードされません。
そのため、カーネルイメージをビルドしたソースと構成に十分近いソースと構成でカーネルをコンパイルすると、カーネルイメージと同じ署名を取得できない可能性があり、それが呪われましたモジュールをロードするとき:
hello: disagrees about version of symbol symbol_name
つまり、カーネルイメージに対応する正しい
Module.symvers
ファイルが必要なのです。
コアを学ぶ
カーネルはモジュールのロード時にこれらのチェックを行うため、エクスポートする文字と対応する署名のリストも含まれています。 カーネルがモジュールをロードすると、モジュールが必要とするすべての文字を調べて、シンボルテーブル(またはモジュールが使用するモジュールの他のシンボルテーブル)でそれらを見つけ、対応する署名を確認します。
カーネルは、次の関数を使用して、そのシンボルテーブル(kernel / module.c内)を検索します。
bool each_symbol_section(bool (*fn)(const struct symsearch *arr, struct module *owner, void *data), void *data) { struct module *mod; static const struct symsearch arr[] = { { __start___ksymtab, __stop___ksymtab, __start___kcrctab, NOT_GPL_ONLY, false }, { __start___ksymtab_gpl, __stop___ksymtab_gpl, __start___kcrctab_gpl, GPL_ONLY, false }, { __start___ksymtab_gpl_future, __stop___ksymtab_gpl_future, __start___kcrctab_gpl_future, WILL_BE_GPL_ONLY, false }, #ifdef CONFIG_UNUSED_SYMBOLS { __start___ksymtab_unused, __stop___ksymtab_unused, __start___kcrctab_unused, NOT_GPL_ONLY, true }, { __start___ksymtab_unused_gpl, __stop___ksymtab_unused_gpl, __start___kcrctab_unused_gpl, GPL_ONLY, true }, #endif }; if (each_symbol_in_section(arr, ARRAY_SIZE(arr), NULL, fn, data)) return true; (...)
この関数で使用される構造は、include / linux / module.hで定義されています。
struct symsearch { const struct kernel_symbol *start, *stop; const unsigned long *crcs; enum { NOT_GPL_ONLY, GPL_ONLY, WILL_BE_GPL_ONLY, } licence; bool unused; };
注:このカーネルコードは、過去4年間で大幅に変更されていません(明らかに、検討中のカーネル3.0のリリース以降-約Per。)。
each_symbol_section
関数で上にあるのは3つのフィールド(または
CONFIG_UNUSED_SYMBOLS
config
CONFIG_UNUSED_SYMBOLS
有効になっている場合
CONFIG_UNUSED_SYMBOLS
5つ)のフィールドです。各フィールドには、シンボルテーブルの開始、終了、2つのフラグが含まれます。
これらのデータは静的で一定です。つまり、カーネルバイナリにそのまま表示されます。 カーネルのアドレス空間にある3つのポインターと、
each_symbol_section
の定義からの
integer
値で構成される3つの連続したシーケンスについてカーネルをスキャンして、シンボルテーブルと署名の場所を特定し、カーネルバイナリからModule.symversファイルを再作成できます。
残念ながら、今日のほとんどのコアは圧縮されているため(
zImage
)、圧縮された画像を簡単に検索することはできません。 圧縮されたコアは、実際には小さなバイナリであり、その後に圧縮ストリームが続きます。
zImage
ファイルをスキャンして、圧縮ストリームを見つけ、そこから解凍されたイメージを取得できます。
自動モードでカーネルシンボルに関する情報を解凍および抽出するスクリプトを作成しました。 カーネルが再配置可能でなく、ロードされているメモリ内のベースアドレスがわかっている場合、これはカーネルの新しいバージョンで動作するはずです。 このスクリプトは、アーキテクチャのビットの数と順序(エンディアン)のオプションを受け入れ、デフォルトでARMに適した値を使用します。 ただし、ベースアドレスは提供する必要があります。 ARMコアの
dmesg
ます。
$ adb shell dmesg | grep "\.init" <5>[01-01 00:00:00.000] [0: swapper] .init : 0xc0008000 - 0xc0037000 ( 188 kB)
(おおよそのレーン-ただし、すべてのコアがこのデータをログに出力するわけではありませんが、設定オプションが切り捨てられたためにこの情報が表示されなかったような、ほとんどユニークなケースが発生しました。この場合、 archファイルのPAGE_OFFSET configを参照できます/ arm / Kconfig 、ベンダーがデフォルト値のいずれかを使用することを願っています)。
上記の例のベースアドレスは
0xc0008000
です。
私のように、Androidデバイスにモジュールをロードすることに興味がある場合、お持ちのカーネルバイナリは完全なブートイメージです。 ブートイメージにはカーネル以外の他のものが含まれているため、上記のスクリプトで直接使用することはできません。 唯一の例外は、 ブートイメージ内のカーネルが圧縮されている場合です。ただし、圧縮されたイメージが入力されることを期待するスクリプトの部分は依然としてカーネルを検出します。
カーネルが圧縮されていない場合、 この投稿で説明されているようにunbootimgプログラムを使用して、 ブートイメージからカーネルイメージを取得できます。 カーネルイメージを取得したら、次のようにスクリプトを実行できます。
$ python extract-symvers.py -B 0xc0008000 kernel-filename > Module.symvers
カーネルアセンブリ
これで、モジュールをロードするカーネルの正しい
Module.symvers
ファイルができたので、最終的にモジュールをビルドできます(再び、
arm-eabi-gcc
PATH
からアクセス可能であり、ターミナルがソースディレクトリで開いていると仮定します)。
$ cp /path/to/Module.symvers build/ $ make M=/path/to/module/source ARCH=arm CROSS_COMPILE=arm-eabi- O=build modules
それだけです。 hello.koファイルをデバイスにコピーして、モジュールをダウンロードできます。
$ adb shell # insmod hello.ko # dmesg | grep insmod <6>[mm-dd hh:mm:ss.xxx] [id: insmod]Hello world # lsmod hello 586 0 - Live 0xbf008000 (P) # rmmod hello # dmesg | grep rmmod <6>[mm-dd hh:mm:ss.xxx] [id: rmmod]Goodbye world
この記事は、 Mike Hommeyのブログ投稿の翻訳です。