How to compile Rust code to bare metal 32 bit x86 (i686) code? What compile target should I use? - rust

I'd like to compile bare metal 32-bit code for x86 (aka i686 aka x86_64 in 32-bit mode) using cargo/Rust. What target do I need? In the official supported target list I can't find anything, that is practical.

Unfortunately, Rust compiler doesn't have a built-in target definition [Rust nightly 1.54, June 2021] for this but you can provide a custom target definition:
<project-root>/x86-unknown-bare_metal.json
{
"llvm-target": "i686-unknown-none",
"data-layout": "e-m:e-i32:32-f80:128-n8:16:32-S128-p:32:32",
"arch": "x86",
"target-endian": "little",
"target-pointer-width": "32",
"target-c-int-width": "32",
"os": "none",
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "+soft-float,+sse"
}
This definition sets the pointer width to 32 and is meant to compile code, that could be used in the early x86 boot process (when you are already in 32-bit protected mode). I'm not 100% sure about the "features", because they may depend on what you want to do/need. i686 refers to x86_64 in 32-bit mode and the data-layout is explained here.
In addition, you should add the file
<project-root>/.cargo/config.toml
[unstable]
# cross compile core library for custom target
build-std = ["core", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]
[build]
# points to file in project root
target = "x86-unknown-bare_metal.json"
Now you can build a bare metal binary with 32-bit x86 code using cargo build.
// disable rust standard library
#![no_std]
// disables Rust runtime init,
#![no_main]
// see https://docs.rust-embedded.org/embedonomicon/smallest-no-std.html
#![feature(lang_items)]
// see https://docs.rust-embedded.org/embedonomicon/smallest-no-std.html
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
use core::panic::PanicInfo;
use core::sync::atomic;
use core::sync::atomic::Ordering;
#[no_mangle]
/// The name **must be** `_start`, otherwise the compiler throws away all code as unused.
/// The name can be changed by passing a different entry symbol as linker argument.
fn _start() -> ! {
loop {}
}
#[inline(never)]
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {
atomic::compiler_fence(Ordering::SeqCst);
}
}
For convenience, you should also add
<project-root>/rust-toolchain.toml
# With this file, another toolchain to the currently selected one will be used, when you execute `cargo build`.
# https://rust-lang.github.io/rustup/overrides.html
[toolchain]
# equals to rust nightly 1.54 as of the release day 2021-05-10
channel = "nightly-2021-05-10"
components = [ "rust-src", "rust-std", "rustc", "cargo" ]

Related

Compiling bevy_dylib v0.5.0 error: linking with `cc` failed: exit status: 1

On Mac freshly upgraded to Monterey, I'm getting the following when attempting to cargo run a trivial Bevy program. I've reinstalled XCode CLTs like recommended here and other places. I've tried messing around with some of the cargo.yml with no success.
Compiling bevy_dylib v0.5.0
error: linking with `cc` failed: exit status: 1
note: "cc" "-Wl,-exported_symbols_list,/var/folders/rp/lky8r76j5v5dk0rqg_yzk35w0000gn/T/rustcDYBmaq/list"
"-m64" "-arch" "x86_64" <......... many pages of warnings>
ld: warning: object file (/Users/BWStearns/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy-glsl-to-spirv-0.2.1/build/osx/libSPIRV-Tools-opt.glsltospirv.a(const_folding_rules.cpp.o)) was built for newer macOS version (10.13) than being linked (10.7)
<many more pages of warnings>
"_CGDisplayCreateUUIDFromDisplayID", referenced from:
_$LT$winit..platform_impl..platform..monitor..MonitorHandle$u20$as$u20$core..cmp..PartialEq$GT$::eq::h541b069daf520ec9 in libwinit-4f8b93ad49cc21f8.rlib(winit-4f8b93ad49cc21f8.winit.355ded65-cgu.6.rcgu.o)
_$LT$winit..platform_impl..platform..monitor..MonitorHandle$u20$as$u20$core..cmp..Ord$GT$::cmp::h951d61f6bd1d7a5f in libwinit-4f8b93ad49cc21f8.rlib(winit-4f8b93ad49cc21f8.winit.355ded65-cgu.6.rcgu.o)
winit::platform_impl::platform::monitor::MonitorHandle::ns_screen::h3207f8aed4eae22f in libwinit-4f8b93ad49cc21f8.rlib(winit-4f8b93ad49cc21f8.winit.355ded65-cgu.6.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)
error: could not compile `bevy_dylib` due to previous error
The cargo.yml is like this:
[package]
name = "my_bevy_game"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = { version = "0.5.0", features = ["dynamic"] }
I just ran into this starting this week. I am pretty sure this exact code was compiling fine a couple weeks ago. Any help would be greatly appreciated.
main.rs looks like
use bevy::prelude::*;
struct Person;
struct Name(String);
fn add_people(mut commands: Commands) {
commands.spawn().insert(Person).insert(Name("Elaina Proctor".to_string()));
commands.spawn().insert(Person).insert(Name("Renzo Hume".to_string()));
commands.spawn().insert(Person).insert(Name("Zayna Nieves".to_string()));
}
fn greet_people(query: Query<&Name, With<Person>>) {
for name in query.iter() {
println!("hello {}!", name.0);
}
}
pub struct HelloPlugin;
impl Plugin for HelloPlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_startup_system(add_people.system())
.add_system(greet_people.system());
}
}
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_plugin(HelloPlugin)
.run();
}
Update:
Definitely something about the environment on my machine because on a different macbook it worked fine.
So I found a solution if you discover this issue and the code works on other machines. Uninstall rust with rustup self uninstall and then renistall with the standard script curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh and then you should be good.

Removing appended "break" instruction in inline mips assembly

I have the following (simplified) function using inline assembly, targeting mips:
#[naked]
pub unsafe extern "C" fn test() {
asm!(
".set noreorder",
"jr $ra",
"li $v0, 0x123",
options(noreturn),
)
}
I expected this to compile into just the 2 specified instructions (in release mode), as it's a naked function, but a break instruction gets appended at the end:
00000000 <test>:
0: 03e00008 jr ra
4: 24020123 li v0,291
8: 0000000d break
I assume this is a countermeasure against undefined behavior by either rustc or llvm, but I need to product the exact assembly I specify in the function.
Is there any way to prevent either rustc, llvm or the assembler from generating this extra instruction generally?
I tested it on existing targets such as mipsel-unknown-none and it also produced a break instruction, but I am compiling on the following custom target, if it matters:
{
"arch": "mips",
"cpu": "mips1",
"data-layout": "e-m:m-p:32:32-i8:8:32-i16:16:32-i32:32-n32-S32",
"emit-debug-gdb-scripts": false,
"executables": false,
"features": "+mips32,+soft-float,+noabicalls",
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"llvm-target": "mipsel-unknown-linux-gnu",
"relocation-model": "static",
"target-pointer-width": "32",
"panic-strategy": "abort",
"singlethread": true,
"dynamic-linking": false,
"function-sections": true
}
I'm also using a #![no_std] and #![no_core] staticlib crate with the required lang items implemented and simply compiling using cargo build --release --target=my-target.json
Edit: After Peter Cordes's suggestion, I tried the same in C with
__attribute__((naked)) void test() {
__asm__(
".set noreorder\n"
"jr $ra\n"
"li $v0, 0x123\n"
);
}
Compiled using
clang -O3 test.c -c -o test.o -target mips-unknown-none
And the result is
00000000 <test>:
0: 03e00008 jr ra
4: 24020123 li v0,291
Without a break, so it seems it was included by the rust compiler.
Yes! Do one of:
Add "trap_unreachable": false to your target.json
Build with RUSTFLAGS=-Ztrap-unreachable=no. (nightly-only though)
Unfortunately it's not very well documented. Further reading: PR where the trap instruction generation was added PR where trap-unreachable=no was added

unknown feature `llvm_asm` when compile rust-src

I tried to compile rust-src using cargo xbuild but get this error:
error[E0635]: unknown feature `llvm_asm`
-> .cargo/registry/src/github.com-1ecc6299db9ec823/compiler_builtins-0.1.28/src/lib.rs:3:12
3 | #![feature(llvm_asm)]
How can I fix this error? It's seem like xbuild tries to compile the new rust-src with an old rustc. I want it to also use the old rust-src.
I can't update to a newer rustc version as it results in lots of "R_x86_32 relocation" errors, so I would prefer to use the 2020-03-24 version.
Minimal example
command
cargo new --bin test
rustup component add rust-src
cargo install cargo-xbuild
cd test
ls test
Cargo.toml rust-toolchain src x86_64-unknown-none.json
rust-toolchain
nightly-2020-03-24
x86_64-unknown-none.json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "none",
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float"
}
src/main.rs
#![no_std] // don't link the Rust standard library
#![no_main] // disable all Rust-level entry points
#![allow(non_snake_case)] // disable non snake case name warning
use core::panic::PanicInfo;
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
#[panic_handler]
pub fn MyPacnicHandler(_panicInfo: &PanicInfo) -> ! {
loop {}
}
compile
cargo xbuild --target x86_64-unknown-none
rustc --version
rustc 1.44.0-nightly (1edd389cc 2020-03-23)
This is a bug in cargo-xbuild. Basically, cargo xbuild unconditionally fetches the latest compiler_builtins.
A patch has been merged, but is not yet in the latest crates.io release. See this PR: https://github.com/rust-osdev/cargo-xbuild/pull/75/commits/eede1a1d4c08064763f1943c0920de2270260b33
Update your rust version by rustup update, which works for me.
The reason may be the feature rename in new version : https://github.com/rust-lang/rust/pull/71007

`xargo build` cannot find library

So I'm following this tutorial on how to create a VERY BASIC Operating System using the Rust programming language. (I intend on buying an actual book on the subject, but I'm using this for now).
Here are some files we created just to clarify things a little:
Cargo.toml
[package]
name = "blog_os"
version = "0.1.0"
authors = ["Philipp Oppermann <dev#phil-opp.com>"] # Here I used my own details
[lib]
crate-type = ["staticlib"]
src/lib.rs
#![feature(lang_items)]
#![no_std]
#[no_mangle]
pub extern fn rust_main() {}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] #[no_mangle] pub extern fn panic_fmt() -> ! {loop{}}
x86_64-blog_os.json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"linker-flavor": "gcc",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"arch": "x86_64",
"os": "none",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float"
}
If you scroll down to the Compiling section in the tutorial, the author shows us how to install xargo and how to use it to build.
We're then asked to run:
> xargo build --target=x86_64-blog_os
But when I do, I get the following error message:
error: failed to parse manifest at '/home/max/TesterOS/src/Cargo.toml'
Caused by:
can't find library 'blog_os', rename file to 'src/lib.rs' or specify lib.path
Is the issue connected to where I saved my files? Because I followed the tutorial down to the letter, but the author wasn't specific in where everything should be saved.
SOLVED: Turns out it was actually related to where I placed my files.
I had to create a blog_os folder and stored my files in there. Hence the error:
can't find library 'blog_os'....
Rookie mistake :)

How to obtain the value of a configuration flag?

Is there a way of obtaining the value of a configuration flag? For example, I would like to get the value of target_os as str/String, without resorting to the following if-else-if chain:
if cfg!(target_os = "windows") {
"windows"
} else if cfg!(target_os = "linux") {
"linux"
// ...
} else {
"unknown"
}
No. You can get some of them by tricking Cargo into telling you. If you place the following into a build script:
use std::env;
fn main() {
for (key, value) in env::vars() {
if key.starts_with("CARGO_CFG_") {
println!("{}: {:?}", key, value);
}
}
panic!("stop and dump stdout");
}
...it will display the cfg flags Cargo is aware of. The panic! is just there as an easy way to get Cargo to actually show the output instead of hiding it. For reference, the output this produces looks like this:
Compiling dump-cfg v0.1.0 (file:///F:/Programming/Rust/sandbox/cargo-test/dump-cfg)
error: failed to run custom build command for `dump-cfg v0.1.0 (file:///F:/Programming/Rust/sandbox/cargo-test/dump-cfg)`
process didn't exit successfully: `F:\Programming\Rust\sandbox\cargo-test\dump-cfg\target\debug\build\dump-cfg-8b04f9ac3818f82a\build-script-build` (exit code: 101)
--- stdout
CARGO_CFG_TARGET_POINTER_WIDTH: "64"
CARGO_CFG_TARGET_ENV: "msvc"
CARGO_CFG_TARGET_OS: "windows"
CARGO_CFG_TARGET_ENDIAN: "little"
CARGO_CFG_TARGET_FAMILY: "windows"
CARGO_CFG_TARGET_ARCH: "x86_64"
CARGO_CFG_TARGET_HAS_ATOMIC: "16,32,64,8,ptr"
CARGO_CFG_TARGET_FEATURE: "sse,sse2"
CARGO_CFG_WINDOWS: ""
CARGO_CFG_TARGET_VENDOR: "pc"
CARGO_CFG_DEBUG_ASSERTIONS: ""
--- stderr
thread 'main' panicked at 'stop', build.rs:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.
You can extract the values you're interested in from this list, and dump them to a generated source file, which you can then import (using #[path] or include!) into your package's source.
For target_os specifically, and also for just target_family and target_arch, there are corresponding &str constants in std::env::consts::{OS, FAMILY, ARCH}.

Resources