1

I have some example code. When I uncomment the invalid_call function call I get a compiler error as expected. Unfortunately, calling my_function with wrong number of arguments still compiles leading to undefined behavior (UB).

main.c

#include <linux/module.h>
#include <linux/kernel.h>
#include "header.h"

extern void my_function(int x);

static int my_init(void)
{
    my_function(5);
    // invalid_call( 5 ); // doesn't compile
    return 0;
}

static void my_exit(void)
{
    pr_info("removing module");
}

module_init(my_init);
module_exit(my_exit);

func_source.c

#include <linux/kernel.h>
void my_function(int x, int y)
{
    pr_info("%d %d",x,y);
}

header.h

void invalid_call(int x, int y)
{
     return;
}

Expected output: Compiler error when calling my_function() with only one argument.

Actual output: Code compiles and prints a random value for y, essentially UB.

I know that extern void my_function(int x); is just another declaration so I don't think compiler needs to throw an error however, when I call my_function with a single argument, it shouldn't be able to find a match to any function definition. Unfortunately, instead of a compiler error I run into UB.

How does this work? I know UB is UB but how why does it become UB. I thought the function signature mismatch would cause a compiler. My suspicion is the extern declaration but still...

Also, bonus question. How do I avoid running into this problem again? Any design patterns or practices I can follow?

Here is the Makefile if you want to test it out yourself.

obj-m += main.o
example-y := ./src/main.o ./src/func_src.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Folder structure:

./Makefile
./src/main.c
./src/my_func.c

Thanks in advance.

Tagger5926
  • 442
  • 3
  • 13
  • I've removed the [tag:linux-kernel] and [tag:kernel-module] tags from this question, as the question you're asking isn't specific to kernel development. –  Aug 22 '19 at 23:41
  • Okay that's fine. Only reason why I added them was because I couldn't reproduce with just C using gcc. I got a compiler error. Only ran into this problem when I was developing a kernel module. – Tagger5926 Aug 22 '19 at 23:43
  • Is the mismatch between `myFunction` and `my_Function` intented or did you mean to use identical name? With the code you show, there is no declaration before calling the function. – Gerhardh Aug 24 '19 at 06:58
  • yeah that's a typo sorry. I use both c++ and c and mixed up the naming convention. I will fix the example thanks. – Tagger5926 Aug 24 '19 at 09:37
  • Related question, [Function declaration vs. definition C - Stack Overflow](https://stackoverflow.com/questions/35781480/function-declaration-vs-definition-c) – user202729 Feb 25 '22 at 16:20

3 Answers3

4

In C, at least, neither the compiler nor the linker has global knowledge of all the functions in all the source files of your program (or kernel module, or whatever). The compiler compiles one source file at a time, and all it has to go on is the declarations -- including the prototypes -- that are visible during that one compilation. So if the prototype is wrong -- you're doomed. The compiler validates your (incorrect) call against the (incorrect) prototype, and finds no mismatch, and so generates an (incorrect) call that passes one argument, leading to the behavior you see.

For this reason it's an excellent idea to:

  • never put external prototypes in .c files, but rather, in .h files, which you include wherever you call the functions, and
  • also include the .h file in the source file where you define the function.

That way, not only does the compiler check that the calls match the prototype, it can also check that the prototype matches the actual definition, and since there's only one copy of the prototype (in that one .h file), it's more or less impossible for it to fall out of sync and end up being incorrect. See also this question.

In your question, you seemed to think that it wouldn't even be possible for the incorrect call to be linked up with the two-argument-accepting definition. That might be true in C++, where "name mangling" arranges for a function's arguments to be part of its signature. But nothing like that happens in C. Your function's sole identity -- in the symbol table, and as far as the linker is concerned -- is the name my_function, and nothing at link time prevents a one-arg-passing call from being matched up with the two-arg-accepting definition.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • Then during link time, what does it throw in for the second argument. Does it just read from some random memory offset? – Tagger5926 Aug 22 '19 at 23:45
  • 1
    @Tagger5926 Nothing happens at link time, but yes, at run time, when `my_function` tries to fetch an argument that wasn't passed, it essentially gets a random value -- whatever happened to be sitting at the stack location, or in the register, where it expected to find the second argument. – Steve Summit Aug 22 '19 at 23:47
1

Some ways to detect this problem:

  • building in gcc with link-time optimization will usually flag this.
  • Using -Wmissing-prototypes would have warned for func_source.c that you have an externally-visible function with no prototype.

You should have the correct prototype in a header file that is included by all units that want to call the function, as well as the unit containing the function definition. The latter warning flag will detect if you forget to put the prototype in the unit containing the definition (which would have caused a compilation error due to the prototype not matching the definition).

M.M
  • 138,810
  • 21
  • 208
  • 365
0

the OPs posted code results in the following messages from the compiler:

gcc    -ggdb -Wall -Wextra -Wconversion -pedantic -std=gnu11  -c "untitled.c"  (in directory: /home/richard/Documents/forum)
untitled.c: In function ‘invalid_call’:
untitled.c:1:23: warning: unused parameter ‘x’ [-Wunused-parameter]
 void invalid_call(int x, int y)
                       ^
untitled.c:1:30: warning: unused parameter ‘y’ [-Wunused-parameter]
 void invalid_call(int x, int y)
                              ^
untitled.c: In function ‘my_init’:
untitled.c:15:5: warning: implicit declaration of function ‘my_function’; did you mean ‘myFunction’? [-Wimplicit-function-declaration]
     my_function(5);
     ^~~~~~~~~~~
     myFunction
untitled.c: In function ‘my_exit’:
untitled.c:22:5: warning: implicit declaration of function ‘pr_info’ [-Wimplicit-function-declaration]
     pr_info("removing module");
     ^~~~~~~
untitled.c: At top level:
untitled.c:25:1: warning: data definition has no type or storage class
 module_init(my_init);
 ^~~~~~~~~~~
untitled.c:25:1: warning: type defaults to ‘int’ in declaration of ‘module_init’ [-Wimplicit-int]
untitled.c:25:1: warning: parameter names (without types) in function declaration
untitled.c:26:1: warning: data definition has no type or storage class
 module_exit(my_exit);
 ^~~~~~~~~~~
untitled.c:26:1: warning: type defaults to ‘int’ in declaration of ‘module_exit’ [-Wimplicit-int]
untitled.c:26:1: warning: parameter names (without types) in function declaration
untitled.c:28:6: warning: conflicting types for ‘my_function’
 void my_function(int x, int y)
      ^~~~~~~~~~~
untitled.c:15:5: note: previous implicit declaration of ‘my_function’ was here
     my_function(5);
     ^~~~~~~~~~~
untitled.c:20:13: warning: ‘my_exit’ defined but not used [-Wunused-function]
 static void my_exit(void)
             ^~~~~~~
untitled.c:13:12: warning: ‘my_init’ defined but not used [-Wunused-function]
 static int my_init(void)
            ^~~~~~~
Compilation finished successfully.

Suggest enabling the warnings when compiling, then fix those warnings

user3629249
  • 16,402
  • 1
  • 16
  • 17