diff --git a/Cargo.toml b/Cargo.toml index 730970f8..14c71a44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ authors = ["Philipp Oppermann "] name = "blog_os" version = "0.2.0" +edition = "2018" [dependencies] bootloader = "0.3.4" diff --git a/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md b/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md index c68bb482..4ca80393 100644 --- a/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md +++ b/blog/content/second-edition/posts/01-freestanding-rust-binary/index.md @@ -52,10 +52,12 @@ By default, all Rust crates link the [standard library], which depends on the op We start by creating a new cargo application project. The easiest way to do this is through the command line: ``` -> cargo new blog_os --bin +> cargo new blog_os --bin --edition 2018 ``` -I named the project `blog_os`, but of course you can choose your own name. The `--bin` flag specifies that we want to create an executable binary (in contrast to a library). When we run the command, cargo creates the following directory structure for us: +I named the project `blog_os`, but of course you can choose your own name. The `--bin` flag specifies that we want to create an executable binary (in contrast to a library) and the `--edition 2018` flag specifies that we want to use the [2018 edition] of Rust for our crate. When we run the command, cargo creates the following directory structure for us: + +[2018 edition]: https://rust-lang-nursery.github.io/edition-guide/rust-2018/index.html ``` blog_os diff --git a/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md b/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md index 5f7b5f62..f816f306 100644 --- a/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md +++ b/blog/content/second-edition/posts/02-minimal-rust-kernel/index.md @@ -332,12 +332,6 @@ Instead of writing our own bootloader, which is a project on its own, we use the bootloader = "0.3.4" ``` -```rust -// in main.rs - -extern crate bootloader; -``` - Adding the bootloader as dependency is not enough to actually create a bootable disk image. The problem is that we need to combine the bootloader with the kernel after it has been compiled, but cargo has no support for additional build steps after successful compilation (see [this issue][post-build script] for more information). [post-build script]: https://github.com/rust-lang/cargo/issues/545 diff --git a/blog/content/second-edition/posts/03-vga-text-buffer/index.md b/blog/content/second-edition/posts/03-vga-text-buffer/index.md index b6395148..429041b3 100644 --- a/blog/content/second-edition/posts/03-vga-text-buffer/index.md +++ b/blog/content/second-edition/posts/03-vga-text-buffer/index.md @@ -285,14 +285,6 @@ The `0.2.3` is the [semantic] version number. For more information, see the [Spe [semantic]: http://semver.org/ [Specifying Dependencies]: http://doc.crates.io/specifying-dependencies.html -Now we've declared that our project depends on the `volatile` crate and are able to import it in `src/main.rs`: - -```rust -// in src/main.rs - -extern crate volatile; -``` - Let's use it to make writes to the VGA buffer volatile. We update our `Buffer` type as follows: ```rust @@ -475,12 +467,6 @@ The one-time initialization of statics with non-const functions is a common prob Let's add the `lazy_static` crate to our project: -```rust -// in src/main.rs - -extern crate lazy_static; -``` - ```toml # in Cargo.toml @@ -532,11 +518,6 @@ To use a spinning mutex, we can add the [spin crate] as a dependency: spin = "0.4.9" ``` -```rust -// in src/main.rs -extern crate spin; -``` - Then we can use the spinning Mutex to add safe [interior mutability] to our static `WRITER`: ```rust @@ -577,25 +558,29 @@ Now that we have a global writer, we can add a `println` macro that can be used [`println!` macro]: https://doc.rust-lang.org/nightly/std/macro.println!.html ```rust +#[macro_export] macro_rules! println { () => (print!("\n")); - ($fmt:expr) => (print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); + ($($arg:tt)*) => (print!("{}\n", format_args!($($arg)*))); } ``` -Macros are defined through one or more rules, which are similar to `match` arms. The `println` macro has three rules: The first rule for is invocations without arguments (e.g `println!()`), the second rule is for invocations with a single argument (e.g. `println!("Hello")`) and the third rule is for invocations with additional parameters (e.g. `println!("{}{}", 4, 2)`). -First line just prints a `\n` symbol which literally means "don't print anything, just break the line". -Last two rules simply append a newline character (`\n`) to the format string and then invoke the [`print!` macro], which is defined as: +Macros are defined through one or more rules, which are similar to `match` arms. The `println` macro has two rules: The first rule for is invocations without arguments (e.g `println!()`), which is expanded to `print!("\n)` and thus just prints a newline. the second rule is for invocations with parameters such as `println!("Hello")` or `println!("Number: {}", 4)`. It is also expanded to an invocation of the `print!` macro, passing all arguments and an additional newline `\n` at the end. + +The `#[macro_export]` attribute makes the available to the whole crate (not just the module it is defined) and external crates. It also places the macro at the crate root, which means that we have to import the macro through `use std::println` instead of `std::macros::println`. + +The [`print!` macro] is defined as: [`print!` macro]: https://doc.rust-lang.org/nightly/std/macro.print!.html ```rust +#[macro_export] macro_rules! print { ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*))); } ``` -The macro expands to a call of the [`_print` function] in the `io` module. The [`$crate` variable] ensures that the macro also works from outside the `std` crate. For example, it expands to `::std` when it's used in other crates. + +The macro expands to a call of the [`_print` function] in the `io` module. The [`$crate` variable] ensures that the macro also works from outside the `std` crate by expanding to `std` when it's used in other crates. The [`format_args` macro] builds a [fmt::Arguments] type from the passed arguments, which is passed to `_print`. The [`_print` function] of libstd calls `print_to`, which is rather complicated because it supports different `Stdout` devices. We don't need that complexity since we just want to print to the VGA buffer. @@ -604,36 +589,46 @@ The [`format_args` macro] builds a [fmt::Arguments] type from the passed argumen [`format_args` macro]: https://doc.rust-lang.org/nightly/std/macro.format_args.html [fmt::Arguments]: https://doc.rust-lang.org/nightly/core/fmt/struct.Arguments.html -To print to the VGA buffer, we just copy the `println!` and `print!` macros, but modify them to use a `print` function: +To print to the VGA buffer, we just copy the `println!` and `print!` macros, but modify them to use our own `_print` function: ```rust // in src/vga_buffer.rs +#[macro_export] macro_rules! print { - ($($arg:tt)*) => ($crate::vga_buffer::print(format_args!($($arg)*))); + ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); } +#[macro_export] macro_rules! println { - () => (print!("\n")); - ($fmt:expr) => (print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } -pub fn print(args: fmt::Arguments) { +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { use core::fmt::Write; WRITER.lock().write_fmt(args).unwrap(); } ``` -The `print` function locks our static `WRITER` and calls the `write_fmt` method on it. This method is from the `Write` trait, we need to import that trait. The additional `unwrap()` at the end panics if printing isn't successful. But since we always return `Ok` in `write_str`, that should not happen. + +One thing that we changed from the original `println` definition is that we prefixed the invocations of the `print!` macro with `$crate` too. This ensures that we don't need to have to import the `print!` macro too if we only want to use `println`. + +Like in the standard library, we add the `#[macro_export]` attribute to both macros to make them available everywhere in our crate. Note that this places the macros in the root namespace of the crate, so importing them via `use crate::vga_buffer::println` does not work. Instead, we have to do `use crate::println`. + +The `_print` function locks our static `WRITER` and calls the `write_fmt` method on it. This method is from the `Write` trait, we need to import that trait. The additional `unwrap()` at the end panics if printing isn't successful. But since we always return `Ok` in `write_str`, that should not happen. + +Since the macros need to be able to call `_print` from outside of the module, the function needs to be public. However, since we consider this a private implementation detail, we add the [`doc(hidden)` attribute] to hide it from the generated documentation. + +[`doc(hidden)` attribute]: https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#dochidden ### Hello World using `println` -To use `println` in `main.rs`, we need to import the macros of the VGA buffer module first. Therefore we add a `#[macro_use]` attribute to the module declaration: +Now we can use `println` in our `_start` function: ```rust // in src/main.rs -#[macro_use] -mod vga_buffer; +use crate::println; #[no_mangle] pub extern "C" fn _start() { @@ -643,7 +638,7 @@ pub extern "C" fn _start() { } ``` -Since we imported the macros at crate level, they are available in all modules and thus provide an easy and safe interface to the VGA buffer. +We have to explicitly import the `println` macro in order to use it. This is different from the standard library where the `println` macro is implicitly imported. Note that the macros live directly under the crate root (i.e. `crate::println` instead of `crate::vga_buffer::println`) because of the `#[macro_export]` attribute. As expected, we now see a _“Hello World!”_ on the screen: diff --git a/blog/content/second-edition/posts/04-unit-testing/index.md b/blog/content/second-edition/posts/04-unit-testing/index.md index 7d6f80cc..ae8962e2 100644 --- a/blog/content/second-edition/posts/04-unit-testing/index.md +++ b/blog/content/second-edition/posts/04-unit-testing/index.md @@ -111,13 +111,13 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out The test framework seems to work as intended. We don't have any tests yet, but we already get a test result summary. ### Silencing the Warnings -We get a few warnings about unused items, because we no longer compile our `_start` function. To silence such unused code warnings, we can add the following to the top of our `main.rs`: +We get a few warnings about unused imports, because we no longer compile our `_start` function. To silence such unused code warnings, we can add the following to the top of our `main.rs`: ``` -#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] +#![cfg_attr(test, allow(unused_imports))] ``` -Like before, the `cfg_attr` attribute sets the passed attribute if the passed condition holds. Here, we set the `allow(…)` attribute when compiling in test mode. We use the `allow` attribute to disable warnings for the `dead_code`, `unused_macro`, and `unused_import` _lints_. +Like before, the `cfg_attr` attribute sets the passed attribute if the passed condition holds. Here, we set the `allow(…)` attribute when compiling in test mode. We use the `allow` attribute to disable warnings for the `unused_import` _lint_. Lints are classes of warnings, for example `dead_code` for unused code or `missing-docs` for missing documentation. Lints can be set to four different states: @@ -131,15 +131,13 @@ Some lints are `allow` by default (such as `missing-docs`), others are `warn` by [clippy]: https://github.com/rust-lang-nursery/rust-clippy ### Including the Standard Library -Unit tests run on the host machine, so it's possible to use the complete standard library inside them. To link the standard library in test mode, we can add the following to our `main.rs`: +Unit tests run on the host machine, so it's possible to use the complete standard library inside them. To link the standard library in test mode, we can make the `#![no_std]` attribute conditional through `cfg_attr` too: -```rust -#[cfg(test)] -extern crate std; +```diff +-#![no_std] ++#![cfg_attr(not(test), no_std)] ``` -Rust knows where to find the `std` crate, so no modification to the `Cargo.toml` is required. - ## Testing the VGA Module Now that we have set up the test framework, we can add a first unit test for our `vga_buffer` module: @@ -246,18 +244,10 @@ To use that crate, we add the following to our `Cargo.toml`: array-init = "0.0.3" ``` -Note that we're using the [`dev-dependencies`] table instead of the `dependencies` table, because we only need the crate for `cargo test` and not for a normal build. Consequently, we also add a `#[cfg(test)]` attribute to the `extern crate` declaration in `main.rs`: +Note that we're using the [`dev-dependencies`] table instead of the `dependencies` table, because we only need the crate for `cargo test` and not for a normal build. [`dev-dependencies`]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#development-dependencies - -```rust -// in main.rs - -#[cfg(test)] -extern crate array_init; -``` - Now we can fix our `construct_buffer` function: ```rust diff --git a/blog/content/second-edition/posts/05-integration-tests/index.md b/blog/content/second-edition/posts/05-integration-tests/index.md index 7b2a6232..89a750c6 100644 --- a/blog/content/second-edition/posts/05-integration-tests/index.md +++ b/blog/content/second-edition/posts/05-integration-tests/index.md @@ -66,12 +66,6 @@ We will use the [`uart_16550`] crate to initialize the UART and send data over t uart_16550 = "0.1.0" ``` -```rust -// in src/main.rs - -extern crate uart_16550; -``` - The `uart_16550` crate contains a `SerialPort` struct that represents the UART registers, but we still need to construct an instance of it ourselves. For that we create a new `serial` module with the following content: ```rust @@ -103,23 +97,26 @@ Like with the [VGA text buffer][vga lazy-static], we use `lazy_static` and a spi To make the serial port easily usable, we add `serial_print!` and `serial_println!` macros: ```rust -pub fn print(args: ::core::fmt::Arguments) { +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { use core::fmt::Write; SERIAL1.lock().write_fmt(args).expect("Printing to serial failed"); } /// Prints to the host through the serial interface. +#[macro_export] macro_rules! serial_print { ($($arg:tt)*) => { - $crate::serial::print(format_args!($($arg)*)); + $crate::serial::_print(format_args!($($arg)*)); }; } /// Prints to the host through the serial interface, appending a newline. +#[macro_export] macro_rules! serial_println { - () => (serial_print!("\n")); - ($fmt:expr) => (serial_print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => (serial_print!(concat!($fmt, "\n"), $($arg)*)); + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(concat!($fmt, "\n"), $($arg)*)); } ``` @@ -132,7 +129,8 @@ Now we can print to the serial interface in our `main.rs`: ```rust // in src/main.rs -#[macro_use] +use crate::serial_println; + mod serial; #[cfg(not(test))] @@ -145,7 +143,7 @@ pub extern "C" fn _start() -> ! { } ``` -Note that we need to add the `#[macro_use]` attribute to the `mod serial` declaration, because otherwise the `serial_println` macro is not imported. +Note that the `serial_println` macro lives directly under the root namespace because we used the `#[macro_export]` attribute, so importing it through `use crate::serial::serial_println` will not work. ### QEMU Arguments @@ -207,8 +205,6 @@ x86_64 = "0.2.8" ```rust // in src/main.rs -extern crate x86_64; - pub unsafe fn exit_qemu() { use x86_64::instructions::port::Port; @@ -371,19 +367,7 @@ Cargo supports hybrid projects that are both a library and a binary. We only nee ```rust // src/lib.rs -#![no_std] // don't link the Rust standard library - -extern crate bootloader; -extern crate spin; -extern crate volatile; -extern crate lazy_static; -extern crate uart_16550; -extern crate x86_64; - -#[cfg(test)] -extern crate array_init; -#[cfg(test)] -extern crate std; +#![cfg_attr(not(test), no_std)] // don't link the Rust standard library // NEW: We need to add `pub` here to make them accessible from the outside pub mod vga_buffer; @@ -404,11 +388,8 @@ pub unsafe fn exit_qemu() { #![cfg_attr(not(test), no_main)] // disable all Rust-level entry points #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] -// NEW: Add the library as dependency (same crate name as executable) -#[macro_use] -extern crate blog_os; - use core::panic::PanicInfo; +use blog_os::println; /// This function is the entry point, since the linker looks for a function /// named `_start` by default. @@ -429,29 +410,7 @@ fn panic(info: &PanicInfo) -> ! { } ``` -So we move everything except `_start` and `panic` to `lib.rs`, make the `vga_buffer` and `serial` modules public, and add an `extern crate` definition to our `main.rs`. - -This doesn't compile yet, because Rust's macros are not exported over crate boundaries by default. To export our printing macros, we need to add the `#[macro_export]` attribute to them: - -```rust -// in src/vga_buffer.rs - -#[macro_export] -macro_rules! print {…} - -#[macro_export] -macro_rules! println {…} - -// in src/serial.rs - -#[macro_export] -macro_rules! serial_print {…} - -#[macro_export] -macro_rules! serial_println {…} -``` - -Now everything should work exactly as before, including `bootimage run` and `cargo test`. +So we move everything except `_start` and `panic` to `lib.rs` and make the `vga_buffer` and `serial` modules public. Everything should work exactly as before, including `bootimage run` and `cargo test`. ### Test Basic Boot @@ -464,12 +423,8 @@ We are finally able to create our first integration test executable. We start si #![cfg_attr(not(test), no_main)] // disable all Rust-level entry points #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] -// add the library as dependency (same crate name as executable) -#[macro_use] -extern crate blog_os; - use core::panic::PanicInfo; -use blog_os::exit_qemu; +use blog_os::{exit_qemu, serial_println}; /// This function is the entry point, since the linker looks for a function /// named `_start` by default. @@ -529,11 +484,8 @@ To test that our panic handler is really invoked on a panic, we create a `test-p #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] -#[macro_use] -extern crate blog_os; - use core::panic::PanicInfo; -use blog_os::exit_qemu; +use blog_os::{exit_qemu, serial_println}; #[cfg(not(test))] #[no_mangle] diff --git a/blog/content/second-edition/posts/06-cpu-exceptions/index.md b/blog/content/second-edition/posts/06-cpu-exceptions/index.md index c6acc5cf..b3ddbb24 100644 --- a/blog/content/second-edition/posts/06-cpu-exceptions/index.md +++ b/blog/content/second-edition/posts/06-cpu-exceptions/index.md @@ -234,6 +234,7 @@ For our use case, we don't need to overwrite any instructions. Instead, we just // in src/interrupts.rs use x86_64::structures::idt::{InterruptDescriptorTable, ExceptionStackFrame}; +use crate::println; pub fn init_idt() { let mut idt = InterruptDescriptorTable::new(); @@ -249,7 +250,7 @@ extern "x86-interrupt" fn breakpoint_handler( Our handler just outputs a message and pretty-prints the exception stack frame. -When we try to compile it, the following errors occur: +When we try to compile it, the following error occurs: ``` error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue #40180) @@ -265,44 +266,6 @@ error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue This error occurs because the `x86-interrupt` calling convention is still unstable. To use it anyway, we have to explicitly enable it by adding `#![feature(abi_x86_interrupt)]` on the top of our `lib.rs`. -``` -error: cannot find macro `println!` in this scope - --> src/interrupts.rs:40:5 - | -40 | println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); - | ^^^^^^^ - | - = help: have you added the `#[macro_use]` on the module/import? -``` - -This happened because we forgot to add `#[macro_use]` before our import of the `vga_buffer` module. - -```rust -// in src/lib.rs - -pub mod gdt; -pub mod interrupts; -pub mod serial; -#[macro_use] // new -pub mod vga_buffer; -``` - -However, after adding `#[macro_use]` before the module import, we still get the same error. Sometimes this can be confusing, but it's actually a quirk of how Rust's macro system works. Macros _must be defined_ before you can use them. This is one case where import order matters in Rust. We can easily fix this by ensuring the order of our imports places the macros first: - -```rust -// in src/lib.rs - -#[macro_use] -pub mod vga_buffer; // import this before other modules so its macros may be used -pub mod gdt; -pub mod interrupts; -pub mod serial; -``` - -Now we can use our `print!` and `println!` macros in `interrupts.rs`. If you'd like to know more about the ins and outs of macros and how they differ from functions [you can find more information here][in-depth-rust-macros]. - -[in-depth-rust-macros]: https://doc.rust-lang.org/book/second-edition/appendix-04-macros.html - ### Loading the IDT In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it: diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index bab486a8..d60dadad 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -85,12 +85,6 @@ To add the crate as dependency, we add the following to our project: pic8259_simple = "0.1.1" ``` -```rust -// in src/lib.rs - -extern crate pic8259_simple; -``` - The main abstraction provided by the crate is the [`ChainedPics`] struct that represents the primary/secondary PIC layout we saw above. It is designed to be used in the following way: [`ChainedPics`]: https://docs.rs/pic8259_simple/0.1.1/pic8259_simple/struct.ChainedPics.html @@ -244,7 +238,7 @@ The hardware timer that we use is called the _Progammable Interval Timer_ or PIT We now have a form of concurrency in our kernel: The timer interrupts occur asynchronously, so they can interrupt our `_start` function at any time. Fortunately Rust's ownership system prevents many types of concurrency related bugs at compile time. One notable exception are deadlocks. Deadlocks occur if a thread tries to aquire a lock that will never become free. Thus the thread hangs indefinitely. -We can already provoke a deadlock in our kernel. Remember, our `println` macro calls the `vga_buffer::print` function, which [locks a global `WRITER`][vga spinlock] using a spinlock: +We can already provoke a deadlock in our kernel. Remember, our `println` macro calls the `vga_buffer::_print` function, which [locks a global `WRITER`][vga spinlock] using a spinlock: [vga spinlock]: ./second-edition/posts/03-vga-text-buffer/index.md#spinlocks @@ -253,7 +247,8 @@ We can already provoke a deadlock in our kernel. Remember, our `println` macro c […] -pub fn print(args: fmt::Arguments) { +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { use core::fmt::Write; WRITER.lock().write_fmt(args).unwrap(); } @@ -308,7 +303,8 @@ To avoid this deadlock, we can disable interrupts as long as the `Mutex` is lock /// Prints the given formatted string to the VGA text buffer /// through the global `WRITER` instance. -pub fn print(args: fmt::Arguments) { +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { use core::fmt::Write; use x86_64::instructions::interrupts; // new @@ -328,7 +324,8 @@ We can apply the same change to our serial printing function to ensure that no d ```rust // in src/serial.rs -pub fn print(args: ::core::fmt::Arguments) { +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { use core::fmt::Write; use x86_64::instructions::interrupts; // new @@ -543,12 +540,6 @@ Translating the other keys works in the same way. Fortunately there is a crate n pc-keyboard = "0.3.1" ``` -```rust -// in src/lib.rs - -extern crate pc_keyboard; -``` - Now we can use this crate to rewrite our `keyboard_interrupt_handler`: ```rust diff --git a/src/bin/test-basic-boot.rs b/src/bin/test-basic-boot.rs index 9b891aad..f4acda07 100644 --- a/src/bin/test-basic-boot.rs +++ b/src/bin/test-basic-boot.rs @@ -2,11 +2,7 @@ #![cfg_attr(not(test), no_main)] // disable all Rust-level entry points #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] -// add the library as dependency (same crate name as executable) -#[macro_use] -extern crate blog_os; - -use blog_os::exit_qemu; +use blog_os::{exit_qemu, serial_println}; use core::panic::PanicInfo; /// This function is the entry point, since the linker looks for a function diff --git a/src/bin/test-exception-breakpoint.rs b/src/bin/test-exception-breakpoint.rs index 5f6db24c..fc3091e4 100644 --- a/src/bin/test-exception-breakpoint.rs +++ b/src/bin/test-exception-breakpoint.rs @@ -3,12 +3,7 @@ #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] -#[macro_use] -extern crate blog_os; -extern crate x86_64; -extern crate lazy_static; - -use blog_os::exit_qemu; +use blog_os::{exit_qemu, serial_println}; use core::panic::PanicInfo; use core::sync::atomic::{AtomicUsize, Ordering}; use lazy_static::lazy_static; diff --git a/src/bin/test-exception-double-fault-stack-overflow.rs b/src/bin/test-exception-double-fault-stack-overflow.rs index 673a8037..3d934a4c 100644 --- a/src/bin/test-exception-double-fault-stack-overflow.rs +++ b/src/bin/test-exception-double-fault-stack-overflow.rs @@ -3,12 +3,7 @@ #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] -#[macro_use] -extern crate blog_os; -extern crate x86_64; -extern crate lazy_static; - -use blog_os::exit_qemu; +use blog_os::{exit_qemu, serial_println}; use core::panic::PanicInfo; use lazy_static::lazy_static; diff --git a/src/bin/test-panic.rs b/src/bin/test-panic.rs index 64131b86..62ed7d7f 100644 --- a/src/bin/test-panic.rs +++ b/src/bin/test-panic.rs @@ -2,10 +2,7 @@ #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] -#[macro_use] -extern crate blog_os; - -use blog_os::exit_qemu; +use blog_os::{exit_qemu, serial_println}; use core::panic::PanicInfo; #[cfg(not(test))] diff --git a/src/interrupts.rs b/src/interrupts.rs index 728e6853..d9394d5c 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -4,7 +4,7 @@ // problem we skip compilation of this module on Windows. #![cfg(not(windows))] -use gdt; +use crate::{gdt, print, println}; use pic8259_simple::ChainedPics; use spin; use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable}; @@ -48,7 +48,7 @@ extern "x86-interrupt" fn double_fault_handler( stack_frame: &mut ExceptionStackFrame, _error_code: u64, ) { - use hlt_loop; + use crate::hlt_loop; println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); hlt_loop(); diff --git a/src/lib.rs b/src/lib.rs index 31a702a6..d2a1d0ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,6 @@ -#![no_std] // don't link the Rust standard library +#![cfg_attr(not(test), no_std)] // don't link the Rust standard library #![feature(abi_x86_interrupt)] -extern crate bootloader; -extern crate spin; -extern crate volatile; -extern crate lazy_static; -extern crate pic8259_simple; -extern crate uart_16550; -extern crate x86_64; -extern crate pc_keyboard; - -#[cfg(test)] -extern crate array_init; -#[cfg(test)] -extern crate std; - -#[macro_use] pub mod vga_buffer; pub mod gdt; pub mod interrupts; diff --git a/src/main.rs b/src/main.rs index c9890b1b..acf8e667 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,11 @@ #![no_std] // don't link the Rust standard library #![cfg_attr(not(test), no_main)] // disable all Rust-level entry points -#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] - -#[macro_use] -extern crate blog_os; -extern crate x86_64; +#![cfg_attr(test, allow(unused_imports))] use core::panic::PanicInfo; +use blog_os::println; + /// This function is the entry point, since the linker looks for a function /// named `_start` by default. #[cfg(not(test))] diff --git a/src/serial.rs b/src/serial.rs index 494273c9..2c105f97 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -10,7 +10,8 @@ lazy_static! { }; } -pub fn print(args: ::core::fmt::Arguments) { +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { use core::fmt::Write; use x86_64::instructions::interrupts; @@ -26,14 +27,14 @@ pub fn print(args: ::core::fmt::Arguments) { #[macro_export] macro_rules! serial_print { ($($arg:tt)*) => { - $crate::serial::print(format_args!($($arg)*)); + $crate::serial::_print(format_args!($($arg)*)); }; } /// Prints to the host through the serial interface, appending a newline. #[macro_export] macro_rules! serial_println { - () => (serial_print!("\n")); - ($fmt:expr) => (serial_print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => (serial_print!(concat!($fmt, "\n"), $($arg)*)); + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(concat!($fmt, "\n"), $($arg)*)); } diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 9c245190..a361a49f 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -151,19 +151,19 @@ impl fmt::Write for Writer { /// Like the `print!` macro in the standard library, but prints to the VGA text buffer. #[macro_export] macro_rules! print { - ($($arg:tt)*) => ($crate::vga_buffer::print(format_args!($($arg)*))); + ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); } -/// Like the `print!` macro in the standard library, but prints to the VGA text buffer. +/// Like the `println!` macro in the standard library, but prints to the VGA text buffer. #[macro_export] macro_rules! println { - () => (print!("\n")); - ($fmt:expr) => (print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } /// Prints the given formatted string to the VGA text buffer through the global `WRITER` instance. -pub fn print(args: fmt::Arguments) { +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { use core::fmt::Write; use x86_64::instructions::interrupts;