flat assembler
Message board for the users of flat assembler.

Index > Linux > Linux LKM.. from assembly perspective.

Author
Thread Post new topic Reply to topic
nocona



Joined: 04 Aug 2007
Posts: 35
nocona 18 Aug 2007, 04:25
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 :

CONFIG_PREEMPT
CONFIG_SMP
CONFIG_DEBUG_SPINLOCK
CONFIG_DEBUG_LOCK_ALLOC
CONFIG_GENERIC_BUG
CONFIG_MODULE_UNLOAD
CONFIG_NR_CPUS
CONFIG_X86_L1_CACHE_SHIFT
CONFIG_KALLSYMS

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
Code:
#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)
Code:
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.

Code:
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-($-.name)
      align 8:0
       .mkobj                  module_kobject64
    .param_attrs            dq ?
        .modinfo_attrs          dq ?
        .version                dq ?
        .srcversion             dq ?
        .holders_dir            dq ?
        .syms                   dq ?
        .num_syms               dd ?
        align 8:0
       .crcs                   dq ?
        .gpl_syms               dq ?
        .gpl_num_syms           dd ?
        align 8:0
       .gpl_crcs               dq ?
        .unused_syms            dq ?
        .unused_num_syms        dd ?
        align 8:0
       .unused_crcs            dq ?
        .unused_gpl_syms        dq ?
        .unused_gpl_num_syms    dd ?
        align 8:0
       .unused_gpl_crcs        dq ?
        .gpl_future_syms        dq ?
        .gpl_future_num_syms    dd ?
        align 8:0
       .gpl_future_crcs        dq ?
        .num_exentries          dd ?
        align 8:0
       .extable                dq ?
        .init                   dq init+0
   .module_init            dq ?
        .module_core            dq ?
        .init_size              dd ?
        .core_size              dd ?
        .init_text_size         dq ?
        .core_text_size         dq ?
        .unwind_info            dq ?
        .arch                   mod_arch_specific64
 .unsafe                 dd ?
        .taints                 dd ?
if CONFIG_GENERIC_BUG=1
     .bug_list               list_head64
 .bug_table              dq ?
        .num_bugs               dq ?    
end if
if CONFIG_MODULE_UNLOAD=1
     if MODULE_REF64_ALIGNMENT > 8
            align MODULE_REF64_ALIGNMENT
        end if
      .ref                    module_ref64
        rb .ref.SIZE*(CONFIG_NR_CPUS-1)
     .modules_which_use_me   list_head64
 .waiter                 dq ?
        .exit                   dq cleanup+0
end if
if CONFIG_KALLSYMS=1  
    .symtab                 dq ?
        .num_symtab             dq ?
        .strtab                 dq ?
        .sect_attrs             dq ?    
end if
  .percpu                 dq ?
        .arg                    dq ?
        if MODULE_REF64_ALIGNMENT > 8
            align MODULE_REF64_ALIGNMENT
        end if
      .SIZE=$-.state
}

;/-----------------------------------------------------------------------------\
;   Description : align offset according to alignment value and fill byte
;                 specified by valueSpec. Format of usage is
;                   align <alignValue>[':'<fillByte>]
;                 e.g
;                   align 32:0x90
;                   align 4
;                 default for fillByte is 0.
;\-----------------------------------------------------------------------------/

macro align @valueSpec* {
local ..pad
  match .value:.byte, @valueSpec \{
    virtual
      align .value
      ..pad = $ - $$
    end virtual
    times ..pad db .byte and 0xff
  define do
  \}
  match =do .value, do @valueSpec \{
    virtual
      align .value
      ..pad = $ - $$
    end virtual
    rb ..pad
  \}
  restore do
}

macro extern [@sym] {extrn @sym}
extrn fix extern

format elf64
extrn    printk, param_set_int, param_get_int, param_get_string, \
  param_set_copystring

section ".text" executable align 4
init_module:
        mov     esi, [lkm_param1]
   mov     rdi, msg_init
       call    printk
      mov     rdi, msg_init2
      mov     rsi, lkm_param2_buffer
      call    printk
      xor     eax, eax
    ret

cleanup: 
    jmp     init_module

msg_init db "<1>lkm_param1 = %d", 10, 0
msg_init2 db "<1>lkm_param2 = %s", 10, 0

param_name_lkm_param1 db "lkm_param1", 0
param_name_lkm_param2 db "lkm_param2", 0

if MODULE_REF64_ALIGNMENT>8
        section ".gnu.linkonce.this_module" align MODULE_REF64_ALIGNMENT
else
  section ".gnu.linkonce.this_module" align 8
end if

__this_module module64 "lkm2", init_module, cleanup

section ".modinfo" align 1
;db "license=foo", 0
db "author=nocona", 0
db "vermagic=2.6.22-kamikaze5 SMP mod_unload ", 0
db "description=test module written using fasm", 0

section "__param" align 8
lkm_param1_parameter:
        dq param_name_lkm_param1
    dd 644o
align 8:0
    dq param_set_int
    dq param_get_int
    dq lkm_param1
lkm_param2_parameter:
  dq param_name_lkm_param2
    dd 644o
align 8:0
    dq param_set_copystring
     dq param_get_string
 dq lkm_param2   ;this is the address of kparam_string structure

section ".data" writeable align 8
lkm_param2_buffer db "default string", 0
rb 32-($-lkm_param2_buffer)
align 8
lkm_param1 dd 1
align 8
lkm_param2 dd 32
           align 8:0
       dq lkm_param2_buffer
    
Post 18 Aug 2007, 04:25
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2023, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.