GPLライセンスの実装
GPLライセンスはOSSで有り続けるためのGNUが提唱するライセンスだと認識している。GPLライセンスのコードを利用して作成されたものはソースを公開しなければならないという。昨年マレーシアで年1度行われるOSSのカンファランスで、GNUのリチャード・ストールマンがきていてGPLライセンスの話をした。そこにシンガポールのレッド・ハットのなんとかモリスって言う人と激しく言い争っていたのを思い出す。あとで調べた分かった事だが、このモリスって言う人カーネルメンテナで有名な人らしい。言ってる詳細は私の英語力ではフォローしえるものでないのだが、ソース公開というGPLライセンスにLinuxを商用で使っている人にとっては大きなネックとなるようで、たぶんこの辺りの激論か?そりゃソース公開だもんな。GPLライセンスとしなければ大抵のLinuxシンボルは使えない。そうなるとGPLライセンスとしなければならない。そうなるとソース公開ってことになる。まあそれはともかくこのGPLライセンスの実装をみてみようと思う。
(リチャード・ストールマンって変わったおっさんって感じ。はい)
GPLライセンスとするにはMODULE_LICENSE( "GPL" )マクロを記述するだけである。これで.modinfoセクションにlicense=GPLが埋め込まれる。EXPORT_SYMBOL_GPLは__EXPORT_SYMBOLマクロをsecをGPLとして展開され、大筋では__ksymtab_stringsセクションにシンボル名が(以下のサンプルではbbb2.aa2.bbb1.aa1)、__ksymtabセクション(GPLの場合__ksymtab_gpl)セクションにstruct kernel_symbol(valueにはアドレス、*name;はシンボル名のポインター)の形態で埋め込まれる。
__ksymtab_gplおよび__ksymtabは以下のとうり。シンボル名のアドレスは__ksymtab_stringsセクション内のオフセットとなる。
モジュールはload_module関数でシステムに組み込まれる。モジュールそのものはElfフォーマットであり、通常はリンカーがアドレス参照を解決するわけだが、動的にロードされる故本関数内でアドレス参照が行われる。
モジュールそのもは module構造体で管理される。そしてload_module関数はElfファーマットであるモジュールファイルを読み込んで、この構造体の各メンバに適切な設定を施す。そしてスタティック変数modulesをヘッドとするリストにつなぐ。
この構造体でGPLライセンスにかかる処理はmodule構造体のstruct kernel_symbol *gpl_symsメンバーである。先の__ksymtab_gpl内のシンボルはこのメンバーに設定される。ちなみに通常のシンボルはstruct kernel_symbol *symsに設定される。
モジュールが外部シンボルのアドレスを解決する必要があると、それがGPLライセンスとするモジュールならすべてのモジュールのgpl_symsを参照していくことでGPLライセンスにかかる機能を実現している。シンボル参照解決は simplify_symbols()->resolve_symbol()->find_symbol()->each_symbol()となり、list_for_each_entryマクロでmodulesのリストをたどって全モジュールのシンボルをチェックしている。このときstruct symsearch arr[]で、GPLライセンス等の有無によって、検索するアドレスが決定される。(たぶん。)
(リチャード・ストールマンって変わったおっさんって感じ。はい)
GPLライセンスとするにはMODULE_LICENSE( "GPL" )マクロを記述するだけである。これで.modinfoセクションにlicense=GPLが埋め込まれる。EXPORT_SYMBOL_GPLは__EXPORT_SYMBOLマクロをsecをGPLとして展開され、大筋では__ksymtab_stringsセクションにシンボル名が(以下のサンプルではbbb2.aa2.bbb1.aa1)、__ksymtabセクション(GPLの場合__ksymtab_gpl)セクションにstruct kernel_symbol(valueにはアドレス、*name;はシンボル名のポインター)の形態で埋め込まれる。
#define __EXPORT_SYMBOL(sym, sec) \ extern typeof(sym) sym; \ __CRC_SYMBOL(sym, sec) \ static const char __kstrtab_##sym[] \ __attribute__((section("__ksymtab_strings"), aligned(1))) \ = MODULE_SYMBOL_PREFIX #sym; \ static const struct kernel_symbol __ksymtab_##sym \ __used \ __attribute__((section("__ksymtab" sec), unused)) \ = { (unsigned long)&sym, __kstrtab_##sym } struct kernel_symbol { unsigned long value; <- アドレス解決されたアドレス(たぶん) const char *name; <- シンボルのオフセット };実際ダンプしたものを見たほうが手っ取り早い。
MODULE_DESCRIPTION("gpl test"); MODULE_LICENSE("GPL"); int aa1 = 1; int aa2 = 1; int bbb1 = 1; int bbb2 = 1; EXPORT_SYMBOL(aa1); EXPORT_SYMBOL(bbb1); EXPORT_SYMBOL_GPL(aa2); EXPORT_SYMBOL_GPL(bbb2); static int smallmod_init_module(void) { return 0; } static void smallmod_cleanup_module(void) { } module_init(smallmod_init_module); module_exit(smallmod_cleanup_module); [root@localhost test]# objdump -s smallmod.ko smallmod.ko: file format elf32-i386 Contents of section .note.gnu.build-id: 0000 04000000 14000000 03000000 474e5500 ............GNU. 0010 bdfe48d4 c6aa701c 278ac144 e30925fe ..H...p.'..D..%. 0020 3282432a 2.C* Contents of section .text: 0000 5531c089 e55dc355 89e55dc3 U1...].U..]. Contents of section __ksymtab_gpl: 0000 00000000 00000000 00000000 05000000 ................ Contents of section __ksymtab: 0000 00000000 09000000 00000000 0e000000 ................ Contents of section .modinfo: 0000 6c696365 6e73653d 47504c00 64657363 license=GPL.desc 0010 72697074 696f6e3d 67706c20 74657374 ription=gpl test 0020 00000000 73726376 65727369 6f6e3d35 ....srcversion=5 0030 45453237 33433832 33444544 44413846 EE273C823DEDDA8F 0040 34364331 30300000 64657065 6e64733d 46C100..depends= 0050 00000000 7665726d 61676963 3d322e36 ....vermagic=2.6 0060 2e32372e 33302d74 6573742e 73797363 .27.30-test.sysc 0070 616c6c2e 66732053 4d50206d 6f645f75 all.fs SMP mod_u 0080 6e6c6f61 64203638 3620344b 53544143 nload 686 4KSTAC 0090 4b532000 KS . Contents of section __ksymtab_strings: 0000 62626232 00616132 00626262 31006161 bbb2.aa2.bbb1.aa 0010 3100 1. Contents of section .data: 0000 01000000 01000000 01000000 01000000 ................ Contents of section .gnu.linkonce.this_module: 0000 00000000 00000000 00000000 736d616c ............smal 0010 6c6d6f64 00000000 00000000 00000000 lmod............
__ksymtab_gplおよび__ksymtabは以下のとうり。シンボル名のアドレスは__ksymtab_stringsセクション内のオフセットとなる。
Contents of section __ksymtab_gpl: 0000 00000000 00000000 00000000 05000000 ................ val bbb2 val aa2 Contents of section __ksymtab: 0000 00000000 09000000 00000000 0e000000 ................ val bbb1 val aa1すなわちEXPORT_SYMBOL_GPLすると、そのシンボルは__ksymtab_gplに設定されていくことになるわけだ。
モジュールはload_module関数でシステムに組み込まれる。モジュールそのものはElfフォーマットであり、通常はリンカーがアドレス参照を解決するわけだが、動的にロードされる故本関数内でアドレス参照が行われる。
モジュールそのもは module構造体で管理される。そしてload_module関数はElfファーマットであるモジュールファイルを読み込んで、この構造体の各メンバに適切な設定を施す。そしてスタティック変数modulesをヘッドとするリストにつなぐ。
この構造体でGPLライセンスにかかる処理はmodule構造体のstruct kernel_symbol *gpl_symsメンバーである。先の__ksymtab_gpl内のシンボルはこのメンバーに設定される。ちなみに通常のシンボルはstruct kernel_symbol *symsに設定される。
モジュールが外部シンボルのアドレスを解決する必要があると、それがGPLライセンスとするモジュールならすべてのモジュールのgpl_symsを参照していくことでGPLライセンスにかかる機能を実現している。シンボル参照解決は simplify_symbols()->resolve_symbol()->find_symbol()->each_symbol()となり、list_for_each_entryマクロでmodulesのリストをたどって全モジュールのシンボルをチェックしている。このときstruct symsearch arr[]で、GPLライセンス等の有無によって、検索するアドレスが決定される。(たぶん。)
static bool each_symbol(bool (*fn)(const struct symsearch *arr, struct module *owner, unsigned int symnum, void *data), void *data) { struct module *mod; 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; list_for_each_entry(mod, &modules, list) { struct symsearch arr[] = { { mod->syms, mod->syms + mod->num_syms, mod->crcs, NOT_GPL_ONLY, false }, { mod->gpl_syms, mod->gpl_syms + mod->num_gpl_syms, mod->gpl_crcs, GPL_ONLY, false }, { mod->gpl_future_syms, mod->gpl_future_syms + mod->num_gpl_future_syms, mod->gpl_future_crcs, WILL_BE_GPL_ONLY, false }, #ifdef CONFIG_UNUSED_SYMBOLS { mod->unused_syms, mod->unused_syms + mod->num_unused_syms, mod->unused_crcs, NOT_GPL_ONLY, true }, { mod->unused_gpl_syms, mod->unused_gpl_syms + mod->num_unused_gpl_syms, mod->unused_gpl_crcs, GPL_ONLY, true }, #endif }; if (each_symbol_in_section(arr, ARRAY_SIZE(arr), mod, fn, data)) return true; } return false; }
補足
get_modinfo関数はmodinfoセクションから引数で指定するキーの設定値を取得する。vermagicを指定してマジックナンバーを取得する。Linuxのバージョンとモジュールのマジックナンバーが同じでないとロードできないようになっているが、マジックナンバーが設定されていないと--force オプションを指定するとロード可能となる。ただしカーネルにCONFIG_MODULE_FORCE_LOADを設定してコンパイルする必要あり。modmagic = get_modinfo(sechdrs, infoindex, "vermagic"); /* This is allowed: modprobe --force will invalidate it. */ if (!modmagic) { err = try_to_force_load(mod, "magic"); if (err) goto free_hdr; } else if (!same_magic(modmagic, vermagic, versindex)) { printk(KERN_ERR "%s: version magic '%s' should be '%s'\n", mod->name, modmagic, vermagic); err = -ENOEXEC; goto free_hdr; } static int try_to_force_load(struct module *mod, const char *symname) { #ifdef CONFIG_MODULE_FORCE_LOAD if (!(tainted & TAINT_FORCED_MODULE)) printk("%s: no version for \"%s\" found: kernel tainted.\n", mod->name, symname); add_taint_module(mod, TAINT_FORCED_MODULE); return 0; #else return -ENOEXEC; #endif }