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> #include <linux/kernel.h> #include <linux/init.h> 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 (...) int (*init)(void); (...) #ifdef CONFIG_GENERIC_BUG (...) #endif #ifdef CONFIG_KALLSYMS (...) #endif (...) (... plenty more ifdefs ...) #ifdef CONFIG_MODULE_UNLOAD (...) 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
この記事は、
Mike Hommeyのブログ投稿の翻訳です。