Message board for the users of flat assembler.
> Linux > Linux LKM.. from assembly perspective.
Linux LKM (loadable kernel module) is a relocateable elf file. This type of file can be created directly by using "format elf" or format elf64" fasm's directive. To build a valid LKM with fasm, we need to create at least 2 sections : one for our code/data and another is a special section named ".gnu.linkonce.this_module", which only content is the "module" structure. Within this structure, only 3 members need to be initialized at assembly time : the "name", "init" and "exit" members.
The "name" member is the internal name of our module (must be unique at runtime).
The "init" member must be set to our module's initialization routine address, and its C prototype is "int init(void)".
The "exit" member should be set to our module's termination routine address, and its C prototype is "void exit(void)"
If we read the definition of "struct module" in kernel source header "module.h", we can see that it will be different across different kernel configuration. The offset of "init" member also differs in different kernel configuration. Also, if we configured our kernel to not support module unloading, the "exit" member should not exist in our module strcuture. In particular, these kernel configuration affect our "module" structure :
Apart than this mandatory section, there are other sections that our module can have, depending on our needs. The '.modinfo" section is the way to provide information about our module (e.g can be retrieve using "modinfo" program). It contains one or more <tag>=<value> strings. For example, to provide license information, you can include the null terminated string "license=whatever your license" in this section. Note that absence of "license" info from our module make the kernel tainted (i.e the kernel assume our module isn't GPL compatible). Although the ".modinfo" section is not mandatory, but when it exist, there is one information in this section that, when inspected by kernel can cause loading failure if the check failed. It is the "vermagic" info. When this info exist, it must be the same as VERMAGIC_STRING, which is defined as
#define VERMAGIC_STRING UTS_RELEASE " " MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT MODULE_VERMAGIC_UNLOAD MODULE_ARCH_VERMAGIC
for example, in my kernel 2.6.22-kamikaze5 configured with smp and module unloading support, I can put "vermagic=2.6.22-kamikaze5 SMP mod_unload " in ".modinfo" section of my module. Note that modprobe can force loading of module where its vermagic is different from the running kernel using the option --force or --force-vermagic.
If our module accept parameter, then it must have the "__param" section. This section consists of array of kernel_param structure, which is defined like below (example for 64-bit)
struc kernel_param .name dq ? .perm dd ? align 8 .set dq ? .get dq ? .arg dq ?
it is easy to understand this structure, where "name" is the address of null terminated string, specifying the name of this parameter. "perm" is the file permission. This is relevant if we allow users to access this parameter in /sys/module/<module_name>/parameters. "set" and "get" is the setter and getter function for the variable, respectively. The kernel already export some predefined setter and getter functions for predefined types, e.g if our variable is of type int, then we should set getter and setter function to the "param_get_int" and "param_set_int" symbols. "arg" is the address of our variable. Whenever we setup a kernel_param structure, we usually also setup a "parmtype=<param_name>:<type>" info in ".modinfo" section, but it's not mandatory.
Others section which is also useful is "__ksymtab" section which allows us to export symbols to be used by other modules. There also "__ksymtab_gpl", "__ksymtab_gpl_future", "__ksymtab_unused" and "__ksymtab_unused_gpl" that has the same sections layout but each has different meanings (for example, exported symbols in __ksymtab_gpl can only be used by others that are licensed under GPL or compatible).
The example code below show an example of a very simple lkm written in fasm. Due to fasm's bug as described here, this code can only be run (loaded) correctly by applying the rela patch here to the fasm's code and assembling it with the new fasm binary.
What this code does is printing the value of its 2 parameters "lkm_param1" (of type int) and "lkm_param2" (of type string) at init and exit routine. Assemble normally with patched fasm, and insmod the object file to load the module, e.g "fasm lkm.asm" then "insmod lkm.o".
You can change the value of the 2 parameter with insmod, e.g "insmod lkm.o lkm_param1=99 lkm_param2="anything"" or at runtime using the /sys interface, e.g "echo 1 > /sys/module/lkm/parameters/lkm_param1". Note that the kernel configuration variable at the beginning of the code must match to your kernel configuration) and this is strictly for 64-bit machine.
MODULE_NAME_LEN64 = 56 KOBJ_NAME_LEN64 = 20 CONFIG_PREEMPT=1 CONFIG_SMP=1 CONFIG_DEBUG_SPINLOCK=0 CONFIG_DEBUG_LOCK_ALLOC=0 CONFIG_GENERIC_BUG=1 CONFIG_MODULE_UNLOAD=1 CONFIG_NR_CPUS=2 CONFIG_X86_L1_CACHE_SHIFT=6 CONFIG_KALLSYMS=1 MODULE_REF64_ALIGNMENT = 1 shl CONFIG_X86_L1_CACHE_SHIFT struc mod_arch_specific64 struc list_head64 .next dq ? .prev dq ? .SIZE=$-.next struc module_kobject64 .kobj kobject64 .mod dq ? .drivers_dir dq ? .SIZE=$-.kobj struc kobject64 .k_name dq ? .name rb KOBJ_NAME_LEN64 ;align 4 0 .kref kref64 .entry list_head64 .parent dq ? .kset dq ? .ktype dq ? .dentry dq ? .poll wait_queue_head64 struc kref64 .refcount dd ? struc module_ref64 .count dq ? align MODULE_REF64_ALIGNMENT .SIZE = $-.count struc wait_queue_head64 .lock spinlock_t64 .task_list list_head64 struc spinlock_t64 .raw_lock dd ? if CONFIG_PREEMPT=1 & CONFIG_SMP=1 .break_lock dd ? end if if CONFIG_DEBUG_SPINLOCK=1 .magic dd ? .owner_cpu dd ? align 8 0 .owner dq ? end if if CONFIG_DEBUG_LOCK_ALLOC .dep_map lockdep_map64 end if struc lockdep_map64 .key dq ? .class_cache dq ? .name dq ? struc module64 name, init, cleanup .state dq ? .list list_head64 .name db name rb MODULE_NAME_LEN64-