I have written a very simple Linux module (see the full source code below). In this module, the .read
function calls an other function of the module:
const char* get(void) {
static const char* string = "String\n";
return string;
}
static ssize_t _read(struct file *file,
char __user *user_buffer,
size_t size, loff_t *offset)
{
const char* string = get();
return 0;
}
In the disassembly, I expect the _read
function to perform a call
to the get
function. But it is not the case:
0000000000000010 <_read>:
10: e8 00 00 00 00 callq 15 <_read+0x5>
15: 55 push %rbp
16: 31 c0 xor %eax,%eax
18: 48 89 e5 mov %rsp,%rbp
1b: 5d pop %rbp
1c: c3 retq
1d: 0f 1f 00 nopl (%rax)
...
0000000000000030 <get>:
...
As far as I know, callq 15
means a call to the instruction at offset 15 (which is the very next instruction) is performed. It doesn't make sense because the get
function is located at offset 30.
I also noticed that every callq
instructions of the module call the very next instruction:
$ objdump -d driver.ko | grep -A 1 callq
0: e8 00 00 00 00 callq 5 <_open+0x5>
5: 55 push %rbp
--
10: e8 00 00 00 00 callq 15 <_read+0x5>
15: 55 push %rbp
--
20: e8 00 00 00 00 callq 25 <_release+0x5>
25: 55 push %rbp
--
30: e8 00 00 00 00 callq 35 <get+0x5>
35: 55 push %rbp
--
50: e8 00 00 00 00 callq 55 <init_module+0x5>
55: 55 push %rbp
--
6c: e8 00 00 00 00 callq 71 <init_module+0x21>
71: 41 89 c4 mov %eax,%r12d
--
8d: e8 00 00 00 00 callq 92 <init_module+0x42>
92: ba 01 00 00 00 mov $0x1,%edx
--
a3: e8 00 00 00 00 callq a8 <init_module+0x58>
a8: 44 89 e0 mov %r12d,%eax
--
b0: e8 00 00 00 00 callq b5 <cleanup_module+0x5>
b5: 55 push %rbp
--
c0: e8 00 00 00 00 callq c5 <cleanup_module+0x15>
c5: be 01 00 00 00 mov $0x1,%esi
--
cf: e8 00 00 00 00 callq d4 <cleanup_module+0x24>
d4: 5d pop %rbp
What does it mean? Why the callq
instructions don't point to the correct functions/instructions?
To reproduce, here is the full source code of my module:
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/module.h>
MODULE_LICENSE("Beerware");
#define MODULE_MAJOR 300
#define MODULE_MINOR 0
#define NUM_MINORS 1
#define MODULE_NAME "simple module"
const char* get(void) {
static const char* string = "String\n";
return string;
}
struct device_data {
struct cdev cdev;
};
struct device_data data[NUM_MINORS];
static int _open(struct inode *inode, struct file *file)
{
return 0;
}
static int _release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t _read(struct file *file,
char __user *user_buffer,
size_t size, loff_t *offset)
{
const char* string = get();
return 0;
}
static const struct file_operations _fops = {
.owner = THIS_MODULE,
.open = _open,
.release = _release,
.read = _read,
};
static int simple_init(void)
{
int err;
int i;
err = register_chrdev_region(MKDEV(MODULE_MAJOR, MODULE_MINOR), NUM_MINORS, MODULE_NAME);
if(err != 0)
return err;
for (i = 0; i < NUM_MINORS; i++) {
cdev_init(&data[i].cdev, &_fops);
cdev_add(&data[i].cdev, MKDEV(MODULE_MAJOR, i), 1);
}
return 0;
}
static void simple_exit(void)
{
int i;
for (i = 0; i < NUM_MINORS; i++) {
cdev_del(&data[i].cdev);
}
unregister_chrdev_region(MKDEV(MODULE_MAJOR, MODULE_MINOR), NUM_MINORS);
}
module_init(simple_init);
module_exit(simple_exit);
The Makefile I use:
ifneq ($(KERNELRELEASE),)
obj-m := driver.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
install:
$(MAKE) -C $(KDIR) M=$(PWD) modules_install
%:
$(MAKE) -C $(KDIR) M=$(PWD) $@
endif
And here is my system information:
$ uname -a
Linux pierre-Inspiron-5567 5.11.0-38-generic #42~20.04.1-Ubuntu SMP Tue Sep 28 20:41:07 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux