RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / scripts / screenshot.sh
2594 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env bash
# Boot the normal (non-headless) kernel with no window, "type" some keys into
# it through the QEMU monitor's `sendkey` command, then dump the VGA screen to
# a PNG. Doubles as an automated keyboard-interrupt test: the typed text is
# echoed on the serial port too, so we can grep for it.
#
# Usage: ./scripts/screenshot.sh [out.png] [text-to-type] [expected-result]
# In text-to-type, "<" means Backspace. The default types a typo and fixes it.
set -euo pipefail
cd "$(dirname "$0")/.."

cargo build
# Force bootimage to repack the disk image (see scripts/runner.sh for why).
touch target/x86_64-rust_kernel/debug/rust_kernel
cargo bootimage
IMG=target/x86_64-rust_kernel/debug/bootimage-rust_kernel.bin
OUT="${1:-$PWD/boot.png}"
TEXT="${2:-Hello, kernex<l!}"
EXPECT="${3:-Hello, kernel!}"
SOCK=$(mktemp -u /tmp/qemu-mon.XXXXXX)
SERIAL_LOG=$(mktemp /tmp/serial.XXXXXX)

qemu-system-x86_64 -drive format=raw,file="$IMG" \
    -serial file:"$SERIAL_LOG" -display none -monitor unix:"$SOCK",server,nowait &
QPID=$!
sleep 6   # boot, and let the demo threads finish their serial output

# Talk to the QEMU monitor over its UNIX socket (python avoids needing socat).
python3 - "$SOCK" "$OUT" "$TEXT" <<'PY'
import socket, sys, time
sock, out, text = sys.argv[1:]
# QEMU key names for the characters we might type.
names = {'<': 'backspace', ' ': 'spc', ',': 'comma', '.': 'dot', '!': 'shift-1', '?': 'shift-slash',
         '-': 'minus', '\n': 'ret'}
def key(c):
    if c in names: return names[c]
    if c.isupper(): return 'shift-' + c.lower()
    return c
s = socket.socket(socket.AF_UNIX); s.connect(sock)
for c in text:
    s.sendall(f"sendkey {key(c)}\n".encode()); time.sleep(0.1)
time.sleep(0.5)
s.sendall(f"screendump {out} -f png\n".encode()); time.sleep(1)
s.sendall(b"quit\n"); time.sleep(0.5)
PY
wait $QPID || true
echo "---- serial output ----"
cat -v "$SERIAL_LOG"; echo   # cat -v shows Backspace as ^H
echo "-----------------------"
# Replay the serial output like a terminal would ("\b \b" = erase one
# character) and check the expected text is there.
if python3 - "$SERIAL_LOG" "$EXPECT" <<'PY'
import sys
data = open(sys.argv[1], 'rb').read().decode(errors='replace').replace('\x08 \x08', '\x08')
out = []
for ch in data:
    if ch == '\x08':
        if out: out.pop()
    else:
        out.append(ch)
sys.exit(0 if sys.argv[2] in ''.join(out) else 1)
PY
then
    echo "[screenshot] keyboard echo OK: typed '$TEXT' -> '$EXPECT'"
else
    echo "[screenshot] keyboard echo NOT found (expected '$EXPECT')"
fi
rm -f "$SERIAL_LOG"
echo "[screenshot] saved $OUT"