Files
LithosAnanake/scripts/qemu_screenshot.sh
T
Robert Allan James ab96ac0970 starkernel: item 4.3.1 -- framebuffer orientation test, found and fixed a real color-swap bug
Adds fb_draw_orientation_test() (framebuffer.c/.h): fills the four raster
corners RED/GREEN/BLUE/YELLOW via fb_fill_rect. Wired into kernel_main.c
calling fb_init() directly -- console_fb_init()/vt100_init() removed from
the boot path, since vt100.c/console.c are superseded by the Console
drawing-fabric redesign (FABRIC.md ss27) and should not be exercised even
incidentally.

The diagnostic caught a real, pre-existing bug on its first run: framebuffer.c's
pack_pixel() had its FB_PIXEL_RGBX32/FB_PIXEL_BGRX32 branches swapped relative
to UEFI GOP's own byte-order naming convention, producing a clean R<->B channel
swap (G unaffected). Spatial placement was already correct -- no flip/rotation.
Fixed by swapping pack_pixel's two return bodies to match framebuffer.h's
already-correct doc comments; kernel_main.c's GOP-format switch needed no change.

Also item 4.3.2 -- QEMU screenshot capability. scripts/qemu_screenshot.sh
already existed (monitor socket + socat + HMP screendump), just unwired and
unused this session. Redirected its PNG output to a new top-level fb/
directory (tracked in git, not logs/, not a gitignored temp dir) and added a
python3+PIL fallback for PPM->PNG conversion since imagemagick isn't
installed here. Left as a standalone script for now, not wired into a
Makefile target.

FABRIC.md items 4.3.1 and 4.3.2 marked done with acceptance evidence.
2026-08-07 11:38:33 -04:00

168 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# qemu_screenshot.sh — Boot LithosAnanke under QEMU, wait for ok>, capture framebuffer.
#
# Usage:
# scripts/qemu_screenshot.sh <LOADER_EFI> <OVMF_CODE> <OVMF_VARS_RO> <BUILD_DIR> <LOG_DIR>
# scripts/qemu_screenshot.sh <LOADER_EFI> <OVMF_CODE> <OVMF_VARS_RO> <BUILD_DIR> <LOG_DIR> <PRE_BUILT_ISO>
#
# If PRE_BUILT_ISO is supplied the ISO build step is skipped (used by the `qemu`
# Makefile target which already built the ISO during the serial acceptance run).
#
# Outputs:
# <BUILD_DIR>/screenshot.ppm — raw PPM from QEMU screendump
# <LOG_DIR>/qemu-screenshot-YYYYMMDD-HHMMSS.log — serial log
# fb/qemu-screenshot-YYYYMMDD-HHMMSS.png — PNG (tracked in git, not build/)
#
# Requires: qemu-system-x86_64, socat, xorriso, mtools, ovmf, imagemagick
set -e
LOADER_EFI="$1"
OVMF_CODE="$2"
OVMF_VARS_RO="$3"
BUILD_DIR="$4"
LOG_DIR="$5"
PRE_BUILT_ISO="$6" # optional: skip ISO build if supplied
if [ -z "$LOADER_EFI" ] || [ -z "$OVMF_CODE" ] || [ -z "$OVMF_VARS_RO" ] \
|| [ -z "$BUILD_DIR" ] || [ -z "$LOG_DIR" ]; then
echo "Usage: $0 <LOADER_EFI> <OVMF_CODE> <OVMF_VARS_RO> <BUILD_DIR> <LOG_DIR> [PRE_BUILT_ISO]"
exit 1
fi
for dep in qemu-system-x86_64 socat xorriso mformat; do
if ! command -v "$dep" >/dev/null 2>&1; then
echo "Error: $dep not found. Install: apt-get install ${dep%%_*}"
exit 1
fi
done
# --------------------------------------------------------------------------
# Build ISO — skipped when a pre-built ISO is provided (e.g. from `qemu` target)
# --------------------------------------------------------------------------
if [ -n "$PRE_BUILT_ISO" ]; then
QEMU_ISO="$PRE_BUILT_ISO"
echo " Using pre-built ISO: ${QEMU_ISO}"
else
echo " Building ISO for screendump..."
mkdir -p "${BUILD_DIR}/iso"
dd if=/dev/zero of="${BUILD_DIR}/iso/efi.img" bs=512 count=8192 2>/dev/null
mformat -i "${BUILD_DIR}/iso/efi.img" ::
mmd -i "${BUILD_DIR}/iso/efi.img" ::/EFI
mmd -i "${BUILD_DIR}/iso/efi.img" ::/EFI/BOOT
mcopy -i "${BUILD_DIR}/iso/efi.img" "${LOADER_EFI}" ::/EFI/BOOT/BOOTX64.EFI
xorriso -as mkisofs -r -J \
-e efi.img -no-emul-boot \
-o "${BUILD_DIR}/starkernel.iso" "${BUILD_DIR}/iso" 2>&1 | grep -v "^xorriso" || true
QEMU_ISO="${BUILD_DIR}/starkernel.iso"
fi
# --------------------------------------------------------------------------
# Setup paths
# --------------------------------------------------------------------------
FB_DIR="fb"
mkdir -p "${LOG_DIR}" "${BUILD_DIR}" "${FB_DIR}"
TS=$(date +%Y%m%d-%H%M%S)
SERIAL_LOG="${LOG_DIR}/qemu-screenshot-${TS}.log"
VARS_COPY="${BUILD_DIR}/OVMF_VARS_screenshot.fd"
MONITOR_SOCK="${BUILD_DIR}/qemu-monitor-${TS}.sock"
PPM_OUT="${BUILD_DIR}/screenshot.ppm"
PNG_LOG="${FB_DIR}/qemu-screenshot-${TS}.png" # committed artifact
QEMU_PID_FILE="${BUILD_DIR}/qemu-screenshot.pid"
cp "${OVMF_VARS_RO}" "${VARS_COPY}"
echo " Serial log : ${SERIAL_LOG}"
echo " Monitor : ${MONITOR_SOCK}"
# --------------------------------------------------------------------------
# Launch QEMU in background with:
# -serial file:<log> — capture text output
# -monitor unix:<sock>,server,nowait — HMP monitor on UNIX socket
# -display none — headless; VGA surface still exists
# (VGA is the default device on q35; OVMF GOP maps to it)
# --------------------------------------------------------------------------
qemu-system-x86_64 \
-machine q35,accel=tcg \
-cpu qemu64 \
-m 1024 \
-drive if=pflash,format=raw,readonly=on,file="${OVMF_CODE}" \
-drive if=pflash,format=raw,file="${VARS_COPY}" \
-cdrom "${QEMU_ISO}" -boot d \
-serial file:"${SERIAL_LOG}" \
-monitor unix:"${MONITOR_SOCK}",server,nowait \
-display none \
-no-reboot \
&
QEMU_PID=$!
echo "$QEMU_PID" > "${QEMU_PID_FILE}"
echo " QEMU PID : ${QEMU_PID}"
# --------------------------------------------------------------------------
# Wait for ok> prompt in serial log (up to 120 s)
# --------------------------------------------------------------------------
echo " Waiting for ok> prompt..."
DEADLINE=$(( $(date +%s) + 1800 ))
while true; do
if [ "$(date +%s)" -ge "$DEADLINE" ]; then
echo " TIMEOUT waiting for ok> — killing QEMU"
kill "$QEMU_PID" 2>/dev/null || true
exit 1
fi
if grep -q "ok>" "${SERIAL_LOG}" 2>/dev/null; then
echo " ok> detected!"
break
fi
sleep 1
done
# Give the framebuffer a moment to fully paint before capturing
sleep 2
# --------------------------------------------------------------------------
# Issue screendump via HMP monitor socket
# --------------------------------------------------------------------------
echo "screendump ${PPM_OUT}" | socat - "UNIX-CONNECT:${MONITOR_SOCK}" 2>/dev/null || true
sleep 1
# --------------------------------------------------------------------------
# Terminate QEMU
# --------------------------------------------------------------------------
echo "quit" | socat - "UNIX-CONNECT:${MONITOR_SOCK}" 2>/dev/null || true
sleep 1
kill "$QEMU_PID" 2>/dev/null || true
wait "$QEMU_PID" 2>/dev/null || true
rm -f "${QEMU_PID_FILE}" "${MONITOR_SOCK}" "${VARS_COPY}"
# --------------------------------------------------------------------------
# Convert PPM → PNG
# --------------------------------------------------------------------------
if [ -f "${PPM_OUT}" ]; then
echo " PPM : ${PPM_OUT} ($(wc -c < "${PPM_OUT}") bytes)"
if command -v convert >/dev/null 2>&1; then
convert "${PPM_OUT}" "${PNG_LOG}"
rm -f "${PPM_OUT}"
echo " PNG : ${PNG_LOG}"
elif command -v python3 >/dev/null 2>&1 && python3 -c "import PIL" >/dev/null 2>&1; then
python3 -c "from PIL import Image; Image.open('${PPM_OUT}').save('${PNG_LOG}')"
rm -f "${PPM_OUT}"
echo " PNG : ${PNG_LOG} (via python3/PIL)"
else
echo " WARN: no PPM->PNG converter found (imagemagick 'convert' or python3+PIL) — PPM only"
PNG_LOG="${FB_DIR}/$(basename "${PNG_LOG}" .png).ppm"
mv "${PPM_OUT}" "${PNG_LOG}"
fi
echo ""
echo "=== Screendump complete ==="
echo " Serial log : ${SERIAL_LOG}"
echo " Screenshot : ${PNG_LOG}"
else
echo " ERROR: PPM not written — screendump may have failed"
echo " Serial log: ${SERIAL_LOG}"
cat "${SERIAL_LOG}" 2>/dev/null | tail -20
exit 1
fi