diff --git a/apps/starry/llama-cpp/riscv64-static-pie.md b/apps/starry/llama-cpp/riscv64-static-pie.md new file mode 100644 index 0000000000..16c0a597b7 --- /dev/null +++ b/apps/starry/llama-cpp/riscv64-static-pie.md @@ -0,0 +1,50 @@ +# riscv64 static-pie segfault + +## Reproduction + +- Minimal C program (printf only) +- Built with `riscv64-linux-musl-gcc -static-pie` +- ELF Type: DYN (Position-Independent Executable) +- QEMU on StarryOS riscv64 +- Result: segfault, RC=139 + +```c +#include +int main(void) { + printf("static-pie test OK\n"); + return 0; +} +``` + +## Crash + +``` +pc(sepc)=0x00000000000003b0 +Segmentation fault (core dumped) +``` + +## Root cause + +PC=0x3b0 is the start of `.plt` section, NOT `.text` (0x3f0). + +The binary has unresolved PLT relocations (from `readelf -r`): + +- `.rela.plt`: `R_RISCV_JUMP_SLOT` for `puts` and `__libc_start_main` +- `.rela.dyn`: `R_RISCV_RELATIVE` entries for data pointers + +The StarryOS ELF loader (`os/StarryOS/kernel/src/mm/loader.rs`) maps segments +and jumps to entry, but does NOT process `.rela.dyn` or `.rela.plt` relocations. +The unresolved PLT entries cause a jump to 0x3b0 (PLT stub) which segfaults. + +aarch64/x86_64 static-pie works because their musl CRT handles relocations +differently, or their PLT stubs happen to work without relocation processing. + +## Conclusion + +Not a llama.cpp issue. The ELF loader needs relocation processing for +static-PIE (Type=DYN) binaries. The fix requires: +1. Parsing `.rela.dyn` and `.rela.plt` sections +2. Applying R_RISCV_RELATIVE and R_RISCV_JUMP_SLOT relocations +3. Then jumping to the entry point + +This is a kernel loader enhancement, tracked separately. diff --git a/apps/starry/static-pie-test/build-riscv64gc-unknown-none-elf.toml b/apps/starry/static-pie-test/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..3307976592 --- /dev/null +++ b/apps/starry/static-pie-test/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,13 @@ +env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} +features = [ + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] +log = "Warn" +plat_dyn = false +target = "riscv64gc-unknown-none-elf" diff --git a/apps/starry/static-pie-test/prebuild.sh b/apps/starry/static-pie-test/prebuild.sh new file mode 100755 index 0000000000..9dd7eeaa2d --- /dev/null +++ b/apps/starry/static-pie-test/prebuild.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +app_dir="${STARRY_APP_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" +overlay_dir="${STARRY_OVERLAY_DIR:-}" + +if [[ -z "$overlay_dir" ]]; then + echo "error: STARRY_OVERLAY_DIR is required" >&2 + exit 1 +fi + +install -Dm0755 "$app_dir/static-pie-test.sh" "$overlay_dir/usr/bin/static-pie-test.sh" + +# Compile and copy static-pie test binary +TOOLCHAIN="/root/project/toolchains/riscv64-linux-musl-cross/bin/riscv64-linux-musl-gcc" +TEST_SRC="/tmp/opencode/static-pie-test.c" +TEST_BIN="/tmp/opencode/static-pie-test" + +mkdir -p /tmp/opencode +cat > "$TEST_SRC" << "CEOF" +#include +int main(void) { + printf("static-pie test OK\n"); + return 0; +} +CEOF + +if [[ -f "$TOOLCHAIN" ]]; then + "$TOOLCHAIN" -static -o "$TEST_BIN" "$TEST_SRC" + install -Dm0755 "$TEST_BIN" "$overlay_dir/usr/bin/static-pie-test" + echo "Static-pie binary compiled and installed to /usr/bin/static-pie-test" +else + echo "Error: riscv64 toolchain not found" >&2 + exit 1 +fi diff --git a/apps/starry/static-pie-test/qemu-riscv64.toml b/apps/starry/static-pie-test/qemu-riscv64.toml new file mode 100755 index 0000000000..e2ba2576fb --- /dev/null +++ b/apps/starry/static-pie-test/qemu-riscv64.toml @@ -0,0 +1,22 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "rv64", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-static-pie-test.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/static-pie-test.sh" +success_regex = ["(?m)^STATIC_PIE_TEST_PASSED"] +fail_regex = ["(?i)panic", "(?i)segmentation fault", "(?i)SIGSEGV"] +timeout = 30 diff --git a/apps/starry/static-pie-test/static-pie-test.sh b/apps/starry/static-pie-test/static-pie-test.sh new file mode 100755 index 0000000000..3e48a5e4f7 --- /dev/null +++ b/apps/starry/static-pie-test/static-pie-test.sh @@ -0,0 +1,10 @@ +#!/bin/sh +echo "BEFORE_BINARY" +/usr/bin/static-pie-test +RC=$? +echo "AFTER_BINARY RC=$RC" +if [ $RC -eq 0 ]; then + echo "STATIC_PIE_TEST_PASSED" +else + echo "STATIC_PIE_TEST_FAILED: RC=$RC" +fi diff --git a/os/StarryOS/kernel/src/mm/loader.rs b/os/StarryOS/kernel/src/mm/loader.rs index 2abf08bb6a..0ba4d3aafa 100644 --- a/os/StarryOS/kernel/src/mm/loader.rs +++ b/os/StarryOS/kernel/src/mm/loader.rs @@ -31,6 +31,16 @@ const RISCV_COMPAT_HWCAP_IMAFDC: usize = (1 << (b'I' - b'A')) | (1 << (b'D' - b'A')) | (1 << (b'C' - b'A')); +// RISC-V relocation types +#[cfg(target_arch = "riscv64")] +const R_RISCV_RELATIVE: u32 = 3; +#[cfg(target_arch = "riscv64")] +const R_RISCV_JUMP_SLOT: u32 = 5; +#[cfg(target_arch = "riscv64")] +const R_RISCV_64: u32 = 2; +#[cfg(target_arch = "riscv64")] +const R_RISCV_COPY: u32 = 4; + /// Creates a new empty user address space. pub fn new_user_aspace_empty() -> AxResult { AddrSpace::new_empty(VirtAddr::from_usize(USER_SPACE_BASE), USER_SPACE_SIZE) @@ -138,9 +148,271 @@ fn map_elf<'a>( // TDOO: flush the I-cache } + // Apply relocations for static-pie binaries + // On non-riscv64 architectures, apply_relocations() is a no-op stub. + if elf_parser.headers().header.pt1.class() == xmas_elf::header::Class::SixtyFour { + let is_pie = elf_parser.headers().header.pt2.type_().as_type() + == xmas_elf::header::Type::SharedObject; + if is_pie { + #[cfg(target_arch = "riscv64")] + { + // Populate PT_LOAD segments so relocation writes can access pages + for seg in elf_parser + .headers() + .ph + .iter() + .filter(|p| p.get_type() == Ok(xmas_elf::program::Type::Load)) + { + let seg_start = + VirtAddr::from_usize(base + seg.virtual_addr as usize).align_down_4k(); + let seg_pad = (base + seg.virtual_addr as usize).align_offset_4k(); + let seg_size = + (seg.mem_size as usize + seg_pad + PAGE_SIZE_4K - 1) & !(PAGE_SIZE_4K - 1); + uspace.populate_area(seg_start, seg_size, mapping_flags(seg.flags))?; + } + } + apply_relocations(uspace, base, entry.borrow_cache(), &elf_parser.headers().ph)?; + } + } + Ok(elf_parser) } +/// Convert a virtual address to a file offset using PT_LOAD segments. +/// +/// This function searches through the program headers to find which PT_LOAD +/// segment contains the given virtual address, then calculates the +/// corresponding file offset. +/// +/// Returns None if the address is not within any PT_LOAD segment. +#[cfg(target_arch = "riscv64")] +fn vaddr_to_file_offset(vaddr: u64, ph: &[xmas_elf::program::ProgramHeader64]) -> Option { + let vaddr = vaddr as usize; + for seg in ph { + if seg.get_type() != Ok(xmas_elf::program::Type::Load) { + continue; + } + let seg_vaddr = seg.virtual_addr as usize; + let seg_filesz = seg.file_size as usize; + if vaddr >= seg_vaddr && vaddr < seg_vaddr + seg_filesz { + let offset_in_segment = vaddr - seg_vaddr; + return Some(seg.offset as usize + offset_in_segment); + } + } + None +} + +/// Apply relocations for static-pie binaries. +/// +/// This processes .rela.dyn and .rela.plt sections to apply +/// R_RISCV_RELATIVE and R_RISCV_JUMP_SLOT relocations. +#[cfg(target_arch = "riscv64")] +fn apply_relocations( + uspace: &mut AddrSpace, + base: usize, + cache: &CachedFile, + ph: &[xmas_elf::program::ProgramHeader64], +) -> AxResult { + // Find PT_DYNAMIC segment + let dynamic_ph = ph + .iter() + .find(|p| p.get_type() == Ok(xmas_elf::program::Type::Dynamic)); + + let dynamic_ph = match dynamic_ph { + Some(ph) => ph, + None => return Ok(()), // No dynamic section, nothing to do + }; + + // Read dynamic entries from file + let dyn_offset = dynamic_ph.offset as usize; + let dyn_size = dynamic_ph.file_size as usize; + + if dyn_offset + dyn_size > (cache.location().len().unwrap_or(0) as usize) { + debug!("Dynamic section extends beyond file"); + return Err(AxError::InvalidData); + } + + let mut dyn_data = vec![0u8; dyn_size]; + cache.read_at(&mut dyn_data, dyn_offset as u64)?; + let entry_size = 16; // sizeof(Dynamic) = 16 bytes + let num_entries = dyn_size / entry_size; + + // Parse dynamic entries using byte-by-byte reading + let mut rela_addr: u64 = 0; + let mut rela_size: u64 = 0; + let mut jmprel_addr: u64 = 0; + let mut jmprel_size: u64 = 0; + let mut symtab_addr: u64 = 0; + let mut strtab_addr: u64 = 0; + + for i in 0..num_entries { + let offset = i * entry_size; + let entry_data = &dyn_data[offset..offset + entry_size]; + + // Dynamic entry: tag (8 bytes) + value (8 bytes) + let tag = u64::from_le_bytes(entry_data[0..8].try_into().unwrap()); + let value = u64::from_le_bytes(entry_data[8..16].try_into().unwrap()); + + match tag { + 7 => rela_addr = value, // DT_RELA + 8 => rela_size = value, // DT_RELASZ + 23 => jmprel_addr = value, // DT_JMPREL + 2 => jmprel_size = value, // DT_PLTRELSZ + 6 => symtab_addr = value, // DT_SYMTAB + 5 => strtab_addr = value, // DT_STRTAB + 0 => break, // DT_NULL + _ => {} + } + } + + // Process .rela.dyn (R_RISCV_RELATIVE) + if rela_addr != 0 && rela_size != 0 { + let rela_offset = vaddr_to_file_offset(rela_addr, ph).ok_or(AxError::InvalidData)?; + let rela_entry_size = 24; // sizeof(Rela) = 24 bytes + let rela_count = rela_size as usize / rela_entry_size; + let mut copy_count: usize = 0; + + debug!("Processing {} RELATIVE relocations", rela_count); + + for i in 0..rela_count { + let entry_offset = rela_offset + i * rela_entry_size; + if entry_offset + rela_entry_size > (cache.location().len().unwrap_or(0) as usize) { + break; + } + + let mut entry_data = vec![0u8; rela_entry_size]; + cache.read_at(&mut entry_data, entry_offset as u64)?; + + // Rela entry: offset (8 bytes) + info (8 bytes) + addend (8 bytes) + let offset = u64::from_le_bytes(entry_data[0..8].try_into().unwrap()) as usize; + let info = u64::from_le_bytes(entry_data[8..16].try_into().unwrap()); + let addend = i64::from_le_bytes(entry_data[16..24].try_into().unwrap()); + + let reloc_type = (info & 0xffffffff) as u32; + + match reloc_type { + R_RISCV_RELATIVE => { + // *(base + offset) = base + addend + let target = base + offset; + let value = (base as i64 + addend) as u64; + uspace.write(VirtAddr::from_usize(target), &value.to_le_bytes())?; + debug!("RELATIVE: [{:#x}] = {:#x}", target, value); + } + R_RISCV_64 => { + // S + A (symbol value + addend) + let sym_idx = (info >> 32) as usize; + if symtab_addr == 0 || strtab_addr == 0 { + debug!("Missing symtab/strtab for R_RISCV_64"); + continue; + } + + let sym_file_offset = + vaddr_to_file_offset(symtab_addr, ph).ok_or(AxError::InvalidData)?; + let sym_entry_offset = sym_file_offset + sym_idx * 24; + let file_len = cache.location().len().unwrap_or(0) as usize; + if sym_entry_offset + 24 > file_len { + continue; + } + let mut sym_data = vec![0u8; 24]; + cache.read_at(&mut sym_data, sym_entry_offset as u64)?; + let st_value = u64::from_le_bytes(sym_data[8..16].try_into().unwrap()); + if st_value == 0 { + continue; + } + let target = base + offset; + let value = (base as i64 + st_value as i64 + addend) as u64; + uspace.write(VirtAddr::from_usize(target), &value.to_le_bytes())?; + } + R_RISCV_COPY => { + copy_count += 1; + } + _ => { + debug!("[apply_relocations] unknown .rela.dyn type={}", reloc_type); + } + } + } + if copy_count > 0 { + debug!( + "[apply_relocations] skipped {} R_RISCV_COPY relocations", + copy_count + ); + } + } + + // Process .rela.plt (R_RISCV_JUMP_SLOT) + if jmprel_addr != 0 && jmprel_size != 0 { + let jmprel_offset = vaddr_to_file_offset(jmprel_addr, ph).ok_or(AxError::InvalidData)?; + let rela_entry_size = 24; // sizeof(Rela) = 24 bytes + let jmprel_count = jmprel_size as usize / rela_entry_size; + + debug!("Processing {} JUMP_SLOT relocations", jmprel_count); + + for i in 0..jmprel_count { + let entry_offset = jmprel_offset + i * rela_entry_size; + if entry_offset + rela_entry_size > (cache.location().len().unwrap_or(0) as usize) { + break; + } + + let mut entry_data = vec![0u8; rela_entry_size]; + cache.read_at(&mut entry_data, entry_offset as u64)?; + + // Rela entry: offset (8 bytes) + info (8 bytes) + addend (8 bytes) + let offset = u64::from_le_bytes(entry_data[0..8].try_into().unwrap()) as usize; + let info = u64::from_le_bytes(entry_data[8..16].try_into().unwrap()); + let _addend = i64::from_le_bytes(entry_data[16..24].try_into().unwrap()); + + let reloc_type = (info & 0xffffffff) as u32; + let sym_idx = (info >> 32) as usize; + + match reloc_type { + R_RISCV_JUMP_SLOT => { + // For static-pie, symbols are in the binary itself + // We need to look up the symbol in .dynsym + if symtab_addr == 0 || strtab_addr == 0 { + debug!("Missing symtab/strtab for JUMP_SLOT"); + continue; + } + + // Read symbol from .dynsym + let sym_file_offset = + vaddr_to_file_offset(symtab_addr, ph).ok_or(AxError::InvalidData)?; + let sym_entry_offset = sym_file_offset + sym_idx * 24; + let file_len = cache.location().len().unwrap_or(0) as usize; + if sym_entry_offset + 24 > file_len { + continue; + } + let mut sym_data = vec![0u8; 24]; + cache.read_at(&mut sym_data, sym_entry_offset as u64)?; + let st_value = u64::from_le_bytes(sym_data[8..16].try_into().unwrap()); + + if st_value == 0 { + continue; + } + let target = base + offset; + let value = base as u64 + st_value; + uspace.write(VirtAddr::from_usize(target), &value.to_le_bytes())?; + } + _ => { + debug!("Unsupported relocation type: {}", reloc_type); + } + } + } + } + + Ok(()) +} + +/// Stub for non-riscv64 architectures +#[cfg(not(target_arch = "riscv64"))] +fn apply_relocations( + _uspace: &mut AddrSpace, + _base: usize, + _cache: &CachedFile, + _ph: &[xmas_elf::program::ProgramHeader64], +) -> AxResult { + Ok(()) +} + fn map_elf_error(err: &'static str) -> AxError { debug!("Failed to parse ELF file: {err}"); AxError::InvalidExecutable