diff options
Diffstat (limited to 'rsnix/src/load.rs')
| -rw-r--r-- | rsnix/src/load.rs | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/rsnix/src/load.rs b/rsnix/src/load.rs new file mode 100644 index 0000000..cebca89 --- /dev/null +++ b/rsnix/src/load.rs | |||
| @@ -0,0 +1,283 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // load — PE32+ executable loader (ADR 0006 Phase 2). | ||
| 3 | // | ||
| 4 | // Parses a PE32+ image read through the VFS, allocates identity-mapped | ||
| 5 | // pages for it (the kernel runs identity-mapped, so "user" code lives at | ||
| 6 | // its link address), applies base relocations, and returns the entry | ||
| 7 | // point. The spawn path (exec.rs glue in sched) builds an Exports table, | ||
| 8 | // copies the argv block into the image's memory, and starts a task whose | ||
| 9 | // trampoline calls entry(&Exports, argc, argv). | ||
| 10 | // | ||
| 11 | // Scope: what rustc/lld emits for a no_std UEFI-ish binary — DOS header, | ||
| 12 | // COFF header, optional header PE32+, section table, .reloc base | ||
| 13 | // relocations. No imports, no TLS, no exceptions. | ||
| 14 | // --------------------------------------------------------------------------- | ||
| 15 | use crate::paging; | ||
| 16 | |||
| 17 | /// Where binaries are loaded: above the heap, inside conventional RAM. | ||
| 18 | /// The page allocator's span covers the conventional-memory range of the | ||
| 19 | /// boot memory map; LOAD_BASE must sit inside it (heap ends ~0xbb6c000 | ||
| 20 | /// with 256 MiB guests; conventional RAM typically ends near 0x0FF00000). | ||
| 21 | const LOAD_BASE: usize = 0x0C00_0000; // 192 MiB | ||
| 22 | const LOAD_LIMIT: usize = 0x0F00_0000; // stop before top-of-RAM | ||
| 23 | |||
| 24 | const PAGE_SIZE: usize = 4096; | ||
| 25 | |||
| 26 | /// A loaded image: pages to free on exit + the entry point. | ||
| 27 | pub struct Image { | ||
| 28 | /// First allocated page (free this..+page_count on exit). | ||
| 29 | pub base: usize, | ||
| 30 | pub page_count: usize, | ||
| 31 | pub entry: usize, | ||
| 32 | } | ||
| 33 | |||
| 34 | #[derive(Debug)] | ||
| 35 | pub enum LoadError { | ||
| 36 | ReadFailed, | ||
| 37 | NotPe, | ||
| 38 | NotPe32Plus, | ||
| 39 | BadSectionTable, | ||
| 40 | NoEntryPoint, | ||
| 41 | AllocFailed, | ||
| 42 | MapFailed, | ||
| 43 | BadReloc, | ||
| 44 | TooBig, | ||
| 45 | } | ||
| 46 | |||
| 47 | // --------------------------------------------------------------------------- | ||
| 48 | // PE file-format structures (packed reads via byte slices — no unaligned | ||
| 49 | // struct pointer derefs). | ||
| 50 | // --------------------------------------------------------------------------- | ||
| 51 | |||
| 52 | fn u16_at(b: &[u8], off: usize) -> Option<u16> { | ||
| 53 | b.get(off..off + 2).map(|s| u16::from_le_bytes([s[0], s[1]])) | ||
| 54 | } | ||
| 55 | fn u32_at(b: &[u8], off: usize) -> Option<u32> { | ||
| 56 | b.get(off..off + 4) | ||
| 57 | .map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]])) | ||
| 58 | } | ||
| 59 | fn u64_at(b: &[u8], off: usize) -> Option<u64> { | ||
| 60 | b.get(off..off + 8).map(|s| { | ||
| 61 | let mut t = [0u8; 8]; | ||
| 62 | t.copy_from_slice(s); | ||
| 63 | u64::from_le_bytes(t) | ||
| 64 | }) | ||
| 65 | } | ||
| 66 | |||
| 67 | const DOS_MAGIC: u16 = 0x5A4D; // "MZ" | ||
| 68 | const PE_MAGIC: u32 = 0x0000_4550; // "PE\0\0" | ||
| 69 | const OPT_PE32PLUS: u16 = 0x20B; | ||
| 70 | const DIR_BASERELOC: usize = 5; | ||
| 71 | |||
| 72 | /// Map an RVA to its raw file offset via the section table. | ||
| 73 | fn rva_to_file(sections: &[Option<Section>], count: usize, rva: usize) -> Option<usize> { | ||
| 74 | for s in sections.iter().take(count).flatten() { | ||
| 75 | if rva >= s.virt_addr && rva < s.virt_addr + s.raw_size.max(s.virt_size) { | ||
| 76 | return Some(s.raw_ptr + (rva - s.virt_addr)); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | None | ||
| 80 | } | ||
| 81 | |||
| 82 | #[derive(Clone, Copy)] | ||
| 83 | struct Section { | ||
| 84 | virt_addr: usize, | ||
| 85 | virt_size: usize, | ||
| 86 | raw_ptr: usize, | ||
| 87 | raw_size: usize, | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Parse headers, apply relocations into `mem`, return the entry RVA. | ||
| 91 | fn parse_and_relocate(img: &[u8], mem: usize) -> Result<usize, LoadError> { | ||
| 92 | // ---- DOS header ---- | ||
| 93 | if u16_at(img, 0) != Some(DOS_MAGIC) { | ||
| 94 | return Err(LoadError::NotPe); | ||
| 95 | } | ||
| 96 | let e_lfanew = u32_at(img, 0x3C).ok_or(LoadError::NotPe)? as usize; | ||
| 97 | |||
| 98 | // ---- COFF header ---- | ||
| 99 | if u32_at(img, e_lfanew) != Some(PE_MAGIC) { | ||
| 100 | return Err(LoadError::NotPe); | ||
| 101 | } | ||
| 102 | let coff = e_lfanew + 4; | ||
| 103 | let num_sections = u16_at(img, coff + 2).ok_or(LoadError::NotPe)? as usize; | ||
| 104 | let opt_size = u16_at(img, coff + 16).ok_or(LoadError::NotPe)? as usize; | ||
| 105 | if opt_size == 0 { | ||
| 106 | return Err(LoadError::NotPe); | ||
| 107 | } | ||
| 108 | |||
| 109 | // ---- Optional header (PE32+) ---- | ||
| 110 | let opt = coff + 20; | ||
| 111 | if u16_at(img, opt) != Some(OPT_PE32PLUS) { | ||
| 112 | return Err(LoadError::NotPe32Plus); | ||
| 113 | } | ||
| 114 | let entry_rva = u32_at(img, opt + 16).ok_or(LoadError::NotPe32Plus)? as usize; | ||
| 115 | let image_base = u64_at(img, opt + 24).ok_or(LoadError::NotPe32Plus)? as usize; | ||
| 116 | |||
| 117 | let num_dirs = u32_at(img, opt + 108).ok_or(LoadError::NotPe32Plus)? as usize; | ||
| 118 | let dir_base = opt + 112; | ||
| 119 | let (reloc_rva, reloc_size) = if num_dirs > DIR_BASERELOC { | ||
| 120 | ( | ||
| 121 | u32_at(img, dir_base + DIR_BASERELOC * 8).ok_or(LoadError::BadReloc)? as usize, | ||
| 122 | u32_at(img, dir_base + DIR_BASERELOC * 8 + 4).ok_or(LoadError::BadReloc)? as usize, | ||
| 123 | ) | ||
| 124 | } else { | ||
| 125 | (0, 0) | ||
| 126 | }; | ||
| 127 | |||
| 128 | // ---- Section table ---- | ||
| 129 | let sect_tab = opt + opt_size; | ||
| 130 | let mut sections: [Option<Section>; 96] = [None; 96]; | ||
| 131 | if num_sections > sections.len() { | ||
| 132 | return Err(LoadError::BadSectionTable); | ||
| 133 | } | ||
| 134 | for i in 0..num_sections { | ||
| 135 | let s = sect_tab + i * 40; | ||
| 136 | let virt_size = u32_at(img, s + 8).ok_or(LoadError::BadSectionTable)? as usize; | ||
| 137 | let virt_addr = u32_at(img, s + 12).ok_or(LoadError::BadSectionTable)? as usize; | ||
| 138 | let raw_size = u32_at(img, s + 16).ok_or(LoadError::BadSectionTable)? as usize; | ||
| 139 | let raw_ptr = u32_at(img, s + 20).ok_or(LoadError::BadSectionTable)? as usize; | ||
| 140 | sections[i] = Some(Section { | ||
| 141 | virt_addr, | ||
| 142 | virt_size, | ||
| 143 | raw_ptr, | ||
| 144 | raw_size, | ||
| 145 | }); | ||
| 146 | } | ||
| 147 | |||
| 148 | // ---- Copy section contents ---- | ||
| 149 | for s in sections.iter_mut().take(num_sections).flatten() { | ||
| 150 | let n = s.raw_size.min(s.virt_size); | ||
| 151 | if n == 0 { | ||
| 152 | continue; | ||
| 153 | } | ||
| 154 | let src = img.get(s.raw_ptr..s.raw_ptr + n).ok_or(LoadError::BadSectionTable)?; | ||
| 155 | unsafe { | ||
| 156 | let dst = (mem + s.virt_addr) as *mut u8; | ||
| 157 | core::ptr::copy_nonoverlapping(src.as_ptr(), dst, n); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | // ---- Apply base relocations ---- | ||
| 162 | if reloc_size > 0 && reloc_rva != 0 { | ||
| 163 | // The .reloc directory is an RVA; the section table maps it to a | ||
| 164 | // raw file offset. | ||
| 165 | let mut off = rva_to_file(§ions, num_sections, reloc_rva).ok_or(LoadError::BadReloc)?; | ||
| 166 | let end = off + reloc_size; | ||
| 167 | while off + 8 <= end { | ||
| 168 | let page_rva = u32_at(img, off).ok_or(LoadError::BadReloc)? as usize; | ||
| 169 | let block_size = u32_at(img, off + 4).ok_or(LoadError::BadReloc)? as usize; | ||
| 170 | if block_size < 8 || off + block_size > end { | ||
| 171 | break; | ||
| 172 | } | ||
| 173 | let entries = (block_size - 8) / 2; | ||
| 174 | for i in 0..entries { | ||
| 175 | let e = u16_at(img, off + 8 + i * 2).ok_or(LoadError::BadReloc)?; | ||
| 176 | let r#type = e >> 12; | ||
| 177 | let pos = e & 0xFFF; | ||
| 178 | if r#type == 0 { | ||
| 179 | continue; // IMAGE_REL_BASED_ABSOLUTE — padding | ||
| 180 | } | ||
| 181 | let target = mem + page_rva + pos as usize; | ||
| 182 | match r#type { | ||
| 183 | 10 => { | ||
| 184 | // DIR64: 8-bit image base delta | ||
| 185 | unsafe { | ||
| 186 | let p = target as *mut u64; | ||
| 187 | let v = p.read_unaligned(); | ||
| 188 | p.write_unaligned(v.wrapping_add((mem as u64).wrapping_sub(image_base as u64))); | ||
| 189 | } | ||
| 190 | } | ||
| 191 | _ => return Err(LoadError::BadReloc), | ||
| 192 | } | ||
| 193 | } | ||
| 194 | off += block_size; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | Ok(entry_rva) | ||
| 199 | } | ||
| 200 | |||
| 201 | /// Load a PE32+ image from the given bytes. Returns the loaded image | ||
| 202 | /// (identity-mapped at LOAD_BASE+) with its entry point resolved. | ||
| 203 | pub fn load_image(bytes: &[u8]) -> Result<Image, LoadError> { | ||
| 204 | if bytes.len() > 8 * 1024 * 1024 { | ||
| 205 | return Err(LoadError::TooBig); | ||
| 206 | } | ||
| 207 | // Quick header sanity before allocating. | ||
| 208 | if u16_at(bytes, 0) != Some(DOS_MAGIC) { | ||
| 209 | return Err(LoadError::NotPe); | ||
| 210 | } | ||
| 211 | let e_lfanew = u32_at(bytes, 0x3C).unwrap_or(0) as usize; | ||
| 212 | if u32_at(bytes, e_lfanew) != Some(PE_MAGIC) { | ||
| 213 | return Err(LoadError::NotPe); | ||
| 214 | } | ||
| 215 | |||
| 216 | let page_count = bytes.len().div_ceil(PAGE_SIZE); | ||
| 217 | // Allocate physical pages at a fixed window (identity mapping makes | ||
| 218 | // the link address = runtime address after relocation). | ||
| 219 | let base = crate::memory::alloc_pages_above(page_count, LOAD_BASE).ok_or(LoadError::AllocFailed)?; | ||
| 220 | if base >= LOAD_LIMIT { | ||
| 221 | crate::memory::free_pages(base, page_count); | ||
| 222 | return Err(LoadError::TooBig); | ||
| 223 | } | ||
| 224 | // Identity-map them user+writable+nx-default (relocations need write; | ||
| 225 | // we do not enforce W^X here — single-privilege-level kernel). | ||
| 226 | if !paging::map_range(base, page_count, paging::PAGE_USER | paging::PAGE_WRITABLE | paging::PAGE_NX) { | ||
| 227 | crate::memory::free_pages(base, page_count); | ||
| 228 | return Err(LoadError::MapFailed); | ||
| 229 | } | ||
| 230 | |||
| 231 | let entry_rva = match parse_and_relocate(bytes, base) { | ||
| 232 | Ok(e) => e, | ||
| 233 | Err(e) => { | ||
| 234 | crate::memory::free_pages(base, page_count); | ||
| 235 | return Err(e); | ||
| 236 | } | ||
| 237 | }; | ||
| 238 | // Clear BSS tails: sections were copied raw-size bytes each; anything | ||
| 239 | // between raw_size and virt_size is zero per PE spec. parse_and_relocate | ||
| 240 | // only copied min(raw,virt); zero-fill the remainder now. | ||
| 241 | zero_bss_tails(bytes, base); | ||
| 242 | |||
| 243 | Ok(Image { | ||
| 244 | base, | ||
| 245 | page_count, | ||
| 246 | entry: base + entry_rva, | ||
| 247 | }) | ||
| 248 | } | ||
| 249 | |||
| 250 | /// Zero the part of each section between raw_size and virt_size (BSS). | ||
| 251 | fn zero_bss_tails(img: &[u8], mem: usize) { | ||
| 252 | // Re-walk the section table minimally (headers are small). | ||
| 253 | let Some(e_lfanew) = u32_at(img, 0x3C) else { return }; | ||
| 254 | let coff = e_lfanew as usize + 4; | ||
| 255 | let Some(num_sections) = u16_at(img, coff + 2) else { return }; | ||
| 256 | let Some(opt_size) = u16_at(img, coff + 16) else { return }; | ||
| 257 | let sect_tab = coff + 20 + opt_size as usize; | ||
| 258 | for i in 0..num_sections as usize { | ||
| 259 | let Some(virt_size) = u32_at(img, sect_tab + i * 40 + 8) else { return }; | ||
| 260 | let Some(raw_size) = u32_at(img, sect_tab + i * 40 + 16) else { return }; | ||
| 261 | let Some(virt_addr) = u32_at(img, sect_tab + i * 40 + 12) else { return }; | ||
| 262 | if virt_size > raw_size { | ||
| 263 | unsafe { | ||
| 264 | core::ptr::write_bytes( | ||
| 265 | (mem + virt_addr as usize + raw_size as usize) as *mut u8, | ||
| 266 | 0, | ||
| 267 | virt_size as usize - raw_size as usize, | ||
| 268 | ); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | /// Free a previously loaded image. | ||
| 275 | /// | ||
| 276 | /// The task that ran the image must be dead — no code may still execute | ||
| 277 | /// from or reference these pages. | ||
| 278 | pub unsafe fn unload_image(img: &Image) { | ||
| 279 | for i in 0..img.page_count { | ||
| 280 | let _ = paging::unmap_page(img.base + i * PAGE_SIZE); | ||
| 281 | } | ||
| 282 | crate::memory::free_pages(img.base, img.page_count); | ||
| 283 | } | ||
