Inspecting Kernel Modules on Linux – free linux kernel development course

Linux Kernel Module Inspection: lsmod, modinfo, /proc/modules | Free Linux Kernel Programming Course

EmbeddedPathashalaLinux Kernel Programming › Module Inspection

Inspecting Kernel Modules on Linux 6.x
lsmod · modinfo · /proc/modules · /sys/module/ — what they are, how they work, and what every field means
Level
Beginner–Intermediate
Kernel
Linux 6.x
Part
1 of 2
This series: PREV_LEC NEXT_LEC
What you will learn in this tutorial
lsmod columns explained modinfo fields in kernel 6.x /proc/modules format /sys/module/ sysfs interface kmod multi-call binary module load states module reference counting

Before you begin

When you write a Linux kernel module and load it, the kernel keeps a record of it in memory. That record contains the module’s name, how much kernel memory it occupies, whether any other module depends on it, and its current state (loading, live, or going away). Linux gives you several tools to read that record — from the simple lsmod command all the way down to the raw virtual files in /proc/modules and /sys/module/.

This tutorial explains all of them from first principles, using Linux kernel 6.x as the reference. No assumptions are made about prior kernel internals knowledge.

1. One binary, many names: kmod

You probably know commands like lsmod, insmod, rmmod, modinfo, modprobe, and depmod. Here is something surprising: every single one of them is the same program.

They are all symbolic links pointing to a single binary called kmod. When you run any of them, kmod reads its own invocation name (argv[0]) and behaves accordingly.

$ ls -la /usr/bin/lsmod /usr/bin/rmmod /usr/bin/modinfo /sbin/modprobe /sbin/depmod
lrwxrwxrwx 1 root root 4  /usr/bin/lsmod    -> kmod
lrwxrwxrwx 1 root root 4  /usr/bin/rmmod    -> kmod
lrwxrwxrwx 1 root root 4  /usr/bin/modinfo  -> kmod
lrwxrwxrwx 1 root root 4  /sbin/modprobe    -> ../bin/kmod
lrwxrwxrwx 1 root root 4  /sbin/depmod      -> ../bin/kmod
Note: The exact directory (/bin, /usr/bin, or /sbin) varies by Linux distribution. The key point is that all these tools are symlinks to one binary.

The kmod project replaced the older module-init-tools package (which reached end-of-life in 2011). kmod is built on top of a library called libkmod — that library does the actual heavy lifting of reading dependency files, applying module options, and calling kernel system calls.

kmod Architecture — How User Space Talks to the Kernel
lsmod
modinfo
insmod
rmmod
modprobe
depmod
▼ all symlinks to ▼
/usr/bin/kmod
▼ built on ▼
libkmod
▼ issues system calls ▼
init_module(2)
finit_module(2)
delete_module(2)
▼ enters ▼
Linux Kernel 6.x — kernel/module/

2. lsmod — reading the module list

lsmod is the simplest inspection tool. It takes no arguments, reads /proc/modules, reformats it into a neat table with a header row, and prints it. That is literally everything it does.

$ lsmod
Module                  Size  Used by
bluetooth             983040  38 btrtl,btusb,btbcm,btintel,rfcomm,bnep
cfg80211             1138688  3  iwlwifi,mac80211,iwlmvm
e1000e               299008  0
drm                   618496  6  drm_kms_helper,i915

The output has three columns. Here is what each one means:

ColumnWhat it showsDetails
Module The module’s name Usually the filename of the .ko file without any extension. A custom name can be embedded at build time using MODULE_ALIAS().
Size Kernel memory used (bytes) How much kernel RAM this module occupies right now. This is not the size of the .ko file on disk — it is the in-memory footprint after loading and relocation.
Used by Reference count + dependents The number shows how many things are actively using this module. The comma-separated names are other loaded modules that depend on this one. A count of 0 usually means nothing is using it and it can be unloaded.
Why does “Used by” show 0 but the module still can’t be removed?
The count can be nonzero because of kernel-internal users (for example, an open file handle, an active network connection, or a mounted filesystem) that are not themselves modules. Those internal users are not listed by name, but they still increment the reference count.
Built-in modules never appear in lsmod. If a driver is compiled directly into the kernel image (vmlinux) rather than as a loadable .ko file, lsmod will not list it. Use cat /boot/config-$(uname -r) | grep CONFIG_E1000 to see if something is built-in (=y) versus a module (=m).

3. /proc/modules — the raw data behind lsmod

/proc/modules is a virtual file that the kernel generates fresh every time you read it. lsmod is just a pretty-printer on top of this file. Reading it directly gives you two extra fields that lsmod hides: the module’s load state and its kernel memory address.

$ cat /proc/modules
bluetooth 983040 38 btrtl,btusb,btbcm,btintel,rfcomm,bnep, Live 0xffffffffc0b40000
cfg80211 1138688 3 iwlwifi,mac80211,iwlmvm, Live 0xffffffffc0800000
e1000e 299008 0 - Live 0xffffffffc06c0000

Each line has six space-separated fields:

/proc/modules — Anatomy of a Single Line
bluetooth 983040 38 btrtl,btusb,bnep, Live 0xffffffffc0b40000
Field 1
bluetooth
Module name
Field 2
983040
Memory (bytes)
Field 3
38
Reference count
Field 4
btrtl,btusb,bnep,
Dependent modules
Field 5
Live
Load state
Field 6
0xffff…c000
Kernel address

The three load states

Field 5 can be one of three values. These map directly to internal kernel states:

Module Lifecycle — Load States in Linux 6.x
Module .ko file on disk
▼ insmod / modprobe
UNFORMED
Early setup — ELF validation, memory allocation
(not visible in /proc/modules yet)
COMING → /proc/modules shows “Loading”
init function is running (your module_init() code executes here)
▼ init returns 0
LIVE → /proc/modules shows “Live”
Module is fully operational and can be used
▼ rmmod / modprobe -r
GOING → /proc/modules shows “Unloading”
exit function runs, memory is freed, module disappears
Address shown as zeros? On most modern systems, kptr_restrict is set to 1 or 2, which hides kernel pointer values from non-root users. Running sudo cat /proc/modules will show the real addresses.

4. /sys/module/ — the per-module sysfs directory

Every loaded module gets its own directory under /sys/module/. Unlike /proc/modules which is one big file, sysfs gives you individual attributes as separate readable (and sometimes writable) files.

$ ls /sys/module/bluetooth/
coresize  holders  initsize  initstate  notes  parameters  refcnt  sections  srcversion  taint  uevent
File / DirectoryWhat it contains
initstateText form of the module state: live, coming, or going
refcntCurrent reference count (same as the third column in /proc/modules)
coresizeSize of the module’s core memory region in bytes (text + data + rodata)
initsizeSize of the init-only memory region (freed after module_init() returns)
srcversionA hash of the source code used to build this module, matches modinfo srcversion
taintTaint flags this module added to the kernel (e.g. unsigned, out-of-tree)
holders/Symlinks to modules that depend on this one (the reverse of the “Used by” list)
parameters/One file per module parameter — some are writable at runtime!
sections/Kernel virtual addresses of ELF sections (useful for debuggers / oops decoding)

Changing a module parameter at runtime

If a module exposes a writable parameter via module_param(), you can change its value without reloading the module:

# Read current value of a parameter
$ cat /sys/module/usbcore/parameters/autosuspend

# Change it (requires root)
$ echo 2 | sudo tee /sys/module/usbcore/parameters/autosuspend
This change is not permanent. Writing to /sys/module/ affects the running system only. The change disappears on reboot. For a permanent default, add a file under /etc/modprobe.d/ with an options directive.

5. modinfo — reading module metadata

modinfo reads metadata that was embedded by the compiler directly into the .ko file at build time. You can run it on a module that is not even loaded — you just need the .ko file or the module name (it searches /lib/modules/$(uname -r)/).

$ modinfo bluetooth
filename:       /lib/modules/6.8.0-45-generic/kernel/net/bluetooth/bluetooth.ko.xz
version:        2.22
license:        GPL
description:    Bluetooth Core ver 2.22
author:         Marcel Holtmann <marcel@holtmann.org>
srcversion:     AC3C4F82E1E52F85E07AABB
alias:          net-pf-31
depends:
retpoline:      Y
intree:         Y
name:           bluetooth
vermagic:       6.8.0-45-generic SMP preempt mod_unload modversions
sig_id:         PKCS#7
signer:         Build time autogenerated kernel key
sig_hashalgo:   sha512

What every field means

FieldMeaningKernel macro that sets it
filenameFull path to the .ko file on disk. Note: modern distros compress modules as .ko.xz or .ko.zst
licenseThe module’s license declaration. Must be a GPL-compatible string to access GPL-only kernel symbolsMODULE_LICENSE()
descriptionA short human-readable description of what the module doesMODULE_DESCRIPTION()
authorModule author contactMODULE_AUTHOR()
versionModule version stringMODULE_VERSION()
srcversionA hash computed from the source files at build time. Useful for checking if a loaded module matches a .ko on diskauto-generated
aliasDevice ID patterns used for automatic loading (e.g. PCI IDs, USB IDs)MODULE_ALIAS()
dependsComma-separated list of other modules this module requires at load time (hard dependencies)auto-derived from symbol imports
retpolineY if the module was compiled with the Spectre v2 retpoline mitigationcompiler flag
intreeY if built from the official kernel source tree. Out-of-tree modules omit this and taint the kernelMODULE_INFO(intree, “Y”)
vermagicThe kernel version + key config flags that must match the running kernel exactly. A mismatch causes load failureauto-generated
sig_id, signer, sig_hashalgoModule signing information (PKCS#7). Present when the module is cryptographically signedsigning step at build
parmModule parameters — one line per parameter, with its description and data typeMODULE_PARM_DESC()

Useful modinfo options

# Show only the license field
$ modinfo -F license bluetooth

# Show only the filename (useful in scripts)
$ modinfo -n bluetooth

# Show parameters with descriptions
$ modinfo -F parm e1000e
vermagic is the key to why kernel modules are not portable. A module built for kernel 6.8.0-45-generic will be refused by kernel 6.8.0-47-generic even if the API did not change. The vermagic string encodes the kernel version plus critical config flags like SMP, preempt, and modversions.

6. What changed in kernel 6.x that you should know

If you read older Linux kernel books or tutorials, you will see references to core_size and init_size fields inside the kernel’s module structure. These no longer exist in Linux 6.4 and later.

Starting with Linux 6.4, the kernel replaced a single monolithic memory layout for each module with a new per-type memory array. Each type of module memory (executable code, read-only data, writable data, init-only code, etc.) now has its own separate allocation with its own size and address. This is called the module_memory model.

Module Memory Layout: Before vs After Linux 6.4
Before 6.4 (old model)
module_layout (core)
text, data, rodata — all in one allocation
module_layout (init)
init text + data — freed after load
Fields: core_size, init_size
Linux 6.4+ (new model)
MOD_TEXT
MOD_DATA
MOD_RODATA
MOD_RO_AFTER_INIT
MOD_INIT_TEXT
MOD_INIT_DATA / RODATA
Each has its own base + size

The practical benefit: each memory region can now be given strict permissions independently. Executable code pages are not writable. Read-only data pages cannot be executed. This makes kernel modules significantly harder to exploit.

Kernel 5.18 also reorganized the source code. Before 5.18, all kernel module handling logic was in one large file called kernel/module.c. In 5.18, this was split into a directory kernel/module/ with separate files for signing, decompression, symbol resolution, and so on. If you ever read the kernel source, look there.

Interview Questions & Answers

Q1. What does the “Used by” column in lsmod actually count?
It shows the module’s reference count — the number of active users of that module at that moment. This includes both other loaded modules that depend on it (listed by name) and kernel-internal users like open device files, active connections, or mounted filesystems (counted but not named). You cannot safely unload a module while its reference count is above zero.
Q2. Is lsmod the same as cat /proc/modules?
Almost, but not exactly. lsmod reads /proc/modules and reformats it into a neat three-column table with a header. /proc/modules itself has six fields per line including the load state and kernel address, which lsmod omits from its output.
Q3. What is vermagic and why does it matter?
vermagic is a string embedded in every kernel module at build time. It encodes the exact kernel version plus key configuration flags (SMP, preempt, modversions). When you try to load a module, the kernel compares the module’s vermagic against its own. If they differ, the load is refused. This is why a .ko file built for one kernel version will not load on another — even a minor version bump changes the vermagic.
Q4. What is the difference between insmod and modprobe?
insmod loads exactly one .ko file and does no dependency resolution at all — if the module needs symbols from another module, the load fails. modprobe reads the dependency database generated by depmod, automatically loads all required dependency modules in the correct order, and then loads your module. For everyday use, always prefer modprobe.
Q5. What changed in kernel 6.4 about module memory layout?
Linux 6.4 replaced the old module_layout structure (which had a single core_size and init_size) with a new module_memory model. In the new model, each type of module memory (text, data, rodata, ro_after_init, init_text, init_data, init_rodata) has its own separate allocation with its own base address and size. This enables per-region memory protection — executable code, read-only data, and writable data are now strictly separated.
Q6. Why are lsmod, rmmod, insmod and others all the same binary?
They are all symbolic links to kmod, which inspects the name it was called with (argv[0]) and behaves accordingly — similar to how the busybox utility works. This approach simplifies maintenance: there is one codebase (kmod) built on one shared library (libkmod), rather than six separate programs.

Continue to Part 2

Now that you know how to inspect kernel modules, the next tutorial covers how to load and unload them — including what really happens when rmmod fails, how reference counting prevents data corruption, and how kernel 6.x security features affect module loading.

Part 2: Loading and Unloading Modules →

Leave a Reply

Your email address will not be published. Required fields are marked *