As I continue with my Linux kernel development and after I have talked very briefly about a minimal and simple example on how to implement a very basic kernel module in this previous post , in this post I will further elaborate about some “related” things worth noting when talking about kernel module development.
- Kernel symbol table
The kernel holds a table of all public symbols (exposed by it or by other modules) – which are, for instance, functions and/or variables that some module wishes to “expose” (one can look at it as a public class member or function in OOP). The symbol table can be displayed using the command:
Note that when this command is invoked like so, all the addresses that will be displayed are zero’s –> this is essentially a security precaution of the kernel – cause this table contains important information about the system.
In order to see the actual addresses of the entries in the symbol table, one should run the above command with sudo privileges.
- Exporting symbols from kernel module
By default, all symbols (functions and variables) defined in a module (which means essentially in its source file(s)) are NOT exported (considered as “private”).If a module wishes to “expose” some function/variable for other module – it should use the following macro:
In this case, I have “exported” a simple const int global variable with the value of 17. Each other module that would like to “use” this variable will have to declare on this variable as extern. Below is the entire code of the module, with the C file named module1.c:
Note that it is the exact same implementation as of the simple module form the previous post, only this time it also “export” the const int variable.
- Passing command line argument to kernel module
It is possible to pass command line argument to a loadable kernel module when one load it (for instance with the insmod command). In the module below that is implemented in the source file named module2.c, the “global exported” variable from module1.c will be used (by declaring it as extern at the beginning) and it will receive a single command line argument of type int.
The code for such a module is as follows:
4.1) Point 1 is where this module declares on the symbol that is defined in the other module (module1). Note that essentially in the “syntax” wise perspective, this is no different than two C source files sharing a “global” variable among them.
4.2) Point 2 is where the place holder for the argument that will be received from the command line is declared.In this case I named it arg1.
- Source code folder structure
In this example, I’m maintaining the source files and “build artifacts” in this structure:
So when I’m building each one of the module, by running the command make in the module’s folder – for each one of them a different Module.symvers file is generated.
- Loading the modules (first attempt)
6.1) Now after I have done writing the modules code it is time to load and “run them”. First thing is to load module1. This is done by the command:
Now module1 is loaded into the kernel.
6.2) Now it is time to load module2, which can be done by the command:
6.2.1) The name of the command line argument must be set exactly as it in the C file, followed by the equal sign (=) and then the value (no spaces between the name of the argument and the value of it – similarly as when defining a variable in a bash script).
As can be seen the in the output – module2 was NOT loaded into the kernel.
When inspecting the dmesg last “log prints” the following can be noticed:
It complains that it does NOT know the symbol of the exported variable.
- Enabling symbols “sharing” between kernel modules
The reason that the error raised when I tried to load module2 is due to the fact that each one of the modules has its OWN Modules.symvers file cause it was built on “its own”, so they are “unaware” to “each other” symbols. There are three approaches to solve this situation that are described section 6. Module Versioning from the original kernel documentation.
- Solving the symbols issue
In this case I will “tackle” the issue by “letting” module2 know about the symbols exported in module1 by adding to its Makefile the Modules.symvers file of module1.
This can be done by adding the first line in the Makefile of module2 as follows:
8.1) The first line in the above Makefile indicates to module2 about the existing and content of module1’s symbols.
8.2) The Makefile for module1 is exactly the same expect that it does not need the first line and obj-m is set to : obj-m += module1.o
- Loading the module (second attempt)
Now, I will do the following:
9.1) Build module1 by running its Makefile (note that it needs to be built first cause module2 will use its Modules.symvers)
9.2) Build module2 by running its Makefile.
9.3) Loading module1: sudo insmod /home/module1/build/module1.ko
9.4) Loading module2: sudo insmod /home/module2/build/module2.ko arg1=15
After that sequence of actions, dmesg will display the following:
In this post the following were discussed:
a) The notation of sharing symbols between external modules was introduced.
b) What (in general) is the Modules.symvers and how one should (can) use it when sharing symbols between several external modules.
c) A simple Makefile to build a single source code module was introduced.
d) Passing command line argument to a loadable module.
a) Good Q&A on stackoverflow regarding how to compile two different kernel modules and be able to share (export) variable between them
b) Modules.symvers nice short explanation
c) Another Q&A on Stackoverflow regarding sharing symbols between kernel modulesThe picture: Foz do Iguacu, state of Parana, Brazil.