正確なヘッダーファイルなしでLinuxカーネルモジュールを構築する


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_STRINGVERMAGIC_STRING 、カーネルビルドシステムによってinclude/generated/utsrelease.hれたinclude/generated/utsrelease.hあるVERMAGIC_STRINGマクロによって決定されinclude/generated/utsrelease.h 。 デフォルトでは、この値はカーネルのバージョンとgitリポジトリのステータスによって決定されます。 これは、カーネルを構成するときにKERNELRELEASE設定するものKERNELRELEASEVERMAGIC_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_BUGCONFIG_KALLSYMSCONFIG_TRACEPOINTSCONFIG_JUMP_LABELCONFIG_TRACINGCONFIG_TRACINGCONFIG_EVENT_TRACINGCONFIG_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のブログ投稿の翻訳です。

Source: https://habr.com/ru/post/J331202/


All Articles