Skip to content

Commit b29ecb6

Browse files
committed
add support for position independent executables
1 parent 2a44fd4 commit b29ecb6

File tree

14 files changed

+405
-11
lines changed

14 files changed

+405
-11
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ members = [
1414
"tests/test_kernels/default_settings",
1515
"tests/test_kernels/map_phys_mem",
1616
"tests/test_kernels/higher_half",
17+
"tests/test_kernels/pie",
1718
]
1819
exclude = [
1920
"examples/basic",

src/binary/level_4_entries.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,22 @@ impl UsedLevel4Entries {
1616
/// Initializes a new instance from the given ELF program segments.
1717
///
1818
/// Marks the virtual address range of all segments as used.
19-
pub fn new<'a>(segments: impl Iterator<Item = ProgramHeader<'a>>) -> Self {
19+
pub fn new<'a>(
20+
segments: impl Iterator<Item = ProgramHeader<'a>>,
21+
virtual_address_offset: u64,
22+
) -> Self {
2023
let mut used = UsedLevel4Entries {
2124
entry_state: [false; 512],
2225
};
2326

2427
used.entry_state[0] = true; // TODO: Can we do this dynamically?
2528

2629
for segment in segments {
27-
let start_page: Page = Page::containing_address(VirtAddr::new(segment.virtual_addr()));
30+
let start_page: Page = Page::containing_address(VirtAddr::new(
31+
segment.virtual_addr() + virtual_address_offset,
32+
));
2833
let end_page: Page = Page::containing_address(VirtAddr::new(
29-
segment.virtual_addr() + segment.mem_size(),
34+
segment.virtual_addr() + virtual_address_offset + segment.mem_size(),
3035
));
3136

3237
for p4_index in u64::from(start_page.p4_index())..=u64::from(end_page.p4_index()) {

src/binary/load_kernel.rs

Lines changed: 146 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use x86_64::{
1111
PhysAddr, VirtAddr,
1212
};
1313
use xmas_elf::{
14-
header,
15-
program::{self, ProgramHeader, Type},
14+
dynamic, header,
15+
program::{self, ProgramHeader, SegmentData, Type},
16+
sections::Rela,
1617
ElfFile,
1718
};
1819

@@ -23,6 +24,7 @@ struct Loader<'a, M, F> {
2324

2425
struct Inner<'a, M, F> {
2526
kernel_offset: PhysAddr,
27+
virtual_address_offset: u64,
2628
page_table: &'a mut M,
2729
frame_allocator: &'a mut F,
2830
}
@@ -44,11 +46,22 @@ where
4446
}
4547

4648
let elf_file = ElfFile::new(bytes)?;
49+
50+
let virtual_address_offset = match elf_file.header.pt2.type_().as_type() {
51+
header::Type::None => unimplemented!(),
52+
header::Type::Relocatable => unimplemented!(),
53+
header::Type::Executable => 0,
54+
header::Type::SharedObject => 0x400000,
55+
header::Type::Core => unimplemented!(),
56+
header::Type::ProcessorSpecific(_) => unimplemented!(),
57+
};
58+
4759
header::sanity_check(&elf_file)?;
4860
let loader = Loader {
4961
elf_file,
5062
inner: Inner {
5163
kernel_offset,
64+
virtual_address_offset,
5265
page_table,
5366
frame_allocator,
5467
},
@@ -58,9 +71,21 @@ where
5871
}
5972

6073
fn load_segments(&mut self) -> Result<Option<TlsTemplate>, &'static str> {
61-
let mut tls_template = None;
6274
for program_header in self.elf_file.program_iter() {
6375
program::sanity_check(program_header, &self.elf_file)?;
76+
}
77+
78+
// Apply relocations in physical memory.
79+
for program_header in self.elf_file.program_iter() {
80+
if let Type::Dynamic = program_header.get_type()? {
81+
self.inner
82+
.handle_dynamic_segment(program_header, &self.elf_file)?
83+
}
84+
}
85+
86+
// Load the segments into virtual memory.
87+
let mut tls_template = None;
88+
for program_header in self.elf_file.program_iter() {
6489
match program_header.get_type()? {
6590
Type::Load => self.inner.handle_load_segment(program_header)?,
6691
Type::Tls => {
@@ -85,11 +110,14 @@ where
85110
}
86111

87112
fn entry_point(&self) -> VirtAddr {
88-
VirtAddr::new(self.elf_file.header.pt2.entry_point())
113+
VirtAddr::new(self.elf_file.header.pt2.entry_point() + self.inner.virtual_address_offset)
89114
}
90115

91116
fn used_level_4_entries(&self) -> UsedLevel4Entries {
92-
UsedLevel4Entries::new(self.elf_file.program_iter())
117+
UsedLevel4Entries::new(
118+
self.elf_file.program_iter(),
119+
self.inner.virtual_address_offset,
120+
)
93121
}
94122
}
95123

@@ -106,7 +134,7 @@ where
106134
let end_frame: PhysFrame =
107135
PhysFrame::containing_address(phys_start_addr + segment.file_size() - 1u64);
108136

109-
let virt_start_addr = VirtAddr::new(segment.virtual_addr());
137+
let virt_start_addr = VirtAddr::new(segment.virtual_addr()) + self.virtual_address_offset;
110138
let start_page: Page = Page::containing_address(virt_start_addr);
111139

112140
let mut segment_flags = Flags::PRESENT;
@@ -146,7 +174,7 @@ where
146174
) -> Result<(), &'static str> {
147175
log::info!("Mapping bss section");
148176

149-
let virt_start_addr = VirtAddr::new(segment.virtual_addr());
177+
let virt_start_addr = VirtAddr::new(segment.virtual_addr()) + self.virtual_address_offset;
150178
let phys_start_addr = self.kernel_offset + segment.offset();
151179
let mem_size = segment.mem_size();
152180
let file_size = segment.file_size();
@@ -262,11 +290,121 @@ where
262290

263291
fn handle_tls_segment(&mut self, segment: ProgramHeader) -> Result<TlsTemplate, &'static str> {
264292
Ok(TlsTemplate {
265-
start_addr: segment.virtual_addr(),
293+
start_addr: segment.virtual_addr() + self.virtual_address_offset,
266294
mem_size: segment.mem_size(),
267295
file_size: segment.file_size(),
268296
})
269297
}
298+
299+
fn handle_dynamic_segment(
300+
&mut self,
301+
segment: ProgramHeader,
302+
elf_file: &ElfFile,
303+
) -> Result<(), &'static str> {
304+
let data = segment.get_data(elf_file)?;
305+
let data = if let SegmentData::Dynamic64(data) = data {
306+
data
307+
} else {
308+
unreachable!()
309+
};
310+
311+
// Find the `Rela`, `RelaSize` and `RelaEnt` entries.
312+
let mut rela = None;
313+
let mut rela_size = None;
314+
let mut rela_ent = None;
315+
for rel in data {
316+
let tag = rel.get_tag()?;
317+
match tag {
318+
dynamic::Tag::Rela => {
319+
let ptr = rel.get_ptr()?;
320+
let prev = rela.replace(ptr);
321+
if prev.is_some() {
322+
return Err("Dynamic section contains more than one Rela entry");
323+
}
324+
}
325+
dynamic::Tag::RelaSize => {
326+
let val = rel.get_val()?;
327+
let prev = rela_size.replace(val);
328+
if prev.is_some() {
329+
return Err("Dynamic section contains more than one RelaSize entry");
330+
}
331+
}
332+
dynamic::Tag::RelaEnt => {
333+
let val = rel.get_val()?;
334+
let prev = rela_ent.replace(val);
335+
if prev.is_some() {
336+
return Err("Dynamic section contains more than one RelaEnt entry");
337+
}
338+
}
339+
_ => {}
340+
}
341+
}
342+
let offset = if let Some(rela) = rela {
343+
rela
344+
} else {
345+
// The section doesn't contain any relocations.
346+
347+
assert_eq!(rela_size, None);
348+
assert_eq!(rela_ent, None);
349+
350+
return Ok(());
351+
};
352+
let total_size = rela_size.ok_or("RelaSize entry is missing")?;
353+
let entry_size = rela_ent.ok_or("RelaEnt entry is missing")?;
354+
355+
// Apply the mappings.
356+
let entries = total_size / entry_size;
357+
let relas = unsafe {
358+
core::slice::from_raw_parts::<Rela<u64>>(
359+
elf_file.input.as_ptr().add(offset as usize).cast(),
360+
entries as usize,
361+
)
362+
};
363+
for rela in relas {
364+
let idx = rela.get_symbol_table_index();
365+
assert_eq!(
366+
idx, 0,
367+
"relocations using the symbol table are not supported"
368+
);
369+
370+
match rela.get_type() {
371+
8 => {
372+
let offset_in_file = find_offset(elf_file, rela.get_offset())?
373+
.ok_or("Destination of relocation is not mapped in physical memory")?;
374+
let dest_addr = self.kernel_offset + offset_in_file;
375+
let dest_ptr = dest_addr.as_u64() as *mut u64;
376+
377+
let value = self
378+
.virtual_address_offset
379+
.checked_add(rela.get_addend())
380+
.unwrap();
381+
382+
unsafe {
383+
// write new value, utilizing that the address identity-mapped
384+
dest_ptr.write(value);
385+
}
386+
}
387+
ty => unimplemented!("relocation type {:x} not supported", ty),
388+
}
389+
}
390+
391+
Ok(())
392+
}
393+
}
394+
395+
/// Locate the offset into the elf file corresponding to a virtual address.
396+
fn find_offset(elf_file: &ElfFile, virt_offset: u64) -> Result<Option<u64>, &'static str> {
397+
for program_header in elf_file.program_iter() {
398+
if let Type::Load = program_header.get_type()? {
399+
if program_header.virtual_addr() <= virt_offset {
400+
let offset_in_segment = virt_offset - program_header.virtual_addr();
401+
if offset_in_segment < program_header.file_size() {
402+
return Ok(Some(program_header.offset() + offset_in_segment));
403+
}
404+
}
405+
}
406+
}
407+
Ok(None)
270408
}
271409

272410
/// Loads the kernel ELF file given in `bytes` in the given `page_table`.

tests/pie.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use std::process::Command;
2+
3+
#[test]
4+
fn basic_boot() {
5+
run_test_binary("basic_boot");
6+
}
7+
8+
#[test]
9+
fn should_panic() {
10+
run_test_binary("should_panic");
11+
}
12+
13+
#[test]
14+
fn check_boot_info() {
15+
run_test_binary("check_boot_info");
16+
}
17+
18+
#[test]
19+
fn global_variable() {
20+
run_test_binary("global_variable");
21+
}
22+
23+
fn run_test_binary(bin_name: &str) {
24+
let mut cmd = Command::new(env!("CARGO"));
25+
cmd.current_dir("tests/test_kernels/pie");
26+
cmd.arg("run");
27+
cmd.arg("--bin").arg(bin_name);
28+
cmd.arg("--target").arg("x86_64-pie.json");
29+
cmd.arg("-Zbuild-std=core");
30+
cmd.arg("-Zbuild-std-features=compiler-builtins-mem");
31+
assert!(cmd.status().unwrap().success());
32+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[unstable]
2+
# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged
3+
# build-std = ["core"]
4+
5+
[build]
6+
# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged
7+
# target = "x86_64-example-kernel.json"
8+
9+
[target.'cfg(target_os = "none")']
10+
runner = "cargo run --manifest-path ../../runner/Cargo.toml"

tests/test_kernels/pie/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
target

tests/test_kernels/pie/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "test_kernel_pie"
3+
version = "0.1.0"
4+
authors = ["Tom Dohrmann <erbse.13@gmx.de>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
bootloader = { path = "../../.." }
9+
x86_64 = { version = "0.13.2", default-features = false, features = ["instructions", "inline_asm"] }
10+
uart_16550 = "0.2.10"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![no_std] // don't link the Rust standard library
2+
#![no_main] // disable all Rust-level entry points
3+
4+
use bootloader::{entry_point, BootInfo};
5+
use core::panic::PanicInfo;
6+
use test_kernel_pie::{exit_qemu, QemuExitCode};
7+
8+
entry_point!(kernel_main);
9+
10+
fn kernel_main(_boot_info: &'static mut BootInfo) -> ! {
11+
exit_qemu(QemuExitCode::Success);
12+
}
13+
14+
/// This function is called on panic.
15+
#[panic_handler]
16+
fn panic(info: &PanicInfo) -> ! {
17+
use core::fmt::Write;
18+
19+
let _ = writeln!(test_kernel_pie::serial(), "PANIC: {}", info);
20+
exit_qemu(QemuExitCode::Failed);
21+
}

0 commit comments

Comments
 (0)