Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for linking with C libraries #4

Merged
merged 7 commits into from Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Expand Up @@ -83,6 +83,9 @@ jobs:
echo CARGO_TARGET_${upcase}_LINKER=${{ matrix.gcc }} >> $GITHUB_ENV
upcase=$(echo ${{ matrix.mustang_target }} | awk '{ print toupper($0) }' | sed 's/-/_/g')
echo CARGO_TARGET_${upcase}_LINKER=${{ matrix.gcc }} >> $GITHUB_ENV
# Configure the `*-mustang` C compiler for the `cc` crate per
# <https://github.com/sunfishcode/mustang/blob/main/README.md#the-c-runtime>
echo CC_${{ matrix.mustang_target }}=${{ matrix.gcc }} >> $GITHUB_ENV
if: matrix.gcc_package != '' && matrix.os == 'ubuntu-latest'

- name: Install cross-compilation libraries
Expand Down
11 changes: 8 additions & 3 deletions Cargo.toml
Expand Up @@ -12,19 +12,24 @@ edition = "2018"
exclude = ["/.github"]
publish = false

[dependencies]
mustang = { path = "mustang" }

[dev-dependencies]
c-scape = { path = "c-scape" }
origin = { path = "origin" }
mustang = { path = "mustang" }

# Test that the ctor crate works under mustang.
ctor = "0.1.21"

# Test that the core_simd crate works under mustang.
# TODO: Re-enable this when core_simd works on Rust nightly.
#core_simd = { git = "https://github.com/rust-lang/portable-simd" }

[features]
# Off by default; enable this to initialize the C runtime, so that C libraries
# can be called. See [here] for details.
# [here]: https://github.com/sunfishcode/mustang/blob/main/README.md#the-c-runtime
initialize-c-runtime = ["mustang/initialize-c-runtime"]

[workspace]
members = [
"mustang",
Expand Down
16 changes: 13 additions & 3 deletions README.md
Expand Up @@ -89,15 +89,25 @@ $ nm -u target/x86_64-unknown-linux-mustang/debug/examples/hello
$
```

## The C Runtime

C has a runtime, and if you wish to link with any C libraries, the C runtime
needs to be initialized. `mustang` doesn't do this by default, but it does
support this when the cargo feature "initialize-c-runtime" is enabled.

To compile C code with a `*-mustang` target, you may need to
[tell the `cc` crate which C compiler to use]; for example, for `i686-unknown-linux-mustang`,
set the environment variable `CC_i686-unknown-linux-mustang` to
`i686-linux-gnu-gcc`.

[tell the `cc` crate which C compiler to use]: https://github.com/alexcrichton/cc-rs#external-configuration-via-environment-variables

## Known Limitations

Known limitations in `mustang` include:

- Lots of stuff in `std` doesn't work yet. Hello world works, but lots of
other stuff doesn't yet.
- Linking to C libraries is not supported. There doesn't appear to be a
robust way to initialize the C runtime without letting the C runtime
start up and shutdown the process.
- No support for dynamic linking yet.
- No support for stack smashing protection (ssp) yet.
- The ELF `init` function is not supported, however the more modern
Expand Down
20 changes: 14 additions & 6 deletions c-scape/src/c.rs
Expand Up @@ -185,9 +185,13 @@ pub unsafe extern "C" fn pipe2() {

// malloc

// Large enough for any C type, including v128.
const MALLOC_ALIGN: usize = 16;

#[no_mangle]
pub unsafe extern "C" fn malloc(_size: usize) -> *mut c_void {
unimplemented!("malloc")
pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
let layout = std::alloc::Layout::from_size_align(size, MALLOC_ALIGN).unwrap();
std::alloc::alloc(layout).cast::<_>()
}

#[no_mangle]
Expand All @@ -208,11 +212,15 @@ pub unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {

#[no_mangle]
pub unsafe extern "C" fn posix_memalign(
_memptr: *mut *mut c_void,
_alignment: usize,
_size: usize,
memptr: *mut *mut c_void,
alignment: usize,
size: usize,
) -> c_int {
unimplemented!("posix_memalign")
// Note that we don't currently record `alignment` anywhere. This is only
// safe because our `free` doesn't actually call `dealloc`.
let layout = std::alloc::Layout::from_size_align(size, alignment).unwrap();
*memptr = std::alloc::alloc(layout).cast::<_>();
0
}

#[no_mangle]
Expand Down
3 changes: 2 additions & 1 deletion c-scape/src/environ.rs
Expand Up @@ -42,6 +42,8 @@ static INIT_ARRAY: unsafe extern "C" fn(c_int, *mut *mut c_char, *mut *mut c_cha
/// original environment from the kernel, so we can find the auxv array in
/// memory after it. Use priority 99 so that we run before any normal
/// user-defined constructor functions.
///
/// <https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/baselib---environ.html>
#[cfg(not(target_env = "gnu"))]
#[used]
#[link_section = ".init_array.00099"]
Expand All @@ -66,7 +68,6 @@ fn init_from_envp(envp: *mut *mut c_char) {
unsafe { environ = envp };
}

#[cfg(target_env = "gnu")]
#[no_mangle]
#[used]
pub static mut environ: *mut *mut c_char = null_mut();
42 changes: 42 additions & 0 deletions c-scape/src/pthread.rs
Expand Up @@ -143,3 +143,45 @@ pub unsafe extern "C" fn pthread_sigmask() -> c_int {
pub unsafe extern "C" fn pthread_attr_setstacksize() -> c_int {
0
}

#[no_mangle]
pub unsafe extern "C" fn pthread_cancel() -> c_int {
// `pthread_cancel` may be tricky to implement, because it seems glibc's
// cancellation mechanism uses `setjmp` to a `jmp_buf` store in
// `__libc_start_main`'s stack, and the `initialize-c-runtime` crate
// itself `longjmp`s out of `__libc_start_main`.
//
// <https://github.com/sunfishcode/mustang/pull/4#issuecomment-915872029>
unimplemented!("pthread_cancel")
}

#[no_mangle]
pub unsafe extern "C" fn pthread_exit() -> c_int {
// As with `pthread_cancel`, `pthread_exit` may be tricky to implement.
unimplemented!("pthread_exit")
}

#[no_mangle]
pub unsafe extern "C" fn pthread_cleanup_push() -> c_int {
unimplemented!("pthread_cleanup_push")
}

#[no_mangle]
pub unsafe extern "C" fn pthread_cleanup_pop() -> c_int {
unimplemented!("pthread_cleanup_pop")
}

#[no_mangle]
pub unsafe extern "C" fn pthread_setcancelstate() -> c_int {
unimplemented!("pthread_setcancelstate")
}

#[no_mangle]
pub unsafe extern "C" fn pthread_setcanceltype() -> c_int {
unimplemented!("pthread_setcanceltype")
}

#[no_mangle]
pub unsafe extern "C" fn pthread_testcancel() -> c_int {
unimplemented!("pthread_testcancel")
}
17 changes: 17 additions & 0 deletions examples/test-initialize-c-runtime.rs
@@ -0,0 +1,17 @@
extern crate mustang;

#[cfg(feature = "initialize-c-runtime")]
#[link(name = "hello-c-world")]
extern "C" {
fn hello_c_world();
}

fn main() {
#[cfg(feature = "initialize-c-runtime")]
unsafe {
hello_c_world();
}

#[cfg(not(feature = "initialize-c-runtime"))]
panic!("The \"initialize-c-runtime\" feature is not enabled.");
}
14 changes: 14 additions & 0 deletions initialize-c-runtime/Cargo.toml
@@ -0,0 +1,14 @@
[package]
name = "initialize-c-runtime"
version = "0.0.0"
authors = [
"Dan Gohman <dev@sunfishcode.online>",
]
description = "A crate to explicitly inititialize the C runtime"
documentation = "https://docs.rs/mustang"
license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
repository = "https://github.com/sunfishcode/mustang"
edition = "2018"

[build-dependencies]
cc = "1.0"
5 changes: 5 additions & 0 deletions initialize-c-runtime/README.md
@@ -0,0 +1,5 @@
This is an optional crate which adds the ability to link to C libraries.

See [here] for details.

[here]: https://github.com/sunfishcode/mustang/blob/main/README.md#the-c-runtime
10 changes: 10 additions & 0 deletions initialize-c-runtime/build.rs
@@ -0,0 +1,10 @@
fn main() {
cc::Build::new()
.flag("-ffreestanding")
.file("c/initialize-c-runtime.c")
.compile("initialize-c-runtime");

cc::Build::new()
.file("c/hello-c-world.c")
.compile("hello-c-world");
}
3 changes: 3 additions & 0 deletions initialize-c-runtime/c/hello-c-world.c
@@ -0,0 +1,3 @@
#include <stdio.h>

void hello_c_world(void) { printf("Hello from C!\n"); }
90 changes: 90 additions & 0 deletions initialize-c-runtime/c/initialize-c-runtime.c
@@ -0,0 +1,90 @@
//! Before any (hosted) C code can be executed, it's necessary to initialize
//! the C runtime. When enabled, this crate provides a small (freestanding) C
//! function which performs this task.
//!
//! It also registers a cleanup function to flush the `stdio` streams on exit.

// Use only "freestanding" headers here.
#include <stddef.h>

/// This uses `__builtin_setjmp`, which is different than `setjmp` from
/// `<setjmp.h>`. See this page:
///
/// <https://gcc.gnu.org/onlinedocs/gcc/Nonlocal-Gotos.htm>
///
/// for details on `__builtin_setjmp` and declaring `buf`.
///
/// We use `__builtin_setjmp` rather than plain `setjmp` because `setjmp` is
/// not a "freestanding" function. Technically, there is no guarantee that
/// `__builtin_setjmp` is safe to be used either, but it's provided by the
/// compiler itself, and it does less work, so it's the best we can do.
///
/// Declare the jmp buf according to the documentation linked above. Except we
/// use `void *` instead of `intptr_t` for compatibility with clang.
static void *buf[5];

/// A "main" function for `__libc_start_main` to call. We want to call the
/// `real` main ourselves, so this main just does a `longjmp` so we can regain
/// control.
static int catch_main(int argc, char **argv, char **envp) {
(void)argc;
(void)argv;
(void)envp;

// Jump out through `__libc_start_main`, just before it would call `exit`,
// back to the `setjmp` in `mustang_initialize_c_runtime`.
__builtin_longjmp(buf, 1);

return 0; // Not reached.
}

/// Depending on which C runtime we have and which version, it may either call
/// the `.init_array` functions itself, or expect to be passed a function which
/// does so. Our main requirement is to know what it's going to do, so we
/// pass it a function that does the initialization, so that either way, the
/// initialization gets done, and we can know that we shouldn't do it
/// ourselves.
///
/// We don't do this for the `.fini_array` functions because those are called
/// from `exit`.
static void call_init_array_functions(int argc, char **argv, char **envp) {
extern void (*__init_array_start[])(int, char **, char **);
extern void (*__init_array_end[])(int, char **, char **);

for (void (**p)(int, char **, char **) = __init_array_start;
p != __init_array_end; ++p) {
(*p)(argc, argv, envp);
}
}

/// The `__libc_start_main` function, as specified here:
///
/// <https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/baselib---libc-start-main-.html>
///
/// Except that we support the GLIBC extension of passing `argc`, `argv`, and
/// `envp` to `.init_array` functions.
int __libc_start_main(int (*main)(int, char **, char **), int argc,
char **ubp_av, void (*init)(int, char **, char **),
void (*fini)(void), void (*rtld_fini)(void),
void *stack_end) __attribute__((noreturn));

void mustang_initialize_c_runtime(int argc, char **ubp_av);
void mustang_initialize_c_runtime(int argc, char **ubp_av) {
if (__builtin_setjmp(buf) == 0) {
(void)__libc_start_main(catch_main, argc, ubp_av, call_init_array_functions,
NULL, NULL,
ubp_av // stack_end, or close enough.
);
}
}

// For cleanup, we can use "hosted" headers.
#include <stdio.h>

/// The C runtime flushes all open `FILE` streams before exiting.
static void cleanup(void) { (void)fflush(NULL); }

/// Register the `cleanup` function, with priority 0, so that it runs after
/// other cleanups.
__attribute__((used, section(".fini_array.00000"))) static void (
*register_cleanup)(void) = cleanup;
1 change: 1 addition & 0 deletions initialize-c-runtime/src/lib.rs
@@ -0,0 +1 @@
#![doc = include_str!("../README.md")]
6 changes: 6 additions & 0 deletions mustang/Cargo.toml
Expand Up @@ -16,3 +16,9 @@ origin = { path = "../origin" }

# A minimal `global_allocator` implementation.
wee_alloc = "0.4.5"

[features]
# Off by default; enable this to initialize the C runtime, so that C libraries
# can be called. See [here] for details.
# [here]: https://github.com/sunfishcode/mustang/blob/main/README.md#the-c-runtime
initialize-c-runtime = ["origin/initialize-c-runtime"]
2 changes: 2 additions & 0 deletions mustang/src/lib.rs
@@ -1,6 +1,8 @@
#![doc = include_str!("../README.md")]

extern crate c_scape;
#[cfg(feature = "initialize_c_runtime")]
extern crate initialize_c_runtime;
extern crate origin;

type GlobalAlloc = wee_alloc::WeeAlloc<'static>;
Expand Down
6 changes: 6 additions & 0 deletions origin/Cargo.toml
Expand Up @@ -9,3 +9,9 @@ documentation = "https://docs.rs/origin"
license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
repository = "https://github.com/sunfishcode/mustang"
edition = "2018"

[dependencies]
# Off by default; enable this to initialize the C runtime, so that C libraries
# can be called. See [here] for details.
# [here]: https://github.com/sunfishcode/mustang/blob/main/README.md#the-c-runtime
initialize-c-runtime = { path = "../initialize-c-runtime", optional = true }
22 changes: 21 additions & 1 deletion origin/src/lib.rs
Expand Up @@ -115,7 +115,9 @@ unsafe extern "C" fn rust(mem: *mut usize) -> ! {
debug_assert_eq!(*mem, argc as _);
debug_assert_eq!(*argv.add(argc as usize), std::ptr::null_mut());

// Call the `.init_array` functions.
// Call the `.init_array` functions (unless the C runtime is doing it;
// see below).
#[cfg(not(feature = "initialize-c-runtime"))]
{
type InitFn = fn(c_int, *mut *mut c_char, *mut *mut c_char);
let mut init = &__init_array_start as *const _ as usize as *const InitFn;
Expand All @@ -129,6 +131,11 @@ unsafe extern "C" fn rust(mem: *mut usize) -> ! {
}
}

// If enabled, initialize the C runtime. This also handles calling the
// .init_array functions.
#[cfg(feature = "initialize-c-runtime")]
initialize_c_runtime(argc, argv);

// Call `main`.
let ret = main(argc, argv, envp);

Expand All @@ -147,3 +154,16 @@ unsafe impl Sync for SendSyncVoidStar {}
#[no_mangle]
#[used]
static __dso_handle: SendSyncVoidStar = SendSyncVoidStar(&__dso_handle as *const _ as *mut c_void);

#[cfg(feature = "initialize-c-runtime")]
unsafe fn initialize_c_runtime(argc: c_int, argv: *mut *mut c_char) {
#[link(name = "initialize-c-runtime")]
extern "C" {
fn mustang_initialize_c_runtime(argc: c_int, argv: *mut *mut c_char);
}

#[cfg(debug_assertions)]
eprintln!(".。oO(C runtime initialization called by origin! ℂ)");

mustang_initialize_c_runtime(argc, argv);
}