1

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
Pierre
  • 1,942
  • 3
  • 23
  • 43
  • 9
    A `ko` file is subject to relocation. What you observe are just relocations that haven't been filled in yet. Try `objdump -r` to see the relocation entries next to the disassembly. – fuz Nov 01 '21 at 15:43
  • 1
    Note that since `get()` doesn't do anything and `_read` doesn't use its value, it could be optimized out. I think those `call`s might be something else, especially since they are not between the prologue and epilogue - maybe a profiling function or something like that? But as fuz says, the zero displacement is just a placeholder and the actual address of whatever is being called will be inserted there when the module is loaded. – Nate Eldredge Nov 01 '21 at 17:11
  • If the compiler hasn't gotten to `get`, how would it know where it is? It might be in a different file. calls are filled in at link time. – stark Nov 01 '21 at 17:33

0 Answers0