Loading content...
When you compile a Linux kernel module, the build system produces a file with the .ko extension—a kernel object file. This file is not just compiled code; it's a precisely structured container carrying everything the kernel needs to integrate new functionality at runtime. Understanding the .ko format reveals how Linux implements dynamic loading in practice.
The .ko file is an ELF (Executable and Linkable Format) file, the same format used for regular Linux executables and shared libraries. However, kernel modules include additional sections, specialized metadata, and licensing information that distinguish them from user-space programs.
This page dissects the .ko format in detail, examining each component that enables a kernel module to be dynamically loaded, initialized, and integrated with the running Linux kernel.
By the end of this page, you will understand the ELF format as applied to kernel modules, the specialized sections that carry module metadata, the build process that creates .ko files, and how to use tools to examine module contents and dependencies.
The Executable and Linkable Format (ELF) is the standard binary format for executables, object files, shared libraries, and kernel modules on Linux and most Unix-like systems. Understanding ELF is essential for working with kernel modules.
ELF file structure:
An ELF file consists of several hierarchical components:
12345678910111213141516171819202122232425262728
// ELF64 header structure (simplified)typedef struct { unsigned char e_ident[16]; // Magic number and platform info uint16_t e_type; // Object file type uint16_t e_machine; // Target architecture uint32_t e_version; // ELF version uint64_t e_entry; // Entry point virtual address uint64_t e_phoff; // Program header table offset uint64_t e_shoff; // Section header table offset uint32_t e_flags; // Processor-specific flags uint16_t e_ehsize; // ELF header size uint16_t e_phentsize; // Program header entry size uint16_t e_phnum; // Number of program headers uint16_t e_shentsize; // Section header entry size uint16_t e_shnum; // Number of section headers uint16_t e_shstrndx; // Section name string table index} Elf64_Ehdr; // ELF magic number: 0x7F 'E' 'L' 'F'// e_type values:// ET_REL = 1 (Relocatable file - .ko files are this type)// ET_EXEC = 2 (Executable file)// ET_DYN = 3 (Shared object file) // e_machine values:// EM_X86_64 = 62 (AMD x86-64)// EM_ARM = 40 (ARM)// EM_AARCH64 = 183 (ARM 64-bit)Key distinction: ET_REL files:
Kernel modules are relocatable object files (type ET_REL), not executables. This is crucial:
This relocation capability is what allows the kernel to load modules at different addresses each time, critical for security (KASLR) and memory management flexibility.
Examining an ELF header:
The readelf tool displays ELF structure:
$ readelf -h mymodule.ko
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Entry point address: 0x0
Start of section headers: 123456 (bytes into file)
Number of section headers: 34
Note that the entry point is 0—relocatable files don't have a fixed entry point; the loader determines where to start based on module metadata.
ELF replaced older formats like a.out (Unix) and COFF (System V). Windows uses PE (Portable Executable), and macOS uses Mach-O. Each has different structures, but all serve similar purposes: organizing code, data, and metadata for the loader.
A typical .ko file contains dozens of sections. Each section has a specific purpose, and the kernel module loader expects certain sections to be present and correctly formatted.
Standard ELF sections:
| Section | Purpose | Characteristics |
|---|---|---|
| .text | Machine code for module functions | Executable, read-only after loading |
| .rodata | Read-only data (strings, constants) | Non-executable, read-only |
| .data | Initialized global/static variables | Writable, non-executable |
| .bss | Uninitialized global/static variables | Zero-filled at load, writable |
| .symtab | Symbol table (function/variable names) | Used for linking and debugging |
| .strtab | String table for symbol names | Referenced by .symtab |
| .rela.text | Relocations for .text section | Patching instructions |
| .rela.data | Relocations for .data section | Patching data references |
Linux-specific sections:
Kernel modules contain additional sections that are Linux-specific, carrying metadata the module loader needs:
| Section | Purpose | Content |
|---|---|---|
| .modinfo | Module metadata | License, author, description, parameters, aliases |
| .init.text | Initialization code | Code executed once at module load |
| .exit.text | Cleanup code | Code executed at module unload |
| .init.data | Init-time data | Data used only during initialization |
| __versions | Symbol versioning (CRC) | Ensures ABI compatibility |
| .gnu.linkonce.this_module | Module descriptor | struct module instance |
| __param | Module parameters | Sysfs-exposed parameters |
12345678910111213141516171819202122
# List all sections in a module$ readelf -S mymodule.ko There are 34 section headers, starting at offset 0x1e4f0: Section Headers: [Nr] Name Type Address Off Size [ 0] NULL 0000000000000000 000000 000000 [ 1] .note.gnu.build-id NOTE 0000000000000000 000040 000024 [ 2] .text PROGBITS 0000000000000000 000070 001234 [ 3] .rela.text RELA 0000000000000000 016e80 000d08 [ 4] .init.text PROGBITS 0000000000000000 0012b0 000120 [ 5] .rela.init.text RELA 0000000000000000 017b88 000180 [ 6] .exit.text PROGBITS 0000000000000000 0013d0 000080 [ 7] .rodata PROGBITS 0000000000000000 001460 000340 [11] .modinfo PROGBITS 0000000000000000 001a20 0001a0 [12] __versions PROGBITS 0000000000000000 001bc0 000280 [17] .data PROGBITS 0000000000000000 002100 000040 [18] .gnu.linkonce.this_module PROGBITS 0000000000000000 002140 000380 [21] .bss NOBITS 0000000000000000 002500 000020 [25] .symtab SYMTAB 0000000000000000 002520 000f00 [26] .strtab STRTAB 0000000000000000 003420 000680Each section has flags indicating its properties: A (allocatable), W (writable), X (executable). The kernel loader uses these flags to set up memory protection. For example, .text has AX (allocatable + executable), while .data has WA (writable + allocatable).
The .modinfo section contains human-readable and machine-parseable metadata about the module. This information is crucial for module management, dependency resolution, and system documentation.
How .modinfo is generated:
Module metadata is declared in source code using macros:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alice Developer <alice@example.com>");
MODULE_DESCRIPTION("High-performance network driver");
MODULE_VERSION("1.0.0");
MODULE_ALIAS("pci:v00001234d00005678sv*sd*bc*sc*i*");
These macros expand to specially named string variables that the linker collects into the .modinfo section:
// Expansion of MODULE_LICENSE("GPL"):
static const char __UNIQUE_ID_license[]
__attribute__((section(".modinfo"), used, aligned(1)))
= "license=GPL";
12345678910111213141516171819202122232425262728
# View module information$ modinfo ./mynetdriver.ko filename: /path/to/mynetdriver.kolicense: GPLauthor: Alice Developer <alice@example.com>description: High-performance network driverversion: 1.0.0srcversion: A1B2C3D4E5F6G7H8I9J0alias: pci:v00001234d00005678sv*sd*bc02sc00i*alias: pci:v00001234d00009ABCsv*sd*bc02sc00i*depends: usbcoreretpoline: Yintree: Yname: mynetdriververmagic: 5.15.0-generic SMP mod_unload x86_64 # Raw hex dump of .modinfo section$ objdump -s -j .modinfo mynetdriver.ko Contents of section .modinfo: 0000 6c696365 6e73653d 47504c00 61757468 license=GPL.auth 0010 6f723d41 6c696365 20446576 656c6f70 or=Alice Develop 0020 6572203c 616c6963 65406578 616d706c er <alice@exampl 0030 652e636f 6d3e0064 65736372 69707469 e.com>.descripti 0040 6f6e3d48 6967682d 70657266 6f726d61 on=High-performa 0050 6e636520 6e657477 6f726b20 64726976 nce network driv 0060 65720076 65727369 6f6e3d31 2e302e30 er.version=1.0.0Critical modinfo fields:
GPL grants access to GPL-only kernel symbols. Other values (Proprietary, BSD, etc.) restrict available symbols.The kernel's symbol export mechanism enforces license restrictions. EXPORT_SYMBOL_GPL() symbols are only available to modules declaring a GPL-compatible license. Attempting to load a proprietary module that uses GPL-only symbols will fail with 'unknown symbol' errors.
The __versions section is Linux's solution to a fundamental problem: how do you know if a module compiled for one kernel will work with another?
The ABI compatibility challenge:
The kernel does not have a stable internal API. Functions change their signatures, structure layouts evolve, and semantics shift between kernel versions. A module compiled against kernel 5.10 might call kmalloc() with certain parameters, but kernel 5.15 might have changed how kmalloc() works internally.
If you load an incompatible module, the results can be catastrophic—silent data corruption, crashes, or security vulnerabilities.
CONFIG_MODVERSIONS:
Linux addresses this with module versioning (enabled with CONFIG_MODVERSIONS). For each exported symbol, the kernel computes a CRC checksum based on the function signature and related type definitions:
// During kernel build, for each exported symbol:
unsigned long crc_kmalloc = <computed from prototype and types>;
// In the module, for each referenced symbol:
struct modversion_info {
unsigned long crc; // Expected CRC
char name[64]; // Symbol name
};
1234567891011121314151617181920
# Examine symbol versions in a module$ modprobe --dump-modversions mymodule.ko 0x12345678 kmalloc0x87654321 kfree0xabcdef01 printk0x11223344 pci_register_driver0x55667788 mutex_lock0x99aabbcc mutex_unlock... # Compare with kernel's exported symbol CRCs$ grep kmalloc /lib/modules/$(uname -r)/build/Module.symvers0x12345678 kmalloc vmlinux EXPORT_SYMBOL_GPL # If CRCs don't match, the module fails to load:$ insmod incompatible_module.koinsmod: ERROR: could not insert module: Invalid module format$ dmesg | tail -1[12345.678] incompatible_module: disagrees about version of symbol kmallocHow CRCs are computed:
The CRC for a symbol is derived from:
This creates a cryptographic fingerprint of the symbol's interface. If anything changes—a structure field is added, a type is redefined, a parameter order changes—the CRC changes, and the module won't load.
Module.symvers:
During kernel builds, a file called Module.symvers is generated containing all exported symbols and their CRCs:
0x12345678 kmalloc vmlinux EXPORT_SYMBOL_GPL
0x87654321 kfree vmlinux EXPORT_SYMBOL
0x11111111 usb_register usbcore EXPORT_SYMBOL_GPL
When building out-of-tree modules, you must provide the correct Module.symvers to ensure CRC computation matches.
Module version checking can be bypassed with modprobe --force or by passing --force-modversion to insmod. This is dangerous and typically only used during development. Production systems should never force-load modules with version mismatches.
Every loaded kernel module is represented by a struct module instance. This structure is the module's identity card within the kernel, containing metadata, state information, and pointers to module resources.
The struct module:
123456789101112131415161718192021222324252627282930313233343536373839404142
// Simplified struct module (actual structure has many more fields)struct module { enum module_state state; // MODULE_STATE_LIVE, GOING, COMING struct list_head list; // Linked list of all modules char name[MODULE_NAME_LEN]; // Module name // Symbol information const struct kernel_symbol *syms; // Exported symbols const unsigned long *crcs; // CRCs for symbols unsigned int num_syms; // Number of symbols // Code and data sections struct module_layout core_layout; // Core (permanent) sections struct module_layout init_layout; // Init (freeable) sections // Function pointers int (*init)(void); // Initialization function void (*exit)(void); // Cleanup function // Reference counting struct module_ref __percpu *refptr; // Dependencies struct list_head module_dependencies; // Licensing const char *license; bool sig_ok; // Signature verification status // Debugging struct bug_entry *bug_table; unsigned num_bugs; // Many more fields...}; enum module_state { MODULE_STATE_LIVE, // Normal operating state MODULE_STATE_COMING, // Being loaded MODULE_STATE_GOING, // Being unloaded MODULE_STATE_UNFORMED, // Under construction};The .gnu.linkonce.this_module section:
The .ko file contains a pre-initialized struct module in the .gnu.linkonce.this_module section. This is partially filled in at compile time:
The kernel loader completes the initialization at load time:
MODULE_STATE_COMINGModule state transitions:
Loading: UNFORMED → COMING → LIVE
Unloading: LIVE → GOING → (freed)
Load fail: COMING → (freed)
The /proc/modules file lists all loaded modules with their addresses, sizes, and reference counts. lsmod formats this information for human consumption. The kernel exposes module information through /sys/module/<name>/ with detailed sysfs entries.
Building a kernel module requires more than just compiling C code—it requires integration with the kernel's build system (Kbuild) to ensure the module is compiled with the correct flags, linked properly, and contains all required metadata.
The Kbuild system:
Kernel modules are built using the same build infrastructure as the kernel itself. This ensures consistent compilation flags, architecture settings, and module formatting.
Minimal module build setup:
123456789101112131415161718192021222324252627282930313233343536373839
# Directory structure for an out-of-tree modulehello_module/├── hello.c # Module source code└── Makefile # Build instructions # hello.c - Minimal kernel module#include <linux/module.h>#include <linux/kernel.h>#include <linux/init.h> static int __init hello_init(void){ printk(KERN_INFO "Hello, kernel!\n"); return 0;} static void __exit hello_exit(void){ printk(KERN_INFO "Goodbye, kernel!\n");} module_init(hello_init);module_exit(hello_exit); MODULE_LICENSE("GPL");MODULE_AUTHOR("Developer");MODULE_DESCRIPTION("Hello World Module"); # Makefile for out-of-tree moduleobj-m := hello.o # If building from the module directory:KDIR := /lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) cleanBuild process:
$ make
make -C /lib/modules/5.15.0/build M=/home/dev/hello_module modules
make[1]: Entering directory '/usr/src/linux-5.15.0'
CC [M] /home/dev/hello_module/hello.o
MODPOST /home/dev/hello_module/Module.symvers
CC [M] /home/dev/hello_module/hello.mod.o
LD [M] /home/dev/hello_module/hello.ko
make[1]: Leaving directory '/usr/src/linux-5.15.0'
Build artifacts:
hello.o — Compiled object filehello.mod.c — Generated file with module metadatahello.mod.o — Compiled module metadatahello.ko — Final module (linked from hello.o + hello.mod.o)Module.symvers — Symbol version informationmodules.order — Build order for multiple modulesModules must be compiled against headers matching the running kernel. The /lib/modules/$(uname -r)/build symlink should point to the correct kernel source or headers. Mismatched headers cause subtle bugs or prevent loading entirely.
Linux provides several tools for examining .ko files. These tools are invaluable for debugging, understanding module structure, and verifying module compatibility.
Essential tools:
| Tool | Purpose | Example Command |
|---|---|---|
| modinfo | Display module metadata | modinfo mymodule.ko |
| readelf | Examine ELF structure | readelf -a mymodule.ko |
| objdump | Disassemble and dump sections | objdump -d mymodule.ko |
| nm | List symbols | nm mymodule.ko |
| file | Identify file type | file mymodule.ko |
| size | Show section sizes | size mymodule.ko |
1234567891011121314151617181920212223242526272829303132333435
# List all symbols in a module$ nm -a mymodule.ko U __fentry__0000000000000000 T init_module0000000000000080 T cleanup_module0000000000000000 t my_device_open0000000000000040 t my_device_release U printk U __register_chrdev U __unregister_chrdev # U = Undefined (external reference)# T = Text (code) section, global# t = Text section, local# D = Data section, global# B = BSS section # Disassemble the initialization function$ objdump -d --start-address=0x0 --stop-address=0x80 mymodule.ko 0000000000000000 <init_module>: 0: e8 00 00 00 00 call 5 <init_module+0x5> 1: R_X86_64_PLT32 __fentry__-0x4 5: 55 push %rbp 6: 48 89 e5 mov %rsp,%rbp 9: 48 83 ec 10 sub $0x10,%rsp ... # Check dependencies$ modinfo --field depends mymodule.kousbcore,scsi_mod # Verify module signature (if CONFIG_MODULE_SIG is enabled)$ modinfo --field sig_hashalgo mymodule.kosha256Understanding undefined symbols:
The U entries in nm output represent symbols the module needs but doesn't define. These must be resolved from the kernel or other loaded modules. Common undefined symbols include:
printk — Kernel loggingkmalloc / kfree — Memory allocation__register_chrdev — Character device registration__fentry__ — Function tracing entry pointIf any undefined symbol cannot be resolved at load time, the module fails to load.
Production modules are often stripped to reduce size. The 'strip --strip-debug' command removes debug symbols while preserving necessary relocation information. Full 'strip' removes too much and breaks module loading.
Linux kernel modules (.ko files) are sophisticated containers that package executable code with comprehensive metadata. We've examined the format in detail:
What's next:
With a deep understanding of the .ko format, we'll explore the advantages of loadable kernel modules—why this architecture has become the dominant approach for Linux drivers and how it benefits system administrators, hardware vendors, and end users alike.
You now understand the internal structure of Linux kernel modules—ELF organization, metadata sections, symbol versioning, and the module descriptor. This knowledge enables debugging module loading issues, understanding compatibility requirements, and developing your own kernel modules.