Repository navigation
libc: dl_iterate_phdr, from program headers the kernel reports - #599
Conversation
libunwind finds a module's unwind tables through `dl_iterate_phdr`, which M3
of issues/build/toyos-builds-itself.md needs. Nothing told userland where a
loaded module's program headers are: a process starts with no auxiliary
vector, and `SYS_QUERY_MODULES`'s `ModuleInfo` carried a base, an extent and
`.eh_frame_hdr` but no `PT_*` table.
ABI: `ModuleInfo` gains `phdr` (the absolute address of the module's program
header table), `phnum` and `phentsize`, between `eh_frame_hdr_size` and
`path_offset`. It grows from 40 to 56 bytes, still with no padding, which its
const assert and the wire-decoding test now say. No auxiliary vector is added.
toyos-elf: `Layout::program_headers()` is where the image holds
`e_phoff..+e_phnum*56`: the first `PT_LOAD` whose file bytes hold all of it,
as an `ImageRange`, or `None`. It is derived from where the loader copies the
file, not from `PT_PHDR`, which is only the file's claim; bytes past
`p_filesz` are zeroes and hold nothing. The bootloader parses the kernel with
the same `Layout`, so this is an answer, not a refusal.
Kernel: an executable or library whose table no `PT_LOAD` maps is refused —
`spawn` right after the rebase check, `load_shared_lib` right after the
vaddr-0 check — rather than mapped or copied in. Refusing keeps the answer
true with no new mapping, and every ELF the tree builds already maps its
table: `llvm-readobj -h -l` over the 24 guest ELFs `--build-only` makes, the
98 test binaries and four test libraries of the primary's last build, and
this branch's C test, found all 127 with `e_phoff` 64 and the table inside
one `PT_LOAD`'s file bytes. The crafted ELFs in abuse_elf_loader and abuse_elf_segments
that do not map their table (filesz-0 segments at kernel-half and
arena vaddrs) are refused by `rebase_base` before this check, so each still
exercises the refusal it was written for. `ElfInfo` carries the executable's
table as (address, count), `LoadedLib` and the cache snapshot carry a
library's, and `sys_query_modules` reports both.
toyos-abi: `syscall::modules` decodes one `query_modules` answer into its
records and paths, which libc uses; host-tested beside the layout test.
libc: `dl_iterate_phdr` and `link.h` (`struct dl_phdr_info` with `dlpi_addr`,
`dlpi_name`, `dlpi_phdr`, `dlpi_phnum` — no `dlpi_adds`/`dlpi_subs`), with an
`elf.h` holding the program header vocabulary `link.h` needs. The contract is
glibc's dl_iterate_phdr(3), which the LSB adopts: the executable first, named
"", then each library in load order; the first non-zero callback return stops
the walk and is returned, else 0. `dlpi_addr` is the load bias, so
`dlpi_addr + p_vaddr` is a runtime address.
Test: tests/testcases/tinycc/204_dl_iterate_phdr walks every module and checks
each against facts the kernel did not report: its table lies in one of its
own `PT_LOAD`s, `PT_PHDR` (where the linker emits one) names the same address,
the executable's `main` is in an executable `PT_LOAD` and its `PT_DYNAMIC` is
at the linker's `_DYNAMIC`; after `dlopen("/system/lib/libtls_lib.so")` the
library is visited by that name with `dlsym`'s `tls_get_label` in one of its
executable segments; callbacks returning 1, 7 and -1 stop the walk where they
say and are returned. The corpus licence counts two more files of ours.
The std fork's unwinder reads `ModuleInfo` through `size_of` and by field
name, so it builds unchanged against the larger record; it needed no fork
commit. Reading it found it takes the byte count `query_modules` returns as a
record count, filed as
issues/diagnostics/std-reads-a-query-modules-byte-count-as-a-module-count.md.
Closes issues/build/libc-has-no-dl-iterate-phdr.md, and drops its citation
from issues/build/toyos-builds-itself.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A C program with no startup libraries gets module index 0 from its first SYS_DLOPEN, and libc handed that back as the pointer 0, so dlopen reported failure for a load the kernel had committed. 204_dl_iterate_phdr reds on exactly that: the kernel log shows the module registered (its DTPMOD64 relocations applied under module_id 2), and the case prints "FAIL dlopen /system/lib/libtls_lib.so". The log's "dlopen: unresolved: main" is not the refusal: libtls_lib.so carries a global undefined `main`, the C executable exports none, and resolve_dlopen_relocs logs and leaves an unresolved slot by contract (kernel/src/elf/reloc.rs's module header). libc now hands out index + 1 and takes one off in dlsym and dlclose. dlsym(RTLD_DEFAULT) panics as unimplemented instead of reading module 0, and dlclose of a handle dlopen never returned answers -1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dlopen'ed library resolves only against other libraries, never the executable's exports; and one host gate run on this branch reddened two build-system tests on a TempDir removal that a plain rerun of the step did not reproduce. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review of #599 at 11b89c8 (high-risk: ABI, kernel ELF loader). Evidence: CI Net: +590 −40. Production +262 −14 (kernel +30 −2, toyos-abi +34 −5, toyos-elf +56 −4, libc +142 −3). Tests +262 −5. Issues +66 −21. BLOCKER
NOTE
REMOVE
SEND BACK |
…cutable off vaddr 0 checks its table Review of 11b89c8, SEND BACK. BLOCKER 1. abuse_elf_loader gains case 21, `phdrs_unmapped`: a 0x2000-byte ELF whose only PT_LOAD is file 0x1000..0x2000 at vaddr 0, entry 0. The table at e_phoff 0x40 lies in no segment's file bytes, and vaddr_min is 0, so `rebase_base` and `load_shared_lib`'s vaddr-0 check pass it; the same bytes are spawned (`spawn_refused`) and dlopened (`dlopen_refused`). toyos-elf's crafted tests pin the premise on the host: `Layout::parse` accepts those bytes with extent min 0 and no table. BLOCKER 2. 205_dl_iterate_phdr_image_base is linked with -Wl,--image-base=0x200000 (tests/common/compile.rs, LINK_FLAGS, keyed by case name, applied in `link_toyos`, which every C link goes through). It compares the executable's dlpi_phdr with __ehdr_start + e_phoff and dlpi_phnum with e_phnum, both read PC-relative from the linker's own header, and __ehdr_start with dlpi_addr + the lowest p_vaddr. It prints that lowest p_vaddr, so a flag that stops reaching the case reds its .expect. Linked on the host with the toolchain's clang: llvm-readobj -h -l gives e_phoff 0x40, 10 headers, first PT_LOAD at offset 0 vaddr 0x200000, PT_PHDR at 0x200040; llvm-nm gives __ehdr_start 0x200000. Corpus licence: 318 -> 320 files. NOTEs. - ModuleInfo drops `phentsize`: Layout refuses any e_phentsize but 56 and nothing read it. `phnum` becomes a u64: after six u64s the two u32 path fields fill one 8-byte slot, so the record stays 56 bytes with no padding, where a u32 phnum would have left 4 bytes of tail padding the const assert refuses. - issues/diagnostics/std-reads-a-query-modules-byte-count-as-a-module-count.md names the backtrace crate's `native_libraries` beside std's `load_modules`, with both exits. - issues/kernel/a-dlopened-library-never-binds-to-the-executables-exports.md gains its exit. - issues/build/libc-dlsym-rtld-default-panics-as-unimplemented.md records dlsym(RTLD_DEFAULT) dying as unimplemented. REMOVEs. - The tempdir-flake issue goes: #600 (bab6ed8, merged in with origin/main 7e15181) fixed the ENOTEMPTY it recorded. - toyos-abi's field-count sentence over ModuleInfo's const assert goes. - elf.h's opening comment goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its doc named relocation writes as what each case is, which the unmapped-table case (21) is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs
|
Negative-control patches for round 3, against af62dd5 (whole-change controls against the merge base 7e15181). Each one: m1-refusals-panic.patchdiff --git a/kernel/src/elf/mod.rs b/kernel/src/elf/mod.rs
index 41618d3d..0bd74496 100644
--- a/kernel/src/elf/mod.rs
+++ b/kernel/src/elf/mod.rs
@@ -361,7 +361,7 @@ pub fn load_shared_lib(
return Err("ELF: a shared object must begin at vaddr 0");
}
// `SYS_QUERY_MODULES` answers with the mapped table.
- let phdrs = layout.program_headers().ok_or("ELF: no PT_LOAD maps the program header table")?;
+ let phdrs = layout.program_headers().expect("phdr table");
// No writable segment yields an empty window; no relocation can target it.
let (rw_lo, rw_hi) = layout.writable_window().unwrap_or((layout.span(), layout.span()));
diff --git a/kernel/src/loader/mod.rs b/kernel/src/loader/mod.rs
index fffdbc7f..c7dc0a45 100644
--- a/kernel/src/loader/mod.rs
+++ b/kernel/src/loader/mod.rs
@@ -398,10 +398,7 @@ pub fn spawn(
let image_start = UserAddr::new(USER_VM_BASE);
// `SYS_QUERY_MODULES` answers with the mapped table, so an image without
// one has no true answer to give.
- let Some(phdrs) = layout.program_headers() else {
- log!("spawn: {}: no PT_LOAD maps the program header table", path);
- return Err(SyscallError::InvalidArgument.into());
- };
+ let phdrs = layout.program_headers().expect("phdr table");
let exe = read_exe_tables(backing.as_ref(), &layout, path)?;
let t1 = crate::clock::nanos_since_boot();m1b-dlopen-refusal-panics.patchdiff --git a/kernel/src/elf/mod.rs b/kernel/src/elf/mod.rs
index 41618d3d..0bd74496 100644
--- a/kernel/src/elf/mod.rs
+++ b/kernel/src/elf/mod.rs
@@ -361,7 +361,7 @@ pub fn load_shared_lib(
return Err("ELF: a shared object must begin at vaddr 0");
}
// `SYS_QUERY_MODULES` answers with the mapped table.
- let phdrs = layout.program_headers().ok_or("ELF: no PT_LOAD maps the program header table")?;
+ let phdrs = layout.program_headers().expect("phdr table");
// No writable segment yields an empty window; no relocation can target it.
let (rw_lo, rw_hi) = layout.writable_window().unwrap_or((layout.span(), layout.span()));
m2-exe-phdr-from-load-bias.patchdiff --git a/kernel/src/loader/mod.rs b/kernel/src/loader/mod.rs
index fffdbc7f..8bde6a06 100644
--- a/kernel/src/loader/mod.rs
+++ b/kernel/src/loader/mod.rs
@@ -603,7 +603,7 @@ pub fn spawn(
.eh_frame_hdr()
.map_or((0, 0), |r| ((image_start + r.start().get()).raw(), r.len())),
exe_vaddr_max: image_end,
- exe_phdrs: ((image_start + phdrs.image().start().get()).raw(), phdrs.count()),
+ exe_phdrs: ((UserAddr::new(base) + phdrs.image().start().get()).raw(), phdrs.count()),
lib_paths,
},
mmap_regions: Vec::new(),mutation-a-kernel-phnum-zero.patchdiff --git a/kernel/src/syscall/vm.rs b/kernel/src/syscall/vm.rs
index fb2b798f..9298c3d2 100644
--- a/kernel/src/syscall/vm.rs
+++ b/kernel/src/syscall/vm.rs
@@ -484,14 +484,14 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
let mut path_offset = (module_count * info_size) as u32;
let (eh_addr, eh_size) = data.elf.exe_eh_frame_hdr;
- let (phdr, phnum) = data.elf.exe_phdrs;
+ let (phdr, _) = data.elf.exe_phdrs;
let exe_info = ModuleInfo {
base: data.elf.elf_base.raw(),
text_end: data.elf.exe_vaddr_max,
eh_frame_hdr: eh_addr,
eh_frame_hdr_size: eh_size,
phdr,
- phnum: phnum.into(),
+ phnum: 0,
path_offset,
path_len: exe_path_bytes.len() as u32,
};
@@ -511,7 +511,7 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
eh_frame_hdr: lib.eh_frame_hdr.map_or(0, |r| (lib.user_base + r.start().get()).raw()),
eh_frame_hdr_size: lib.eh_frame_hdr.map_or(0, |r| r.len()),
phdr: (lib.user_base + lib.phdrs.image().start().get()).raw(),
- phnum: lib.phdrs.count().into(),
+ phnum: 0,
path_offset,
path_len: lib_path_bytes.len() as u32,
};mutation-b-libc-ignores-stop.patchdiff --git a/userland/libc/src/link.rs b/userland/libc/src/link.rs
index 7faffb24..32d1f347 100644
--- a/userland/libc/src/link.rs
+++ b/userland/libc/src/link.rs
@@ -40,7 +40,7 @@ pub unsafe extern "C" fn dl_iterate_phdr(callback: Callback, data: *mut u8) -> i
// SAFETY: the caller's function, handed a record valid for the call.
let stop = unsafe { callback(&mut info, core::mem::size_of::<DlPhdrInfo>(), data) };
if stop != 0 {
- return stop;
+ return 0;
}
}
0mutation-c-libc-handle-is-the-index.patchdiff --git b/userland/libc/src/misc.rs a/userland/libc/src/misc.rs
index 4443d6cd..fee86cf4 100644
--- b/userland/libc/src/misc.rs
+++ a/userland/libc/src/misc.rs
@@ -432,20 +432,13 @@ pub unsafe extern "C" fn longjmp(_env: *mut u8, _val: i32) -> ! {
}
// dlopen/dlsym/dlclose
-//
-// A C handle is the kernel's module index plus one: index 0 is a module, and
-// NULL is dlopen's failure.
-
-fn module_index(handle: *mut u8) -> Option<u64> {
- (handle as u64).checked_sub(1)
-}
#[no_mangle]
pub unsafe extern "C" fn dlopen(path: *const u8, _flags: i32) -> *mut u8 {
if path.is_null() { return ptr::null_mut(); }
let path_bytes = super::posix_io::c_str_to_bytes(path);
match syscall::dl_open(path_bytes) {
- Ok(index) => (index + 1) as *mut u8,
+ Ok(handle) => handle as *mut u8,
Err(_) => ptr::null_mut(),
}
}
@@ -453,10 +446,9 @@ pub unsafe extern "C" fn dlopen(path: *const u8, _flags: i32) -> *mut u8 {
#[no_mangle]
pub unsafe extern "C" fn dlsym(handle: *mut u8, symbol: *const u8) -> *mut u8 {
if symbol.is_null() { return ptr::null_mut(); }
- let index = module_index(handle).expect("dlsym: RTLD_DEFAULT not implemented");
let name = super::posix_io::c_str_to_bytes(symbol);
// SAFETY: handle is from a prior dlopen, name is a valid C string
- match unsafe { syscall::dl_sym(index, name) } {
+ match unsafe { syscall::dl_sym(handle as u64, name) } {
Ok(addr) => addr as *mut u8,
Err(_) => ptr::null_mut(),
}
@@ -464,10 +456,7 @@ pub unsafe extern "C" fn dlsym(handle: *mut u8, symbol: *const u8) -> *mut u8 {
#[no_mangle]
pub unsafe extern "C" fn dlclose(handle: *mut u8) -> i32 {
- match module_index(handle) {
- Some(index) => syscall::dl_close(index) as i32,
- None => -1,
- }
+ syscall::dl_close(handle as u64) as i32
}
#[no_mangle]control-c1-revert-kernel-abi-elf-libc.patchdiff --git a/kernel/src/elf/cache.rs b/kernel/src/elf/cache.rs
index 96322295..7eb03a5d 100644
--- a/kernel/src/elf/cache.rs
+++ b/kernel/src/elf/cache.rs
@@ -22,7 +22,7 @@ use crate::vfs::BackingId;
use crate::UserAddr;
use toyos_elf::dynamic::InitArray;
use toyos_elf::rela::Rules;
-use toyos_elf::{ImageRange, Op, ProgramHeaderTable, RelaCounts, RelocKind, SymIndex, TlsRef};
+use toyos_elf::{ImageRange, Op, RelaCounts, RelocKind, SymIndex, TlsRef};
/// A module's non-`RELATIVE` relocations, parsed and extracted once at cache
/// time, each as `(r_offset, what it names)`.
@@ -82,7 +82,6 @@ struct Snapshot {
gnu_hash: Option<KernelSlice>,
rules: Rules,
eh_frame_hdr: Option<ImageRange>,
- phdrs: ProgramHeaderTable,
init_array: Option<InitArray>,
span: u64,
rw_lo: u64,
@@ -101,7 +100,6 @@ impl Snapshot {
gnu_hash: lib.gnu_hash,
rules: lib.rules,
eh_frame_hdr: lib.eh_frame_hdr,
- phdrs: lib.phdrs,
init_array: lib.init_array,
span: lib.span,
rw_lo: lib.rw_lo,
@@ -129,7 +127,6 @@ impl Snapshot {
cached_relocs,
rules: self.rules,
eh_frame_hdr: self.eh_frame_hdr,
- phdrs: self.phdrs,
init_array: self.init_array,
span: self.span,
rw_lo: self.rw_lo,
diff --git a/kernel/src/elf/mod.rs b/kernel/src/elf/mod.rs
index 41618d3d..9df1d73a 100644
--- a/kernel/src/elf/mod.rs
+++ b/kernel/src/elf/mod.rs
@@ -27,10 +27,7 @@ use crate::UserAddr;
use toyos_elf::dynamic::{Dynamic, InitArray};
use toyos_elf::section::{SectionTable, SHT_DYNSYM};
use toyos_elf::sym::{Sym, SymTab};
-use toyos_elf::{
- rela, Extent, GnuHash, ImageRange, Layout, ProgramHeaderTable, Rela, RelaTable, Reloc, RelocError, SymIndex,
- TlsSegment,
-};
+use toyos_elf::{rela, Extent, GnuHash, ImageRange, Layout, Rela, RelaTable, Reloc, RelocError, SymIndex, TlsSegment};
/// `toyos_elf::MAX_TLS_ALIGN` must equal the kernel's largest page.
const _: () = assert!(toyos_elf::MAX_TLS_ALIGN == PAGE_2M);
@@ -104,8 +101,6 @@ pub struct LoadedLib {
rules: rela::Rules,
/// `PT_GNU_EH_FRAME`, for DWARF unwinding.
pub eh_frame_hdr: Option<ImageRange>,
- /// The program header table, which `load_shared_lib` refuses a module without.
- pub phdrs: ProgramHeaderTable,
/// `DT_INIT_ARRAY`.
pub init_array: Option<InitArray>,
/// Bytes between the image's lowest and highest virtual address.
@@ -360,8 +355,6 @@ pub fn load_shared_lib(
if extent.min() != 0 {
return Err("ELF: a shared object must begin at vaddr 0");
}
- // `SYS_QUERY_MODULES` answers with the mapped table.
- let phdrs = layout.program_headers().ok_or("ELF: no PT_LOAD maps the program header table")?;
// No writable segment yields an empty window; no relocation can target it.
let (rw_lo, rw_hi) = layout.writable_window().unwrap_or((layout.span(), layout.span()));
@@ -520,7 +513,6 @@ pub fn load_shared_lib(
cached_relocs: None,
rules,
eh_frame_hdr: layout.eh_frame_hdr(),
- phdrs,
init_array,
span: layout.span(),
rw_lo,
diff --git a/kernel/src/loader/mod.rs b/kernel/src/loader/mod.rs
index fffdbc7f..ddd821de 100644
--- a/kernel/src/loader/mod.rs
+++ b/kernel/src/loader/mod.rs
@@ -396,12 +396,6 @@ pub fn spawn(
// Where the image's first byte lands: every `ImageOffset` the file's
// numbers were parsed into is added to it, and its span fits above it.
let image_start = UserAddr::new(USER_VM_BASE);
- // `SYS_QUERY_MODULES` answers with the mapped table, so an image without
- // one has no true answer to give.
- let Some(phdrs) = layout.program_headers() else {
- log!("spawn: {}: no PT_LOAD maps the program header table", path);
- return Err(SyscallError::InvalidArgument.into());
- };
let exe = read_exe_tables(backing.as_ref(), &layout, path)?;
let t1 = crate::clock::nanos_since_boot();
@@ -603,7 +597,6 @@ pub fn spawn(
.eh_frame_hdr()
.map_or((0, 0), |r| ((image_start + r.start().get()).raw(), r.len())),
exe_vaddr_max: image_end,
- exe_phdrs: ((image_start + phdrs.image().start().get()).raw(), phdrs.count()),
lib_paths,
},
mmap_regions: Vec::new(),
diff --git a/kernel/src/process.rs b/kernel/src/process.rs
index b747865c..f8680f7b 100644
--- a/kernel/src/process.rs
+++ b/kernel/src/process.rs
@@ -492,8 +492,6 @@ pub struct ElfInfo {
pub exe_eh_frame_hdr: (u64, u64),
/// One past the executable's last byte.
pub exe_vaddr_max: u64,
- /// The executable's program header table as (address, count), `(0, 0)` with no executable.
- pub exe_phdrs: (u64, u16),
/// Paths of dlopen'd libraries (parallel to loaded_libs).
pub lib_paths: Vec<String>,
}
@@ -512,7 +510,6 @@ impl ElfInfo {
elf_base: UserAddr::new(0),
exe_eh_frame_hdr: (0, 0),
exe_vaddr_max: 0,
- exe_phdrs: (0, 0),
lib_paths: Vec::new(),
}
}
diff --git a/kernel/src/syscall/vm.rs b/kernel/src/syscall/vm.rs
index fb2b798f..fb52c534 100644
--- a/kernel/src/syscall/vm.rs
+++ b/kernel/src/syscall/vm.rs
@@ -484,14 +484,11 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
let mut path_offset = (module_count * info_size) as u32;
let (eh_addr, eh_size) = data.elf.exe_eh_frame_hdr;
- let (phdr, phnum) = data.elf.exe_phdrs;
let exe_info = ModuleInfo {
base: data.elf.elf_base.raw(),
text_end: data.elf.exe_vaddr_max,
eh_frame_hdr: eh_addr,
eh_frame_hdr_size: eh_size,
- phdr,
- phnum: phnum.into(),
path_offset,
path_len: exe_path_bytes.len() as u32,
};
@@ -510,8 +507,6 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
text_end: lib.user_end(),
eh_frame_hdr: lib.eh_frame_hdr.map_or(0, |r| (lib.user_base + r.start().get()).raw()),
eh_frame_hdr_size: lib.eh_frame_hdr.map_or(0, |r| r.len()),
- phdr: (lib.user_base + lib.phdrs.image().start().get()).raw(),
- phnum: lib.phdrs.count().into(),
path_offset,
path_len: lib_path_bytes.len() as u32,
};
diff --git a/toyos-abi/src/syscall.rs b/toyos-abi/src/syscall.rs
index 4a84a27c..62e20a02 100644
--- a/toyos-abi/src/syscall.rs
+++ b/toyos-abi/src/syscall.rs
@@ -2185,11 +2185,6 @@ pub struct ModuleInfo {
pub eh_frame_hdr: u64,
/// Size of `.eh_frame_hdr` in bytes.
pub eh_frame_hdr_size: u64,
- /// Absolute virtual address of the module's program header table: the
- /// kernel loads no module whose table a `PT_LOAD` does not map.
- pub phdr: u64,
- /// Entries in that table, `e_phnum`: a `u64` so the record has no padding.
- pub phnum: u64,
/// Byte offset of the module's path string within the buffer.
pub path_offset: u32,
/// Length of the path string in bytes.
@@ -2199,9 +2194,11 @@ pub struct ModuleInfo {
/// Every byte belongs to a field: this crosses the boundary through
/// [`ModuleInfo::as_bytes`], so a gap would publish whatever the kernel stack
/// held. **This is the type where that matters most**, because the buffer it
-/// is written into is a user address. A field of any other width added here
-/// reds here rather than publishing kernel stack bytes to userland.
-const _: () = assert!(core::mem::size_of::<ModuleInfo>() == 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4);
+/// is written into is a user address: the two `u32`s sit at the end of four
+/// `u64`s, which is 8 bytes together at an 8-aligned offset, so there is no
+/// tail padding today — and this is what says so. A field of any other width
+/// added here reds here rather than publishing kernel stack bytes to userland.
+const _: () = assert!(core::mem::size_of::<ModuleInfo>() == 8 + 8 + 8 + 8 + 4 + 4);
impl ModuleInfo {
/// The record's own bytes, which is what `SYS_QUERY_MODULES` writes.
@@ -2236,28 +2233,6 @@ pub fn query_modules(buf: &mut [u8]) -> Result<usize, SyscallError> {
check(syscall(SYS_QUERY_MODULES, buf.as_mut_ptr() as u64, buf.len() as u64, 0, 0)).map(|n| n as usize)
}
-/// Every record of one [`query_modules`] answer with its path, executable
-/// first: `answer` is the `n` bytes the call reported.
-///
-/// A record or path outside `answer` is a kernel that broke the layout above,
-/// and panics.
-pub fn modules(answer: &[u8]) -> impl Iterator<Item = (ModuleInfo, &[u8])> {
- let size = core::mem::size_of::<ModuleInfo>();
- let record = move |i: usize| {
- let bytes = &answer[i * size..(i + 1) * size];
- // SAFETY: `bytes` holds `size_of::<ModuleInfo>()` bytes, read without
- // an alignment requirement, and every bit pattern of its integer
- // fields is a `ModuleInfo`.
- unsafe { (bytes.as_ptr() as *const ModuleInfo).read_unaligned() }
- };
- let count = if answer.is_empty() { 0 } else { record(0).path_offset as usize / size };
- (0..count).map(move |i| {
- let info = record(i);
- let path = info.path_offset as usize;
- (info, &answer[path..path + info.path_len as usize])
- })
-}
-
/// Scheduler info for the calling process.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
@@ -2342,70 +2317,17 @@ mod tests {
text_end: 0x2233_4455_6677_8899,
eh_frame_hdr: 0x3344_5566_7788_99aa,
eh_frame_hdr_size: 0x44,
- phdr: 0x5566_7788_99aa_bbcc,
- phnum: 0x77,
path_offset: 0x55,
path_len: 0x66,
};
let b = info.as_bytes();
- assert_eq!(b.len(), 56);
+ assert_eq!(b.len(), 40);
assert_eq!(u64::from_ne_bytes(b[0..8].try_into().unwrap()), info.base);
assert_eq!(u64::from_ne_bytes(b[8..16].try_into().unwrap()), info.text_end);
assert_eq!(u64::from_ne_bytes(b[16..24].try_into().unwrap()), info.eh_frame_hdr);
assert_eq!(u64::from_ne_bytes(b[24..32].try_into().unwrap()), info.eh_frame_hdr_size);
- assert_eq!(u64::from_ne_bytes(b[32..40].try_into().unwrap()), info.phdr);
- assert_eq!(u64::from_ne_bytes(b[40..48].try_into().unwrap()), info.phnum);
- assert_eq!(u32::from_ne_bytes(b[48..52].try_into().unwrap()), info.path_offset);
- assert_eq!(u32::from_ne_bytes(b[52..56].try_into().unwrap()), info.path_len);
- }
-
- fn record(base: u64, path_offset: u32, path_len: u32) -> ModuleInfo {
- ModuleInfo {
- base,
- text_end: base + 0x1000,
- eh_frame_hdr: 0,
- eh_frame_hdr_size: 0,
- phdr: base + 0x40,
- phnum: 3,
- path_offset,
- path_len,
- }
- }
-
- /// An answer laid out as the kernel writes one — records, then the paths
- /// packed in module order — decodes to those records and paths, in order,
- /// at an offset whatever alignment the buffer has.
- #[test]
- fn modules_decodes_every_record_and_its_path() {
- let size = core::mem::size_of::<ModuleInfo>() as u32;
- let exe = record(0x100_0000_0000, 2 * size, 9);
- let lib = record(0x200_0000_0000, 2 * size + 9, 13);
- let mut answer = [0u8; 1 + 2 * 56 + 9 + 13];
- let wire = &mut answer[1..];
- wire[..56].copy_from_slice(exe.as_bytes());
- wire[56..112].copy_from_slice(lib.as_bytes());
- wire[112..121].copy_from_slice(b"/bin/prog");
- wire[121..].copy_from_slice(b"/lib/libx.so\0");
- let got: [(u64, u64, &[u8]); 2] = {
- let mut it = modules(&answer[1..]).map(|(m, path)| (m.base, m.phdr, path));
- let pair = [it.next().unwrap(), it.next().unwrap()];
- assert!(it.next().is_none(), "two records, and no third read out of the paths");
- pair
- };
- assert_eq!(got[0], (exe.base, exe.phdr, &b"/bin/prog"[..]));
- assert_eq!(got[1], (lib.base, lib.phdr, &b"/lib/libx.so\0"[..]));
- assert_eq!(modules(&[]).count(), 0);
- }
-
- /// A path the kernel says runs past its own answer is a broken layout,
- /// not a shorter name.
- #[test]
- #[should_panic]
- fn modules_refuses_a_path_past_the_answer() {
- let size = core::mem::size_of::<ModuleInfo>() as u32;
- let mut answer = [0u8; 56 + 4];
- answer[..56].copy_from_slice(record(0, size, 5).as_bytes());
- modules(&answer).for_each(drop);
+ assert_eq!(u32::from_ne_bytes(b[32..36].try_into().unwrap()), info.path_offset);
+ assert_eq!(u32::from_ne_bytes(b[36..40].try_into().unwrap()), info.path_len);
}
/// Four lowercase hex digits each and nothing else, because two spellings
diff --git a/toyos-elf/src/layout.rs b/toyos-elf/src/layout.rs
index 397acacd..c51bab16 100644
--- a/toyos-elf/src/layout.rs
+++ b/toyos-elf/src/layout.rs
@@ -10,8 +10,8 @@
//! address it placed the image at. A raw `p_vaddr` never leaves this module.
use crate::header::{
- FileHeader, Machine, ProgramHeader, PROGRAM_HEADER_SIZE, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_LOAD,
- PT_TLS, SECTION_HEADER_SIZE,
+ FileHeader, Machine, ProgramHeader, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_LOAD, PT_TLS,
+ SECTION_HEADER_SIZE,
};
use crate::{Error, MAX_LOAD_SEGMENTS, MAX_TLS_ALIGN};
@@ -237,29 +237,6 @@ impl DynamicSegment {
}
}
-/// Where the loaded image holds its own program header table.
-///
-/// Derived from where a `PT_LOAD` copies `e_phoff` out of the file, not from
-/// `PT_PHDR`: that header is the file's claim about this, and this is what the
-/// loader actually puts there.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct ProgramHeaderTable {
- image: ImageRange,
- count: u16,
-}
-
-impl ProgramHeaderTable {
- /// The table's bytes, inside the file-backed part of one `PT_LOAD`.
- pub const fn image(&self) -> ImageRange {
- self.image
- }
-
- /// `e_phnum`, at least one.
- pub const fn count(&self) -> u16 {
- self.count
- }
-}
-
/// Where the section header table is, when the file has a usable one.
///
/// Section headers are optional metadata — symbol names for backtraces, and
@@ -294,8 +271,7 @@ impl SectionTableRef {
/// - the extent runs from the smallest `p_vaddr` to the largest
/// `p_vaddr + p_memsz` over those segments, so it covers every one of them;
/// - the entry point, the file-backed extent of `PT_TLS`, and all of
-/// `PT_DYNAMIC` and `PT_GNU_EH_FRAME`, lie inside the extent, and the
-/// program header table, when one is held, inside one `PT_LOAD`'s file bytes;
+/// `PT_DYNAMIC` and `PT_GNU_EH_FRAME`, lie inside the extent;
/// - the TLS alignment is zero or a power of two no larger than
/// [`MAX_TLS_ALIGN`], so that `!(align - 1)` is a mask and the TLS block's
/// size cannot be dominated by a number the file chose.
@@ -313,7 +289,6 @@ pub struct Layout {
dynamic: Option<DynamicSegment>,
section_headers: Option<SectionTableRef>,
eh_frame_hdr: Option<ImageRange>,
- program_headers: Option<ProgramHeaderTable>,
}
impl Layout {
@@ -435,7 +410,6 @@ impl Layout {
None => None,
Some(e) => Some(extent.range(e.vaddr, e.memsz).ok_or(Error::EhFrameOutsideImage)?),
};
- let program_headers = program_header_table(&ehdr, segments.get(..placed).unwrap_or(&[]));
Ok(Layout {
extent,
@@ -446,7 +420,6 @@ impl Layout {
dynamic,
section_headers: section_table(&ehdr),
eh_frame_hdr,
- program_headers,
})
}
@@ -487,13 +460,6 @@ impl Layout {
self.eh_frame_hdr
}
- /// Where the loaded image holds the program header table, or `None` when
- /// no `PT_LOAD`'s file bytes hold all of it and the table exists only in
- /// the file.
- pub const fn program_headers(&self) -> Option<ProgramHeaderTable> {
- self.program_headers
- }
-
/// The writable window `[lo, hi)` in image offsets, or `None` when no
/// segment is writable.
///
@@ -594,24 +560,6 @@ impl Layout {
}
}
-/// The program header table as the first `PT_LOAD` whose file bytes hold all
-/// of it places it in the image.
-///
-/// Only `p_filesz` counts: past it a segment is zeroes, not the file.
-fn program_header_table(ehdr: &FileHeader, segments: &[Segment]) -> Option<ProgramHeaderTable> {
- let len = u64::from(ehdr.phnum).checked_mul(PROGRAM_HEADER_SIZE as u64)?;
- segments.iter().find_map(|seg| {
- let within = ehdr.phoff.checked_sub(seg.file_offset)?;
- if within.checked_add(len)? > seg.filesz {
- return None;
- }
- // Inside the segment's own range, which the extent holds: `within +
- // len <= filesz <= memsz`.
- let start = seg.image.start.checked_add(within)?;
- Some(ProgramHeaderTable { image: ImageRange { start, len }, count: ehdr.phnum })
- })
-}
-
/// A section header table the loader can index, or `None`.
///
/// `e_shentsize` must be exactly 64: consumers divide a byte count by it and
diff --git a/toyos-elf/src/lib.rs b/toyos-elf/src/lib.rs
index 1d9685d7..46ee094f 100644
--- a/toyos-elf/src/lib.rs
+++ b/toyos-elf/src/lib.rs
@@ -47,7 +47,7 @@ pub use gnu_hash::GnuHash;
pub use header::{FileHeader, Machine};
pub use layout::{
DynamicSegment, Extent, ImageOffset, ImageRange, Layout, Segment, SegmentFlags,
- ProgramHeaderTable, SectionTableRef, StackedImage, TlsSegment,
+ SectionTableRef, StackedImage, TlsSegment,
};
pub use rela::{Op, Rela, RelaCounts, RelaTable, Reloc, RelocError, RelocKind, Rules, TlsRef};
pub use section::{SectionHeader, SectionTable};
diff --git a/toyos-elf/tests/crafted.rs b/toyos-elf/tests/crafted.rs
index 4d44324f..baff9336 100644
--- a/toyos-elf/tests/crafted.rs
+++ b/toyos-elf/tests/crafted.rs
@@ -347,56 +347,6 @@ fn the_file_bytes_behind_a_vaddr_are_the_containing_segments() {
assert_eq!(layout.file_bytes_from(0x2400), Some(0x400));
}
-/// `(image offset, bytes, count)` of the table a layout places, for comparing.
-fn table_of(layout: &Layout) -> Option<(u64, u64, u16)> {
- layout.program_headers().map(|t| (t.image().start().get(), t.image().len(), t.count()))
-}
-
-/// The table is where the segment holding its file bytes puts them, measured
-/// from the image's lowest address, whichever segment that is.
-#[test]
-fn the_program_header_table_is_where_its_segment_places_it() {
- let two = 2 * PROGRAM_HEADER_SIZE as u64;
- let at_zero = accepted(Elf::new(0x1000).ph(Phdr::load(0, 0, 0x1000, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8)).build());
- assert_eq!(table_of(&at_zero), Some((PH_OFF as u64, two, 2)));
-
- // The first segment does not hold the table and the second does, at a
- // vaddr above the first's: the offset is from the extent's minimum.
- let second = accepted(
- Elf::new(0x2000)
- .entry(0x1000)
- .ph(Phdr::load(0x1000, 0x1000, 0x1000, 0x1000, PF_R | PF_X))
- .ph(Phdr::load(0, 0x10_0000, 0x1000, 0x1000, PF_R))
- .build(),
- );
- assert_eq!(table_of(&second), Some((0x10_0000 + PH_OFF as u64 - 0x1000, two, 2)));
-}
-
-/// Held only where the file's bytes are: a segment whose file image stops
-/// inside the table, or one that holds it only as zero-filled memory, holds
-/// no table.
-#[test]
-fn a_table_no_segment_holds_whole_is_absent() {
- let one = PROGRAM_HEADER_SIZE as u64;
- let short = PH_OFF as u64 + one;
- // Two headers; the file image ends one header in.
- let cut = Elf::new(0x1000).ph(Phdr::load(0, 0, short, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8));
- assert_eq!(table_of(&accepted(cut.build())), None);
- // Exactly the table's end and it is held.
- let whole = Elf::new(0x1000).ph(Phdr::load(0, 0, short + one, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8));
- assert_eq!(table_of(&accepted(whole.build())), Some((PH_OFF as u64, 2 * one, 2)));
- // Memory but no file bytes.
- let zeroes = Elf::new(0x1000).ph(Phdr::load(0, 0, 0, 0x1000, PF_R));
- assert_eq!(table_of(&accepted(zeroes.build())), None);
- // A segment that starts past the table's first byte.
- let after = Elf::new(0x2000).entry(0x1000).ph(Phdr::load(PH_OFF as u64 + 8, 0x1000, 0x1000, 0x1000, PF_R));
- assert_eq!(table_of(&accepted(after.build())), None);
- // And one at vaddr 0, so a loader's vaddr-0 checks pass and only the table
- // refuses it: `abuse_elf_loader`'s `phdrs_unmapped`.
- let unmapped = accepted(Elf::new(0x2000).ph(Phdr::load(0x1000, 0, 0x1000, 0x1000, PF_R | PF_X)).build());
- assert_eq!((unmapped.extent().min(), table_of(&unmapped)), (0, None));
-}
-
#[test]
fn the_writable_window_spans_every_writable_segment() {
let layout = accepted(
diff --git a/toyos-elf/tests/fuzz.rs b/toyos-elf/tests/fuzz.rs
index 2314fb05..871ba397 100644
--- a/toyos-elf/tests/fuzz.rs
+++ b/toyos-elf/tests/fuzz.rs
@@ -309,9 +309,6 @@ fn load(case: &Case, placement: Placement, reached: &mut Reached) -> Result<(),
assert!(offset <= span, "offset {offset:#x} past the span {span:#x}");
image_start.checked_add(offset).expect("an offset inside the span leaves the placement")
};
- if let Some(table) = layout.program_headers() {
- place(table.image().end().get());
- }
let dyn_bytes = at(&case.bytes, layout.dynamic().ok_or(())?.image());
let dynamic = Dynamic::parse(dyn_bytes);
diff --git a/toyos-elf/tests/real.rs b/toyos-elf/tests/real.rs
index 3fe96367..6da7bdaa 100644
--- a/toyos-elf/tests/real.rs
+++ b/toyos-elf/tests/real.rs
@@ -37,11 +37,6 @@ fn a_toyos_ld_binary_parses_to_what_readelf_says() {
assert_eq!(layout.tls().unwrap().memsz(), 0x90);
assert_eq!(layout.tls().unwrap().align(), 0x40);
- // `e_phoff` 0x40, six headers of 56 bytes, inside the text segment's file
- // bytes at offset 0.
- let table = layout.program_headers().map(|t| (t.image().start().get(), t.image().len(), t.count()));
- assert_eq!(table, Some((0x40, 6 * 56, 6)));
-
let sections = layout.section_headers().expect("a section header table");
assert_eq!((sections.count, sections.entry_size), (9, 64));
diff --git a/userland/libc/include/elf.h b/userland/libc/include/elf.h
deleted file mode 100644
index e6ff35bb..00000000
--- a/userland/libc/include/elf.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef _ELF_H
-#define _ELF_H
-
-#include <stdint.h>
-
-typedef uint64_t Elf64_Addr;
-typedef uint64_t Elf64_Off;
-typedef uint16_t Elf64_Half;
-typedef uint32_t Elf64_Word;
-typedef int32_t Elf64_Sword;
-typedef uint64_t Elf64_Xword;
-typedef int64_t Elf64_Sxword;
-
-typedef struct {
- Elf64_Word p_type;
- Elf64_Word p_flags;
- Elf64_Off p_offset;
- Elf64_Addr p_vaddr;
- Elf64_Addr p_paddr;
- Elf64_Xword p_filesz;
- Elf64_Xword p_memsz;
- Elf64_Xword p_align;
-} Elf64_Phdr;
-
-#define PT_NULL 0
-#define PT_LOAD 1
-#define PT_DYNAMIC 2
-#define PT_INTERP 3
-#define PT_NOTE 4
-#define PT_SHLIB 5
-#define PT_PHDR 6
-#define PT_TLS 7
-#define PT_GNU_EH_FRAME 0x6474e550
-#define PT_GNU_STACK 0x6474e551
-#define PT_GNU_RELRO 0x6474e552
-
-#define PF_X 0x1
-#define PF_W 0x2
-#define PF_R 0x4
-
-#endif
diff --git a/userland/libc/include/link.h b/userland/libc/include/link.h
deleted file mode 100644
index 2eef0a32..00000000
--- a/userland/libc/include/link.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef _LINK_H
-#define _LINK_H
-
-#include <elf.h>
-#include <stddef.h>
-
-#define ElfW(type) Elf64_##type
-
-/* The first four fields of glibc's layout. There is no dlpi_adds or
- dlpi_subs, so the size a callback is passed ends at dlpi_phnum.
- dlpi_name is "" for the executable, and lives only until the callback
- returns. */
-struct dl_phdr_info {
- ElfW(Addr) dlpi_addr;
- const char *dlpi_name;
- const ElfW(Phdr) *dlpi_phdr;
- ElfW(Half) dlpi_phnum;
-};
-
-int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *info, size_t size, void *data), void *data);
-
-#endif
diff --git a/userland/libc/src/lib.rs b/userland/libc/src/lib.rs
index 1052461c..9acf206a 100644
--- a/userland/libc/src/lib.rs
+++ b/userland/libc/src/lib.rs
@@ -6,7 +6,6 @@ extern crate alloc;
mod arch;
mod ctype;
mod errno;
-mod link;
mod math;
mod memory;
mod misc;
diff --git a/userland/libc/src/link.rs b/userland/libc/src/link.rs
deleted file mode 100644
index 7faffb24..00000000
--- a/userland/libc/src/link.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-//! `dl_iterate_phdr`, from the kernel's `SYS_QUERY_MODULES` answer.
-//!
-//! The contract is glibc's `dl_iterate_phdr(3)`, which the LSB adopts: the
-//! executable first, named by the empty string, then every library in load
-//! order; the walk stops at the first callback that returns non-zero and
-//! returns that value, else 0. The walk is over one answer, so a module a
-//! callback or another thread loads meanwhile is not visited.
-
-use alloc::vec::Vec;
-
-use toyos_abi::syscall;
-
-/// `struct dl_phdr_info`, as `link.h` declares it.
-#[repr(C)]
-pub struct DlPhdrInfo {
- dlpi_addr: u64,
- dlpi_name: *const u8,
- dlpi_phdr: *const u8,
- dlpi_phnum: u16,
-}
-
-type Callback = unsafe extern "C" fn(*mut DlPhdrInfo, usize, *mut u8) -> i32;
-
-#[no_mangle]
-pub unsafe extern "C" fn dl_iterate_phdr(callback: Callback, data: *mut u8) -> i32 {
- let answer = answer();
- let mut name = Vec::new();
- for (i, (module, path)) in syscall::modules(&answer).enumerate() {
- name.clear();
- if i > 0 {
- name.extend_from_slice(path);
- }
- name.push(0);
- let mut info = DlPhdrInfo {
- dlpi_addr: module.base,
- dlpi_name: name.as_ptr(),
- dlpi_phdr: module.phdr as *const u8,
- dlpi_phnum: u16::try_from(module.phnum).expect("SYS_QUERY_MODULES: a phnum past e_phnum's u16"),
- };
- // SAFETY: the caller's function, handed a record valid for the call.
- let stop = unsafe { callback(&mut info, core::mem::size_of::<DlPhdrInfo>(), data) };
- if stop != 0 {
- return stop;
- }
- }
- 0
-}
-
-/// One whole `SYS_QUERY_MODULES` answer: asked again while a `dlopen`
-/// elsewhere grows it between the size and the read.
-fn answer() -> Vec<u8> {
- let mut buf = Vec::new();
- loop {
- let need = syscall::query_modules(&mut buf).expect("SYS_QUERY_MODULES refused a buffer this process owns");
- if need <= buf.len() {
- buf.truncate(need);
- return buf;
- }
- buf.resize(need, 0);
- }
-}
diff --git a/userland/libc/src/misc.rs b/userland/libc/src/misc.rs
index 4443d6cd..fee86cf4 100644
--- a/userland/libc/src/misc.rs
+++ b/userland/libc/src/misc.rs
@@ -432,20 +432,13 @@ pub unsafe extern "C" fn longjmp(_env: *mut u8, _val: i32) -> ! {
}
// dlopen/dlsym/dlclose
-//
-// A C handle is the kernel's module index plus one: index 0 is a module, and
-// NULL is dlopen's failure.
-
-fn module_index(handle: *mut u8) -> Option<u64> {
- (handle as u64).checked_sub(1)
-}
#[no_mangle]
pub unsafe extern "C" fn dlopen(path: *const u8, _flags: i32) -> *mut u8 {
if path.is_null() { return ptr::null_mut(); }
let path_bytes = super::posix_io::c_str_to_bytes(path);
match syscall::dl_open(path_bytes) {
- Ok(index) => (index + 1) as *mut u8,
+ Ok(handle) => handle as *mut u8,
Err(_) => ptr::null_mut(),
}
}
@@ -453,10 +446,9 @@ pub unsafe extern "C" fn dlopen(path: *const u8, _flags: i32) -> *mut u8 {
#[no_mangle]
pub unsafe extern "C" fn dlsym(handle: *mut u8, symbol: *const u8) -> *mut u8 {
if symbol.is_null() { return ptr::null_mut(); }
- let index = module_index(handle).expect("dlsym: RTLD_DEFAULT not implemented");
let name = super::posix_io::c_str_to_bytes(symbol);
// SAFETY: handle is from a prior dlopen, name is a valid C string
- match unsafe { syscall::dl_sym(index, name) } {
+ match unsafe { syscall::dl_sym(handle as u64, name) } {
Ok(addr) => addr as *mut u8,
Err(_) => ptr::null_mut(),
}
@@ -464,10 +456,7 @@ pub unsafe extern "C" fn dlsym(handle: *mut u8, symbol: *const u8) -> *mut u8 {
#[no_mangle]
pub unsafe extern "C" fn dlclose(handle: *mut u8) -> i32 {
- match module_index(handle) {
- Some(index) => syscall::dl_close(index) as i32,
- None => -1,
- }
+ syscall::dl_close(handle as u64) as i32
}
#[no_mangle]control-c2-revert-and-drop-cases.patchdiff --git a/kernel/src/elf/cache.rs b/kernel/src/elf/cache.rs
index 96322295..7eb03a5d 100644
--- a/kernel/src/elf/cache.rs
+++ b/kernel/src/elf/cache.rs
@@ -22,7 +22,7 @@ use crate::vfs::BackingId;
use crate::UserAddr;
use toyos_elf::dynamic::InitArray;
use toyos_elf::rela::Rules;
-use toyos_elf::{ImageRange, Op, ProgramHeaderTable, RelaCounts, RelocKind, SymIndex, TlsRef};
+use toyos_elf::{ImageRange, Op, RelaCounts, RelocKind, SymIndex, TlsRef};
/// A module's non-`RELATIVE` relocations, parsed and extracted once at cache
/// time, each as `(r_offset, what it names)`.
@@ -82,7 +82,6 @@ struct Snapshot {
gnu_hash: Option<KernelSlice>,
rules: Rules,
eh_frame_hdr: Option<ImageRange>,
- phdrs: ProgramHeaderTable,
init_array: Option<InitArray>,
span: u64,
rw_lo: u64,
@@ -101,7 +100,6 @@ impl Snapshot {
gnu_hash: lib.gnu_hash,
rules: lib.rules,
eh_frame_hdr: lib.eh_frame_hdr,
- phdrs: lib.phdrs,
init_array: lib.init_array,
span: lib.span,
rw_lo: lib.rw_lo,
@@ -129,7 +127,6 @@ impl Snapshot {
cached_relocs,
rules: self.rules,
eh_frame_hdr: self.eh_frame_hdr,
- phdrs: self.phdrs,
init_array: self.init_array,
span: self.span,
rw_lo: self.rw_lo,
diff --git a/kernel/src/elf/mod.rs b/kernel/src/elf/mod.rs
index 41618d3d..9df1d73a 100644
--- a/kernel/src/elf/mod.rs
+++ b/kernel/src/elf/mod.rs
@@ -27,10 +27,7 @@ use crate::UserAddr;
use toyos_elf::dynamic::{Dynamic, InitArray};
use toyos_elf::section::{SectionTable, SHT_DYNSYM};
use toyos_elf::sym::{Sym, SymTab};
-use toyos_elf::{
- rela, Extent, GnuHash, ImageRange, Layout, ProgramHeaderTable, Rela, RelaTable, Reloc, RelocError, SymIndex,
- TlsSegment,
-};
+use toyos_elf::{rela, Extent, GnuHash, ImageRange, Layout, Rela, RelaTable, Reloc, RelocError, SymIndex, TlsSegment};
/// `toyos_elf::MAX_TLS_ALIGN` must equal the kernel's largest page.
const _: () = assert!(toyos_elf::MAX_TLS_ALIGN == PAGE_2M);
@@ -104,8 +101,6 @@ pub struct LoadedLib {
rules: rela::Rules,
/// `PT_GNU_EH_FRAME`, for DWARF unwinding.
pub eh_frame_hdr: Option<ImageRange>,
- /// The program header table, which `load_shared_lib` refuses a module without.
- pub phdrs: ProgramHeaderTable,
/// `DT_INIT_ARRAY`.
pub init_array: Option<InitArray>,
/// Bytes between the image's lowest and highest virtual address.
@@ -360,8 +355,6 @@ pub fn load_shared_lib(
if extent.min() != 0 {
return Err("ELF: a shared object must begin at vaddr 0");
}
- // `SYS_QUERY_MODULES` answers with the mapped table.
- let phdrs = layout.program_headers().ok_or("ELF: no PT_LOAD maps the program header table")?;
// No writable segment yields an empty window; no relocation can target it.
let (rw_lo, rw_hi) = layout.writable_window().unwrap_or((layout.span(), layout.span()));
@@ -520,7 +513,6 @@ pub fn load_shared_lib(
cached_relocs: None,
rules,
eh_frame_hdr: layout.eh_frame_hdr(),
- phdrs,
init_array,
span: layout.span(),
rw_lo,
diff --git a/kernel/src/loader/mod.rs b/kernel/src/loader/mod.rs
index fffdbc7f..ddd821de 100644
--- a/kernel/src/loader/mod.rs
+++ b/kernel/src/loader/mod.rs
@@ -396,12 +396,6 @@ pub fn spawn(
// Where the image's first byte lands: every `ImageOffset` the file's
// numbers were parsed into is added to it, and its span fits above it.
let image_start = UserAddr::new(USER_VM_BASE);
- // `SYS_QUERY_MODULES` answers with the mapped table, so an image without
- // one has no true answer to give.
- let Some(phdrs) = layout.program_headers() else {
- log!("spawn: {}: no PT_LOAD maps the program header table", path);
- return Err(SyscallError::InvalidArgument.into());
- };
let exe = read_exe_tables(backing.as_ref(), &layout, path)?;
let t1 = crate::clock::nanos_since_boot();
@@ -603,7 +597,6 @@ pub fn spawn(
.eh_frame_hdr()
.map_or((0, 0), |r| ((image_start + r.start().get()).raw(), r.len())),
exe_vaddr_max: image_end,
- exe_phdrs: ((image_start + phdrs.image().start().get()).raw(), phdrs.count()),
lib_paths,
},
mmap_regions: Vec::new(),
diff --git a/kernel/src/process.rs b/kernel/src/process.rs
index b747865c..f8680f7b 100644
--- a/kernel/src/process.rs
+++ b/kernel/src/process.rs
@@ -492,8 +492,6 @@ pub struct ElfInfo {
pub exe_eh_frame_hdr: (u64, u64),
/// One past the executable's last byte.
pub exe_vaddr_max: u64,
- /// The executable's program header table as (address, count), `(0, 0)` with no executable.
- pub exe_phdrs: (u64, u16),
/// Paths of dlopen'd libraries (parallel to loaded_libs).
pub lib_paths: Vec<String>,
}
@@ -512,7 +510,6 @@ impl ElfInfo {
elf_base: UserAddr::new(0),
exe_eh_frame_hdr: (0, 0),
exe_vaddr_max: 0,
- exe_phdrs: (0, 0),
lib_paths: Vec::new(),
}
}
diff --git a/kernel/src/syscall/vm.rs b/kernel/src/syscall/vm.rs
index fb2b798f..fb52c534 100644
--- a/kernel/src/syscall/vm.rs
+++ b/kernel/src/syscall/vm.rs
@@ -484,14 +484,11 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
let mut path_offset = (module_count * info_size) as u32;
let (eh_addr, eh_size) = data.elf.exe_eh_frame_hdr;
- let (phdr, phnum) = data.elf.exe_phdrs;
let exe_info = ModuleInfo {
base: data.elf.elf_base.raw(),
text_end: data.elf.exe_vaddr_max,
eh_frame_hdr: eh_addr,
eh_frame_hdr_size: eh_size,
- phdr,
- phnum: phnum.into(),
path_offset,
path_len: exe_path_bytes.len() as u32,
};
@@ -510,8 +507,6 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
text_end: lib.user_end(),
eh_frame_hdr: lib.eh_frame_hdr.map_or(0, |r| (lib.user_base + r.start().get()).raw()),
eh_frame_hdr_size: lib.eh_frame_hdr.map_or(0, |r| r.len()),
- phdr: (lib.user_base + lib.phdrs.image().start().get()).raw(),
- phnum: lib.phdrs.count().into(),
path_offset,
path_len: lib_path_bytes.len() as u32,
};
diff --git a/tests/testcases/LICENSE b/tests/testcases/LICENSE
index 2e02f9cc..8eb68294 100644
--- a/tests/testcases/LICENSE
+++ b/tests/testcases/LICENSE
@@ -22,9 +22,9 @@ against tinycc upstream at 64552b3faa39ee7948a9ea21bfcc11045b90c70d
(repo.or.cz/tinycc.git, 2026-08-05), allowing for the `-` → `_` renames this
project made to some filenames.
- tinycc/ 320 files: 239 byte-identical to tinycc's `tests/tests2`,
+ tinycc/ 316 files: 239 byte-identical to tinycc's `tests/tests2`,
17 tinycc files this project modified,
- 64 not upstream at all — 63 cases written here, plus
+ 60 not upstream at all — 59 cases written here, plus
`fred.txt`, which is output `40_stdio.c` writes when it
runs and which was committed by accident.
diff --git a/tests/testcases/tinycc/204_dl_iterate_phdr.c b/tests/testcases/tinycc/204_dl_iterate_phdr.c
deleted file mode 100644
index d0f9b152..00000000
--- a/tests/testcases/tinycc/204_dl_iterate_phdr.c
+++ /dev/null
@@ -1,139 +0,0 @@
-/* dl_iterate_phdr visits the executable and every loaded library with the
- program headers the loader mapped, and stops where its callback says.
- Each module's addresses are checked against where its own code and
- _DYNAMIC really are, not against the kernel's other answers. */
-
-#include <dlfcn.h>
-#include <link.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-/* The image's test library; naming it is also what stages it beside this
- case. */
-#define LIB "/system/lib/libtls_lib.so"
-
-extern const char _DYNAMIC[] __attribute__((visibility("hidden")));
-int main(void);
-
-static int failed;
-
-static void fail(const char *what, const char *name) {
- printf("FAIL %s: \"%s\"\n", what, name);
- failed = 1;
-}
-
-/* The PT_LOAD holding [addr, addr + len) at dlpi_addr + p_vaddr, or NULL. */
-static const ElfW(Phdr) *load_holding(const struct dl_phdr_info *info, uintptr_t addr, size_t len) {
- for (int i = 0; i < info->dlpi_phnum; i++) {
- const ElfW(Phdr) *ph = &info->dlpi_phdr[i];
- uintptr_t lo = info->dlpi_addr + ph->p_vaddr;
- if (ph->p_type == PT_LOAD && addr >= lo && addr + len <= lo + ph->p_memsz)
- return ph;
- }
- return NULL;
-}
-
-/* Whether `code` lies in an executable PT_LOAD of this module. */
-static int runs(const struct dl_phdr_info *info, const void *code) {
- const ElfW(Phdr) *ph = load_holding(info, (uintptr_t)code, 1);
- return ph && (ph->p_flags & PF_X);
-}
-
-struct walk {
- int visited;
- int stop_at;
- int stop_with;
- void *lib_code;
-};
-
-static int visit(struct dl_phdr_info *info, size_t size, void *data) {
- struct walk *walk = data;
- walk->visited++;
- if (walk->stop_at)
- return walk->visited == walk->stop_at ? walk->stop_with : 0;
-
- const char *name = info->dlpi_name;
- if (size != sizeof(struct dl_phdr_info))
- fail("the size passed is not the record's", name);
- if (info->dlpi_phnum == 0 || info->dlpi_phdr == NULL) {
- fail("no program headers", name);
- return 0;
- }
- uintptr_t table = (uintptr_t)info->dlpi_phdr;
- if (!load_holding(info, table, info->dlpi_phnum * sizeof(ElfW(Phdr))))
- fail("the program headers lie in no PT_LOAD", name);
- for (int i = 0; i < info->dlpi_phnum; i++) {
- const ElfW(Phdr) *ph = &info->dlpi_phdr[i];
- if (ph->p_type == PT_PHDR && info->dlpi_addr + ph->p_vaddr != table)
- fail("PT_PHDR names another address", name);
- }
-
- if (walk->visited == 1) {
- if (strcmp(name, "") != 0)
- fail("the executable is not first, or is not named by the empty string", name);
- if (!runs(info, (const void *)main))
- fail("main lies in no executable PT_LOAD", name);
- int dynamic = 0;
- for (int i = 0; i < info->dlpi_phnum; i++) {
- const ElfW(Phdr) *ph = &info->dlpi_phdr[i];
- if (ph->p_type != PT_DYNAMIC)
- continue;
- dynamic = 1;
- if (info->dlpi_addr + ph->p_vaddr != (uintptr_t)_DYNAMIC)
- fail("PT_DYNAMIC is not at _DYNAMIC", name);
- if (!load_holding(info, info->dlpi_addr + ph->p_vaddr, ph->p_memsz))
- fail("PT_DYNAMIC lies in no PT_LOAD", name);
- }
- if (!dynamic)
- fail("no PT_DYNAMIC", name);
- printf("executable: \"%s\"\n", name);
- } else {
- if (strcmp(name, LIB) != 0)
- fail("a library other than the one loaded", name);
- if (!runs(info, walk->lib_code))
- fail("tls_get_label lies in no executable PT_LOAD", name);
- printf("library: %s\n", name);
- }
- return 0;
-}
-
-/* Walk every module, checking each; the count visited. */
-static int walk_all(void *lib_code) {
- struct walk walk = { 0, 0, 0, lib_code };
- int ret = dl_iterate_phdr(visit, &walk);
- if (ret != 0) {
- printf("FAIL a walk no callback stopped returned %d\n", ret);
- failed = 1;
- }
- return walk.visited;
-}
-
-/* A walk the callback stops at module `at` with `with`. */
-static void stop(int at, int with, int of) {
- struct walk walk = { 0, at, with, NULL };
- int ret = dl_iterate_phdr(visit, &walk);
- printf("stopped at %d of %d: visited %d, returned %d\n", at, of, walk.visited, ret);
- if (walk.visited != at || ret != with)
- failed = 1;
-}
-
-int main(void) {
- printf("modules: %d\n", walk_all(NULL));
- stop(1, 1, 1);
-
- void *lib = dlopen(LIB, RTLD_NOW);
- if (!lib) {
- printf("FAIL dlopen %s\n", LIB);
- return 1;
- }
- void *code = dlsym(lib, "tls_get_label");
- if (!code) {
- printf("FAIL dlsym tls_get_label\n");
- return 1;
- }
- printf("modules: %d\n", walk_all(code));
- stop(1, 7, 2);
- stop(2, -1, 2);
- return failed;
-}
diff --git a/tests/testcases/tinycc/204_dl_iterate_phdr.expect b/tests/testcases/tinycc/204_dl_iterate_phdr.expect
deleted file mode 100644
index a07ee158..00000000
--- a/tests/testcases/tinycc/204_dl_iterate_phdr.expect
+++ /dev/null
@@ -1,8 +0,0 @@
-executable: ""
-modules: 1
-stopped at 1 of 1: visited 1, returned 1
-executable: ""
-library: /system/lib/libtls_lib.so
-modules: 2
-stopped at 1 of 2: visited 1, returned 7
-stopped at 2 of 2: visited 2, returned -1
diff --git a/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.c b/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.c
deleted file mode 100644
index 2e5ade2e..00000000
--- a/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.c
+++ /dev/null
@@ -1,53 +0,0 @@
-/* dl_iterate_phdr reports the executable's program headers where they are when
- its lowest address is not 0: tests/common/compile.rs links this case at
- --image-base=0x200000, so the load bias and the image's first byte differ.
- Every fact the answer is checked against is the linker's, reached through
- __ehdr_start and never through the kernel's answer. */
-
-#include <link.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-extern const unsigned char __ehdr_start[] __attribute__((visibility("hidden")));
-
-/* The executable is visited first; keep its record and stop. */
-static int first(struct dl_phdr_info *info, size_t size, void *data) {
- (void)size;
- *(struct dl_phdr_info *)data = *info;
- return 1;
-}
-
-static int failed;
-
-static void check(int holds, const char *what) {
- printf("%s: %s\n", what, holds ? "yes" : "no");
- if (!holds)
- failed = 1;
-}
-
-int main(void) {
- struct dl_phdr_info exe;
- if (dl_iterate_phdr(first, &exe) != 1) {
- printf("FAIL the walk did not stop at the executable\n");
- return 1;
- }
-
- /* e_phoff and e_phnum, at their System V gABI offsets in Elf64_Ehdr. */
- uint64_t phoff;
- uint16_t phnum;
- memcpy(&phoff, __ehdr_start + 32, sizeof phoff);
- memcpy(&phnum, __ehdr_start + 56, sizeof phnum);
- const ElfW(Phdr) *table = (const ElfW(Phdr) *)(__ehdr_start + phoff);
-
- uintptr_t lowest = UINTPTR_MAX;
- for (int i = 0; i < phnum; i++)
- if (table[i].p_type == PT_LOAD && table[i].p_vaddr < lowest)
- lowest = table[i].p_vaddr;
- printf("lowest PT_LOAD: 0x%lx\n", (unsigned long)lowest);
-
- check(exe.dlpi_phnum == phnum, "dlpi_phnum is e_phnum");
- check(exe.dlpi_phdr == table, "dlpi_phdr is __ehdr_start + e_phoff");
- check((uintptr_t)__ehdr_start == exe.dlpi_addr + lowest, "__ehdr_start is dlpi_addr + the lowest p_vaddr");
- return failed;
-}
diff --git a/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.expect b/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.expect
deleted file mode 100644
index 17a85849..00000000
--- a/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.expect
+++ /dev/null
@@ -1,4 +0,0 @@
-lowest PT_LOAD: 0x200000
-dlpi_phnum is e_phnum: yes
-dlpi_phdr is __ehdr_start + e_phoff: yes
-__ehdr_start is dlpi_addr + the lowest p_vaddr: yes
diff --git a/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs b/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
index e471c1e7..36b42ab8 100644
--- a/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
+++ b/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
@@ -552,14 +552,6 @@ fn main() {
// 20. The apply-time TLS refusals, each named by its reason in the log.
tls_apply_time_refusals_are_reached();
- // 21. A program header table no `PT_LOAD` maps: the one segment's file
- // bytes start at 0x1000, past the table at 0x40. Its vaddr is 0, so
- // `rebase_base` and `load_shared_lib`'s vaddr-0 check pass it and only
- // the table refuses it, in `spawn` and in `load_shared_lib` alike.
- let unmapped = Elf::new(0x2000).ph(Phdr::load(0x1000, 0, 0x1000, 0x1000, PF_R | PF_X)).entry(0).build();
- spawn_refused("phdrs_unmapped", &unmapped);
- dlopen_refused("phdrs_unmapped.so", &unmapped);
-
// The kernel heap is intact: allocate and touch enough to walk it, then
// prove the real loader still works.
let mut blocks: Vec<Vec<u8>> = Vec::new();
diff --git a/toyos-abi/src/syscall.rs b/toyos-abi/src/syscall.rs
index 4a84a27c..62e20a02 100644
--- a/toyos-abi/src/syscall.rs
+++ b/toyos-abi/src/syscall.rs
@@ -2185,11 +2185,6 @@ pub struct ModuleInfo {
pub eh_frame_hdr: u64,
/// Size of `.eh_frame_hdr` in bytes.
pub eh_frame_hdr_size: u64,
- /// Absolute virtual address of the module's program header table: the
- /// kernel loads no module whose table a `PT_LOAD` does not map.
- pub phdr: u64,
- /// Entries in that table, `e_phnum`: a `u64` so the record has no padding.
- pub phnum: u64,
/// Byte offset of the module's path string within the buffer.
pub path_offset: u32,
/// Length of the path string in bytes.
@@ -2199,9 +2194,11 @@ pub struct ModuleInfo {
/// Every byte belongs to a field: this crosses the boundary through
/// [`ModuleInfo::as_bytes`], so a gap would publish whatever the kernel stack
/// held. **This is the type where that matters most**, because the buffer it
-/// is written into is a user address. A field of any other width added here
-/// reds here rather than publishing kernel stack bytes to userland.
-const _: () = assert!(core::mem::size_of::<ModuleInfo>() == 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4);
+/// is written into is a user address: the two `u32`s sit at the end of four
+/// `u64`s, which is 8 bytes together at an 8-aligned offset, so there is no
+/// tail padding today — and this is what says so. A field of any other width
+/// added here reds here rather than publishing kernel stack bytes to userland.
+const _: () = assert!(core::mem::size_of::<ModuleInfo>() == 8 + 8 + 8 + 8 + 4 + 4);
impl ModuleInfo {
/// The record's own bytes, which is what `SYS_QUERY_MODULES` writes.
@@ -2236,28 +2233,6 @@ pub fn query_modules(buf: &mut [u8]) -> Result<usize, SyscallError> {
check(syscall(SYS_QUERY_MODULES, buf.as_mut_ptr() as u64, buf.len() as u64, 0, 0)).map(|n| n as usize)
}
-/// Every record of one [`query_modules`] answer with its path, executable
-/// first: `answer` is the `n` bytes the call reported.
-///
-/// A record or path outside `answer` is a kernel that broke the layout above,
-/// and panics.
-pub fn modules(answer: &[u8]) -> impl Iterator<Item = (ModuleInfo, &[u8])> {
- let size = core::mem::size_of::<ModuleInfo>();
- let record = move |i: usize| {
- let bytes = &answer[i * size..(i + 1) * size];
- // SAFETY: `bytes` holds `size_of::<ModuleInfo>()` bytes, read without
- // an alignment requirement, and every bit pattern of its integer
- // fields is a `ModuleInfo`.
- unsafe { (bytes.as_ptr() as *const ModuleInfo).read_unaligned() }
- };
- let count = if answer.is_empty() { 0 } else { record(0).path_offset as usize / size };
- (0..count).map(move |i| {
- let info = record(i);
- let path = info.path_offset as usize;
- (info, &answer[path..path + info.path_len as usize])
- })
-}
-
/// Scheduler info for the calling process.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
@@ -2342,70 +2317,17 @@ mod tests {
text_end: 0x2233_4455_6677_8899,
eh_frame_hdr: 0x3344_5566_7788_99aa,
eh_frame_hdr_size: 0x44,
- phdr: 0x5566_7788_99aa_bbcc,
- phnum: 0x77,
path_offset: 0x55,
path_len: 0x66,
};
let b = info.as_bytes();
- assert_eq!(b.len(), 56);
+ assert_eq!(b.len(), 40);
assert_eq!(u64::from_ne_bytes(b[0..8].try_into().unwrap()), info.base);
assert_eq!(u64::from_ne_bytes(b[8..16].try_into().unwrap()), info.text_end);
assert_eq!(u64::from_ne_bytes(b[16..24].try_into().unwrap()), info.eh_frame_hdr);
assert_eq!(u64::from_ne_bytes(b[24..32].try_into().unwrap()), info.eh_frame_hdr_size);
- assert_eq!(u64::from_ne_bytes(b[32..40].try_into().unwrap()), info.phdr);
- assert_eq!(u64::from_ne_bytes(b[40..48].try_into().unwrap()), info.phnum);
- assert_eq!(u32::from_ne_bytes(b[48..52].try_into().unwrap()), info.path_offset);
- assert_eq!(u32::from_ne_bytes(b[52..56].try_into().unwrap()), info.path_len);
- }
-
- fn record(base: u64, path_offset: u32, path_len: u32) -> ModuleInfo {
- ModuleInfo {
- base,
- text_end: base + 0x1000,
- eh_frame_hdr: 0,
- eh_frame_hdr_size: 0,
- phdr: base + 0x40,
- phnum: 3,
- path_offset,
- path_len,
- }
- }
-
- /// An answer laid out as the kernel writes one — records, then the paths
- /// packed in module order — decodes to those records and paths, in order,
- /// at an offset whatever alignment the buffer has.
- #[test]
- fn modules_decodes_every_record_and_its_path() {
- let size = core::mem::size_of::<ModuleInfo>() as u32;
- let exe = record(0x100_0000_0000, 2 * size, 9);
- let lib = record(0x200_0000_0000, 2 * size + 9, 13);
- let mut answer = [0u8; 1 + 2 * 56 + 9 + 13];
- let wire = &mut answer[1..];
- wire[..56].copy_from_slice(exe.as_bytes());
- wire[56..112].copy_from_slice(lib.as_bytes());
- wire[112..121].copy_from_slice(b"/bin/prog");
- wire[121..].copy_from_slice(b"/lib/libx.so\0");
- let got: [(u64, u64, &[u8]); 2] = {
- let mut it = modules(&answer[1..]).map(|(m, path)| (m.base, m.phdr, path));
- let pair = [it.next().unwrap(), it.next().unwrap()];
- assert!(it.next().is_none(), "two records, and no third read out of the paths");
- pair
- };
- assert_eq!(got[0], (exe.base, exe.phdr, &b"/bin/prog"[..]));
- assert_eq!(got[1], (lib.base, lib.phdr, &b"/lib/libx.so\0"[..]));
- assert_eq!(modules(&[]).count(), 0);
- }
-
- /// A path the kernel says runs past its own answer is a broken layout,
- /// not a shorter name.
- #[test]
- #[should_panic]
- fn modules_refuses_a_path_past_the_answer() {
- let size = core::mem::size_of::<ModuleInfo>() as u32;
- let mut answer = [0u8; 56 + 4];
- answer[..56].copy_from_slice(record(0, size, 5).as_bytes());
- modules(&answer).for_each(drop);
+ assert_eq!(u32::from_ne_bytes(b[32..36].try_into().unwrap()), info.path_offset);
+ assert_eq!(u32::from_ne_bytes(b[36..40].try_into().unwrap()), info.path_len);
}
/// Four lowercase hex digits each and nothing else, because two spellings
diff --git a/toyos-elf/src/layout.rs b/toyos-elf/src/layout.rs
index 397acacd..c51bab16 100644
--- a/toyos-elf/src/layout.rs
+++ b/toyos-elf/src/layout.rs
@@ -10,8 +10,8 @@
//! address it placed the image at. A raw `p_vaddr` never leaves this module.
use crate::header::{
- FileHeader, Machine, ProgramHeader, PROGRAM_HEADER_SIZE, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_LOAD,
- PT_TLS, SECTION_HEADER_SIZE,
+ FileHeader, Machine, ProgramHeader, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_LOAD, PT_TLS,
+ SECTION_HEADER_SIZE,
};
use crate::{Error, MAX_LOAD_SEGMENTS, MAX_TLS_ALIGN};
@@ -237,29 +237,6 @@ impl DynamicSegment {
}
}
-/// Where the loaded image holds its own program header table.
-///
-/// Derived from where a `PT_LOAD` copies `e_phoff` out of the file, not from
-/// `PT_PHDR`: that header is the file's claim about this, and this is what the
-/// loader actually puts there.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct ProgramHeaderTable {
- image: ImageRange,
- count: u16,
-}
-
-impl ProgramHeaderTable {
- /// The table's bytes, inside the file-backed part of one `PT_LOAD`.
- pub const fn image(&self) -> ImageRange {
- self.image
- }
-
- /// `e_phnum`, at least one.
- pub const fn count(&self) -> u16 {
- self.count
- }
-}
-
/// Where the section header table is, when the file has a usable one.
///
/// Section headers are optional metadata — symbol names for backtraces, and
@@ -294,8 +271,7 @@ impl SectionTableRef {
/// - the extent runs from the smallest `p_vaddr` to the largest
/// `p_vaddr + p_memsz` over those segments, so it covers every one of them;
/// - the entry point, the file-backed extent of `PT_TLS`, and all of
-/// `PT_DYNAMIC` and `PT_GNU_EH_FRAME`, lie inside the extent, and the
-/// program header table, when one is held, inside one `PT_LOAD`'s file bytes;
+/// `PT_DYNAMIC` and `PT_GNU_EH_FRAME`, lie inside the extent;
/// - the TLS alignment is zero or a power of two no larger than
/// [`MAX_TLS_ALIGN`], so that `!(align - 1)` is a mask and the TLS block's
/// size cannot be dominated by a number the file chose.
@@ -313,7 +289,6 @@ pub struct Layout {
dynamic: Option<DynamicSegment>,
section_headers: Option<SectionTableRef>,
eh_frame_hdr: Option<ImageRange>,
- program_headers: Option<ProgramHeaderTable>,
}
impl Layout {
@@ -435,7 +410,6 @@ impl Layout {
None => None,
Some(e) => Some(extent.range(e.vaddr, e.memsz).ok_or(Error::EhFrameOutsideImage)?),
};
- let program_headers = program_header_table(&ehdr, segments.get(..placed).unwrap_or(&[]));
Ok(Layout {
extent,
@@ -446,7 +420,6 @@ impl Layout {
dynamic,
section_headers: section_table(&ehdr),
eh_frame_hdr,
- program_headers,
})
}
@@ -487,13 +460,6 @@ impl Layout {
self.eh_frame_hdr
}
- /// Where the loaded image holds the program header table, or `None` when
- /// no `PT_LOAD`'s file bytes hold all of it and the table exists only in
- /// the file.
- pub const fn program_headers(&self) -> Option<ProgramHeaderTable> {
- self.program_headers
- }
-
/// The writable window `[lo, hi)` in image offsets, or `None` when no
/// segment is writable.
///
@@ -594,24 +560,6 @@ impl Layout {
}
}
-/// The program header table as the first `PT_LOAD` whose file bytes hold all
-/// of it places it in the image.
-///
-/// Only `p_filesz` counts: past it a segment is zeroes, not the file.
-fn program_header_table(ehdr: &FileHeader, segments: &[Segment]) -> Option<ProgramHeaderTable> {
- let len = u64::from(ehdr.phnum).checked_mul(PROGRAM_HEADER_SIZE as u64)?;
- segments.iter().find_map(|seg| {
- let within = ehdr.phoff.checked_sub(seg.file_offset)?;
- if within.checked_add(len)? > seg.filesz {
- return None;
- }
- // Inside the segment's own range, which the extent holds: `within +
- // len <= filesz <= memsz`.
- let start = seg.image.start.checked_add(within)?;
- Some(ProgramHeaderTable { image: ImageRange { start, len }, count: ehdr.phnum })
- })
-}
-
/// A section header table the loader can index, or `None`.
///
/// `e_shentsize` must be exactly 64: consumers divide a byte count by it and
diff --git a/toyos-elf/src/lib.rs b/toyos-elf/src/lib.rs
index 1d9685d7..46ee094f 100644
--- a/toyos-elf/src/lib.rs
+++ b/toyos-elf/src/lib.rs
@@ -47,7 +47,7 @@ pub use gnu_hash::GnuHash;
pub use header::{FileHeader, Machine};
pub use layout::{
DynamicSegment, Extent, ImageOffset, ImageRange, Layout, Segment, SegmentFlags,
- ProgramHeaderTable, SectionTableRef, StackedImage, TlsSegment,
+ SectionTableRef, StackedImage, TlsSegment,
};
pub use rela::{Op, Rela, RelaCounts, RelaTable, Reloc, RelocError, RelocKind, Rules, TlsRef};
pub use section::{SectionHeader, SectionTable};
diff --git a/toyos-elf/tests/crafted.rs b/toyos-elf/tests/crafted.rs
index 4d44324f..baff9336 100644
--- a/toyos-elf/tests/crafted.rs
+++ b/toyos-elf/tests/crafted.rs
@@ -347,56 +347,6 @@ fn the_file_bytes_behind_a_vaddr_are_the_containing_segments() {
assert_eq!(layout.file_bytes_from(0x2400), Some(0x400));
}
-/// `(image offset, bytes, count)` of the table a layout places, for comparing.
-fn table_of(layout: &Layout) -> Option<(u64, u64, u16)> {
- layout.program_headers().map(|t| (t.image().start().get(), t.image().len(), t.count()))
-}
-
-/// The table is where the segment holding its file bytes puts them, measured
-/// from the image's lowest address, whichever segment that is.
-#[test]
-fn the_program_header_table_is_where_its_segment_places_it() {
- let two = 2 * PROGRAM_HEADER_SIZE as u64;
- let at_zero = accepted(Elf::new(0x1000).ph(Phdr::load(0, 0, 0x1000, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8)).build());
- assert_eq!(table_of(&at_zero), Some((PH_OFF as u64, two, 2)));
-
- // The first segment does not hold the table and the second does, at a
- // vaddr above the first's: the offset is from the extent's minimum.
- let second = accepted(
- Elf::new(0x2000)
- .entry(0x1000)
- .ph(Phdr::load(0x1000, 0x1000, 0x1000, 0x1000, PF_R | PF_X))
- .ph(Phdr::load(0, 0x10_0000, 0x1000, 0x1000, PF_R))
- .build(),
- );
- assert_eq!(table_of(&second), Some((0x10_0000 + PH_OFF as u64 - 0x1000, two, 2)));
-}
-
-/// Held only where the file's bytes are: a segment whose file image stops
-/// inside the table, or one that holds it only as zero-filled memory, holds
-/// no table.
-#[test]
-fn a_table_no_segment_holds_whole_is_absent() {
- let one = PROGRAM_HEADER_SIZE as u64;
- let short = PH_OFF as u64 + one;
- // Two headers; the file image ends one header in.
- let cut = Elf::new(0x1000).ph(Phdr::load(0, 0, short, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8));
- assert_eq!(table_of(&accepted(cut.build())), None);
- // Exactly the table's end and it is held.
- let whole = Elf::new(0x1000).ph(Phdr::load(0, 0, short + one, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8));
- assert_eq!(table_of(&accepted(whole.build())), Some((PH_OFF as u64, 2 * one, 2)));
- // Memory but no file bytes.
- let zeroes = Elf::new(0x1000).ph(Phdr::load(0, 0, 0, 0x1000, PF_R));
- assert_eq!(table_of(&accepted(zeroes.build())), None);
- // A segment that starts past the table's first byte.
- let after = Elf::new(0x2000).entry(0x1000).ph(Phdr::load(PH_OFF as u64 + 8, 0x1000, 0x1000, 0x1000, PF_R));
- assert_eq!(table_of(&accepted(after.build())), None);
- // And one at vaddr 0, so a loader's vaddr-0 checks pass and only the table
- // refuses it: `abuse_elf_loader`'s `phdrs_unmapped`.
- let unmapped = accepted(Elf::new(0x2000).ph(Phdr::load(0x1000, 0, 0x1000, 0x1000, PF_R | PF_X)).build());
- assert_eq!((unmapped.extent().min(), table_of(&unmapped)), (0, None));
-}
-
#[test]
fn the_writable_window_spans_every_writable_segment() {
let layout = accepted(
diff --git a/toyos-elf/tests/fuzz.rs b/toyos-elf/tests/fuzz.rs
index 2314fb05..871ba397 100644
--- a/toyos-elf/tests/fuzz.rs
+++ b/toyos-elf/tests/fuzz.rs
@@ -309,9 +309,6 @@ fn load(case: &Case, placement: Placement, reached: &mut Reached) -> Result<(),
assert!(offset <= span, "offset {offset:#x} past the span {span:#x}");
image_start.checked_add(offset).expect("an offset inside the span leaves the placement")
};
- if let Some(table) = layout.program_headers() {
- place(table.image().end().get());
- }
let dyn_bytes = at(&case.bytes, layout.dynamic().ok_or(())?.image());
let dynamic = Dynamic::parse(dyn_bytes);
diff --git a/toyos-elf/tests/real.rs b/toyos-elf/tests/real.rs
index 3fe96367..6da7bdaa 100644
--- a/toyos-elf/tests/real.rs
+++ b/toyos-elf/tests/real.rs
@@ -37,11 +37,6 @@ fn a_toyos_ld_binary_parses_to_what_readelf_says() {
assert_eq!(layout.tls().unwrap().memsz(), 0x90);
assert_eq!(layout.tls().unwrap().align(), 0x40);
- // `e_phoff` 0x40, six headers of 56 bytes, inside the text segment's file
- // bytes at offset 0.
- let table = layout.program_headers().map(|t| (t.image().start().get(), t.image().len(), t.count()));
- assert_eq!(table, Some((0x40, 6 * 56, 6)));
-
let sections = layout.section_headers().expect("a section header table");
assert_eq!((sections.count, sections.entry_size), (9, 64));
diff --git a/userland/libc/include/elf.h b/userland/libc/include/elf.h
deleted file mode 100644
index e6ff35bb..00000000
--- a/userland/libc/include/elf.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef _ELF_H
-#define _ELF_H
-
-#include <stdint.h>
-
-typedef uint64_t Elf64_Addr;
-typedef uint64_t Elf64_Off;
-typedef uint16_t Elf64_Half;
-typedef uint32_t Elf64_Word;
-typedef int32_t Elf64_Sword;
-typedef uint64_t Elf64_Xword;
-typedef int64_t Elf64_Sxword;
-
-typedef struct {
- Elf64_Word p_type;
- Elf64_Word p_flags;
- Elf64_Off p_offset;
- Elf64_Addr p_vaddr;
- Elf64_Addr p_paddr;
- Elf64_Xword p_filesz;
- Elf64_Xword p_memsz;
- Elf64_Xword p_align;
-} Elf64_Phdr;
-
-#define PT_NULL 0
-#define PT_LOAD 1
-#define PT_DYNAMIC 2
-#define PT_INTERP 3
-#define PT_NOTE 4
-#define PT_SHLIB 5
-#define PT_PHDR 6
-#define PT_TLS 7
-#define PT_GNU_EH_FRAME 0x6474e550
-#define PT_GNU_STACK 0x6474e551
-#define PT_GNU_RELRO 0x6474e552
-
-#define PF_X 0x1
-#define PF_W 0x2
-#define PF_R 0x4
-
-#endif
diff --git a/userland/libc/include/link.h b/userland/libc/include/link.h
deleted file mode 100644
index 2eef0a32..00000000
--- a/userland/libc/include/link.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef _LINK_H
-#define _LINK_H
-
-#include <elf.h>
-#include <stddef.h>
-
-#define ElfW(type) Elf64_##type
-
-/* The first four fields of glibc's layout. There is no dlpi_adds or
- dlpi_subs, so the size a callback is passed ends at dlpi_phnum.
- dlpi_name is "" for the executable, and lives only until the callback
- returns. */
-struct dl_phdr_info {
- ElfW(Addr) dlpi_addr;
- const char *dlpi_name;
- const ElfW(Phdr) *dlpi_phdr;
- ElfW(Half) dlpi_phnum;
-};
-
-int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *info, size_t size, void *data), void *data);
-
-#endif
diff --git a/userland/libc/src/lib.rs b/userland/libc/src/lib.rs
index 1052461c..9acf206a 100644
--- a/userland/libc/src/lib.rs
+++ b/userland/libc/src/lib.rs
@@ -6,7 +6,6 @@ extern crate alloc;
mod arch;
mod ctype;
mod errno;
-mod link;
mod math;
mod memory;
mod misc;
diff --git a/userland/libc/src/link.rs b/userland/libc/src/link.rs
deleted file mode 100644
index 7faffb24..00000000
--- a/userland/libc/src/link.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-//! `dl_iterate_phdr`, from the kernel's `SYS_QUERY_MODULES` answer.
-//!
-//! The contract is glibc's `dl_iterate_phdr(3)`, which the LSB adopts: the
-//! executable first, named by the empty string, then every library in load
-//! order; the walk stops at the first callback that returns non-zero and
-//! returns that value, else 0. The walk is over one answer, so a module a
-//! callback or another thread loads meanwhile is not visited.
-
-use alloc::vec::Vec;
-
-use toyos_abi::syscall;
-
-/// `struct dl_phdr_info`, as `link.h` declares it.
-#[repr(C)]
-pub struct DlPhdrInfo {
- dlpi_addr: u64,
- dlpi_name: *const u8,
- dlpi_phdr: *const u8,
- dlpi_phnum: u16,
-}
-
-type Callback = unsafe extern "C" fn(*mut DlPhdrInfo, usize, *mut u8) -> i32;
-
-#[no_mangle]
-pub unsafe extern "C" fn dl_iterate_phdr(callback: Callback, data: *mut u8) -> i32 {
- let answer = answer();
- let mut name = Vec::new();
- for (i, (module, path)) in syscall::modules(&answer).enumerate() {
- name.clear();
- if i > 0 {
- name.extend_from_slice(path);
- }
- name.push(0);
- let mut info = DlPhdrInfo {
- dlpi_addr: module.base,
- dlpi_name: name.as_ptr(),
- dlpi_phdr: module.phdr as *const u8,
- dlpi_phnum: u16::try_from(module.phnum).expect("SYS_QUERY_MODULES: a phnum past e_phnum's u16"),
- };
- // SAFETY: the caller's function, handed a record valid for the call.
- let stop = unsafe { callback(&mut info, core::mem::size_of::<DlPhdrInfo>(), data) };
- if stop != 0 {
- return stop;
- }
- }
- 0
-}
-
-/// One whole `SYS_QUERY_MODULES` answer: asked again while a `dlopen`
-/// elsewhere grows it between the size and the read.
-fn answer() -> Vec<u8> {
- let mut buf = Vec::new();
- loop {
- let need = syscall::query_modules(&mut buf).expect("SYS_QUERY_MODULES refused a buffer this process owns");
- if need <= buf.len() {
- buf.truncate(need);
- return buf;
- }
- buf.resize(need, 0);
- }
-}
diff --git a/userland/libc/src/misc.rs b/userland/libc/src/misc.rs
index 4443d6cd..fee86cf4 100644
--- a/userland/libc/src/misc.rs
+++ b/userland/libc/src/misc.rs
@@ -432,20 +432,13 @@ pub unsafe extern "C" fn longjmp(_env: *mut u8, _val: i32) -> ! {
}
// dlopen/dlsym/dlclose
-//
-// A C handle is the kernel's module index plus one: index 0 is a module, and
-// NULL is dlopen's failure.
-
-fn module_index(handle: *mut u8) -> Option<u64> {
- (handle as u64).checked_sub(1)
-}
#[no_mangle]
pub unsafe extern "C" fn dlopen(path: *const u8, _flags: i32) -> *mut u8 {
if path.is_null() { return ptr::null_mut(); }
let path_bytes = super::posix_io::c_str_to_bytes(path);
match syscall::dl_open(path_bytes) {
- Ok(index) => (index + 1) as *mut u8,
+ Ok(handle) => handle as *mut u8,
Err(_) => ptr::null_mut(),
}
}
@@ -453,10 +446,9 @@ pub unsafe extern "C" fn dlopen(path: *const u8, _flags: i32) -> *mut u8 {
#[no_mangle]
pub unsafe extern "C" fn dlsym(handle: *mut u8, symbol: *const u8) -> *mut u8 {
if symbol.is_null() { return ptr::null_mut(); }
- let index = module_index(handle).expect("dlsym: RTLD_DEFAULT not implemented");
let name = super::posix_io::c_str_to_bytes(symbol);
// SAFETY: handle is from a prior dlopen, name is a valid C string
- match unsafe { syscall::dl_sym(index, name) } {
+ match unsafe { syscall::dl_sym(handle as u64, name) } {
Ok(addr) => addr as *mut u8,
Err(_) => ptr::null_mut(),
}
@@ -464,10 +456,7 @@ pub unsafe extern "C" fn dlsym(handle: *mut u8, symbol: *const u8) -> *mut u8 {
#[no_mangle]
pub unsafe extern "C" fn dlclose(handle: *mut u8) -> i32 {
- match module_index(handle) {
- Some(index) => syscall::dl_close(index) as i32,
- None => -1,
- }
+ syscall::dl_close(handle as u64) as i32
}
#[no_mangle] |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs
|
Review of #599 at f7fae8c, round 4 (high-risk: ABI, kernel ELF loader). Evidence: CI Net: +662 −43. Production +253 −14 (kernel +28 −2, toyos-abi +30 −5, toyos-elf +56 −4, libc +139 −3). Tests +337 −8. Issues +72 −21. Earlier findings
BLOCKER
NOTE
REMOVE
SEND BACK 🤖 Generated with Claude Code |
204 now reads the ELF header at each reported module's dlpi_addr and checks dlpi_phnum against e_phnum and dlpi_phdr against dlpi_addr + e_phoff, as 205 does for the executable through __ehdr_start. Before this, a kernel that reported a library's phnum as 3, or its table 56 bytes late, passed every check 204 made: the first three headers of libtls_lib.so are PT_PHDR and two PT_LOADs, and the shifted table still starts with a PT_LOAD. Both modules 204 walks are linked at vaddr 0 with their first PT_LOAD at file offset 0 and e_phoff 0x40 (llvm-readobj -h -l on the host-linked 204 and on libtls_lib.so), so the header is at dlpi_addr. toyos-elf's program_header_table computes the table's length and its image start with plain arithmetic: a u16 count of 56-byte entries cannot overflow, and the start is inside the segment's own range. A broken invariant now panics under the kernel's and bootloader's overflow checks instead of reading as a table no segment holds. Filed issues/build/libc-dl-iterate-phdr-allocates-on-every-walk.md: every walk allocates twice, so libunwind looking up a frame with the heap exhausted dies in libc's allocator. The answer grows with every library and path, so a fixed buffer does not hold it without an ABI change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs
|
Round-4 patches at a7305ee. Each was checked the same way:
The old c1 and c2 no longer apply at a7305ee, because 204 and c1r4 is c3 is c3b is c3 plus this hunk (sha256 diff --git a/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs b/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
index e471c1e7..a7aea2af 100644
--- a/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
+++ b/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
@@ -557,7 +557,6 @@ fn main() {
// `rebase_base` and `load_shared_lib`'s vaddr-0 check pass it and only
// the table refuses it, in `spawn` and in `load_shared_lib` alike.
let unmapped = Elf::new(0x2000).ph(Phdr::load(0x1000, 0, 0x1000, 0x1000, PF_R | PF_X)).entry(0).build();
- spawn_refused("phdrs_unmapped", &unmapped);
dlopen_refused("phdrs_unmapped.so", &unmapped);
// The kernel heap is intact: allocate and touch enough to walk it, thenc2r4 is c3 plus this hunk (sha256 diff --git a/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs b/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
index e471c1e7..36b42ab8 100644
--- a/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
+++ b/tests/toyos-rust-tests/src/bin/abuse_elf_loader.rs
@@ -552,14 +552,6 @@ fn main() {
// 20. The apply-time TLS refusals, each named by its reason in the log.
tls_apply_time_refusals_are_reached();
- // 21. A program header table no `PT_LOAD` maps: the one segment's file
- // bytes start at 0x1000, past the table at 0x40. Its vaddr is 0, so
- // `rebase_base` and `load_shared_lib`'s vaddr-0 check pass it and only
- // the table refuses it, in `spawn` and in `load_shared_lib` alike.
- let unmapped = Elf::new(0x2000).ph(Phdr::load(0x1000, 0, 0x1000, 0x1000, PF_R | PF_X)).entry(0).build();
- spawn_refused("phdrs_unmapped", &unmapped);
- dlopen_refused("phdrs_unmapped.so", &unmapped);
-
// The kernel heap is intact: allocate and touch enough to walk it, then
// prove the real loader still works.
let mut blocks: Vec<Vec<u8>> = Vec::new();mphnum3: the kernel reports every library's diff --git a/kernel/src/syscall/vm.rs b/kernel/src/syscall/vm.rs
index fb2b798f..726643ae 100644
--- a/kernel/src/syscall/vm.rs
+++ b/kernel/src/syscall/vm.rs
@@ -511,7 +511,7 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
eh_frame_hdr: lib.eh_frame_hdr.map_or(0, |r| (lib.user_base + r.start().get()).raw()),
eh_frame_hdr_size: lib.eh_frame_hdr.map_or(0, |r| r.len()),
phdr: (lib.user_base + lib.phdrs.image().start().get()).raw(),
- phnum: lib.phdrs.count().into(),
+ phnum: 3,
path_offset,
path_len: lib_path_bytes.len() as u32,
};mphdr56: the kernel reports every library's table one entry late. Expected: diff --git a/kernel/src/syscall/vm.rs b/kernel/src/syscall/vm.rs
index fb2b798f..6ef86e1c 100644
--- a/kernel/src/syscall/vm.rs
+++ b/kernel/src/syscall/vm.rs
@@ -510,7 +510,7 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
text_end: lib.user_end(),
eh_frame_hdr: lib.eh_frame_hdr.map_or(0, |r| (lib.user_base + r.start().get()).raw()),
eh_frame_hdr_size: lib.eh_frame_hdr.map_or(0, |r| r.len()),
- phdr: (lib.user_base + lib.phdrs.image().start().get()).raw(),
+ phdr: (lib.user_base + lib.phdrs.image().start().get() + 56).raw(),
phnum: lib.phdrs.count().into(),
path_offset,
path_len: lib_path_bytes.len() as u32,c3 in full: diff --git a/kernel/src/elf/cache.rs b/kernel/src/elf/cache.rs
index 96322295..7eb03a5d 100644
--- a/kernel/src/elf/cache.rs
+++ b/kernel/src/elf/cache.rs
@@ -22,7 +22,7 @@ use crate::vfs::BackingId;
use crate::UserAddr;
use toyos_elf::dynamic::InitArray;
use toyos_elf::rela::Rules;
-use toyos_elf::{ImageRange, Op, ProgramHeaderTable, RelaCounts, RelocKind, SymIndex, TlsRef};
+use toyos_elf::{ImageRange, Op, RelaCounts, RelocKind, SymIndex, TlsRef};
/// A module's non-`RELATIVE` relocations, parsed and extracted once at cache
/// time, each as `(r_offset, what it names)`.
@@ -82,7 +82,6 @@ struct Snapshot {
gnu_hash: Option<KernelSlice>,
rules: Rules,
eh_frame_hdr: Option<ImageRange>,
- phdrs: ProgramHeaderTable,
init_array: Option<InitArray>,
span: u64,
rw_lo: u64,
@@ -101,7 +100,6 @@ impl Snapshot {
gnu_hash: lib.gnu_hash,
rules: lib.rules,
eh_frame_hdr: lib.eh_frame_hdr,
- phdrs: lib.phdrs,
init_array: lib.init_array,
span: lib.span,
rw_lo: lib.rw_lo,
@@ -129,7 +127,6 @@ impl Snapshot {
cached_relocs,
rules: self.rules,
eh_frame_hdr: self.eh_frame_hdr,
- phdrs: self.phdrs,
init_array: self.init_array,
span: self.span,
rw_lo: self.rw_lo,
diff --git a/kernel/src/elf/mod.rs b/kernel/src/elf/mod.rs
index 41618d3d..9df1d73a 100644
--- a/kernel/src/elf/mod.rs
+++ b/kernel/src/elf/mod.rs
@@ -27,10 +27,7 @@ use crate::UserAddr;
use toyos_elf::dynamic::{Dynamic, InitArray};
use toyos_elf::section::{SectionTable, SHT_DYNSYM};
use toyos_elf::sym::{Sym, SymTab};
-use toyos_elf::{
- rela, Extent, GnuHash, ImageRange, Layout, ProgramHeaderTable, Rela, RelaTable, Reloc, RelocError, SymIndex,
- TlsSegment,
-};
+use toyos_elf::{rela, Extent, GnuHash, ImageRange, Layout, Rela, RelaTable, Reloc, RelocError, SymIndex, TlsSegment};
/// `toyos_elf::MAX_TLS_ALIGN` must equal the kernel's largest page.
const _: () = assert!(toyos_elf::MAX_TLS_ALIGN == PAGE_2M);
@@ -104,8 +101,6 @@ pub struct LoadedLib {
rules: rela::Rules,
/// `PT_GNU_EH_FRAME`, for DWARF unwinding.
pub eh_frame_hdr: Option<ImageRange>,
- /// The program header table, which `load_shared_lib` refuses a module without.
- pub phdrs: ProgramHeaderTable,
/// `DT_INIT_ARRAY`.
pub init_array: Option<InitArray>,
/// Bytes between the image's lowest and highest virtual address.
@@ -360,8 +355,6 @@ pub fn load_shared_lib(
if extent.min() != 0 {
return Err("ELF: a shared object must begin at vaddr 0");
}
- // `SYS_QUERY_MODULES` answers with the mapped table.
- let phdrs = layout.program_headers().ok_or("ELF: no PT_LOAD maps the program header table")?;
// No writable segment yields an empty window; no relocation can target it.
let (rw_lo, rw_hi) = layout.writable_window().unwrap_or((layout.span(), layout.span()));
@@ -520,7 +513,6 @@ pub fn load_shared_lib(
cached_relocs: None,
rules,
eh_frame_hdr: layout.eh_frame_hdr(),
- phdrs,
init_array,
span: layout.span(),
rw_lo,
diff --git a/kernel/src/loader/mod.rs b/kernel/src/loader/mod.rs
index fffdbc7f..ddd821de 100644
--- a/kernel/src/loader/mod.rs
+++ b/kernel/src/loader/mod.rs
@@ -396,12 +396,6 @@ pub fn spawn(
// Where the image's first byte lands: every `ImageOffset` the file's
// numbers were parsed into is added to it, and its span fits above it.
let image_start = UserAddr::new(USER_VM_BASE);
- // `SYS_QUERY_MODULES` answers with the mapped table, so an image without
- // one has no true answer to give.
- let Some(phdrs) = layout.program_headers() else {
- log!("spawn: {}: no PT_LOAD maps the program header table", path);
- return Err(SyscallError::InvalidArgument.into());
- };
let exe = read_exe_tables(backing.as_ref(), &layout, path)?;
let t1 = crate::clock::nanos_since_boot();
@@ -603,7 +597,6 @@ pub fn spawn(
.eh_frame_hdr()
.map_or((0, 0), |r| ((image_start + r.start().get()).raw(), r.len())),
exe_vaddr_max: image_end,
- exe_phdrs: ((image_start + phdrs.image().start().get()).raw(), phdrs.count()),
lib_paths,
},
mmap_regions: Vec::new(),
diff --git a/kernel/src/process.rs b/kernel/src/process.rs
index b747865c..f8680f7b 100644
--- a/kernel/src/process.rs
+++ b/kernel/src/process.rs
@@ -492,8 +492,6 @@ pub struct ElfInfo {
pub exe_eh_frame_hdr: (u64, u64),
/// One past the executable's last byte.
pub exe_vaddr_max: u64,
- /// The executable's program header table as (address, count), `(0, 0)` with no executable.
- pub exe_phdrs: (u64, u16),
/// Paths of dlopen'd libraries (parallel to loaded_libs).
pub lib_paths: Vec<String>,
}
@@ -512,7 +510,6 @@ impl ElfInfo {
elf_base: UserAddr::new(0),
exe_eh_frame_hdr: (0, 0),
exe_vaddr_max: 0,
- exe_phdrs: (0, 0),
lib_paths: Vec::new(),
}
}
diff --git a/kernel/src/syscall/vm.rs b/kernel/src/syscall/vm.rs
index fb2b798f..fb52c534 100644
--- a/kernel/src/syscall/vm.rs
+++ b/kernel/src/syscall/vm.rs
@@ -484,14 +484,11 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
let mut path_offset = (module_count * info_size) as u32;
let (eh_addr, eh_size) = data.elf.exe_eh_frame_hdr;
- let (phdr, phnum) = data.elf.exe_phdrs;
let exe_info = ModuleInfo {
base: data.elf.elf_base.raw(),
text_end: data.elf.exe_vaddr_max,
eh_frame_hdr: eh_addr,
eh_frame_hdr_size: eh_size,
- phdr,
- phnum: phnum.into(),
path_offset,
path_len: exe_path_bytes.len() as u32,
};
@@ -510,8 +507,6 @@ pub(super) fn sys_query_modules(out: &mut UserBytesMut) -> u64 {
text_end: lib.user_end(),
eh_frame_hdr: lib.eh_frame_hdr.map_or(0, |r| (lib.user_base + r.start().get()).raw()),
eh_frame_hdr_size: lib.eh_frame_hdr.map_or(0, |r| r.len()),
- phdr: (lib.user_base + lib.phdrs.image().start().get()).raw(),
- phnum: lib.phdrs.count().into(),
path_offset,
path_len: lib_path_bytes.len() as u32,
};
diff --git a/tests/testcases/LICENSE b/tests/testcases/LICENSE
index 2e02f9cc..8eb68294 100644
--- a/tests/testcases/LICENSE
+++ b/tests/testcases/LICENSE
@@ -22,9 +22,9 @@ against tinycc upstream at 64552b3faa39ee7948a9ea21bfcc11045b90c70d
(repo.or.cz/tinycc.git, 2026-08-05), allowing for the `-` → `_` renames this
project made to some filenames.
- tinycc/ 320 files: 239 byte-identical to tinycc's `tests/tests2`,
+ tinycc/ 316 files: 239 byte-identical to tinycc's `tests/tests2`,
17 tinycc files this project modified,
- 64 not upstream at all — 63 cases written here, plus
+ 60 not upstream at all — 59 cases written here, plus
`fred.txt`, which is output `40_stdio.c` writes when it
runs and which was committed by accident.
diff --git a/tests/testcases/tinycc/204_dl_iterate_phdr.c b/tests/testcases/tinycc/204_dl_iterate_phdr.c
deleted file mode 100644
index da198908..00000000
--- a/tests/testcases/tinycc/204_dl_iterate_phdr.c
+++ /dev/null
@@ -1,160 +0,0 @@
-/* dl_iterate_phdr visits the executable and every loaded library with the
- program headers the loader mapped, and stops where its callback says.
- Each module's addresses are checked against where its own header, code and
- _DYNAMIC really are, not against the kernel's other answers. */
-
-#include <dlfcn.h>
-#include <link.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-/* The image's test library; naming it is also what stages it beside this
- case. */
-#define LIB "/system/lib/libtls_lib.so"
-
-extern const char _DYNAMIC[] __attribute__((visibility("hidden")));
-int main(void);
-
-static int failed;
-
-static void fail(const char *what, const char *name) {
- printf("FAIL %s: \"%s\"\n", what, name);
- failed = 1;
-}
-
-/* The PT_LOAD holding [addr, addr + len) at dlpi_addr + p_vaddr, or NULL. */
-static const ElfW(Phdr) *load_holding(const struct dl_phdr_info *info, uintptr_t addr, size_t len) {
- for (int i = 0; i < info->dlpi_phnum; i++) {
- const ElfW(Phdr) *ph = &info->dlpi_phdr[i];
- uintptr_t lo = info->dlpi_addr + ph->p_vaddr;
- if (ph->p_type == PT_LOAD && addr >= lo && addr + len <= lo + ph->p_memsz)
- return ph;
- }
- return NULL;
-}
-
-/* The table and its count are the ones the module's own ELF header names:
- every module here is linked at vaddr 0 with its first PT_LOAD at file
- offset 0, so that header is at dlpi_addr. */
-static void header_names_the_table(const struct dl_phdr_info *info, const char *name) {
- const unsigned char *ehdr = (const unsigned char *)info->dlpi_addr;
- if (memcmp(ehdr, "\177ELF", 4) != 0) {
- fail("no ELF header at dlpi_addr", name);
- return;
- }
- /* e_phoff and e_phnum, at their System V gABI offsets in Elf64_Ehdr. */
- uint64_t phoff;
- uint16_t phnum;
- memcpy(&phoff, ehdr + 32, sizeof phoff);
- memcpy(&phnum, ehdr + 56, sizeof phnum);
- if (info->dlpi_phnum != phnum)
- fail("dlpi_phnum is not e_phnum", name);
- if ((uintptr_t)info->dlpi_phdr != info->dlpi_addr + phoff)
- fail("dlpi_phdr is not dlpi_addr + e_phoff", name);
-}
-
-/* Whether `code` lies in an executable PT_LOAD of this module. */
-static int runs(const struct dl_phdr_info *info, const void *code) {
- const ElfW(Phdr) *ph = load_holding(info, (uintptr_t)code, 1);
- return ph && (ph->p_flags & PF_X);
-}
-
-struct walk {
- int visited;
- int stop_at;
- int stop_with;
- void *lib_code;
-};
-
-static int visit(struct dl_phdr_info *info, size_t size, void *data) {
- struct walk *walk = data;
- walk->visited++;
- if (walk->stop_at)
- return walk->visited == walk->stop_at ? walk->stop_with : 0;
-
- const char *name = info->dlpi_name;
- if (size != sizeof(struct dl_phdr_info))
- fail("the size passed is not the record's", name);
- if (info->dlpi_phnum == 0 || info->dlpi_phdr == NULL) {
- fail("no program headers", name);
- return 0;
- }
- header_names_the_table(info, name);
- uintptr_t table = (uintptr_t)info->dlpi_phdr;
- if (!load_holding(info, table, info->dlpi_phnum * sizeof(ElfW(Phdr))))
- fail("the program headers lie in no PT_LOAD", name);
- for (int i = 0; i < info->dlpi_phnum; i++) {
- const ElfW(Phdr) *ph = &info->dlpi_phdr[i];
- if (ph->p_type == PT_PHDR && info->dlpi_addr + ph->p_vaddr != table)
- fail("PT_PHDR names another address", name);
- }
-
- if (walk->visited == 1) {
- if (strcmp(name, "") != 0)
- fail("the executable is not first, or is not named by the empty string", name);
- if (!runs(info, (const void *)main))
- fail("main lies in no executable PT_LOAD", name);
- int dynamic = 0;
- for (int i = 0; i < info->dlpi_phnum; i++) {
- const ElfW(Phdr) *ph = &info->dlpi_phdr[i];
- if (ph->p_type != PT_DYNAMIC)
- continue;
- dynamic = 1;
- if (info->dlpi_addr + ph->p_vaddr != (uintptr_t)_DYNAMIC)
- fail("PT_DYNAMIC is not at _DYNAMIC", name);
- if (!load_holding(info, info->dlpi_addr + ph->p_vaddr, ph->p_memsz))
- fail("PT_DYNAMIC lies in no PT_LOAD", name);
- }
- if (!dynamic)
- fail("no PT_DYNAMIC", name);
- printf("executable: \"%s\"\n", name);
- } else {
- if (strcmp(name, LIB) != 0)
- fail("a library other than the one loaded", name);
- if (!runs(info, walk->lib_code))
- fail("tls_get_label lies in no executable PT_LOAD", name);
- printf("library: %s\n", name);
- }
- return 0;
-}
-
-/* Walk every module, checking each; the count visited. */
-static int walk_all(void *lib_code) {
- struct walk walk = { 0, 0, 0, lib_code };
- int ret = dl_iterate_phdr(visit, &walk);
- if (ret != 0) {
- printf("FAIL a walk no callback stopped returned %d\n", ret);
- failed = 1;
- }
- return walk.visited;
-}
-
-/* A walk the callback stops at module `at` with `with`. */
-static void stop(int at, int with, int of) {
- struct walk walk = { 0, at, with, NULL };
- int ret = dl_iterate_phdr(visit, &walk);
- printf("stopped at %d of %d: visited %d, returned %d\n", at, of, walk.visited, ret);
- if (walk.visited != at || ret != with)
- failed = 1;
-}
-
-int main(void) {
- printf("modules: %d\n", walk_all(NULL));
- stop(1, 1, 1);
-
- void *lib = dlopen(LIB, RTLD_NOW);
- if (!lib) {
- printf("FAIL dlopen %s\n", LIB);
- return 1;
- }
- void *code = dlsym(lib, "tls_get_label");
- if (!code) {
- printf("FAIL dlsym tls_get_label\n");
- return 1;
- }
- printf("modules: %d\n", walk_all(code));
- stop(1, 7, 2);
- stop(2, -1, 2);
- return failed;
-}
diff --git a/tests/testcases/tinycc/204_dl_iterate_phdr.expect b/tests/testcases/tinycc/204_dl_iterate_phdr.expect
deleted file mode 100644
index a07ee158..00000000
--- a/tests/testcases/tinycc/204_dl_iterate_phdr.expect
+++ /dev/null
@@ -1,8 +0,0 @@
-executable: ""
-modules: 1
-stopped at 1 of 1: visited 1, returned 1
-executable: ""
-library: /system/lib/libtls_lib.so
-modules: 2
-stopped at 1 of 2: visited 1, returned 7
-stopped at 2 of 2: visited 2, returned -1
diff --git a/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.c b/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.c
deleted file mode 100644
index 2e5ade2e..00000000
--- a/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.c
+++ /dev/null
@@ -1,53 +0,0 @@
-/* dl_iterate_phdr reports the executable's program headers where they are when
- its lowest address is not 0: tests/common/compile.rs links this case at
- --image-base=0x200000, so the load bias and the image's first byte differ.
- Every fact the answer is checked against is the linker's, reached through
- __ehdr_start and never through the kernel's answer. */
-
-#include <link.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-extern const unsigned char __ehdr_start[] __attribute__((visibility("hidden")));
-
-/* The executable is visited first; keep its record and stop. */
-static int first(struct dl_phdr_info *info, size_t size, void *data) {
- (void)size;
- *(struct dl_phdr_info *)data = *info;
- return 1;
-}
-
-static int failed;
-
-static void check(int holds, const char *what) {
- printf("%s: %s\n", what, holds ? "yes" : "no");
- if (!holds)
- failed = 1;
-}
-
-int main(void) {
- struct dl_phdr_info exe;
- if (dl_iterate_phdr(first, &exe) != 1) {
- printf("FAIL the walk did not stop at the executable\n");
- return 1;
- }
-
- /* e_phoff and e_phnum, at their System V gABI offsets in Elf64_Ehdr. */
- uint64_t phoff;
- uint16_t phnum;
- memcpy(&phoff, __ehdr_start + 32, sizeof phoff);
- memcpy(&phnum, __ehdr_start + 56, sizeof phnum);
- const ElfW(Phdr) *table = (const ElfW(Phdr) *)(__ehdr_start + phoff);
-
- uintptr_t lowest = UINTPTR_MAX;
- for (int i = 0; i < phnum; i++)
- if (table[i].p_type == PT_LOAD && table[i].p_vaddr < lowest)
- lowest = table[i].p_vaddr;
- printf("lowest PT_LOAD: 0x%lx\n", (unsigned long)lowest);
-
- check(exe.dlpi_phnum == phnum, "dlpi_phnum is e_phnum");
- check(exe.dlpi_phdr == table, "dlpi_phdr is __ehdr_start + e_phoff");
- check((uintptr_t)__ehdr_start == exe.dlpi_addr + lowest, "__ehdr_start is dlpi_addr + the lowest p_vaddr");
- return failed;
-}
diff --git a/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.expect b/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.expect
deleted file mode 100644
index 17a85849..00000000
--- a/tests/testcases/tinycc/205_dl_iterate_phdr_image_base.expect
+++ /dev/null
@@ -1,4 +0,0 @@
-lowest PT_LOAD: 0x200000
-dlpi_phnum is e_phnum: yes
-dlpi_phdr is __ehdr_start + e_phoff: yes
-__ehdr_start is dlpi_addr + the lowest p_vaddr: yes
diff --git a/toyos-abi/src/syscall.rs b/toyos-abi/src/syscall.rs
index 4a84a27c..62e20a02 100644
--- a/toyos-abi/src/syscall.rs
+++ b/toyos-abi/src/syscall.rs
@@ -2185,11 +2185,6 @@ pub struct ModuleInfo {
pub eh_frame_hdr: u64,
/// Size of `.eh_frame_hdr` in bytes.
pub eh_frame_hdr_size: u64,
- /// Absolute virtual address of the module's program header table: the
- /// kernel loads no module whose table a `PT_LOAD` does not map.
- pub phdr: u64,
- /// Entries in that table, `e_phnum`: a `u64` so the record has no padding.
- pub phnum: u64,
/// Byte offset of the module's path string within the buffer.
pub path_offset: u32,
/// Length of the path string in bytes.
@@ -2199,9 +2194,11 @@ pub struct ModuleInfo {
/// Every byte belongs to a field: this crosses the boundary through
/// [`ModuleInfo::as_bytes`], so a gap would publish whatever the kernel stack
/// held. **This is the type where that matters most**, because the buffer it
-/// is written into is a user address. A field of any other width added here
-/// reds here rather than publishing kernel stack bytes to userland.
-const _: () = assert!(core::mem::size_of::<ModuleInfo>() == 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4);
+/// is written into is a user address: the two `u32`s sit at the end of four
+/// `u64`s, which is 8 bytes together at an 8-aligned offset, so there is no
+/// tail padding today — and this is what says so. A field of any other width
+/// added here reds here rather than publishing kernel stack bytes to userland.
+const _: () = assert!(core::mem::size_of::<ModuleInfo>() == 8 + 8 + 8 + 8 + 4 + 4);
impl ModuleInfo {
/// The record's own bytes, which is what `SYS_QUERY_MODULES` writes.
@@ -2236,28 +2233,6 @@ pub fn query_modules(buf: &mut [u8]) -> Result<usize, SyscallError> {
check(syscall(SYS_QUERY_MODULES, buf.as_mut_ptr() as u64, buf.len() as u64, 0, 0)).map(|n| n as usize)
}
-/// Every record of one [`query_modules`] answer with its path, executable
-/// first: `answer` is the `n` bytes the call reported.
-///
-/// A record or path outside `answer` is a kernel that broke the layout above,
-/// and panics.
-pub fn modules(answer: &[u8]) -> impl Iterator<Item = (ModuleInfo, &[u8])> {
- let size = core::mem::size_of::<ModuleInfo>();
- let record = move |i: usize| {
- let bytes = &answer[i * size..(i + 1) * size];
- // SAFETY: `bytes` holds `size_of::<ModuleInfo>()` bytes, read without
- // an alignment requirement, and every bit pattern of its integer
- // fields is a `ModuleInfo`.
- unsafe { (bytes.as_ptr() as *const ModuleInfo).read_unaligned() }
- };
- let count = if answer.is_empty() { 0 } else { record(0).path_offset as usize / size };
- (0..count).map(move |i| {
- let info = record(i);
- let path = info.path_offset as usize;
- (info, &answer[path..path + info.path_len as usize])
- })
-}
-
/// Scheduler info for the calling process.
#[repr(C)]
#[derive(Clone, Copy, Debug)]
@@ -2342,70 +2317,17 @@ mod tests {
text_end: 0x2233_4455_6677_8899,
eh_frame_hdr: 0x3344_5566_7788_99aa,
eh_frame_hdr_size: 0x44,
- phdr: 0x5566_7788_99aa_bbcc,
- phnum: 0x77,
path_offset: 0x55,
path_len: 0x66,
};
let b = info.as_bytes();
- assert_eq!(b.len(), 56);
+ assert_eq!(b.len(), 40);
assert_eq!(u64::from_ne_bytes(b[0..8].try_into().unwrap()), info.base);
assert_eq!(u64::from_ne_bytes(b[8..16].try_into().unwrap()), info.text_end);
assert_eq!(u64::from_ne_bytes(b[16..24].try_into().unwrap()), info.eh_frame_hdr);
assert_eq!(u64::from_ne_bytes(b[24..32].try_into().unwrap()), info.eh_frame_hdr_size);
- assert_eq!(u64::from_ne_bytes(b[32..40].try_into().unwrap()), info.phdr);
- assert_eq!(u64::from_ne_bytes(b[40..48].try_into().unwrap()), info.phnum);
- assert_eq!(u32::from_ne_bytes(b[48..52].try_into().unwrap()), info.path_offset);
- assert_eq!(u32::from_ne_bytes(b[52..56].try_into().unwrap()), info.path_len);
- }
-
- fn record(base: u64, path_offset: u32, path_len: u32) -> ModuleInfo {
- ModuleInfo {
- base,
- text_end: base + 0x1000,
- eh_frame_hdr: 0,
- eh_frame_hdr_size: 0,
- phdr: base + 0x40,
- phnum: 3,
- path_offset,
- path_len,
- }
- }
-
- /// An answer laid out as the kernel writes one — records, then the paths
- /// packed in module order — decodes to those records and paths, in order,
- /// at an offset whatever alignment the buffer has.
- #[test]
- fn modules_decodes_every_record_and_its_path() {
- let size = core::mem::size_of::<ModuleInfo>() as u32;
- let exe = record(0x100_0000_0000, 2 * size, 9);
- let lib = record(0x200_0000_0000, 2 * size + 9, 13);
- let mut answer = [0u8; 1 + 2 * 56 + 9 + 13];
- let wire = &mut answer[1..];
- wire[..56].copy_from_slice(exe.as_bytes());
- wire[56..112].copy_from_slice(lib.as_bytes());
- wire[112..121].copy_from_slice(b"/bin/prog");
- wire[121..].copy_from_slice(b"/lib/libx.so\0");
- let got: [(u64, u64, &[u8]); 2] = {
- let mut it = modules(&answer[1..]).map(|(m, path)| (m.base, m.phdr, path));
- let pair = [it.next().unwrap(), it.next().unwrap()];
- assert!(it.next().is_none(), "two records, and no third read out of the paths");
- pair
- };
- assert_eq!(got[0], (exe.base, exe.phdr, &b"/bin/prog"[..]));
- assert_eq!(got[1], (lib.base, lib.phdr, &b"/lib/libx.so\0"[..]));
- assert_eq!(modules(&[]).count(), 0);
- }
-
- /// A path the kernel says runs past its own answer is a broken layout,
- /// not a shorter name.
- #[test]
- #[should_panic]
- fn modules_refuses_a_path_past_the_answer() {
- let size = core::mem::size_of::<ModuleInfo>() as u32;
- let mut answer = [0u8; 56 + 4];
- answer[..56].copy_from_slice(record(0, size, 5).as_bytes());
- modules(&answer).for_each(drop);
+ assert_eq!(u32::from_ne_bytes(b[32..36].try_into().unwrap()), info.path_offset);
+ assert_eq!(u32::from_ne_bytes(b[36..40].try_into().unwrap()), info.path_len);
}
/// Four lowercase hex digits each and nothing else, because two spellings
diff --git a/toyos-elf/src/layout.rs b/toyos-elf/src/layout.rs
index 18068cb0..c51bab16 100644
--- a/toyos-elf/src/layout.rs
+++ b/toyos-elf/src/layout.rs
@@ -10,8 +10,8 @@
//! address it placed the image at. A raw `p_vaddr` never leaves this module.
use crate::header::{
- FileHeader, Machine, ProgramHeader, PROGRAM_HEADER_SIZE, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_LOAD,
- PT_TLS, SECTION_HEADER_SIZE,
+ FileHeader, Machine, ProgramHeader, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_LOAD, PT_TLS,
+ SECTION_HEADER_SIZE,
};
use crate::{Error, MAX_LOAD_SEGMENTS, MAX_TLS_ALIGN};
@@ -237,29 +237,6 @@ impl DynamicSegment {
}
}
-/// Where the loaded image holds its own program header table.
-///
-/// Derived from where a `PT_LOAD` copies `e_phoff` out of the file, not from
-/// `PT_PHDR`: that header is the file's claim about this, and this is what the
-/// loader actually puts there.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub struct ProgramHeaderTable {
- image: ImageRange,
- count: u16,
-}
-
-impl ProgramHeaderTable {
- /// The table's bytes, inside the file-backed part of one `PT_LOAD`.
- pub const fn image(&self) -> ImageRange {
- self.image
- }
-
- /// `e_phnum`, at least one.
- pub const fn count(&self) -> u16 {
- self.count
- }
-}
-
/// Where the section header table is, when the file has a usable one.
///
/// Section headers are optional metadata — symbol names for backtraces, and
@@ -294,8 +271,7 @@ impl SectionTableRef {
/// - the extent runs from the smallest `p_vaddr` to the largest
/// `p_vaddr + p_memsz` over those segments, so it covers every one of them;
/// - the entry point, the file-backed extent of `PT_TLS`, and all of
-/// `PT_DYNAMIC` and `PT_GNU_EH_FRAME`, lie inside the extent, and the
-/// program header table, when one is held, inside one `PT_LOAD`'s file bytes;
+/// `PT_DYNAMIC` and `PT_GNU_EH_FRAME`, lie inside the extent;
/// - the TLS alignment is zero or a power of two no larger than
/// [`MAX_TLS_ALIGN`], so that `!(align - 1)` is a mask and the TLS block's
/// size cannot be dominated by a number the file chose.
@@ -313,7 +289,6 @@ pub struct Layout {
dynamic: Option<DynamicSegment>,
section_headers: Option<SectionTableRef>,
eh_frame_hdr: Option<ImageRange>,
- program_headers: Option<ProgramHeaderTable>,
}
impl Layout {
@@ -435,7 +410,6 @@ impl Layout {
None => None,
Some(e) => Some(extent.range(e.vaddr, e.memsz).ok_or(Error::EhFrameOutsideImage)?),
};
- let program_headers = program_header_table(&ehdr, segments.get(..placed).unwrap_or(&[]));
Ok(Layout {
extent,
@@ -446,7 +420,6 @@ impl Layout {
dynamic,
section_headers: section_table(&ehdr),
eh_frame_hdr,
- program_headers,
})
}
@@ -487,13 +460,6 @@ impl Layout {
self.eh_frame_hdr
}
- /// Where the loaded image holds the program header table, or `None` when
- /// no `PT_LOAD`'s file bytes hold all of it and the table exists only in
- /// the file.
- pub const fn program_headers(&self) -> Option<ProgramHeaderTable> {
- self.program_headers
- }
-
/// The writable window `[lo, hi)` in image offsets, or `None` when no
/// segment is writable.
///
@@ -594,25 +560,6 @@ impl Layout {
}
}
-/// The program header table as the first `PT_LOAD` whose file bytes hold all
-/// of it places it in the image.
-///
-/// Only `p_filesz` counts: past it a segment is zeroes, not the file.
-fn program_header_table(ehdr: &FileHeader, segments: &[Segment]) -> Option<ProgramHeaderTable> {
- // A `u16` count of 56-byte entries: the product cannot overflow a `u64`.
- let len = u64::from(ehdr.phnum) * PROGRAM_HEADER_SIZE as u64;
- segments.iter().find_map(|seg| {
- let within = ehdr.phoff.checked_sub(seg.file_offset)?;
- if within.checked_add(len)? > seg.filesz {
- return None;
- }
- // Inside the segment's own range, which the extent holds: `within +
- // len <= filesz <= memsz`.
- let start = seg.image.start + within;
- Some(ProgramHeaderTable { image: ImageRange { start, len }, count: ehdr.phnum })
- })
-}
-
/// A section header table the loader can index, or `None`.
///
/// `e_shentsize` must be exactly 64: consumers divide a byte count by it and
diff --git a/toyos-elf/src/lib.rs b/toyos-elf/src/lib.rs
index 1d9685d7..46ee094f 100644
--- a/toyos-elf/src/lib.rs
+++ b/toyos-elf/src/lib.rs
@@ -47,7 +47,7 @@ pub use gnu_hash::GnuHash;
pub use header::{FileHeader, Machine};
pub use layout::{
DynamicSegment, Extent, ImageOffset, ImageRange, Layout, Segment, SegmentFlags,
- ProgramHeaderTable, SectionTableRef, StackedImage, TlsSegment,
+ SectionTableRef, StackedImage, TlsSegment,
};
pub use rela::{Op, Rela, RelaCounts, RelaTable, Reloc, RelocError, RelocKind, Rules, TlsRef};
pub use section::{SectionHeader, SectionTable};
diff --git a/toyos-elf/tests/crafted.rs b/toyos-elf/tests/crafted.rs
index 4d44324f..baff9336 100644
--- a/toyos-elf/tests/crafted.rs
+++ b/toyos-elf/tests/crafted.rs
@@ -347,56 +347,6 @@ fn the_file_bytes_behind_a_vaddr_are_the_containing_segments() {
assert_eq!(layout.file_bytes_from(0x2400), Some(0x400));
}
-/// `(image offset, bytes, count)` of the table a layout places, for comparing.
-fn table_of(layout: &Layout) -> Option<(u64, u64, u16)> {
- layout.program_headers().map(|t| (t.image().start().get(), t.image().len(), t.count()))
-}
-
-/// The table is where the segment holding its file bytes puts them, measured
-/// from the image's lowest address, whichever segment that is.
-#[test]
-fn the_program_header_table_is_where_its_segment_places_it() {
- let two = 2 * PROGRAM_HEADER_SIZE as u64;
- let at_zero = accepted(Elf::new(0x1000).ph(Phdr::load(0, 0, 0x1000, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8)).build());
- assert_eq!(table_of(&at_zero), Some((PH_OFF as u64, two, 2)));
-
- // The first segment does not hold the table and the second does, at a
- // vaddr above the first's: the offset is from the extent's minimum.
- let second = accepted(
- Elf::new(0x2000)
- .entry(0x1000)
- .ph(Phdr::load(0x1000, 0x1000, 0x1000, 0x1000, PF_R | PF_X))
- .ph(Phdr::load(0, 0x10_0000, 0x1000, 0x1000, PF_R))
- .build(),
- );
- assert_eq!(table_of(&second), Some((0x10_0000 + PH_OFF as u64 - 0x1000, two, 2)));
-}
-
-/// Held only where the file's bytes are: a segment whose file image stops
-/// inside the table, or one that holds it only as zero-filled memory, holds
-/// no table.
-#[test]
-fn a_table_no_segment_holds_whole_is_absent() {
- let one = PROGRAM_HEADER_SIZE as u64;
- let short = PH_OFF as u64 + one;
- // Two headers; the file image ends one header in.
- let cut = Elf::new(0x1000).ph(Phdr::load(0, 0, short, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8));
- assert_eq!(table_of(&accepted(cut.build())), None);
- // Exactly the table's end and it is held.
- let whole = Elf::new(0x1000).ph(Phdr::load(0, 0, short + one, 0x1000, PF_R)).ph(Phdr::tls(0x800, 0, 8, 8));
- assert_eq!(table_of(&accepted(whole.build())), Some((PH_OFF as u64, 2 * one, 2)));
- // Memory but no file bytes.
- let zeroes = Elf::new(0x1000).ph(Phdr::load(0, 0, 0, 0x1000, PF_R));
- assert_eq!(table_of(&accepted(zeroes.build())), None);
- // A segment that starts past the table's first byte.
- let after = Elf::new(0x2000).entry(0x1000).ph(Phdr::load(PH_OFF as u64 + 8, 0x1000, 0x1000, 0x1000, PF_R));
- assert_eq!(table_of(&accepted(after.build())), None);
- // And one at vaddr 0, so a loader's vaddr-0 checks pass and only the table
- // refuses it: `abuse_elf_loader`'s `phdrs_unmapped`.
- let unmapped = accepted(Elf::new(0x2000).ph(Phdr::load(0x1000, 0, 0x1000, 0x1000, PF_R | PF_X)).build());
- assert_eq!((unmapped.extent().min(), table_of(&unmapped)), (0, None));
-}
-
#[test]
fn the_writable_window_spans_every_writable_segment() {
let layout = accepted(
diff --git a/toyos-elf/tests/fuzz.rs b/toyos-elf/tests/fuzz.rs
index 2314fb05..871ba397 100644
--- a/toyos-elf/tests/fuzz.rs
+++ b/toyos-elf/tests/fuzz.rs
@@ -309,9 +309,6 @@ fn load(case: &Case, placement: Placement, reached: &mut Reached) -> Result<(),
assert!(offset <= span, "offset {offset:#x} past the span {span:#x}");
image_start.checked_add(offset).expect("an offset inside the span leaves the placement")
};
- if let Some(table) = layout.program_headers() {
- place(table.image().end().get());
- }
let dyn_bytes = at(&case.bytes, layout.dynamic().ok_or(())?.image());
let dynamic = Dynamic::parse(dyn_bytes);
diff --git a/toyos-elf/tests/real.rs b/toyos-elf/tests/real.rs
index 3fe96367..6da7bdaa 100644
--- a/toyos-elf/tests/real.rs
+++ b/toyos-elf/tests/real.rs
@@ -37,11 +37,6 @@ fn a_toyos_ld_binary_parses_to_what_readelf_says() {
assert_eq!(layout.tls().unwrap().memsz(), 0x90);
assert_eq!(layout.tls().unwrap().align(), 0x40);
- // `e_phoff` 0x40, six headers of 56 bytes, inside the text segment's file
- // bytes at offset 0.
- let table = layout.program_headers().map(|t| (t.image().start().get(), t.image().len(), t.count()));
- assert_eq!(table, Some((0x40, 6 * 56, 6)));
-
let sections = layout.section_headers().expect("a section header table");
assert_eq!((sections.count, sections.entry_size), (9, 64));
diff --git a/userland/libc/include/elf.h b/userland/libc/include/elf.h
deleted file mode 100644
index e6ff35bb..00000000
--- a/userland/libc/include/elf.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef _ELF_H
-#define _ELF_H
-
-#include <stdint.h>
-
-typedef uint64_t Elf64_Addr;
-typedef uint64_t Elf64_Off;
-typedef uint16_t Elf64_Half;
-typedef uint32_t Elf64_Word;
-typedef int32_t Elf64_Sword;
-typedef uint64_t Elf64_Xword;
-typedef int64_t Elf64_Sxword;
-
-typedef struct {
- Elf64_Word p_type;
- Elf64_Word p_flags;
- Elf64_Off p_offset;
- Elf64_Addr p_vaddr;
- Elf64_Addr p_paddr;
- Elf64_Xword p_filesz;
- Elf64_Xword p_memsz;
- Elf64_Xword p_align;
-} Elf64_Phdr;
-
-#define PT_NULL 0
-#define PT_LOAD 1
-#define PT_DYNAMIC 2
-#define PT_INTERP 3
-#define PT_NOTE 4
-#define PT_SHLIB 5
-#define PT_PHDR 6
-#define PT_TLS 7
-#define PT_GNU_EH_FRAME 0x6474e550
-#define PT_GNU_STACK 0x6474e551
-#define PT_GNU_RELRO 0x6474e552
-
-#define PF_X 0x1
-#define PF_W 0x2
-#define PF_R 0x4
-
-#endif
diff --git a/userland/libc/include/link.h b/userland/libc/include/link.h
deleted file mode 100644
index 2eef0a32..00000000
--- a/userland/libc/include/link.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef _LINK_H
-#define _LINK_H
-
-#include <elf.h>
-#include <stddef.h>
-
-#define ElfW(type) Elf64_##type
-
-/* The first four fields of glibc's layout. There is no dlpi_adds or
- dlpi_subs, so the size a callback is passed ends at dlpi_phnum.
- dlpi_name is "" for the executable, and lives only until the callback
- returns. */
-struct dl_phdr_info {
- ElfW(Addr) dlpi_addr;
- const char *dlpi_name;
- const ElfW(Phdr) *dlpi_phdr;
- ElfW(Half) dlpi_phnum;
-};
-
-int dl_iterate_phdr(int (*callback)(struct dl_phdr_info *info, size_t size, void *data), void *data);
-
-#endif
diff --git a/userland/libc/src/lib.rs b/userland/libc/src/lib.rs
index 1052461c..9acf206a 100644
--- a/userland/libc/src/lib.rs
+++ b/userland/libc/src/lib.rs
@@ -6,7 +6,6 @@ extern crate alloc;
mod arch;
mod ctype;
mod errno;
-mod link;
mod math;
mod memory;
mod misc;
diff --git a/userland/libc/src/link.rs b/userland/libc/src/link.rs
deleted file mode 100644
index 7faffb24..00000000
--- a/userland/libc/src/link.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-//! `dl_iterate_phdr`, from the kernel's `SYS_QUERY_MODULES` answer.
-//!
-//! The contract is glibc's `dl_iterate_phdr(3)`, which the LSB adopts: the
-//! executable first, named by the empty string, then every library in load
-//! order; the walk stops at the first callback that returns non-zero and
-//! returns that value, else 0. The walk is over one answer, so a module a
-//! callback or another thread loads meanwhile is not visited.
-
-use alloc::vec::Vec;
-
-use toyos_abi::syscall;
-
-/// `struct dl_phdr_info`, as `link.h` declares it.
-#[repr(C)]
-pub struct DlPhdrInfo {
- dlpi_addr: u64,
- dlpi_name: *const u8,
- dlpi_phdr: *const u8,
- dlpi_phnum: u16,
-}
-
-type Callback = unsafe extern "C" fn(*mut DlPhdrInfo, usize, *mut u8) -> i32;
-
-#[no_mangle]
-pub unsafe extern "C" fn dl_iterate_phdr(callback: Callback, data: *mut u8) -> i32 {
- let answer = answer();
- let mut name = Vec::new();
- for (i, (module, path)) in syscall::modules(&answer).enumerate() {
- name.clear();
- if i > 0 {
- name.extend_from_slice(path);
- }
- name.push(0);
- let mut info = DlPhdrInfo {
- dlpi_addr: module.base,
- dlpi_name: name.as_ptr(),
- dlpi_phdr: module.phdr as *const u8,
- dlpi_phnum: u16::try_from(module.phnum).expect("SYS_QUERY_MODULES: a phnum past e_phnum's u16"),
- };
- // SAFETY: the caller's function, handed a record valid for the call.
- let stop = unsafe { callback(&mut info, core::mem::size_of::<DlPhdrInfo>(), data) };
- if stop != 0 {
- return stop;
- }
- }
- 0
-}
-
-/// One whole `SYS_QUERY_MODULES` answer: asked again while a `dlopen`
-/// elsewhere grows it between the size and the read.
-fn answer() -> Vec<u8> {
- let mut buf = Vec::new();
- loop {
- let need = syscall::query_modules(&mut buf).expect("SYS_QUERY_MODULES refused a buffer this process owns");
- if need <= buf.len() {
- buf.truncate(need);
- return buf;
- }
- buf.resize(need, 0);
- }
-}
diff --git a/userland/libc/src/misc.rs b/userland/libc/src/misc.rs
index 4443d6cd..fee86cf4 100644
--- a/userland/libc/src/misc.rs
+++ b/userland/libc/src/misc.rs
@@ -432,20 +432,13 @@ pub unsafe extern "C" fn longjmp(_env: *mut u8, _val: i32) -> ! {
}
// dlopen/dlsym/dlclose
-//
-// A C handle is the kernel's module index plus one: index 0 is a module, and
-// NULL is dlopen's failure.
-
-fn module_index(handle: *mut u8) -> Option<u64> {
- (handle as u64).checked_sub(1)
-}
#[no_mangle]
pub unsafe extern "C" fn dlopen(path: *const u8, _flags: i32) -> *mut u8 {
if path.is_null() { return ptr::null_mut(); }
let path_bytes = super::posix_io::c_str_to_bytes(path);
match syscall::dl_open(path_bytes) {
- Ok(index) => (index + 1) as *mut u8,
+ Ok(handle) => handle as *mut u8,
Err(_) => ptr::null_mut(),
}
}
@@ -453,10 +446,9 @@ pub unsafe extern "C" fn dlopen(path: *const u8, _flags: i32) -> *mut u8 {
#[no_mangle]
pub unsafe extern "C" fn dlsym(handle: *mut u8, symbol: *const u8) -> *mut u8 {
if symbol.is_null() { return ptr::null_mut(); }
- let index = module_index(handle).expect("dlsym: RTLD_DEFAULT not implemented");
let name = super::posix_io::c_str_to_bytes(symbol);
// SAFETY: handle is from a prior dlopen, name is a valid C string
- match unsafe { syscall::dl_sym(index, name) } {
+ match unsafe { syscall::dl_sym(handle as u64, name) } {
Ok(addr) => addr as *mut u8,
Err(_) => ptr::null_mut(),
}
@@ -464,10 +456,7 @@ pub unsafe extern "C" fn dlsym(handle: *mut u8, symbol: *const u8) -> *mut u8 {
#[no_mangle]
pub unsafe extern "C" fn dlclose(handle: *mut u8) -> i32 {
- match module_index(handle) {
- Some(index) => syscall::dl_close(index) as i32,
- None => -1,
- }
+ syscall::dl_close(handle as u64) as i32
}
#[no_mangle]🤖 Generated with Claude Code |
|
Orchestrator: guest runs at a7305ee — 204, 205, abuse_elf_loader, abuse_elf_segments, query_modules_size, dlopen_dedup, std_unwind, Fast: exit 0. mphnum3/204: exit 1 🤖 Generated with Claude Code |
tests/testcases/LICENSE: the tinycc count is main's 318 files plus this branch's 204 and 205 (four files): 322 files, 66 not upstream, 65 cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs
|
Orchestrator: after merging main (4cc2542) — 204, 205, abuse_elf_loader, abuse_elf_segments, query_modules_size, dlopen_dedup, std_unwind, 202_stat_mtime, Fast: exit 0; mphnum3, mphdr56, c3, c3b: exit 1. Landing. 🤖 Generated with Claude Code |
Brings #589, #599, #619, #620, #621, #622, #623, #624, #626, #627 and #628. Five conflicts, each hunk accounted for: - issues/isolation/untrusted-sites-not-yet-adopted.md (modify/delete): #624 deleted the sentence naming sourcegate's ban. This branch deletes the issue, whose one site is the kernel's NVMe completion queue, which goes with the kernel's NVMe driver here. The deletion stands. - kernel/src/durability.rs (modify/delete): #624 gave Settlement's field an expect(dead_code) under durability-settle-blind. This branch deletes the durability ledger, its kernel-loom model and that feature; nothing in src/, kernel-loom/ or the manifests names it. The deletion stands. - kernel/src/iod.rs (modify/delete): #589 moved sysret_ss_probe to crate::arch::hw. This branch deletes iod and runs the probe from the first syscall (syscall/dispatch.rs's task_probes), which now calls crate::arch::hw::sysret_ss_probe. - kernel/src/actuator.rs: #589 added irq-storm and timer-floor beside ftruncate-flush-stall, which this branch deletes with the flush it stalled. Both are kept; ftruncate-flush-stall stays deleted. - kernel/src/main.rs: #589 replaced `pub(crate) use arch::hw` with the crate's own `mod hw`; this branch dropped nvme from the drivers import. Both are taken. main's rust pin is still 90697f1401a, which the fork's c4c65e3e87a already merges, so the gitlink does not move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libunwind finds a module's unwind tables through
dl_iterate_phdr, and M3 ofissues/build/toyos-builds-itself.mdneeds it. Nothing told userland where a loaded module's program headers are. This adds that to the kernel's module query and buildsdl_iterate_phdrin libc on top of it. Closesissues/build/libc-has-no-dl-iterate-phdr.md.ABI change
toyos_abi::syscall::ModuleInfo, whichSYS_QUERY_MODULESwrites, gains two fields betweeneh_frame_hdr_sizeandpath_offset:phdr: u64: the absolute address of the module's program header table;phnum: u64:e_phnum.The record grows from 40 to 56 bytes and has no padding. Its const assert and the wire-decoding host test both check this.
phnumis au64rather than au32so that no padding is needed. There is nophentsizefield:Layoutrefuses anye_phentsizeother than 56, so the field could only ever hold one value. No syscall number changes and no auxiliary vector is added.toyos_abi::syscall::modules(answer)is new. It decodes onequery_modulesanswer into its records and paths, and libc uses it. It has host tests, including one where a path runs past the end of the answer, which panics.Consumers of
ModuleInfo:sys_query_modules);toyos-abi's own test;tests/toyos-rust-tests(query_modules_size,abuse_kernel_addr), which size records bysize_of;library/std/src/sys/pal/toyos/mod.rs,eh_frame::load_modules);library/backtrace/src/symbolize/gimli/libs_toyos.rs,native_libraries).Both fork readers read fields by name and step by
size_of, so they build unchanged, and no fork commit or gitlink bump was needed. Both also read the call's byte count as a record count. That is an older defect, filed below.toyos/srchas no consumer.Decisions
PT_PHDR.toyos_elf::Layout::program_headers()finds the firstPT_LOADwhose file bytes (p_offset..p_offset+p_filesz) hold all ofe_phoff..+e_phnum*56.Noneif no segment holds it.PT_PHDRis only the file's claim about where the table is.p_fileszare zero-fill and do not count.Layout::parseitself does not refuse a file without the table, because the bootloader loads the kernel with the sameLayout.e_phnum * 56, from au16) and its image start (inside the segment's own range) are computed with plain arithmetic, because neither can overflow. If that invariant broke, the kernel's and bootloader's overflow checks would panic, rather than the loader quietly reporting "no table".spawnchecks right afterrebase_base, andload_shared_libright after its vaddr-0 check.map_librarytakes the firstPT_LOADwhose file bytes hold the table. glibc copies an unmapped table instead, so ToyOS is stricter than glibc. That difference is a refusal made on purpose.abuse_elf_loadercase 21 is a 0x2000-byte ELF whose onlyPT_LOADis file0x1000..0x2000at vaddr 0, with entry 0.e_phoff0x40 lies in no segment's file bytes.vaddr_minis 0, sorebase_baseand the vaddr-0 check both pass it.spawn_refused) and dlopened (dlopen_refused).dlopen_refused's doc no longer claims that every case is a relocation write.dlpi_addris the load bias, sodlpi_addr + p_vaddris a runtime address.ElfInfo::elf_base(USER_VM_BASE - vaddr_min), and its table is placed fromimage_start, the image's first byte.user_base, because libraries start at vaddr 0.dl_iterate_phdr(3), which the LSB adopts:"", then every library in load order;dlpi_nameis valid only until the callback returns;dlpi_adds/dlpi_subsare not declared, so thesizea callback receives ends atdlpi_phnum;link.hdeclaresstruct dl_phdr_infoandElfW. It includes a newelf.hthat holds theElf64_*scalar types,Elf64_Phdr,PT_*andPF_*, because users oflink.h(libunwind among them) expect it to pull those in.dlopenhandle is the kernel's module index plus one.SYS_DLOPENanswers with the library's index, so a C program with no startup libraries gets index 0 for its firstdlopen. libc handed that back as the pointer 0, which C reads as failure.dlsymanddlclosesubtract the one again.dlcloseof a handle thatdlopennever returned answers -1.dlsym(RTLD_DEFAULT)now panics as unimplemented instead of reading module 0, as libc'slongjmpstub already does.issues/build/libc-dlsym-rtld-default-panics-as-unimplemented.mdrecords it. It is a separate file from the kernel's global-scope issue because the fix is in libc and it has its own exit.toyos_abi::syscall::dl_open/dl_sym) keep the raw index.dlopen: unresolved: mainline in 204's kernel log is not a refusal.libtls_lib.socarries a global undefinedmain, the C executable exports none, andresolve_dlopen_relocslogs an unresolved slot and leaves it, which is its contract (kernel/src/elf/reloc.rs).Tests
tests/testcases/tinycc/204_dl_iterate_phdr.c+.expect. Each module is checked against facts that did not come from the kernel's answer:dlpi_phnumise_phnum, and itsdlpi_phdrisdlpi_addr + e_phoff, both read from the ELF header atdlpi_addr. Both modules have that header atdlpi_addr:llvm-readobj -h -l, run on the host-linked 204 and onlibtls_lib.so, shows each linked at vaddr 0, with its firstPT_LOADat file offset 0,e_phoff0x40 and 10 headers;PT_LOADs atdlpi_addr + p_vaddr;PT_PHDR, it names the same address;mainlies in an executablePT_LOAD;PT_DYNAMICsits at the linker's_DYNAMICand inside aPT_LOAD.The case then calls
dlopen("/system/lib/libtls_lib.so"). That is the program's firstdlopen, so the library is also module index 0. The library must then be visited under that name, anddlsym'stls_get_labelmust lie inside one of its executable segments. Callbacks that return 1, 7 and -1 must stop the walk where they say, and the walk must return their value.tests/testcases/tinycc/205_dl_iterate_phdr_image_base.c+.expectcovers an executable whose lowest vaddr is not 0.-Wl,--image-base=0x200000. The flag comes fromtests/common/compile.rs'sLINK_FLAGS, keyed by case name and applied inlink_toyos, which every C link goes through.dlpi_phdrwith__ehdr_start + e_phoff, anddlpi_phnumwithe_phnum. Both are read from the linker's own ELF header.__ehdr_startequalsdlpi_addrplus the lowestp_vaddr.p_vaddr, so its.expectturns red if the flag stops reaching the case.llvm-readobj -h -lgivese_phoff0x40, 10 headers, the firstPT_LOADat offset 0 and vaddr 0x200000, andPT_PHDRat 0x200040.llvm-nmgives__ehdr_startat 0x200000.Host tests:
(0x40, 336, 6), which matches readelf'se_phoff/e_phnum;modulesdecoding two records and their paths from an unaligned buffer.Gates at a7305ee
origin/main576e558 is merged in and is the merge base.cargo run -- --ci hostcargo run -- --build-onlycargo test --test toyos-build -- --listFast 204_dl_iterate_phdr,Fast 205_dl_iterate_phdr_image_base,Fast abuse_elf_loader,Fast query_modules_size)Guest results at f7fae8c, as the round-4 review records them: 204, 205, abuse_elf_loader, abuse_elf_segments, query_modules_size, dlopen_dedup, std_unwind and Fast exit 0. m1 and m1b each turn abuse_elf_loader red with exit 1. m2 turns 205 red with exit 1 and leaves 204 green. a, b and c turn 204 red with exit 1. c2 leaves query_modules_size and abuse_elf_loader green. 204 has changed since then (293af00). No round-4 patch has had a guest run.
Negative controls
The round-3 patches are in #599 (comment). The round-4 patches are in #599 (comment), with their sha256s.
Every patch below was put through the same checks, and each step exited 0:
git apply --check, apply,cargo run -- --build-only,git apply -R, thengit status --porcelain, which came back empty. The m1, m1b, m2, a, b and c patches were built this way in earlier rounds. At a7305ee each still passesgit apply --check.The harness builds every C case before any test runs, and one case that fails to build makes every test exit 101. A control that removes what a C case needs therefore measures only that case, and never measures
abuse_elf_loader.m1-refusals-panic.expect("phdr table")abuse_elf_loaderred: kernel panic at case 21's spawnm1b-dlopen-refusal-panicsload_shared_lib's refusal becomes.expectabuse_elf_loaderred: kernel panic at case 21's dlopen, after its spawn is refusedm2-exe-phdr-from-load-biasphdradded tobaseinstead ofimage_start205_dl_iterate_phdr_image_basered, exit 1 (dlpi_phdr is __ehdr_start + e_phoff: no);204_dl_iterate_phdrgreen, because itsvaddr_minis 0mutation-a-kernel-phnum-zerophnum0 for every module204_dl_iterate_phdrred, exit 1 (FAIL no program headers)mutation-b-libc-ignores-stop204_dl_iterate_phdrred, exit 1 (stopped at 1 of 1: visited 1, returned 0)mutation-c-libc-handle-is-the-indexdlopenhands out the raw index204_dl_iterate_phdrred, exit 1 (FAIL dlopen /system/lib/libtls_lib.so)mphnum3-lib-phnum-threephnumas 3204_dl_iterate_phdrred, exit 1 (FAIL dlpi_phnum is not e_phnum)mphdr56-lib-phdr-one-entry-latephdris 56 bytes late204_dl_iterate_phdrred, exit 1 (FAIL dlpi_phdr is not dlpi_addr + e_phoff)control-c1r4-revert-kernel-abi-elf-libc--listexits 101 while building the C corpus,'link.h' file not foundon bothcontrol-c3-revert-and-drop-204-205abuse_elf_loaderred, exit 1 (phdrs_unmapped: spawn returned pid)control-c3b-c3-without-the-spawn-armspawn_refusedline deletedabuse_elf_loaderred, exit 1 (phdrs_unmapped.so: dlopen loaded an image the loader must refuse)control-c2r4-revert-and-drop-casesquery_modules_sizeandabuse_elf_loadergreen, exit 0The whole-change controls:
abuse_elf_loader.m1, m1b, m2, mphnum3 and mphdr56 are the behavioural controls for the review blockers. a, b and c are the behavioural controls for the walk and the handle.
Independent oracle
dl_iterate_phdr(3)contract: walk order,""for the executable, stop-and-return. The case asserts each of these.dlopen(3): a NULL return means failure, so no successful load may answer NULL.main,_DYNAMIC, thePT_PHDRaddress,dlsym, and__ehdr_startwith thee_phoff/e_phnumit heads.llvm-readobj/llvm-nm: the toyos-ld fixture'se_phoff/e_phnum, and 205's image base, table and__ehdr_start.map_library: the rule that decides whether the table is mapped.Unsure
load_shared_libthat refuses case 21's bytes before its table would be checked, but only the guest run can show it.dlsym(RTLD_DEFAULT)panicking is new behaviour for any C program that probes the global scope. No program in the tree calls it.Filed
issues/diagnostics/std-reads-a-query-modules-byte-count-as-a-module-count.md: std's unwinder and the backtrace crate both takequery_modules's byte count as a record count.issues/kernel/a-dlopened-library-never-binds-to-the-executables-exports.md:resolve_dlopen_relocssearches other libraries only, unlike glibc's global scope and unlike the startup path.issues/build/libc-dlsym-rtld-default-panics-as-unimplemented.md:dlsym(RTLD_DEFAULT)dies as unimplemented.issues/build/libc-dl-iterate-phdr-allocates-on-every-walk.md: every walk allocates twice and makes at least two syscalls. libunwind calls the walk while unwinding, so throwingbad_allocwith the heap exhausted dies in libc's allocator. The answer grows with every library and path, so the walk cannot be made allocation-free without an ABI change.🤖 Generated with Claude Code
https://claude.ai/code/session_01U6SVYFkdvV2t38KzNrESxs