Using Rust in a Linux kernel module

Hey all,

I'm trying to figure out how feasible it would be to use Rust for Linux kernel development. I'd love to be able to hack the kernel in a language that's more high-level and safe than C. Looks like getting started is not straightforward, though.

My problem is very similar to that discussed in this thread. However, I'm trying to write a kernel module for Linux, not FreeBSD, so I figured it might be better to open a new thread instead of reviving the old one.

For now, my code simply defines a rust_add function that is called from the kernel module's entry point (written in C). As the name suggests, this function adds its two i32 arguments and returns the result. For reference, here is the code.

Loading this module fails, however. This is the dmesg output:

hello: Unknown symbol __muloti4 (err 0)
hello: Unknown symbol __floatundidf (err 0)
hello: Unknown symbol __floatundisf (err 0)
hello: Unknown symbol __umodti3 (err 0)
hello: Unknown symbol __udivti3 (err 0)

There were more unknown symbols previously, e.g. memcpy and memcmp, but I could resolve this by including the rlibc crate.

I am linking with --gc-sections, which doesn't help, so it seems like those symbols are actually used by my Rust code. What I figured out so far is that it must be because of panic handling, specifically integer overflow checking. If I let rust_add return a constant instead of the sum, those symbols are not present in the final binary and it loads just fine. Similarly, if I compile in release mode everything works as well, since overflow checking is disabled there. I don't know, however, what kind of panic handling would require these kinds of functions. AFAICT they are (soft) floating point and 128-bit integer operations.

I checked the compiler-builtins crate, which implements most but now all of the missing functions, so it wouldn't solve the problem. The only other solution I can think of is mocking the missing functions with dummy implementations. But I don't feel comfortable doing this if they can actually be called at runtime. Is there anything else I could try?

3 Likes

I looked at the code of rust_add in a disassembler. As it turns out, panic handling doesn't call any of the above unknown functions, but instead transfers execution to my panic_fmt function in a straightforward way. I also didn't find any other references to the unknown functions in the disassembly. So it looks like those symbols are not actually required at all.

I also found that doing a

$ strip --strip-unneeded hello.ko

on the final binary removes the offending symbols from the symbol table. The module can then successfully be loaded.

I still don't know why these symbols are included in the first place, if they are not actually needed. The strip solution also seems rather hacky to me and it probably also removes symbols that could still be useful during development (e.g. debug symbols).

2 Likes

Have you seen this? https://github.com/tsgates/rust.ko

Yes, rust.ko has been really helpful so far. However, the project is a bit dated and doesn't build anymore, so I don't know if it has the same problems as I am encountering. I believe I have tried all the config options and compiler flags in there, but nothing has helped so far.

The main thing rust.ko does differently from me (aside from having a wrapper for the kernel API which I didn't get to yet) is using https://github.com/phil-opp/nightly-libcore with the disable_float feature to get a working core crate. In contrast, I'm using Xargo with a +soft-float target. From what I read this is the way to go nowadays. But it seems to have its complications...

1 Like

I theory you should be using the compiler builtins crate and if the compiler is generating calls that aren't provided by it, then I would consider that a bug (likely in the builtins crate, because rustc doesn't really chose what builtins LLVM decides to call).

1 Like

Thanks for the hint!

I had another look at the compiler_builtins crate. As I mentioned, it doesn't have implementations for all the functions I was missing, but it does provide an optional feature to fall back to the compiler-rt implementation for unimplemented intrinsics. So by using it, I managed to get rid of all the undefined symbols and the kmod is now usable in debug mode without the need to strip!

2 Likes

Very cool! How about writing a blog post describing the procedure an sharing a link to a working example?

3 Likes

Good idea! I feel like I'll need to document this stuff for my future self anyway, so I will definitely write something up.

For now, the working code is already available. Check the link in my first post in this thread.

4 Likes