Rust and C linking problems with minimal program and no_std - c

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.

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).

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!

Related

How to link a C library in Rust WASI

I want to use a C library in my rust-wasi program. but I am having trouble linking external libraries. My Current setup is this.
main.rs
#[link(name = "mylib")]
extern "C" {
pub fn add_one(i: i32) -> i32;
}
pub fn main() {
let res = unsafe { add_one(10) };
println!("I + 1: {}", res);
}
https://github.com/codeplea/tinyexpr
mylib.cpp
#include "tinyexpr.h"
extern "C" int add_one(int i)
{
te_interp("i + 1", 0);
return i + 1;
}
build.rs
fn main() {
cc::Build::new()
.archiver("llvm-ar")
.cpp_link_stdlib(None)
.cpp(true)
.flag("--sysroot=/opt/wasi-sysroot/")
.file("mylib.cpp")
.compile("libmylib.a");
}
leading to this error when I try to execute it with wasmtime.
cargo build --target wasm32-wasi --release
wasmtime --dir=. --dir=/tmp target/wasm32-wasi/release/reverser.wasm
Error: failed to run main module `target/wasm32-wasi/release/so.wasm`
Caused by:
0: failed to instantiate "target/wasm32-wasi/release/so.wasm"
1: unknown import: `env::te_interp` has not been defined
I don't have any issues linking to the headers in the sys-root directory. Just with c headers in the same directory
tinyexpr is not a header-only library, you also need to compile tinyexpr.c:
cc::Build::new()
.archiver("llvm-ar")
.flag(&sysroot)
.file("tinyexpr.c")
.compile("tinyexpr");
though you don't necessarily need to give it its own library, you could also compile tinyexpr.c and mylib.cpp into the same .a. From what I understand about C/C++ build processes, that should give you the same result.
If you wanted to be real pretty about it, you'd make a new tinyexpr-sys crate that contains just tinyexpr.c (plus a cbindgen-generated lib.rs).
Side note: For finding the sysroot, I'd go with something like
let sysroot = var("MYLIB_WASI_SYSROOT")
.or(var("WASI_SYSROOT"))
.ok()
.or_else(|| Some(format!("{}/share/wasi-sysroot", var("WASI_SDK_PATH").ok()?)));
let sysroot = match sysroot {
Some(sysroot) => format!("--sysroot={}", sysroot),
None => {
eprintln!(
"Install wasi-sdk or wasi-libc and specify WASI_SYSROOT path in environment!"
);
exit(1);
}
};
though you could also just expect people to set CFLAGS/CXXFLAGS.
Other side notes:
You might have less of a headache with the rust version of this library
Related answer

Why does use libc stop cargo from linking my program correctly?

I have some C code which I compiled to a .so file which i want to be called from a Rust program.
// hello.c
void greet() {
printf("Hello, world");
}
so I compiled it to a shared object file and added it to my build.rs and it worked fine
// main.rs
#[link(name = "hello")]
extern "C" {
fn greet();
}
fn main() {
unsafe {
greet();
}
}
The problem is the I have a second function in my C code which accepts a char* as a parameter so I tried to use libc::c_char to communicate between C and Rust but whenever my program doesn't compile when I import libc.
// main.rs
#[link(name = "hello")]
use libc::c_char;
extern "C" {
greet();
}
And I already tried to compile just with import libc (because I thought that might have been the problem) but it works perfectly so it seems like the program only doesn't compile when I am using my C shared object and importing the libc crate.
This is the error message
error: linking with `cc` failed: exit code: 1
= note: "cc"
= note: Undefined symbols for architecture x86_64:
"_greet", referenced from:
project::main::h501a37fa09c5db9f in project.2q2eogqn7p5k3u7s.rcgu.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
The #[link] attribute must be just before the extern block. By inserting a use between the #[link] attribute and the extern block, the #[link] attribute becomes attached to the use and has no effect. (There really should be a warning for this...)
Works just fine for me, are you sure you compiled a static library that Rust linker can use regardless of what else gets linked into the final executable?
I can only guess that this is whats wrong as you haven't provided how exactly you setup your project, and I'd recommend letting cc crate handle it for you, and if you really need something it doesn't have, contribute to it, instead of manually compiling C code and trying to link it in.
Example
build.rs
fn main() {
cc::Build::new()
.file("src/hello.c")
.compile("hello");
}
src/hello.c
#include <stdio.h>
void greet() {
printf("Hello, world\n");
}
src/main.rs
use libc::c_char;
#[link(name = "hello")]
extern "C" {
fn greet();
}
fn main() {
unsafe {
greet();
}
}
cli
$ cargo run
Compiling link v0.1.0 (~/Desktop/link)
warning: unused import: `libc::c_char`
--> src/main.rs:4:5
|
4 | use libc::c_char;
| ^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: 1 warning emitted
Finished dev [unoptimized + debuginfo] target(s) in 0.50s
Running `~/.cargo/target/debug/link`
Hello, world

Failed to invoke functions when running wasm file with wasmtime

Mozilla shared WASI and how to use Wasmtime to run .wasm file in their blog post. The programming language they demonstrated is Rust:
#[wasm_bindgen]
pub fn render(input: &str) -> String {
let parser = Parser::new(input);
let mut html_output = String::new();
html::push_html(&mut html_output, parser);
return html_output;
}
However, I want to do the same thing in C.
I've downloaded wasi-libc and tried to build a 'hello world' program with Clang.
I created two functions in test.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int foo1()
{
printf("Hello foo1()\n");
return 0;
}
int foo2(char* filename)
{
printf("Hello foo2()\n");
printf("filename is %s\n", filename);
return 0;
}
Build it with the command:
clang --target=wasm32-wasi --sysroot=/mnt/d/code/wasi-libc/sysroot test.c -o test.wasm -nostartfiles -Wl,--no-entry,--export=foo1,--export=foo2
Run the wasm file to invoke functions:
$ wasmtime test.wasm --invoke foo1
Hello foo1()
warning: using `--render` with a function that returns values is experimental and may break in the future
0
$ wasmtime test.wasm --invoke foo2 "hello"
warning: using `--render` with a function that takes arguments is experimental and may break in the future
error: failed to process main module `test.wasm`
caused by: invalid digit found in string
I failed to invoke the function with an input parameter.
What's the difference between Rust and C? Is Rust currently the only way to build wasm lib file?
The difference is that the Rust toolchain has experimental support for Interface Types, whereas that doesn't yet exist for C, unfortunately. The #[wasm_bindgen] above the render function is what turns render into a function exported with Interface Types bindings.

How to make LoadLibrary to show error dialog when there are missing dependencies

Suppose we have two dynamic libraries libfoo.dll and libbar.dll, given that libbar.dll depends on libfoo.dll. Further we compile an executable test.exe that loads our libbar.dll using WinAPI function LoadLibrary().
If we run text.exe on Windows XP with missing libfoo.dll, LoadLibrary() shows dialog box alerting than libfoo.dll is actually missing and sets LastError to ERROR_MOD_NOT_FOUND (126).
If we run same text.exe in same conditions on Windows 10, LoadLibrary() sets LastError to ERROR_MOD_NOT_FOUND only, no dialog box appears.
In both cases ErrorMode is 0. So is there any possibility to catch a name of missing dependency in LoadLibrary() calling process, or, at least, how to make LoadLibrary() show error dialog on Windows 10?
Here is a sample code (using MinGW):
foo.c
int foo(int a, int b)
{
return a + b;
}
Compile with: gcc foo.c -o libfoo.dll -fPIC -shared
bar.c
int foo(int a, int b);
int bar(int a, int b)
{
return foo(a, b);
}
Compile with: gcc bar.c -o libbar.dll -fPIC -shared -L. -lfoo
test.c
#include <windows.h>
#include <stdio.h>
typedef int (*pfn)(int a, int b);
int main()
{
SetErrorMode(0);
HMODULE hmod = LoadLibrary("libbar.dll");
if(!hmod)
{
fprintf(stderr, "error loading library %d\n", GetLastError());
return 1;
}
pfn bar = (pfn)GetProcAddress(hmod, "bar");
if(bar)
{
fprintf(stdout, "bar(3, 1) = %d\n", bar(3, 1));
}
else
{
fprintf(stderr, "can't load bar foonction\n");
}
FreeLibrary(hmod);
return 0;
}
Compile with: gcc test.c -o test
At the moment it seems like there is no elegant solution to the question posed.
As #DavidHeffernan noted in the comments to the original post, the problem should be solved on a fundamentally different level. As LoadLibrary() behaves just like it should behave, the key is the proper installation and error handling.
However, if the one needs to explicitly catch missing dependencies of the dynamically loaded libraries, the techniques provided by #IInspectable and #eryksun could be applied:
enabling Delay-Loded DLLs for the libraries that are going to be dynamically loaded. Such an approach provides helper callbacks for each dependent module, so missing dependencies could be handled in place. The main disadvantage of this approach is that the target library should be recompiled with proper linker flags;
the helper utility that dumps debug strings from the application could be written (see #eryksun comment to the original post for details). Disadvantages: besides the need of writing an additional module it also includes some registry manipulations.

dlsym returns NULL, even though the symbol exists

I am using dlsym to look up symbols in my program, but it always returns NULL, which I am not expecting. According to the manpage, dlsym may return NULL if there was an error somehow, or if the symbol indeed is NULL. In my case, I am getting an error. I will show you the MCVE I have made this evening.
Here is the contents of instr.c:
#include <stdio.h>
void * testing(int i) {
printf("You called testing(%d)\n", i);
return 0;
}
A very simple thing containing only an unremarkable example function.
Here is the contents of test.c:
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
typedef void * (*dltest)(int);
int main(int argc, char ** argv) {
/* Declare and set a pointer to a function in the executable */
void * handle = dlopen(NULL, RTLD_NOW | RTLD_GLOBAL);
dlerror();
dltest fn = dlsym(handle, "testing");
if(fn == NULL) {
printf("%s\n", dlerror());
dlclose(handle);
return 1;
}
dlclose(handle);
return 0;
}
As I step through the code with the debugger, I see the dlopen is returning a handle. According to the manpage, If filename is NULL, then the returned handle is for the main program. So if I link a symbol called testing into the main program, dlsym should find it, right?
Here is the way that I am compiling and linking the program:
all: test
instr.o: instr.c
gcc -ggdb -Wall -c instr.c
test.o: test.c
gcc -ggdb -Wall -c test.c
test: test.o instr.o
gcc -ldl -o test test.o instr.o
clean:
rm -f *.o test
And when I build this program, and then do objdump -t test | grep testing, I see that the symbol testing is indeed there:
08048632 g F .text 00000020 testing
Yet the output of my program is the error:
./test: undefined symbol: testing
I am not sure what I am doing wrong. I would appreciate if someone could shed some light on this problem.
I don't think you can do that, dlsym works on exported symbols. Because you're doing dlsym on NULL (current image), even though the symbols are present in the executable ELF image, they're not exported (since it's not a shared library).
Why not call it directly and let the linker take care of it? There's no point in using dlsym to get symbols from the same image as your dlsym call. If your testing symbol was in a shared library that you either linked against or loaded using dlopen then you would be able to retrieve it.
I believe there's also a way of exporting symbols when building executables (-Wl,--export-dynamic as mentioned in a comment by Brandon) but I'm not sure why you'd want to do that.
I faced the similar issue in my code.
I did the following to export symbols
#ifndef EXPORT_API
#define EXPORT_API __attribute__ ((visibility("default")))
#endif
Now for each of the function definition I used the above attribute.
For example the earlier code was
int func() { printf(" I am a func %s ", __FUNCTION__ ) ;
I changed to
EXPORT_API int func() { printf(" I am a func %s ", __FUNCTION__ ) ;
Now it works.
dlsym gives no issues after this.
Hope this works for you as well.

Resources