歡迎光臨
每天分享高質量文章

linux 內核模塊鏈接過程

學無止境,一直在做內核開發,卻從來沒有仔細想過這個過程。因為所有的程式都有個編譯,鏈接的過程。在linux內核模塊中,我們可以使用很多內核export出的函式,來實現我們的功能,作為內核附屬功能的擴展。但是這些export出來的函式在hook的時候是遠遠不夠用的,所以我們往往還要獲取一些沒有export出來的函式。使用這些沒有export出來的函式就要我們清楚的知道內核模塊是如何link的,也就是如何尋找符號的。

kallsyms_lookup_name

首先來談下這個函式,我們往往發現kallsyms_lookup_name能找到很多沒有匯出的符號。它的原理和insmod插入時的尋找符號是不一樣的,它取決於一個config(CONFIG_KALLSYMS),開啟了這個開關就會從一個陣列中找到很多符號。這個開關一個是給/proc/kallsysms用,還有一個是當kernel panic的時候,打出stack tree。所以kallsyms_lookup_name只有代碼段的符號(匯出和沒有匯出的都有),這樣會導致一個初始化的全域性變數的符號是通過kallsyms_lookup_name找不到的。

內核模塊中符號的鏈接過程

內核模塊載入後,解析模塊中符號的函式在

kernel/module.c::find_symbol->each_symbol。

  • 第一步會從一個叫做ksymtab的段中找符號。

  • 如果沒有找到就會去遍歷所有的內核模塊,看有沒有匯出的符號。

如果都沒有找到,就會報找不到函式的錯誤。口說無憑,看代碼:

 1//尋找符號的入口函式
 2const struct kernel_symbol *find_symbol(const char *name,
 3                    struct module **owner,
 4                    const unsigned long **crc,
 5                    bool gplok,
 6                    bool warn)
 7{
 8    struct find_symbol_arg fsa;
 9
10    fsa.name = name;
11    fsa.gplok = gplok;
12    fsa.warn = warn;
13
14    if (each_symbol_section(find_symbol_in_section, &fsa;)) {
15        if (owner)
16            *owner = fsa.owner;
17        if (crc)
18            *crc = fsa.crc;
19        return fsa.sym;
20    }
21
22    pr_debug("Failed to find symbol %s\n", name);
23    return NULL;
24}
25EXPORT_SYMBOL_GPL(find_symbol);

在這個函式中首先會從ksymtab段中去尋找,這個段中是在編譯期間,就會把kernel export出來的函式填充在vmlinux的這個段中的。

 1bool each_symbol_section(bool (*fn)(const struct symsearch *arr,
 2                    struct module *owner,
 3                    void *data),
 4             void *data)
 5{
 6    struct module *mod;
 7    static const struct symsearch arr[] = {
 8        { __start___ksymtab, __stop___ksymtab, __start___kcrctab,
 9          NOT_GPL_ONLY, false },
10        { __start___ksymtab_gpl, __stop___ksymtab_gpl,
11          __start___kcrctab_gpl,
12          GPL_ONLY, false },
13        { __start___ksymtab_gpl_future, __stop___ksymtab_gpl_future,
14          __start___kcrctab_gpl_future,
15          WILL_BE_GPL_ONLY, false },
16#ifdef CONFIG_UNUSED_SYMBOLS
17        { __start___ksymtab_unused, __stop___ksymtab_unused,
18          __start___kcrctab_unused,
19          NOT_GPL_ONLY, true },
20        { __start___ksymtab_unused_gpl, __stop___ksymtab_unused_gpl,
21          __start___kcrctab_unused_gpl,
22          GPL_ONLY, true },
23#endif
24    };
25
26//在上面的ksymtab段中找地址,是個陣列,找到了就傳回
27    if (each_symbol_in_section(arr, ARRAY_SIZE(arr), NULL, fn, data))
28        return true;
29//沒找到就會遍歷所有的內核模塊,找export出來的符號
30    list_for_each_entry_rcu(mod, &modules;, list) {
31        struct symsearch arr[] = {
32            { mod->syms, mod->syms + mod->num_syms, mod->crcs,
33              NOT_GPL_ONLY, false },
34            { mod->gpl_syms, mod->gpl_syms + mod->num_gpl_syms,
35              mod->gpl_crcs,
36              GPL_ONLY, false },
37            { mod->gpl_future_syms,
38              mod->gpl_future_syms + mod->num_gpl_future_syms,
39              mod->gpl_future_crcs,
40              WILL_BE_GPL_ONLY, false },
41#ifdef CONFIG_UNUSED_SYMBOLS
42            { mod->unused_syms,
43              mod->unused_syms + mod->num_unused_syms,
44              mod->unused_crcs,
45              NOT_GPL_ONLY, true },
46            { mod->unused_gpl_syms,
47              mod->unused_gpl_syms + mod->num_unused_gpl_syms,
48              mod->unused_gpl_crcs,
49              GPL_ONLY, true },
50#endif
51        };
52        if (each_symbol_in_section(arr, ARRAY_SIZE(arr), mod, fn, data))
53            return true;
54    }
55    return false;
56}
57EXPORT_SYMBOL_GPL(each_symbol_section);

 

註意:不管在內核模塊還是ksymtab段中,都必須是export的符號才能找到。對於找不到的符號要使用system.map或者vmlinux找到,然後將對應的符號替換為地址。

赞(0)

分享創造快樂