Refine relocations in x86-64 EFI boot

This commit is contained in:
Zhang Junyang
2024-10-15 09:44:45 +08:00
committed by Tate, Hongliang Tian
parent 68adca4b40
commit 662894ee90
6 changed files with 147 additions and 74 deletions

View File

@ -167,7 +167,37 @@ fn install_setup_with_arch(
let target_dir = std::fs::canonicalize(target_dir).unwrap(); let target_dir = std::fs::canonicalize(target_dir).unwrap();
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.env("RUSTFLAGS", "-Ccode-model=kernel -Crelocation-model=pie -Ctarget-feature=+crt-static -Zplt=yes -Zrelax-elf-relocations=yes -Crelro-level=full"); let mut rustflags = vec![
"-Cdebuginfo=2",
"-Ccode-model=kernel",
"-Crelocation-model=pie",
"-Zplt=yes",
"-Zrelax-elf-relocations=yes",
"-Crelro-level=full",
];
let target_feature_args = match arch {
SetupInstallArch::X86_64 => {
concat!(
"-Ctarget-feature=",
"+crt-static",
",-adx",
",-aes",
",-avx",
",-avx2",
",-fxsr",
",-sse",
",-sse2",
",-sse3",
",-sse4.1",
",-sse4.2",
",-ssse3",
",-xsave",
)
}
SetupInstallArch::Other(_) => "-Ctarget-feature=+crt-static",
};
rustflags.push(target_feature_args);
cmd.env("RUSTFLAGS", rustflags.join(" "));
cmd.arg("install").arg("linux-bzimage-setup"); cmd.arg("install").arg("linux-bzimage-setup");
cmd.arg("--force"); cmd.arg("--force");
cmd.arg("--root").arg(install_dir.as_ref()); cmd.arg("--root").arg(install_dir.as_ref());

View File

@ -2,17 +2,17 @@
use linux_boot_params::BootParams; use linux_boot_params::BootParams;
use uefi::{ use uefi::{
boot::{exit_boot_services, open_protocol_exclusive},
mem::memory_map::{MemoryMap, MemoryMapOwned},
prelude::*, prelude::*,
proto::loaded_image::LoadedImage, proto::loaded_image::LoadedImage,
boot::{exit_boot_services, open_protocol_exclusive},
mem::memory_map::{MemoryMapOwned, MemoryMap},
}; };
use uefi_raw::table::system::SystemTable; use uefi_raw::table::system::SystemTable;
use super::{ use super::{
decoder::decode_payload, decoder::decode_payload,
paging::{Ia32eFlags, PageNumber, PageTableCreator}, paging::{Ia32eFlags, PageNumber, PageTableCreator},
relocation::apply_rela_dyn_relocations, relocation::apply_rela_relocations,
}; };
// Suppress warnings since using todo!. // Suppress warnings since using todo!.
@ -21,10 +21,9 @@ use super::{
#[allow(clippy::diverging_sub_expression)] #[allow(clippy::diverging_sub_expression)]
#[export_name = "efi_stub_entry"] #[export_name = "efi_stub_entry"]
extern "sysv64" fn efi_stub_entry(handle: Handle, system_table: *const SystemTable) -> ! { extern "sysv64" fn efi_stub_entry(handle: Handle, system_table: *const SystemTable) -> ! {
unsafe { // SAFETY: handle and system_table are valid pointers. It is only called once.
boot::set_image_handle(handle); unsafe { system_init(handle, system_table) };
uefi::table::set_system_table(system_table);
}
uefi::helpers::init().unwrap(); uefi::helpers::init().unwrap();
let boot_params = todo!("Use EFI boot services to fill boot params"); let boot_params = todo!("Use EFI boot services to fill boot params");
@ -38,10 +37,9 @@ extern "sysv64" fn efi_handover_entry(
system_table: *const SystemTable, system_table: *const SystemTable,
boot_params_ptr: *mut BootParams, boot_params_ptr: *mut BootParams,
) -> ! { ) -> ! {
unsafe { // SAFETY: handle and system_table are valid pointers. It is only called once.
boot::set_image_handle(handle); unsafe { system_init(handle, system_table) };
uefi::table::set_system_table(system_table);
}
uefi::helpers::init().unwrap(); uefi::helpers::init().unwrap();
// SAFETY: boot_params is a valid pointer. // SAFETY: boot_params is a valid pointer.
@ -50,18 +48,36 @@ extern "sysv64" fn efi_handover_entry(
efi_phase_boot(boot_params) efi_phase_boot(boot_params)
} }
fn efi_phase_boot(boot_params: &mut BootParams) -> ! { /// Initialize the system.
///
/// # Safety
///
/// This function should be called only once with valid parameters before all
/// operations.
unsafe fn system_init(handle: Handle, system_table: *const SystemTable) {
// SAFETY: This is the right time to initialize the console and it is only // SAFETY: This is the right time to initialize the console and it is only
// called once here before all console operations. // called once here before all console operations.
unsafe { unsafe {
crate::console::init(); crate::console::init();
} }
// SAFETY: this is the right time to apply relocations. // SAFETY: This is the right time to apply relocations.
unsafe { apply_rela_dyn_relocations() }; unsafe { apply_rela_relocations() };
// SAFETY: The handle and system_table are valid pointers. They are passed
// from the UEFI firmware. They are only called once.
unsafe {
boot::set_image_handle(handle);
uefi::table::set_system_table(system_table);
}
}
fn efi_phase_boot(boot_params: &mut BootParams) -> ! {
uefi::println!("[EFI stub] Relocations applied."); uefi::println!("[EFI stub] Relocations applied.");
uefi::println!("[EFI stub] Stub loaded at {:#x?}", crate::x86::get_image_loaded_offset()); uefi::println!(
"[EFI stub] Stub loaded at {:#x?}",
crate::x86::get_image_loaded_offset()
);
// Fill the boot params with the RSDP address if it is not provided. // Fill the boot params with the RSDP address if it is not provided.
if boot_params.acpi_rsdp_addr == 0 { if boot_params.acpi_rsdp_addr == 0 {
@ -84,9 +100,7 @@ fn efi_phase_boot(boot_params: &mut BootParams) -> ! {
}; };
// SAFETY: All allocations in the boot services phase are not used after // SAFETY: All allocations in the boot services phase are not used after
// this point. // this point.
let memory_map = unsafe { let memory_map = unsafe { exit_boot_services(memory_type) };
exit_boot_services(memory_type)
};
efi_phase_runtime(memory_map, boot_params); efi_phase_runtime(memory_map, boot_params);
} }

View File

@ -41,14 +41,10 @@ SECTIONS
*(.eh_frame_hdr .eh_frame_hdr.*) *(.eh_frame_hdr .eh_frame_hdr.*)
} }
.rela.dyn : { .rela : {
PROVIDE(__rela_dyn_start = .); PROVIDE(__rela_start = .);
*(.rela.dyn .rela.dyn.*) *(.rela .rela.*)
PROVIDE(__rela_dyn_end = .); PROVIDE(__rela_end = .);
}
.rela.plt : {
*(.rela.plt .rela.plt.*)
} }
.comment : { *(.comment) } .comment : { *(.comment) }

View File

@ -18,6 +18,7 @@ const TABLE_ENTRY_COUNT: usize = 512;
bitflags::bitflags! { bitflags::bitflags! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[repr(C)]
pub struct Ia32eFlags: u64 { pub struct Ia32eFlags: u64 {
const PRESENT = 1 << 0; const PRESENT = 1 << 0;
const WRITABLE = 1 << 1; const WRITABLE = 1 << 1;
@ -32,9 +33,11 @@ bitflags::bitflags! {
} }
} }
#[repr(C)]
pub struct Ia32eEntry(u64); pub struct Ia32eEntry(u64);
/// The table in the IA32E paging specification that occupies a physical page frame. /// The table in the IA32E paging specification that occupies a physical page frame.
#[repr(C)]
pub struct Ia32eTable([Ia32eEntry; TABLE_ENTRY_COUNT]); pub struct Ia32eTable([Ia32eEntry; TABLE_ENTRY_COUNT]);
/// A page number. It could be either a physical page number or a virtual page number. /// A page number. It could be either a physical page number or a virtual page number.

View File

@ -2,62 +2,68 @@
use crate::x86::get_image_loaded_offset; use crate::x86::get_image_loaded_offset;
struct Elf64Rela { /// Apply the relocations in the `.rela.*` sections.
r_offset: u64, ///
r_info: u64, /// The function will enable dyn Trait objects to work since they rely on
r_addend: i64, /// vtable pointers. Vtable won't work without relocations.
///
/// We currently support R_X86_64_RELATIVE relocations only. And this type of
/// relocation seems to be the only existing type if we compile Rust code to
/// PIE ELF binaries.
///
/// # Safety
///
/// This function will modify the memory pointed by the relocations. And the
/// Rust memory safety mechanisms are not aware of these kind of modification.
/// Failure to do relocations will cause `dyn Trait` objects to break.
pub unsafe fn apply_rela_relocations() {
use core::arch::asm;
let image_loaded_offset = get_image_loaded_offset();
let mut start: usize;
let end: usize;
unsafe {
asm!(
"lea {}, [rip + __rela_start]",
out(reg) start,
);
asm!(
"lea {}, [rip + __rela_end]",
out(reg) end,
);
} }
fn get_rela_array() -> &'static [Elf64Rela] {
extern "C" {
fn __rela_dyn_start();
fn __rela_dyn_end();
}
let start = __rela_dyn_start as *const Elf64Rela;
let end = __rela_dyn_end as *const Elf64Rela;
let len = unsafe { end.offset_from(start) } as usize;
#[cfg(feature = "debug_print")] #[cfg(feature = "debug_print")]
unsafe { unsafe {
use crate::console::{print_hex, print_str}; use crate::console::{print_hex, print_str};
print_str("[EFI stub debug] .rela.dyn section size = "); print_str("[EFI stub debug] loaded offset = ");
print_hex(len as u64); print_hex(image_loaded_offset as u64);
print_str("; __rela_dyn_start = "); print_str("\n");
print_str("[EFI stub debug] .rela section start = ");
print_hex(start as u64); print_hex(start as u64);
print_str(", __rela_dyn_end = "); print_str(", end = ");
print_hex(end as u64); print_hex(end as u64);
print_str("\n"); print_str("\n");
} }
// SAFETY: the linker will ensure that the symbols are valid.
unsafe { core::slice::from_raw_parts(start, len) }
}
const R_X86_64_RELATIVE: u32 = 8; #[cfg(feature = "debug_print")]
let mut count = 0;
/// Apply the relocations in the `.rela.dyn` section. while start < end {
/// let rela = (start as *const Elf64Rela).read_volatile();
/// The function will enable dyn Trait objects to work since they rely on vtable pointers. Vtable
/// won't work without relocations.
///
/// We currently support R_X86_64_RELATIVE relocations only. And this type of relocation seems to
/// be the only existing type if we compile Rust code to PIC ELF binaries.
///
/// # Safety
/// This function will modify the memory pointed by the relocations. And the Rust memory safety
/// mechanisms are not aware of these kind of modification. Failure to do relocations will cause
/// dyn Trait objects to break.
pub unsafe fn apply_rela_dyn_relocations() {
let image_loaded_offset = get_image_loaded_offset();
let relas = get_rela_array();
for rela in relas {
let r_type = (rela.r_info & 0xffffffff) as u32; let r_type = (rela.r_info & 0xffffffff) as u32;
let _r_sym = (rela.r_info >> 32) as usize; let _r_sym = (rela.r_info >> 32) as usize;
let r_addend = rela.r_addend; let r_addend = rela.r_addend as isize;
let r_offset = rela.r_offset as usize; let r_offset = rela.r_offset as isize;
let target = (image_loaded_offset + r_offset as isize) as usize; let target = image_loaded_offset.wrapping_add(r_offset) as usize;
#[cfg(feature = "debug_print")] #[cfg(feature = "debug_print")]
unsafe { unsafe {
use crate::console::{print_hex, print_str}; use crate::console::{print_hex, print_str};
print_str("[EFI stub debug] Applying relocation at offset "); count += 1;
print_str("[EFI stub debug] Applying relocation #");
print_hex(count as u64);
print_str(" at offset ");
print_hex(r_offset as u64); print_hex(r_offset as u64);
print_str(", type = "); print_str(", type = ");
print_hex(r_type as u64); print_hex(r_type as u64);
@ -67,12 +73,23 @@ pub unsafe fn apply_rela_dyn_relocations() {
} }
match r_type { match r_type {
R_X86_64_RELATIVE => { R_X86_64_RELATIVE => {
let value = (image_loaded_offset as i64 + r_addend) as usize; let value = image_loaded_offset.wrapping_add(r_addend) as usize;
*(target as *mut usize) = value; (target as *mut usize).write(value);
} }
_ => { _ => {
panic!("Unknown relocation type: {}", r_type); panic!("Unknown relocation type: {}", r_type);
} }
} }
start = start.wrapping_add(core::mem::size_of::<Elf64Rela>());
} }
} }
const R_X86_64_RELATIVE: u32 = 8;
#[derive(Copy, Clone)]
#[repr(C)]
struct Elf64Rela {
r_offset: u64,
r_info: u64,
r_addend: i64,
}

View File

@ -16,10 +16,23 @@ const START_OF_SETUP32_VA: usize = 0x100000;
/// The setup is a position-independent executable. We can get the loaded base /// The setup is a position-independent executable. We can get the loaded base
/// address from the symbol. /// address from the symbol.
#[inline] #[inline]
#[allow(clippy::fn_to_numeric_cast)]
pub fn get_image_loaded_offset() -> isize { pub fn get_image_loaded_offset() -> isize {
extern "C" { let address_of_start: usize;
fn start_of_setup32(); #[cfg(target_arch = "x86_64")]
unsafe {
core::arch::asm!(
"lea {}, [rip + start_of_setup32]",
out(reg) address_of_start,
options(pure, nomem, nostack)
);
} }
start_of_setup32 as isize - START_OF_SETUP32_VA as isize #[cfg(target_arch = "x86")]
unsafe {
core::arch::asm!(
"lea {}, [start_of_setup32]",
out(reg) address_of_start,
options(pure, nomem, nostack)
);
}
address_of_start as isize - START_OF_SETUP32_VA as isize
} }