on the trail of module_init
ok for everyone’s information, i fancy anything ELF to an almost (but not quite) fanatic degree. my motivation for the activity below came when i straced insmod to see what kind of activity it does and there i encountered a few syscalls i haven’t had any chance to use nor to read about.
# strace insmod binfmt_mp3.ko ... create_module(NULL, 0) = -1 ENOSYS (Function not implemented) ... init_module(0x804b060, 3094, "") = 0 ...
grepping for module in unistd.h sort of paid off:
$ grep -i module /usr/include/asm/unistd.h #define __NR_create_module 127 #define __NR_init_module 128 #define __NR_delete_module 129 #define __NR_query_module 167
and just to confirm things …
$ file binfmt_mp3.ko binfmt_mp3.ko: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not strippedi decided i’d try following what happens to a module on module_init() as it’s the entry point of lkms.
linux/init.h
typedef int (*initcall_t)(void);
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
#define device_initcall(fn) __define_initcall("6",fn)
#define __initcall(fn) device_initcall(fn)
#define module_init(x) __initcall(x)
apparently, initcalls also have levels.
linux/init.h
#define core_initcall(fn) __define_initcall("1",fn)
#define postcore_initcall(fn) __define_initcall("2",fn)
#define arch_initcall(fn) __define_initcall("3",fn)
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
#define late_initcall(fn) __define_initcall("7",fn)
and based on the macros’ above, a module_init(function) roughly creates a function pointer entry “__initcall_function” in a section called “.initcall6.init”. __attribute_used__ tells the compiler to generate code even if it appears like the function isn’t refenced.
i have this object file from drivers/block/floppy.o as reference:
$ objdump -t floppy.o | grep -i initcall 00000000 l d .initcall6.init 00000000 .initcall6.init 00000000 l O .initcall6.init 00000004 __initcall_floppy_init
yeah, i couldn’t find any initcallx.init section for lkms though. so i suppose initcall is something that works only if your module is compiled in-kernel.
the kernel linker script dedicates a special block for the “.initcallX.init” sections. bounded by __initcall_start and __initcall_end respectvely.
/arch/i386/kernel/vmlinux.lds
__initcall_start = .;
.initcall.init : {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;
looking for references to those two variables led me to init/main.c where the kernel traverses the .initcall*.init function pointers calling each function till __initcall_end.
init/main.c
extern initcall_t __initcall_start[], __initcall_end[];
init/main.c::static void __init do_initcalls(void)
initcall_t *call;
int count = preempt_count();
for (call = __initcall_start; call < __initcall_end; call++) {
char *msg;
if (initcall_debug) {
printk(KERN_DEBUG "Calling initcall 0x%p", *call);
print_fn_descriptor_symbol(": %s()", (unsigned long) *call);
printk("\n");
}
(*call)();
...
the do_initcall() function itself is found in another init section “.init.text”
that’s all i know for the time being.
