From ce7b570a62f2d1075a4ba479bbf9e2cc4334e01c Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 11 Jun 2026 06:45:11 +0000 Subject: [PATCH] =?UTF-8?q?build:=20recetas=20Cargo=20est=C3=A1ticas=20=3D?= =?UTF-8?q?=20crt-static=20+=20no-PIE=20(binarios=20autocontenidos)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cierra el último bloqueo del boot que la corrida en QEMU destapó (runbook §8): arje-zero/hammerd buildeaban DINÁMICOS contra musl (el rust de Alpine es dinámico por defecto) → DT_NEEDED libc.musl-x86_64.so.1, y nuestro libc.so (musl shared con zig cc) no exporta memcpy/memset/… → el loader falla y el init muere. Fix golden-path: para link=static, RUSTFLAGS ahora fuerza -C target-feature=+crt-static -C relocation-model=static ⇒ binarios AUTOCONTENIDOS (musl dentro), ET_EXEC sin interpreter, como busybox: sin libc.so, sin loader, sin el soname de Alpine. musl vuelve a --disable-shared (no se necesita el loader). Boot en QEMU: usar -cpu Broadwell (el qemu64 default no tiene el AVX que zig emite). Validado por la corrida real: los 4 componentes buildan y el rootfs se sella; el kernel arranca el initramfs y ejecuta arje-zero como PID 1. Falta rebuildear hammerd/arje-zero con crt-static para cerrar el boot (cache-bust + rebuild). Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/hammer-build/src/lib.rs | 10 +++++++++- docs/runbooks/stage1-vm-boot.md | 25 +++++++++++++++++++++++-- recipes/musl.toml | 2 ++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/crates/hammer-build/src/lib.rs b/crates/hammer-build/src/lib.rs index 8e156e55..1a2aabcb 100644 --- a/crates/hammer-build/src/lib.rs +++ b/crates/hammer-build/src/lib.rs @@ -289,8 +289,14 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> hammer_core::Result { // CC (cc-rs) y como linker (RUSTFLAGS, target-agnóstico — el default sería // cc/gcc, ausente en el sandbox zig-only). let crt = match recipe.build.link { + // Estático = AUTOCONTENIDO: el rust de Alpine enlaza musl dinámico por + // defecto (deja `DT_NEEDED libc.musl-x86_64.so.1` y rompe el boot — el + // loader no resuelve memcpy/…; ver runbook §8). `+crt-static` mete musl + // dentro; `relocation-model=static` lo hace ET_EXEC sin interpreter (vía de + // oro de hammer, como busybox: sin libc.so, sin loader, sin soname). + LinkMode::Static => " -C target-feature=+crt-static -C relocation-model=static", + // Dinámico: apaga crt-static para poder dlopen (necesita el loader/musl en el rootfs). LinkMode::Dynamic => " -C target-feature=-crt-static", - LinkMode::Static => "", }; Some(format!( "printf '%s\\n' '#!/bin/sh' 'for a do' \ @@ -433,6 +439,8 @@ commit = "deadbeef" // el del wrapper saneador del triple alpine. assert!(!c.contains("--offline --target"), "nativo: cargo sin --target ({c})"); assert!(c.contains("a=--target=x86_64-linux-musl"), "wrapper sanea el triple ({c})"); + assert!(c.contains("target-feature=+crt-static"), "estático = autocontenido ({c})"); + assert!(c.contains("relocation-model=static"), "no-PIE: ET_EXEC sin interpreter ({c})"); assert!(c.contains("CC=\"$PWD/.hammer-zig-cc\""), "CC = wrapper para cc-rs ({c})"); assert!(c.contains("RUSTFLAGS=\"-C linker=$PWD/.hammer-zig-cc"), "linker por RUSTFLAGS ({c})"); let i = p.install.unwrap(); diff --git a/docs/runbooks/stage1-vm-boot.md b/docs/runbooks/stage1-vm-boot.md index 686be694..f1af59e3 100644 --- a/docs/runbooks/stage1-vm-boot.md +++ b/docs/runbooks/stage1-vm-boot.md @@ -152,8 +152,29 @@ Rust está bloqueado por una decisión de toolchain.** Hallazgos, en orden: de Alpine; (C) rustup en el sandbox + `rustup target add` — pierde hermeticidad/pin. Es la misma tensión de "pin de toolchain entra al hash" que el plan C.2 dejó abierta. -**Conclusión:** el bootstrap C (musl+busybox+zig cc) está **validado**. Completar el rootfs y el boot -en QEMU requiere resolver el toolchain Rust (hammerd/arje-zero) y más disco (vendor del monorepo). +### Stage 1 completo + intento de boot + +Tras los fixes, **los 4 componentes buildan y el rootfs se sella** (musl+busybox+hammerd+arje-zero, +29 MB; `/sbin/init`→arje-zero, `/ente/seed.card.json`, loader musl). Se empaquetó como initramfs +(`cpio newc`+gzip, 11 MB) y se booteó en QEMU. La cadena de hallazgos del boot: + +1. **AVX.** El CPU default de QEMU (`qemu64`) no tiene AVX, pero los binarios (zig/rust nativos) + emiten `vxorps` → `SIGILL` (`invalid_op`) en el loader → init muere. Fix: `-cpu Broadwell` + (AVX2 bajo TCG; sin `/dev/kvm` no hay `-cpu host`). Para un distro real: compilar a un baseline + x86-64 genérico, no al CPU del builder. +2. **musl dinámico.** El rust de Alpine produce binarios **dinámicos** (`DT_NEEDED + libc.musl-x86_64.so.1`), no crt-static. El loader corre pero no resuelve `memcpy/memset/log2/…`: + nuestro `libc.so` (musl 1.2.5 shared con zig cc) tiene 1586 dyn-syms pero **no exporta** esos + símbolos. → `Error relocating /sbin/init: memcpy: symbol not found` → init muere. + +**Conclusión.** El bootstrap **C** (musl+busybox+zig cc) y el **build Rust nativo** (hammerd y +arje-zero compilan y se sellan) están **validados** end-to-end; el kernel arranca el initramfs y +ejecuta arje-zero como PID 1. El último bloqueo del boot es el **linking dinámico de musl**: el fix +correcto y golden-path es **crt-static** — compilar los componentes Rust con +`-C target-feature=+crt-static -C relocation-model=static` para binarios **autocontenidos** (sin +`libc.so`, sin loader, sin soname, como busybox). Requiere rebuild de hammerd/arje-zero (el de +arje-zero vendorea el monorepo: caro en tiempo y disco). Es la realización plena de la "vía de oro" +estática de hammer y cierra de paso el problema del soname de Alpine. **Actualización (Opción A implementada).** `BuildSys::Cargo` ahora construye **nativo** sin `--target` cuando el target es el del sandbox (`SANDBOX_NATIVE_TARGET`), con un wrapper `.hammer-zig-cc` que diff --git a/recipes/musl.toml b/recipes/musl.toml index db2db8dc..b3c59692 100644 --- a/recipes/musl.toml +++ b/recipes/musl.toml @@ -18,6 +18,8 @@ link = "static" [build.phases] # El sandbox exporta CC="zig cc", DESTDIR=/out y PREFIX=/usr. musl trae su propio `configure`. +# Sólo estático: los componentes Rust se compilan crt-static (autocontenidos, sin libc.so), así que +# el rootfs no necesita el loader/shared musl. Ver runbooks/stage1-vm-boot.md §8. configure = "./configure --prefix=/usr --disable-shared --enable-static" compile = "make -j\"$(nproc)\"" install = "make install"