diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-27 16:58:31 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-27 16:58:31 +0200 |
| commit | 300c0acbd7ab3fae8596ff839c39d9a17c70c68d (patch) | |
| tree | 5a600b24c037dc798766e3ce3fc004956e517abb /rsnix/src/load.rs | |
| parent | exec: PE32+ loader, versioned ABI, first external binaries (v0.6.0) (diff) | |
| download | rs-dos-300c0acbd7ab3fae8596ff839c39d9a17c70c68d.tar.gz rs-dos-300c0acbd7ab3fae8596ff839c39d9a17c70c68d.zip | |
streams: per-invocation channel, pumping mode, filter pipeline wiring
exec: LineChannel bounded ring (cap=16) with abort/eof/marker fields.
Shell 'pumping mode' assembles canonical lines from its queue into the
channel while the command runs; ^C marks the channel aborted.
Shell-side grammar: USE/TIL pre-drain into the channel before spawn;
<cmd> <path> TIL <marker> sugar drains paths until the marker line.
Interactive TIL: pump_key checks each submitted line against the marker
and closes the channel when it matches.
Exports callbacks run in the command task but touch shell-owned state:
this is safe by construction — the shell blocks in exec_external until
the command completes. put_line feeds Pipeline::line directly;
get_line pops from the channel (yield-poll when empty).
load: drop NX from image mapping (single-privilege kernel, NX bit 63
reserved when EFER.NXE is set by OVMF); compute image span from section
RVAs not file size.
bins/count: gate binary — reads lines via get_line, reports count via
put_line. Verified: count USE file → 6; count <path> TIL END → 2;
interactive count TIL DONE → 3; -h filter across task boundary;
^C abort.
v1 stream exports: put_line → Pipeline, get_line ← channel, log_line
→ klog append, exit → abort_current.
Accounting: print_one_line → pipe.line() returns false (head kill /
pager ^C) → the binary should wrap up promptly; accepted: binary does
not check the return value in v1 (phase 3 simplification).
Diffstat (limited to 'rsnix/src/load.rs')
| -rw-r--r-- | rsnix/src/load.rs | 68 |
1 files changed, 53 insertions, 15 deletions
diff --git a/rsnix/src/load.rs b/rsnix/src/load.rs index cebca89..488c703 100644 --- a/rsnix/src/load.rs +++ b/rsnix/src/load.rs | |||
| @@ -50,7 +50,8 @@ pub enum LoadError { | |||
| 50 | // --------------------------------------------------------------------------- | 50 | // --------------------------------------------------------------------------- |
| 51 | 51 | ||
| 52 | fn u16_at(b: &[u8], off: usize) -> Option<u16> { | 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]])) | 53 | b.get(off..off + 2) |
| 54 | .map(|s| u16::from_le_bytes([s[0], s[1]])) | ||
| 54 | } | 55 | } |
| 55 | fn u32_at(b: &[u8], off: usize) -> Option<u32> { | 56 | fn u32_at(b: &[u8], off: usize) -> Option<u32> { |
| 56 | b.get(off..off + 4) | 57 | b.get(off..off + 4) |
| @@ -87,7 +88,23 @@ struct Section { | |||
| 87 | raw_size: usize, | 88 | raw_size: usize, |
| 88 | } | 89 | } |
| 89 | 90 | ||
| 90 | /// Parse headers, apply relocations into `mem`, return the entry RVA. | 91 | /// Total virtual span of a PE image: the highest section RVA+virt_size |
| 92 | /// (never smaller than one page for the headers). | ||
| 93 | fn image_span_bytes(img: &[u8]) -> usize { | ||
| 94 | let e_lfanew = u32_at(img, 0x3C).unwrap_or(0) as usize; | ||
| 95 | let coff = e_lfanew + 4; | ||
| 96 | let num_sections = u16_at(img, coff + 2).unwrap_or(0) as usize; | ||
| 97 | let opt_size = u16_at(img, coff + 16).unwrap_or(0) as usize; | ||
| 98 | let sect_tab = coff + 20 + opt_size; | ||
| 99 | let mut end = PAGE_SIZE; // at least one page for headers | ||
| 100 | for i in 0..num_sections { | ||
| 101 | let s = sect_tab + i * 40; | ||
| 102 | let vsz = u32_at(img, s + 8).unwrap_or(0) as usize; | ||
| 103 | let va = u32_at(img, s + 12).unwrap_or(0) as usize; | ||
| 104 | end = end.max(va + vsz); | ||
| 105 | } | ||
| 106 | end | ||
| 107 | } | ||
| 91 | fn parse_and_relocate(img: &[u8], mem: usize) -> Result<usize, LoadError> { | 108 | fn parse_and_relocate(img: &[u8], mem: usize) -> Result<usize, LoadError> { |
| 92 | // ---- DOS header ---- | 109 | // ---- DOS header ---- |
| 93 | if u16_at(img, 0) != Some(DOS_MAGIC) { | 110 | if u16_at(img, 0) != Some(DOS_MAGIC) { |
| @@ -151,7 +168,9 @@ fn parse_and_relocate(img: &[u8], mem: usize) -> Result<usize, LoadError> { | |||
| 151 | if n == 0 { | 168 | if n == 0 { |
| 152 | continue; | 169 | continue; |
| 153 | } | 170 | } |
| 154 | let src = img.get(s.raw_ptr..s.raw_ptr + n).ok_or(LoadError::BadSectionTable)?; | 171 | let src = img |
| 172 | .get(s.raw_ptr..s.raw_ptr + n) | ||
| 173 | .ok_or(LoadError::BadSectionTable)?; | ||
| 155 | unsafe { | 174 | unsafe { |
| 156 | let dst = (mem + s.virt_addr) as *mut u8; | 175 | let dst = (mem + s.virt_addr) as *mut u8; |
| 157 | core::ptr::copy_nonoverlapping(src.as_ptr(), dst, n); | 176 | core::ptr::copy_nonoverlapping(src.as_ptr(), dst, n); |
| @@ -185,7 +204,9 @@ fn parse_and_relocate(img: &[u8], mem: usize) -> Result<usize, LoadError> { | |||
| 185 | unsafe { | 204 | unsafe { |
| 186 | let p = target as *mut u64; | 205 | let p = target as *mut u64; |
| 187 | let v = p.read_unaligned(); | 206 | let v = p.read_unaligned(); |
| 188 | p.write_unaligned(v.wrapping_add((mem as u64).wrapping_sub(image_base as u64))); | 207 | p.write_unaligned( |
| 208 | v.wrapping_add((mem as u64).wrapping_sub(image_base as u64)), | ||
| 209 | ); | ||
| 189 | } | 210 | } |
| 190 | } | 211 | } |
| 191 | _ => return Err(LoadError::BadReloc), | 212 | _ => return Err(LoadError::BadReloc), |
| @@ -213,17 +234,22 @@ pub fn load_image(bytes: &[u8]) -> Result<Image, LoadError> { | |||
| 213 | return Err(LoadError::NotPe); | 234 | return Err(LoadError::NotPe); |
| 214 | } | 235 | } |
| 215 | 236 | ||
| 216 | let page_count = bytes.len().div_ceil(PAGE_SIZE); | 237 | // The memory span is the VIRTUAL layout: the highest section RVA + |
| 238 | // virt_size (headers live at rva 0). Sections are scattered across | ||
| 239 | // RVAs, so this can exceed the file size. | ||
| 240 | let image_span = image_span_bytes(bytes); | ||
| 241 | let page_count = image_span.div_ceil(PAGE_SIZE); | ||
| 217 | // Allocate physical pages at a fixed window (identity mapping makes | 242 | // Allocate physical pages at a fixed window (identity mapping makes |
| 218 | // the link address = runtime address after relocation). | 243 | // the link address = runtime address after relocation). |
| 219 | let base = crate::memory::alloc_pages_above(page_count, LOAD_BASE).ok_or(LoadError::AllocFailed)?; | 244 | let base = |
| 245 | crate::memory::alloc_pages_above(page_count, LOAD_BASE).ok_or(LoadError::AllocFailed)?; | ||
| 220 | if base >= LOAD_LIMIT { | 246 | if base >= LOAD_LIMIT { |
| 221 | crate::memory::free_pages(base, page_count); | 247 | crate::memory::free_pages(base, page_count); |
| 222 | return Err(LoadError::TooBig); | 248 | return Err(LoadError::TooBig); |
| 223 | } | 249 | } |
| 224 | // Identity-map them user+writable+nx-default (relocations need write; | 250 | // Identity-map user+writable: single-privilege-level kernel, no W^X — |
| 225 | // we do not enforce W^X here — single-privilege-level kernel). | 251 | // the image contains code that must execute and data that relocates. |
| 226 | if !paging::map_range(base, page_count, paging::PAGE_USER | paging::PAGE_WRITABLE | paging::PAGE_NX) { | 252 | if !paging::map_range(base, page_count, paging::PAGE_USER | paging::PAGE_WRITABLE) { |
| 227 | crate::memory::free_pages(base, page_count); | 253 | crate::memory::free_pages(base, page_count); |
| 228 | return Err(LoadError::MapFailed); | 254 | return Err(LoadError::MapFailed); |
| 229 | } | 255 | } |
| @@ -250,15 +276,27 @@ pub fn load_image(bytes: &[u8]) -> Result<Image, LoadError> { | |||
| 250 | /// Zero the part of each section between raw_size and virt_size (BSS). | 276 | /// Zero the part of each section between raw_size and virt_size (BSS). |
| 251 | fn zero_bss_tails(img: &[u8], mem: usize) { | 277 | fn zero_bss_tails(img: &[u8], mem: usize) { |
| 252 | // Re-walk the section table minimally (headers are small). | 278 | // Re-walk the section table minimally (headers are small). |
| 253 | let Some(e_lfanew) = u32_at(img, 0x3C) else { return }; | 279 | let Some(e_lfanew) = u32_at(img, 0x3C) else { |
| 280 | return; | ||
| 281 | }; | ||
| 254 | let coff = e_lfanew as usize + 4; | 282 | let coff = e_lfanew as usize + 4; |
| 255 | let Some(num_sections) = u16_at(img, coff + 2) else { return }; | 283 | let Some(num_sections) = u16_at(img, coff + 2) else { |
| 256 | let Some(opt_size) = u16_at(img, coff + 16) else { return }; | 284 | return; |
| 285 | }; | ||
| 286 | let Some(opt_size) = u16_at(img, coff + 16) else { | ||
| 287 | return; | ||
| 288 | }; | ||
| 257 | let sect_tab = coff + 20 + opt_size as usize; | 289 | let sect_tab = coff + 20 + opt_size as usize; |
| 258 | for i in 0..num_sections as usize { | 290 | for i in 0..num_sections as usize { |
| 259 | let Some(virt_size) = u32_at(img, sect_tab + i * 40 + 8) else { return }; | 291 | let Some(virt_size) = u32_at(img, sect_tab + i * 40 + 8) else { |
| 260 | let Some(raw_size) = u32_at(img, sect_tab + i * 40 + 16) else { return }; | 292 | return; |
| 261 | let Some(virt_addr) = u32_at(img, sect_tab + i * 40 + 12) else { return }; | 293 | }; |
| 294 | let Some(raw_size) = u32_at(img, sect_tab + i * 40 + 16) else { | ||
| 295 | return; | ||
| 296 | }; | ||
| 297 | let Some(virt_addr) = u32_at(img, sect_tab + i * 40 + 12) else { | ||
| 298 | return; | ||
| 299 | }; | ||
| 262 | if virt_size > raw_size { | 300 | if virt_size > raw_size { |
| 263 | unsafe { | 301 | unsafe { |
| 264 | core::ptr::write_bytes( | 302 | core::ptr::write_bytes( |
