Skip to content

Commit 710cfbc

Browse files
committed
Add an "initialize-c-runtime" feature to initialize the C runtime.
Fixes #2.
1 parent 5442429 commit 710cfbc

File tree

14 files changed

+207
-14
lines changed

14 files changed

+207
-14
lines changed

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ edition = "2018"
1212
exclude = ["/.github"]
1313
publish = false
1414

15-
[dependencies]
16-
mustang = { path = "mustang" }
17-
1815
[dev-dependencies]
1916
c-scape = { path = "c-scape" }
2017
origin = { path = "origin" }
18+
mustang = { path = "mustang" }
2119

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

26+
[features]
27+
initialize-c-runtime = ["mustang/initialize-c-runtime"]
28+
2829
[workspace]
2930
members = [
3031
"mustang",

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,18 @@ $ nm -u target/x86_64-unknown-linux-mustang/debug/examples/hello
8989
$
9090
```
9191

92+
## The C Runtime
93+
94+
C has a runtime, and if you wish to link with any C libraries, the C runtime
95+
needs to be initialized. `mustang` doesn't do this by default, but it does
96+
support this when the cargo feature "initialize-c-runtime" is enabled.
97+
9298
## Known Limitations
9399

94100
Known limitations in `mustang` include:
95101

96102
- Lots of stuff in `std` doesn't work yet. Hello world works, but lots of
97103
other stuff doesn't yet.
98-
- Linking to C libraries is not supported. There doesn't appear to be a
99-
robust way to initialize the C runtime without letting the C runtime
100-
start up and shutdown the process.
101104
- No support for dynamic linking yet.
102105
- No support for stack smashing protection (ssp) yet.
103106
- The ELF `init` function is not supported, however the more modern

c-scape/src/c.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,13 @@ pub unsafe extern "C" fn pipe2() {
185185

186186
// malloc
187187

188+
// Large enough for any C type, including v128.
189+
const MALLOC_ALIGN: usize = 16;
190+
188191
#[no_mangle]
189-
pub unsafe extern "C" fn malloc(_size: usize) -> *mut c_void {
190-
unimplemented!("malloc")
192+
pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
193+
let layout = std::alloc::Layout::from_size_align(size, MALLOC_ALIGN).unwrap();
194+
std::alloc::alloc(layout).cast::<_>()
191195
}
192196

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

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

218226
#[no_mangle]

examples/test-initialize-c-runtime.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
extern crate mustang;
2+
3+
#[cfg(feature = "initialize-c-runtime")]
4+
#[link(name = "hello-c-world")]
5+
extern "C" {
6+
fn hello_c_world();
7+
}
8+
9+
fn main() {
10+
#[cfg(feature = "initialize-c-runtime")]
11+
unsafe {
12+
hello_c_world();
13+
}
14+
15+
#[cfg(not(feature = "initialize-c-runtime"))]
16+
panic!("The \"initialize-c-runtime\" feature is not enabled.");
17+
}

initialize-c-runtime/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "initialize-c-runtime"
3+
version = "0.0.0"
4+
authors = [
5+
"Dan Gohman <[email protected]>",
6+
]
7+
description = "A crate to explicitly inititialize the C runtime"
8+
documentation = "https://docs.rs/mustang"
9+
license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
10+
repository = "https://github.com/sunfishcode/mustang"
11+
edition = "2018"
12+
13+
[build-dependencies]
14+
cc = "1.0"

initialize-c-runtime/build.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
fn main() {
2+
cc::Build::new()
3+
.flag("-ffreestanding")
4+
.file("c/initialize-c-runtime.c")
5+
.compile("initialize-c-runtime");
6+
7+
cc::Build::new()
8+
.file("c/hello-c-world.c")
9+
.compile("hello-c-world");
10+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include <stdio.h>
2+
3+
void hello_c_world(void) { printf("Hello from C!\n"); }
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! Before any (hosted) C code can be executed, it's necessary to initialize
2+
//! the C runtime. When enabled, this crate provides a small (freestanding) C
3+
//! function which performs this task.
4+
//!
5+
//! It also registers a cleanup function to flush the `stdio` streams on exit.
6+
7+
// Use only "freestanding" headers here.
8+
#include <stddef.h>
9+
10+
/// This uses `__builtin_setjmp`, which is different than `setjmp` from
11+
/// `<setjmp.h>`. See this page:
12+
///
13+
/// <https://gcc.gnu.org/onlinedocs/gcc/Nonlocal-Gotos.htm>
14+
///
15+
/// for details on `__builtin_setjmp` and declaring `buf`.
16+
///
17+
/// We use `__builtin_setjmp` rather than plain `setjmp` because `setjmp` is
18+
/// not a "freestanding" function. Technically, there is no guarantee that
19+
/// `__builtin_setjmp` is safe to be used either, but it's provided by the
20+
/// compiler itself, and it does less work, so it's the best we can do.
21+
///
22+
/// Declare the jmp buf according to the documentation linked above. Except we
23+
/// use `void *` instead of `intptr_t` for compatibility with clang.
24+
static void *buf[5];
25+
26+
/// A "main" function for `__libc_start_main` to call. We want to call the
27+
/// `real` main ourselves, so this main just does a `longjmp` so we can regain
28+
/// control.
29+
static int catch_main(int argc, char **argv, char **envp) {
30+
(void)argc;
31+
(void)argv;
32+
(void)envp;
33+
34+
// Jump out through `__libc_start_main`, just before it would call `exit`,
35+
// back to the `setjmp` in `mustang_initialize_c_runtime`.
36+
__builtin_longjmp(buf, 1);
37+
38+
return 0; // Not reached.
39+
}
40+
41+
/// Depending on which C runtime we have and which version, it may either call
42+
/// the `.init_array` functions itself, or expect to be passed a function which
43+
/// does so. Our main requirement is to know what it's going to do, so we
44+
/// pass it a function that does the initialization, so that either way, the
45+
/// initialization gets done, and we can know that we shouldn't do it
46+
/// ourselves.
47+
///
48+
/// We don't do this for the `.fini_array` functions because those are called
49+
/// from `exit`.
50+
static void call_init_array_functions(int argc, char **argv, char **envp) {
51+
extern void (*__init_array_start[])(int, char **, char **);
52+
extern void (*__init_array_end[])(int, char **, char **);
53+
54+
for (void (**p)(int, char **, char **) = __init_array_start;
55+
p != __init_array_end; ++p) {
56+
(*p)(argc, argv, envp);
57+
}
58+
}
59+
60+
/// The `__libc_start_main` function, as specified here:
61+
///
62+
/// <https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/baselib---libc-start-main-.html>
63+
///
64+
/// Except that we support the GLIBC extension of passing `argc`, `argv`, and
65+
/// `envp` to `.init_array` functions.
66+
int __libc_start_main(int (*main)(int, char **, char **), int argc,
67+
char **ubp_av, void (*init)(int, char **, char **),
68+
void (*fini)(void), void (*rtld_fini)(void),
69+
void *stack_end) __attribute__((noreturn));
70+
71+
void mustang_initialize_c_runtime(int argc, char **ubp_av);
72+
void mustang_initialize_c_runtime(int argc, char **ubp_av) {
73+
if (__builtin_setjmp(buf) == 0) {
74+
(void)__libc_start_main(catch_main, argc, ubp_av, call_init_array_functions,
75+
NULL, NULL,
76+
ubp_av // stack_end, or close enough.
77+
);
78+
}
79+
}
80+
81+
// For cleanup, we can use "hosted" headers.
82+
#include <stdio.h>
83+
84+
/// The C runtime flushes all open `FILE` streams before exiting.
85+
static void cleanup(void) { (void)fflush(NULL); }
86+
87+
/// Register the `cleanup` function, with priority 0, so that it runs after
88+
/// other cleanups.
89+
__attribute__((section(".fini_array.00000"))) static void (*register_cleanup)(
90+
void) = cleanup;

initialize-c-runtime/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

mustang/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ origin = { path = "../origin" }
1616

1717
# A minimal `global_allocator` implementation.
1818
wee_alloc = "0.4.5"
19+
20+
[features]
21+
initialize-c-runtime = ["origin/initialize-c-runtime"]

mustang/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![doc = include_str!("../README.md")]
22

33
extern crate c_scape;
4+
#[cfg(feature = "initialize_c_runtime")]
5+
extern crate initialize_c_runtime;
46
extern crate origin;
57

68
type GlobalAlloc = wee_alloc::WeeAlloc<'static>;

origin/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ documentation = "https://docs.rs/origin"
99
license = "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
1010
repository = "https://github.com/sunfishcode/mustang"
1111
edition = "2018"
12+
13+
[dependencies]
14+
initialize-c-runtime = { path = "../initialize-c-runtime", optional = true }

origin/src/lib.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ unsafe extern "C" fn rust(mem: *mut usize) -> ! {
115115
debug_assert_eq!(*mem, argc as _);
116116
debug_assert_eq!(*argv.add(argc as usize), std::ptr::null_mut());
117117

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

134+
// If enabled, initialize the C runtime. This also handles calling the
135+
// .init_array functions.
136+
#[cfg(feature = "initialize-c-runtime")]
137+
initialize_c_runtime(argc, argv);
138+
132139
// Call `main`.
133140
let ret = main(argc, argv, envp);
134141

@@ -147,3 +154,16 @@ unsafe impl Sync for SendSyncVoidStar {}
147154
#[no_mangle]
148155
#[used]
149156
static __dso_handle: SendSyncVoidStar = SendSyncVoidStar(&__dso_handle as *const _ as *mut c_void);
157+
158+
#[cfg(feature = "initialize-c-runtime")]
159+
unsafe fn initialize_c_runtime(argc: c_int, argv: *mut *mut c_char) {
160+
#[link(name = "initialize-c-runtime")]
161+
extern "C" {
162+
fn mustang_initialize_c_runtime(argc: c_int, argv: *mut *mut c_char);
163+
}
164+
165+
#[cfg(debug_assertions)]
166+
eprintln!(".。oO(C runtime initialization called by origin! ℂ)");
167+
168+
mustang_initialize_c_runtime(argc, argv);
169+
}

tests/tests.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ macro_rules! assert_eq_str {
1010
}};
1111
}
1212

13-
fn test_example(name: &str, stdout: &str, stderr: &str) {
13+
fn test_example(name: &str, features: &str, stdout: &str, stderr: &str) {
1414
use std::process::Command;
1515

1616
#[cfg(target_arch = "x86_64")]
@@ -26,6 +26,8 @@ fn test_example(name: &str, stdout: &str, stderr: &str) {
2626
.arg("+nightly")
2727
.arg("run")
2828
.arg("--quiet")
29+
.arg("--features")
30+
.arg(features)
2931
.arg("-Z")
3032
.arg("build-std")
3133
.arg(&format!(
@@ -63,6 +65,7 @@ fn test_example(name: &str, stdout: &str, stderr: &str) {
6365
fn test() {
6466
test_example(
6567
"hello",
68+
"",
6669
"Hello, world!\n",
6770
".。oO(This process was started by origin! 🎯)\n\
6871
.。oO(Environment variables initialized by c-scape! 🌱)\n\
@@ -72,39 +75,54 @@ fn test() {
7275
test_example(
7376
"test-args",
7477
"",
78+
"",
7579
".。oO(This process was started by origin! 🎯)\n\
7680
.。oO(Environment variables initialized by c-scape! 🌱)\n\
7781
.。oO(This process will be exited by c-scape using rsix! 🚪)\n",
7882
);
7983
test_example(
8084
"test-backtrace",
8185
"",
86+
"",
8287
".。oO(This process was started by origin! 🎯)\n",
8388
);
8489
test_example(
8590
"test-ctor",
8691
"",
92+
"",
8793
".。oO(This process was started by origin! 🎯)\n\
8894
.。oO(Environment variables initialized by c-scape! 🌱)\n\
8995
.。oO(This process will be exited by c-scape using rsix! 🚪)\n",
9096
);
9197
test_example(
9298
"test-environ",
9399
"",
100+
"",
94101
".。oO(This process was started by origin! 🎯)\n\
95102
.。oO(Environment variables initialized by c-scape! 🌱)\n\
96103
.。oO(This process will be exited by c-scape using rsix! 🚪)\n",
97104
);
98105
test_example(
99106
"test-simd",
100107
"",
108+
"",
101109
".。oO(This process was started by origin! 🎯)\n\
102110
.。oO(Environment variables initialized by c-scape! 🌱)\n\
103111
.。oO(This process will be exited by c-scape using rsix! 🚪)\n",
104112
);
105113
test_example(
106114
"test-tls",
107115
"",
116+
"",
108117
".。oO(This process was started by origin! 🎯)\n",
109118
);
119+
test_example(
120+
"test-initialize-c-runtime",
121+
"initialize-c-runtime",
122+
"Hello from C!\n",
123+
".。oO(This process was started by origin! 🎯)\n\
124+
.。oO(C runtime initialization called by origin! ℂ)\n\
125+
.。oO(Environment variables initialized by c-scape! 🌱)\n\
126+
.。oO(This process will be exited by c-scape using rsix! 🚪)\n",
127+
);
110128
}

0 commit comments

Comments
 (0)