Linux Kernel Exported Symbols

Linux kernel exported symbols

The characters in the second column have the same meaning they do in the output from nm:

D
d The symbol is in the initialized data section.

S
s The symbol is in an uninitialized data section for small objects.

T
t The symbol is in the text (code) section.

Uppercase symbols are global/exported; lowercase are local unexported symbols.

What's meaning of EXPORT_SYMBOL in Linux kernel code?

It makes a symbol accessible to dynamically loaded modules (provided that said modules add an extern declaration).

Not long ago, someone asked how to use it.

kernel exported symbols vs global symbols vs static global?

Conceptually, using static keyword with function declaration means internal linkage -- so such function is only visible within single translation unit (*.o file). This may involve inlining of that function (in which case it will be unusable further), but since EXPORT_SYMBOL() takes address of static function, compiler should disable inlining optimization.

Implementation is a bit more complicated. This internal and external linkage rules are only apply to static ld linker which works when vmlinux or kernel module is built. Normally symbol with external linkage is added to symtab ELF section and when dynamic linker ld.so loads shared object it reads that section.

But when module is loaded Linux Kernel uses separate symbol table ksymtab. EXPORT_SYMBOL() adds symbol to that table, but this process is completely transparent to compiler-linker toolchain thus it is not related with internal and external linkage at all.

Kernel module using exported symbols depending on which other module is loaded

You can achieve this by not using exported symbols directly, but by looking for them in the runtime using kallsyms_lookup_name(). That way your module X will not have explicit dependencies on symbols exported by modules Y1 and Y2, so it will be able to load even if one (or even both) of those modules are not loaded.

This also means that your module will be responsible for checking that all the symbols required for its operation are present. It will also need to manually increment refcount for modules providing those symbols via try_module_get(), and then decrement it on module exit via module_put().

Proper way of getting the address of non-exported kernel symbols in a Linux kernel module

Disclaimer: using non-exported symbols is in general not a good idea, so you should only do it for educational purposes, not for production-ready modules/drivers.

Before Linux v5.7, you indeed would have used kallsyms_lookup_name() to look-up non-exported kernel symbols from a module. See How do I access any kernel symbol in a kernel module? if you want to know how.

However, the symbol stopped being exported in v5.7 because nobody was using it outside of core kernel code, and it was just there to be abused by modules to find and use other non-exported symbols. Here's also a relevant LWN article on this. Nowadays there isn't really a "proper way" to work around this problem, but there a number of different "hacks" that you could consider.

The following approaches cover both kernel functions and global objects (i.e. global variables):

  1. If you are already compiling the kernel, you can add EXPORT_SYMBOL() after the definition of the symbol(s) you are interested in. This is the simplest option given you are willing to modify the kernel and build a custom one. You could also export kallsyms_lookup_name() in kernel/kallsyms.c and then use that, if you really want.

  2. You can use an unsigned long module parameter, passing the needed symbol address (taken from /proc/kallsyms) when loading the module, and then cast it to the appropriate type:

    static unsigned long addr;
    module_param_named(addr, addr, ulong, 0);
    MODULE_PARM_DESC(addr, "Address of the `foo` symbol");

    static <type_of_foo_here> *foo_ptr;
    // Examples:
    // int foo(char *) -> int (*foo_ptr)(char *)
    // unsigned long foo -> unsigned long *foo_ptr

    static int __init mymodule_init(void)
    {
    foo_ptr = (typeof(foo_ptr))addr;
    // ...
    return 0;
    }

    Then you'd be able to do something like this:

    sudo insmod mymodule.ko addr=0x$(sudo grep ' some_symbol_name' /proc/kallsyms | cut -d' ' -f1)
  3. If your kernel supports kprobes, you can [ab]use a kprobe to make the kernel lookup a symbol for you through kprobe_register(). This approach is detailed in this other answer. Due to the intended usage of kprobes, this will only work for functions, however you can simply find kallsyms_lookup_name() first, and then use that to lookup any other symbol.

    In order for this to work, your kernel needs to be configured with CONFIG_KPROBES=y as well as CONFIG_KALLSYMS=y (and possibly also CONFIG_KALLSYMS_ALL=y), since register_kprobe() uses exactly kallsyms_lookup_name() under the hood. Automatic symbol address resolution for kprobes has been supported since Linux v2.6.16.

  4. For functions only, you can also consider re-implementing the functionality in your module. For example, task_statm() implemented in fs/proc/task_mmu.c is a rather small function that only uses other exported functions, so "borrowing" it for use in your module would be rather straightforward.

    Chances are that you want to call some non-exported function for a more specific purpose than what it was designed for. In such case, a good idea would be to look at the kernel source to understand how it works, and only re-implement the bare minimum needed for your module.

  5. Finally, you could technically open and read /proc/kallsyms from kernel space using filp_open() + kernel_read() from <linux/fs.h>, though this would probably be the objectively worst solution overall.

Can the loading of exported symbols be delayed?

Such situations are often resolved using callbacks.

Suppose module A exports the functions to register/unregister the callbacks. B and/or C use these functions and provide the appropriate callbacks to A. When it is needed, A checks if the callbacks are set and calls them.

Something like this (without error handling and locking for simplicity):

/* Module A */
struct a_ops /* Better to define struct a_ops in a header file */
{
void (*needed_func)(void);
void (*another_needed_func)(void);
};
...
struct a_ops ops = {
.needed_func = NULL;
.another_needed_func = NULL;
};
...
int a_register_needed_funcs(struct a_ops *a_ops)
{
ops.needed_func = a_ops->needed_func;
ops.another_needed_func = a_ops->another_needed_func;
}
EXPORT_SYMBOL(a_register_needed_funcs);

void a_unregister_needed_funcs()
{
ops.needed_func = NULL;
ops.another_needed_func = NULL;
}
EXPORT_SYMBOL(a_unregister_needed_funcs);

...
/* Call the specified callbacks when needed: */
void do_something(void)
{
if (ops.needed_func != NULL) {
ops.needed_func();
}
else {
/* the callback is not set, handle this: report error, ignore it or
* do something else */
...
}
}
...

/* Modules B and C */
/* Their code #includes the file where struct a_ops is defined.
* The module registers the callbacks, for example, in its init function
* and unregister in exit function. */
...
static void func(void)
{
...
}

static void another_func(void)
{
...
}

struct a_ops my_funcs = {
.needed_func = func;
.another_needed_func = another_func;
};

int __init my_module_init(void)
{
...
result = a_register_needed_funcs(&my_funcs);
...
}
void __exit my_module_exit(void)
{
...
a_unregister_needed_funcs();
...
}

This is similar to file operations and many other callback operations in the kernel. Suppose a user wants to read from, say, a character device maintained by a custom driver. The kernel proper (VFS, to be exact) receives the request but cannot handle it itself. It forwards the request to that custom driver that has registered its file operation callbacks for that device. In turn, the driver uses the functions exported by the kernel proper, like cdev_add(), etc.



Related Topics



Leave a reply



Submit