Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust and C linking problems with minimal program and no_std

Tags:

c

linker

rust

I'm trying to build a minimal program in C that calls Rust functions, preferably compiled with #![no_std], in Windows, using GCC 6.1.0 and rustc 1.11.0-nightly (bb4a79b08 2016-06-15) x86_64-pc-windows-gnu. Here's what I tried first:

main.c

#include <stdio.h>

int sum(int, int);

int main()
{
    printf("Sum is %d.\n", sum(2, 3));
    return 0;
}

sum.rs

#![no_std]
#![feature(libc)]
extern crate libc;

#[no_mangle]
pub extern "C" fn sum(x: libc::c_int, y: libc::c_int) -> libc::c_int
{
    x + y
}

Then I tried running:

rustc --crate-type=staticlib --emit=obj sum.rs

But got:

error: language item required, but not found: `panic_fmt`
error: language item required, but not found: `eh_personality`
error: language item required, but not found: `eh_unwind_resume`
error: aborting due to 3 previous errors

OK, so some of those errors are related to panic unwinding. I found out about a Rust compiler setting to remove unwinding support, -C panic=abort. Using that, the errors about eh_personality and eh_unwind_resume disappeared, but Rust still required the panic_fmt function. So I found its signature at the Rust docs, then I added that to the file:

sum.rs

#![no_std]
#![feature(lang_items, libc)]
extern crate libc;

#[lang = "panic_fmt"]
pub fn panic_fmt(_fmt: core::fmt::Arguments, _file_line: &(&'static str, u32)) -> !
    { loop { } }

#[no_mangle]
pub extern "C" fn sum(x: libc::c_int, y: libc::c_int) -> libc::c_int
{
    x + y
}

Then, I tried building the whole program again:

rustc --crate-type=staticlib --emit=obj -C panic=abort sum.rs
gcc -c main.c
gcc sum.o main.o -o program.exe

But got:

sum.o:(.text+0x3e): undefined reference to `core::panicking::panic::h907815f47e914305'
collect2.exe: error: ld returned 1 exit status

The panic function reference is probably from a overflow check in the addition at sum(). That's all fine and desirable. According to this page, I need to define my own panic function to work with libcore. But I can't find instructions on how to do so: the function for which I am supposed to provide a definition is called panic_impl in the docs, however the linker is complaining about panic::h907815f47e914305, whatever that's supposed to be.

Using objdump, I was able to find the missing function's name, and hacked that into C:

main.c

#include <stdio.h>
#include <stdlib.h>

int sum(int, int);

void _ZN4core9panicking5panic17h907815f47e914305E()
{
    printf("Panic!\n");
    abort();
}

int main()
{
    printf("Sum is %d.\n", sum(2, 3));
    return 0;
}

Now, the whole program compiles and links successfully, and even works correctly.

If I then try using arrays in Rust, another kind of panic function (for bounds checks) is generated, so I need to provide a definition for that too. Whenever I try something more complex in Rust, new errors arise. And, by the way, panic_fmt seems to never be called, even when a panic does happen.

Anyways, this all seems very unreliable, and contradicts every information I could find via Google on the matter. There's this, but I tried to follow the instructions to no avail.

It seems such a simple and fundamental thing, but I can't get it to work the right way. Perhaps it's a Rust nightly bug? But I need libc and lang_items. How can I generate a Rust object file/static library without unwinding or panic support? It should probably just execute an illegal processor instruction when it wants to panic, or call a panic function I can safely define in C.

like image 810
HLorenzi Avatar asked Jun 20 '16 18:06

HLorenzi


2 Answers

You shouldn't use --emit=obj; just rustc --crate-type=staticlib -C panic=abort sum.rs should do the right thing. (This fixes the _ZN4core9panicking5panic17h907815f47e914305E link error.)

To fix another link error, you need to write panic_fmt correctly (note the use of extern):

#[lang="panic_fmt"]
extern fn panic_fmt(_: ::core::fmt::Arguments, _: &'static str, _: u32) -> ! {
    loop {}
}

With those changes, everything appears to work the way it's supposed to.


You need panic_fmt so you can decide what to do when a panic happens: if you use #![no_std], rustc assumes there is no standard library/libc/kernel, so it can't just call abort() or expect an illegal instruction to do anything useful. It's something which should be exposed in stable Rust somehow, but I don't know if anyone is working on stabilizing it.

You don't need to use #![feature(libc)] to get libc; you should use the version posted on crates.io instead (or you can declare the functions you need by hand).

like image 123
Eli Friedman Avatar answered Sep 28 '22 04:09

Eli Friedman


So, the solution, from the accepted answer, was:

main.c

#include <stdio.h>
#include <stdlib.h>

int sum(int, int);

void panic(const char* filename_unterminated, int filename_size, int line_num)
{
    printf("Panic! At line %d, file ", line_num);

    for (int i = 0; i < filename_size; i++)
        printf("%c", filename_unterminated[i]);

    abort();
}

int main()
{
    // Sum as u8 will overflow to test panicking.
    printf("Sum is %d.\n", sum(0xff, 3));
    return 0;
}

sum.rs

#![no_std]
#![feature(lang_items, libc)]
extern crate libc;

extern "C"
{
    fn panic(
        filename_unterminated: *const libc::c_char,
        filename_size: libc::c_int,
        line_num: libc::c_int) -> !;
}

#[lang="panic_fmt"]
extern fn panic_fmt(_: ::core::fmt::Arguments, filename: &'static str, line_num: u32) -> !
{
    unsafe { panic(filename.as_ptr() as _, filename.len() as _, line_num as _); }
}

#[no_mangle]
pub extern "C" fn sum(x: libc::c_int, y: libc::c_int) -> libc::c_int
{
    // Convert to u8 to test overflow panicking.
    ((x as u8) + (y as u8)) as _
}

And compiling with:

rustc --crate-type=staticlib -C panic=abort sum.rs
gcc -c main.c
gcc main.o -L . -l sum -o program.exe

Now everything works, and I have a panic handler in C that shows where the error occurred!

like image 32
HLorenzi Avatar answered Sep 28 '22 02:09

HLorenzi