#[cfg(not(feature = "binary"))]
fn main() {
#[cfg(target_arch = "x86")]
compile_error!(
"This crate currently does not support 32-bit protected mode. \
See https://github.com/rust-osdev/bootloader/issues/70 for more information."
);
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
compile_error!("This crate only supports the x86_64 architecture.");
}
#[cfg(feature = "binary")]
#[derive(Default)]
struct BootloaderConfig {
physical_memory_offset: Option<u64>,
kernel_stack_address: Option<u64>,
kernel_stack_size: Option<u64>,
}
#[cfg(feature = "binary")]
fn parse_aligned_addr(key: &str, value: &str) -> u64 {
let num = if value.starts_with("0x") {
u64::from_str_radix(&value[2..], 16)
} else {
u64::from_str_radix(&value, 10)
};
let num = num.expect(&format!(
"`{}` in the kernel manifest must be an integer (is `{}`)",
key, value
));
if num % 0x1000 != 0 {
panic!(
"`{}` in the kernel manifest must be aligned to 4KiB (is `{}`)",
key, value
);
} else {
num
}
}
#[cfg(feature = "binary")]
fn parse_to_config(cfg: &mut BootloaderConfig, table: &toml::value::Table) {
use toml::Value;
for (key, value) in table {
match (key.as_str(), value.clone()) {
("kernel-stack-address", Value::Integer(i))
| ("physical-memory-offset", Value::Integer(i)) => {
panic!(
"`{0}` in the kernel manifest must be given as a string, \
as toml does not support unsigned 64-bit integers (try `{0} = \"{1}\"`)",
key.as_str(),
i
);
}
("kernel-stack-address", Value::String(s)) => {
cfg.kernel_stack_address = Some(parse_aligned_addr(key.as_str(), &s));
}
#[cfg(not(feature = "map_physical_memory"))]
("physical-memory-offset", Value::String(_)) => {
panic!(
"`physical-memory-offset` is only supported when the `map_physical_memory` \
feature of the crate is enabled"
);
}
#[cfg(feature = "map_physical_memory")]
("physical-memory-offset", Value::String(s)) => {
cfg.physical_memory_offset = Some(parse_aligned_addr(key.as_str(), &s));
}
("kernel-stack-size", Value::Integer(i)) => {
if i <= 0 {
panic!("`kernel-stack-size` in kernel manifest must be positive");
} else {
cfg.kernel_stack_size = Some(i as u64);
}
}
(s, _) => {
panic!(
"unknown key '{}' in kernel manifest \
- you may need to update the bootloader crate",
s
);
}
}
}
}
#[cfg(feature = "binary")]
fn main() {
use std::{
env,
fs::{self, File},
io::Write,
path::{Path, PathBuf},
process::{self, Command},
};
use toml::Value;
let target = env::var("TARGET").expect("TARGET not set");
if Path::new(&target)
.file_stem()
.expect("target has no file stem")
!= "x86_64-bootloader"
{
panic!("The bootloader must be compiled for the `x86_64-bootloader.json` target.");
}
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
let kernel = PathBuf::from(match env::var("KERNEL") {
Ok(kernel) => kernel,
Err(_) => {
eprintln!(
"The KERNEL environment variable must be set for building the bootloader.\n\n\
If you use `bootimage` for building you need at least version 0.7.0. You can \
update `bootimage` by running `cargo install bootimage --force`."
);
process::exit(1);
}
});
let kernel_file_name = kernel
.file_name()
.expect("KERNEL has no valid file name")
.to_str()
.expect("kernel file name not valid utf8");
assert!(
kernel.exists(),
format!("KERNEL does not exist: {}", kernel.display())
);
let llvm_tools = match llvm_tools::LlvmTools::new() {
Ok(tools) => tools,
Err(llvm_tools::Error::NotFound) => {
eprintln!("Error: llvm-tools not found");
eprintln!("Maybe the rustup component `llvm-tools-preview` is missing?");
eprintln!(" Install it through: `rustup component add llvm-tools-preview`");
process::exit(1);
}
Err(err) => {
eprintln!("Failed to retrieve llvm-tools component: {:?}", err);
process::exit(1);
}
};
let llvm_size = llvm_tools
.tool(&llvm_tools::exe("llvm-size"))
.expect("llvm-size not found in llvm-tools");
let mut cmd = Command::new(llvm_size);
cmd.arg(&kernel);
let output = cmd.output().expect("failed to run llvm-size");
let output_str = String::from_utf8_lossy(&output.stdout);
let second_line_opt = output_str.lines().skip(1).next();
let second_line = second_line_opt.expect("unexpected llvm-size line output");
let text_size_opt = second_line.split_ascii_whitespace().next();
let text_size = text_size_opt.expect("unexpected llvm-size output");
if text_size == "0" {
panic!("Kernel executable has an empty text section. Perhaps the entry point was set incorrectly?\n\n\
Kernel executable at `{}`\n", kernel.display());
}
let stripped_kernel_file_name = format!("kernel_stripped-{}", kernel_file_name);
let stripped_kernel = out_dir.join(&stripped_kernel_file_name);
let objcopy = llvm_tools
.tool(&llvm_tools::exe("llvm-objcopy"))
.expect("llvm-objcopy not found in llvm-tools");
let mut cmd = Command::new(&objcopy);
cmd.arg("--strip-debug");
cmd.arg(&kernel);
cmd.arg(&stripped_kernel);
let exit_status = cmd
.status()
.expect("failed to run objcopy to strip debug symbols");
if !exit_status.success() {
eprintln!("Error: Stripping debug symbols failed");
process::exit(1);
}
let stripped_kernel_file_name_replaced = stripped_kernel_file_name.replace('-', "_");
let kernel_bin = out_dir.join(format!("kernel_bin-{}.o", kernel_file_name));
let kernel_archive = out_dir.join(format!("libkernel_bin-{}.a", kernel_file_name));
let mut cmd = Command::new(&objcopy);
cmd.arg("-I").arg("binary");
cmd.arg("-O").arg("elf64-x86-64");
cmd.arg("--binary-architecture=i386:x86-64");
cmd.arg("--rename-section").arg(".data=.kernel");
cmd.arg("--redefine-sym").arg(format!(
"_binary_{}_start=_kernel_start_addr",
stripped_kernel_file_name_replaced
));
cmd.arg("--redefine-sym").arg(format!(
"_binary_{}_end=_kernel_end_addr",
stripped_kernel_file_name_replaced
));
cmd.arg("--redefine-sym").arg(format!(
"_binary_{}_size=_kernel_size",
stripped_kernel_file_name_replaced
));
cmd.current_dir(&out_dir);
cmd.arg(&stripped_kernel_file_name);
cmd.arg(&kernel_bin);
let exit_status = cmd.status().expect("failed to run objcopy");
if !exit_status.success() {
eprintln!("Error: Running objcopy failed");
process::exit(1);
}
let ar = llvm_tools
.tool(&llvm_tools::exe("llvm-ar"))
.unwrap_or_else(|| {
eprintln!("Failed to retrieve llvm-ar component");
eprint!("This component is available since nightly-2019-03-29,");
eprintln!("so try updating your toolchain if you're using an older nightly");
process::exit(1);
});
let mut cmd = Command::new(ar);
cmd.arg("crs");
cmd.arg(&kernel_archive);
cmd.arg(&kernel_bin);
let exit_status = cmd.status().expect("failed to run ar");
if !exit_status.success() {
eprintln!("Error: Running ar failed");
process::exit(1);
}
let mut bootloader_config = BootloaderConfig::default();
match env::var("KERNEL_MANIFEST") {
Err(env::VarError::NotPresent) => {
panic!("The KERNEL_MANIFEST environment variable must be set for building the bootloader.\n\n\
If you use `bootimage` for building you need at least version 0.7.7. You can \
update `bootimage` by running `cargo install bootimage --force`.");
}
Err(env::VarError::NotUnicode(_)) => {
panic!("The KERNEL_MANIFEST environment variable contains invalid unicode")
}
Ok(path) => {
println!("cargo:rerun-if-changed={}", path);
let contents = fs::read_to_string(&path).expect(&format!(
"failed to read kernel manifest file (path: {})",
path
));
let manifest = contents
.parse::<Value>()
.expect("failed to parse kernel's Cargo.toml");
let table = manifest
.get("package")
.and_then(|table| table.get("metadata"))
.and_then(|table| table.get("bootloader"))
.and_then(|table| table.as_table());
if let Some(table) = table {
parse_to_config(&mut bootloader_config, table);
}
}
}
let file_path = out_dir.join("bootloader_config.rs");
let mut file = File::create(file_path).expect("failed to create bootloader_config.rs");
file.write_all(
format!(
"const PHYSICAL_MEMORY_OFFSET: Option<u64> = {:?};
const KERNEL_STACK_ADDRESS: Option<u64> = {:?};
const KERNEL_STACK_SIZE: u64 = {};",
bootloader_config.physical_memory_offset,
bootloader_config.kernel_stack_address,
bootloader_config.kernel_stack_size.unwrap_or(512), )
.as_bytes(),
)
.expect("write to bootloader_config.rs failed");
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!(
"cargo:rustc-link-lib=static=kernel_bin-{}",
kernel_file_name
);
println!("cargo:rerun-if-env-changed=KERNEL");
println!("cargo:rerun-if-env-changed=KERNEL_MANIFEST");
println!("cargo:rerun-if-changed={}", kernel.display());
println!("cargo:rerun-if-changed=build.rs");
}