読者です 読者をやめる 読者になる 読者になる

Linuxのカーネルモジュールがロードされるアドレス範囲

カーネルモジュールがロードされるアドレスって範囲決まってたはずだけど、自分で言っといてホントそうだったけ?とか思ったので確認。

調べるのはLinux 4.8。 まず、Documentation/x86/x86_64/mm.txtを確認して、以下のところをチェック。

 22 ffffffffa0000000 - ffffffffff5fffff (=1526 MB) module mapping space

で、この範囲にロードされるよねってとこです。

それでは、モジュールがどのように配置されるのかをコードで確認してみます。まずkernel/module.cのload_module()からみます。

この関数の最初のほうでlayout_and_allocate()を実行します。

3589         /* Figure out module layout, and allocate all the memory. */
3590         mod = layout_and_allocate(info, flags);

layout_and_allocate()ではmove_module()という関数を呼びます。

3266         /* Allocate and move to the final place */
3267         err = move_module(mod, info);

そして、ここで呼び出しているmodule_alloc()が実際にアドレスを指定してモジュール用のメモリを確保するところです。

3068 static int move_module(struct module *mod, struct load_info *info)
3069 {
3070         int i;
3071         void *ptr;
3072 
3073         /* Do the allocs. */
3074         ptr = module_alloc(mod->core_layout.size);

ただ、module_alloc()はアーキテクチャ依存な関数になります。module.cにあるmodule_alloc()はweak属性が付いているのでx86_64アーキテクチャにおいてはダミーです。

2688 void * __weak module_alloc(unsigned long size)
2689 {
2690         return vmalloc_exec(size);
2691 }

x86_64でのmodule_alloc()はarch/x86/kernel/module.cにあります。

 79 void *module_alloc(unsigned long size)
 80 {
 81         void *p;
 82 
 83         if (PAGE_ALIGN(size) > MODULES_LEN)
 84                 return NULL;
 85 
 86         p = __vmalloc_node_range(size, MODULE_ALIGN,
 87                                     MODULES_VADDR + get_module_load_offset(),
 88                                     MODULES_END, GFP_KERNEL | __GFP_HIGHMEM,
 89                                     PAGE_KERNEL_EXEC, 0, NUMA_NO_NODE,
 90                                     __builtin_return_address(0));
 91         if (p && (kasan_module_alloc(p, size) < 0)) {
 92                 vfree(p);
 93                 return NULL;
 94         }
 95 
 96         return p;
 97 }
 98 

ここでは__vmalloc_node_range()を使ってメモリを確保しますが、この時にアドレスの範囲を指定します。__vmalloc_node_range()の3番目の引数は開始アドレス、4番目が終了アドレスです。

MODULES_VADDRとMODULES_ENDはarch/x86/include/asm/pgtable_64_types.hで定義されています。

 67 #define MODULES_VADDR    (__START_KERNEL_map + KERNEL_IMAGE_SIZE)
 68 #define MODULES_END      _AC(0xffffffffff000000, UL)

__START_KERNEL_mapとKERNEL_IMAGE_SIZEはarch/x86/include/asm/page_64_types.hで定義されています。

__START_KERNEL_mapはこうです。

 46 #define __START_KERNEL_map      _AC(0xffffffff80000000, UL)

KERNEL_IMAGE_SIZEは.configの設定次第ですが、うちはCONFIG_RANDOMIZE_BASEが有効なので60行目のほうを使います。

 59 #if defined(CONFIG_RANDOMIZE_BASE)
 60 #define KERNEL_IMAGE_SIZE       (1024 * 1024 * 1024)
 61 #else
 62 #define KERNEL_IMAGE_SIZE       (512 * 1024 * 1024)
 63 #endif

なんで、こんな感じになります。

>>> hex(int("0xffffffff80000000", 16) + (1024 * 1024 * 1024))
'0xffffffffc0000000'

ということで、カーネルモジュールは0xffffffffc0000000〜0xffffffffff000000に置かれると。

実際にこれを確認するのは/proc/modulesを見れば良いので、こんな感じのスクリプトでチェックできます。

#!/usr/bin/env python

with open('/proc/modules', 'r') as f:
    lines = f.readlines()
    for line in lines:
        data = line.split(' ')
        name = data[0]
        if (data[2] != '0'):
            addr = int(data[len(data) - 1].strip(), 16)
            if addr >= 0xffffffffc0000000 and addr < 0xffffffffff5fffff:
                print("%s:%x is in module mapping space " % (name, addr))
            else:
                print("%s:%x is not in module mapping space" % (name, addr))

stack traceとって流れを確認するならこんなsystemtapスクリプト(これくらいだとbccよりかんたんなのでこっちにしました)を動かして、適当なモジュールをinsmodすればOKです。

#!/usr/bin/env stap

probe begin {
        printf("Start\n")
}

probe kernel.function("module_alloc") {
        printf("%s\n", symfileline(addr()));
        print_backtrace()
        exit()
}

probe end {
        printf("Done\n")
}

こんな結果になります。

masami@saga:~/mod$ sudo stap -g module_alloc.stp
Start
0xffffffffa705d8e0
 0xffffffffa705d8e0 : module_alloc+0x0/0xd0 [kernel]
 0xffffffffa7106cd6 : load_module+0x1576/0x2620 [kernel] (inexact)
 0xffffffffa7204a31 : __vfs_read+0xe1/0x130 [kernel] (inexact)
 0xffffffffa720578c : vfs_read+0x11c/0x130 [kernel] (inexact)
 0xffffffffa720c238 : kernel_read_file+0x1e8/0x210 [kernel] (inexact)
 0xffffffffa720c2a9 : kernel_read_file_from_fd+0x49/0x80 [kernel] (inexact)
 0xffffffffa7107ff4 : SyS_finit_module+0xe4/0x120 [kernel] (inexact)
 0xffffffffa75dc7f2 : entry_SYSCALL_64_fastpath+0x1a/0xa4 [kernel] (inexact)
Done

ということで、一応合ってたようですε-(´∀`*)ホッ