{% raw %}
emk1024

If you decide to add interrupt support to your OS (for keyboard input, for example), you may not want Rust to be generating SSE code. If you use SEE code in the kernel, then you need to save SSE registers in interrupts, and saving SSE registers is slow and takes a lot of RAM. As far as I can tell, a lot of kernels simply avoid floating point to help keep interrupts and system calls efficient.

Also, as you noted in your bug on GitHub, you'll also want to set no-redzone to prevent memory corruption during interrupts.

Since we need to set a bunch of compiler flags for all generated code, including libcore, the right answer may be to replace the target x86_64-unknown-linux-gnu with a custom target that uses the right options by default. There's a discussion here and an example target file in the zinc OS.

emk1024

OK, it took almost a day, but I think I've got this figured out. This is probably overkill for your great blog posts, but I'll leave it here for the next person to pass this way.

Here's the basic strategy to getting an SSE-free, redzone-free kernel:

1. Define a new target x86_64-unknown-none-gnu, where none means running on bare metal without an OS. This can be done by creating a file x86_64-unknown-none-gnu.json and filling it in with the right options. See below. You can just drop this in your top-level build directory and Rust will find it.

2. Check out the same Rust you're compiling with, and patch libcore to remove floating point. You can usually find a current libcore patch in thepowersgang/rust-barebones-kernel on GitHub.

3. Build libcore with --target $(target) --cfg disable_float, and put it in ~/.multirust/toolchains/nightly/lib/rustlib/$(target)/lib.

4. Run cargo normally, specifying your custom target with --target $(target).

Here's my custom x86_64-unknown-none-gnu.json file

{
"llvm-target": "x86_64-unknown-none-gnu",
"target-endian": "little",
"target-pointer-width": "64",
"os": "none",
"arch": "x86_64",
"data-layout": "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128",
"pre-link-args": [ "-m64" ],
"cpu": "x86-64",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3",
"disable-redzone": true,
"eliminate-frame-pointer": false,
"linker-is-gnu": true,
"no-compiler-rt": true,
"archive-format": "gnu"
}

This seems like a good approach, because I'm not compiling Rust code for the Linux user-space, and then trying to convince it to run on bare metal. Instead, I'm compiling Rust code against a properly-configured bare metal target, and if I don't like the compiler options, I can quickly change them for all crates. And if any floating point code tries to sneak into kernel space, I'll get an error immediately, instead of finding it when floating point registers get clobbered by an interrupt that used MMX code.

The osdev wiki claims that this the harder but wiser course of action:

Common examples [of beginner mistakes] include being too lazy to use a Cross-Compiler, developing in Real Mode instead of Protected Mode or Long Mode, relying on BIOS calls rather than writing real hardware drivers, using flat binaries instead of ELF, and so on.

Since they know way more about this than I do, I'm going with their suggestions for now. :-)

Philipp Oppermann

Why does your target.json file include

"features": "-mmx,-sse,-sse2,-sse3,-ssse3"

And doesn't Rust use the `xmm` registers to optimize non-floating-point code, too?

emk1024

In theory, "-mmx" means "disable mmx", and so on. I'm attempting to convince Rust and LLVM to leave all those registers alone in kernel space, and to never generate any code which uses them. The goal here is not to need to save that huge block of registers (plus FPU state) on every interrupt. This seems to be a popular choice for x86 kernels.

Does it work? We'll see.

Philipp Oppermann

Ah, I see... It seems like there is no documentation about this. Hopefully the libcore-without-sse issue gets resolved soon. Manually patching libcore seems like a really ugly solution.

I think I will choose the "slow" solution and just save the sse registers on every interrupt. It's required anyway when switching between (user) processes.

In my opinion the best solution would be an annotation to disable SSE just for the interrupt handlers.

Alister Lee

This is currently also hard on ARM, because I can't work out the features to disable to avoid emitting fpu instructions. I've raised a bug on llvm to seek clarification: https://llvm.org/bugs/show_...

alilee

Thanks heaps Phil. Just a comment for others that the rlib dependency strategy you describe won't work under a cross-compiling (ie. arm / r-pi) because the multirust nightly won't include the libcore necessary for the dependant crates to build, and they won't refer to the ones inside /target. See here: https://github.com/rust-lan...

Alister Lee

Right, have learned a lot in the last month, following you on ARM. I expect I'll need rlibc, but I haven't yet.

What I have needed is `compiler-rt`, which you have avoided because you are building on a (tier 3) supported build target which is [not the case](http://stackoverflow.com/qu...) for `arm-none-eabi`.

Philipp Oppermann

You can avoid compiler-rt by using a custom target file that contains `"no-compiler-rt": true`.

You can then use nightly-libcore to cross compile libcore for your new target.

the_boffin

For OS X/Darwin users who have made it this far:

https://github.com/rust-lan...

It's been a good run, but Apple's modifications to clang and ld for the Darwin system completely destroy rust's existing cross-compile capabilities, which means building an x86_64-compatibile libcore simply isn't possible without monumental amounts of work.

Aaron D

You might want to try again, I've been following along with all of the latest posts on macOS 10.11 without problems.

Doesn't the linker problem still exist? Most of the options used by GNU 'ld' are not supported by macOS 10.11 'ld'. May be you used cross compiled 'ld' ?

Jeff Westfahl

I guess there's a reason you can't enable SSE in your 32-bit assembly file before you switch to long mode?

Philipp Oppermann

It should be totally possible to do it in the 32-bit file. I just tested it and it works without problems. I think I used to do more things in the `long_mode_init` file in a previous system so that there was already an error function. But since it's now the only function that needs the 64-bit error function, we could remove that function if we moved the `setup_SEE` function.

Thanks for the hint! I opened an issue for this.

stephane geneix

on my box, I couldn't reproduce the SSE error. It looks like "a += 1;" doesn't generate SSE instructions anymore by default.

objdump still shows some in what looks like exception handling code but seems to never execute
~/dev/rustOS/$rustc --version
rustc 1.7.0-nightly (81dd3824f 2015-12-11)
~/dev/rustOS/$cargo --version
cargo 0.8.0-nightly (028ac34 2015-12-10)

Thank you very much for this series though, it's probably one of the most interesting ways I've seen to learn about OS boot sequence I've seen

Philipp Oppermann

Thanks a lot! I opened an issue for this.

How about the following example?:

let mut a = ("hello", 42);
a.1 += 1;

stephane geneix

yes, that one breaks down without SSE

Philipp Oppermann

Perfect! I will update it

suvash

so much fun ! Thanks for this 💥🍾🍻 ! Can we have a emoji Hello World ? Just kidding.

Philipp Oppermann

Actually there are two smileys in code page 437.

Smiley Hello World:

....
let hello = b"\x02\x01 Hello World! \x01\x02";
let color_byte = 0x1f;

let mut hello_colored = [color_byte; 36];
...

suvash

omg. this is brilliant, almost forgot about these (ascii?) codes. Thanks !

Aleksey Kladov

Awesome! I've made a buffer overrun error, because I've added a comma to the "Hello, World!", and Rust have actually caught it at run time, and started looping.

Using


#[lang = "panic_fmt" ]
extern fn panic_fmt() -> ! {
let buffer_ptr = (0xb8000) as *mut _;
let red = 0x4f;
unsafe {
*buffer_ptr = [b'P', red, b'a', red, b'n', red, b'i', red, b'c', red, b'!', red];
};
loop { }
}

Helped a lot.

Erdos

Using the most recent nightly build for this, the no-landing-pads snippet also generates SSE, so it's a bit of a two-for-one. :)

Philipp Oppermann

Thanks a lot! I fixed it by removing the superfluous let mut a = ("hello", 42); a.1 += 1; snippet (see PR #153).

tsurai

I think that libcore needs both SSE and SSE2 to be supported. Shouldn't you check the SSE2 CPUID flag to be sure that both SSE and SSE2 is present? Not sure if it could cause any problems within libcore later on

Philipp Oppermann

Good catch! However, SSE2 should always be available if the long mode is available. Citing the OSDev wiki:

When the X86-64 architecture was introduced, AMD demanded a minimum level of SSE support to simplify OS code. Any system capable of long mode should support at least SSE and SSE2

So SSE and SSE2 should always be available in our case (if the wiki is correct). So we could even remove the SSE check. However, I think it's better to leave it in, because we enable SSE before switching to long mode.

Don Rowe

Thanks again for sharing this! FYI, the link https://doc.rust-lang.org/std/rt/unwind/ in http://os.phil-opp.com/set-... is broken.

Philipp Oppermann

Thanks! I've fixed it in #207.

Sorry for the delay, your comment got buried in my inbox somehow…

JamesLewn

#CODE FOR ANIMATED TEXT
If you want text that is moving around the screen like a snake, get that code and replace you rust_main function with it:

#[no_mangle]
pub extern fn rust_main() {

let color_byte: u16 = 0x1f;
let ascii_byte: u16 = 32;
let empty_2byte_character: u16 = (color_byte << 8) | ascii_byte;

let mut poz = 0;
while poz < 4000
{
let buffer = (0xb8000 + poz ) as *mut _;
unsafe{*buffer = empty_2byte_character };
poz+=2;
}

let text = b"ANIMATED TEXT!!!! ->";
let color_byte = 0x1f;

let mut text_colored = [color_byte; 44];
for(i, char_byte) in text.into_iter().enumerate(){
text_colored[i*2] = *char_byte;
}

//animate
let mut offset = 0;
let mut done = false;
let mut delay_counter = 0;

while !done
{
let poprzedni = (0xb8000 + offset) as *mut _;
unsafe{*poprzedni = empty_2byte_character };

offset+=2;

let buffer_ptr = (0xb8000 + offset) as *mut _;
unsafe{*buffer_ptr = text_colored };

if offset == (4000-44)
{
done = true;
}
while delay_counter < 10000000
{
delay_counter+=1;
}
delay_counter = 0;
}

loop
{

}

}

Aaron D

With the latest rust nightly I was getting linker errors after pulling in in the rlibc crate:

target/x86_64-unknown-linux-gnu/debug/libblog_os.a(core-93f19628b61beb76.0.o): In function `core::panicking::panic_fmt':
/buildslave/rust-buildbot/slave/nightly-dist-rustc-linux/build/src/libcore/panicking.rs:69: undefined reference to `rust_begin_unwind'
make: *** [build/kernel-x86_64.bin] Error 1

Apparently the later versions of the compiler are pretty strict about mangling almost anything they can for optimization. Usually the panic_fmt symbol becomes rust_begin_unwind (for some reason), but now it's getting mangled and so the linker can't find that symbol - it's a pretty cryptic error with discussion at https://github.com/rust-lan...

To fix it, you need to mark panic_fmt with no_mangle as well, so the line in lib.rs becomes:
#[lang = "panic_fmt"] #[no_mangle] extern fn panic_fmt() -> ! {loop{}}

This allows it to build properly.

yangxiaoyong

Thank you for that!

toor_

Hello, I have reached the stage of panic = "abort", but when I make run I get this error: target/x86_64-os/debug/libos.a(core-9a5ada2b08448709.0.o): In function core::panicking::panic_fmt': core.cgu-0.rs:(.text.cold._ZN4core9panicking9panic_fmt17h6b6d64bae0e8a2c2E+0x88): undefined reference torust_begin_unwind', I really have no clue what is happening here as I have exactly the same code as you do above.

EDIT: I have read some of the above comments, turns out that other people were having the same issue as me and I have used their solutions. Sorry to waste your time.

Anonym

There is an error when changing the makefile(roughly in the middle of this post). This part

which stands for “garbage collect sections”. Let's add it to the $(kernel) target in our Makefile: $(kernel): xargo $(rust_os) $(assembly_object_files) $(linker_script) @ld -n --gc-sections -T $(linker_script) -o $(kernel) \ $(assembly_object_files) $(rust_os)

results in "no rule to make target 'xargo'

when changing it to this it works again:

$(kernel): kernel $(rust_os) $(assembly_object_files) $(linker_script) @ld -n --gc-sections -T $(linker_script) -o $(kernel) \ $(assembly_object_files) $(rust_os)

Hey, loving the tutorials :) though I'm running into an issue when using xargo build. When I do xargo build --target=x86_64-blog_os, I get the following error:

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

Could this be because of where I've saved my files? Because when I saw src/lib.rs in the tutorial, I just saved lib.rs in the src file we created. Is it something to do with where I placed my Cargo.toml or/and x86_64-blog_os.json file?

Really confused here.

Philipp Oppermann

Cargo assumes that the lib.rs file is in a subfolder named src. So it doesn't work if you put the lib.rs next to the Cargo.toml.

Thanks a million, it's all sorted now :). One more issue though. About the rlibc... make run seems to work fine without extern crate rlibc... But fails when I do add it in, saying it can't compile rlibc.

Sorry to be a bother lol I'm a newb.

Philipp Oppermann

There is currently a problem with cargo/xargo, maybe this is affects you: https://github.com/phil-opp/blog_os/issues/379

{% endraw %}