ci(freebsd): run cross-compiled FreeBSD binaries under emulation (Step 8)
Host-side static ELF checks (EI_OSABI/ET_EXEC/no-PT_INTERP, already asserted by
TInternalLinkerE2ETests.TestCompile_FreeBSDTarget_EmitsStaticFreeBSDExe) cannot
catch a wrong FreeBSD struct-stat offset or syscall number — only running the
binary on a real FreeBSD kernel does.
Add a freebsd-crosscheck CI job (needs: bootstrap) that:
* downloads the -pre binary the bootstrap job built,
* cross-compiles the fixtures in compiler/src/it/freebsd (hello, fileio) with
--target freebsd-x86_64 on the Linux runner via
scripts/freebsd-crosscheck-build.sh (which also sanity-checks each emitted
ELF is a FreeBSD ET_EXEC), and
* boots a FreeBSD VM (vmactions/freebsd-vm, release 14.2 to match the
struct-stat offsets in rtl.platform.layout.freebsd) and runs the binaries
there, asserting hello prints 'Hello' and fileio round-trips a file
(write/read/stat/delete) with 'file-io-ok'/'exists-ok'/'done'.
The FreeBSD output is a static, freestanding raw-syscall ET_EXEC, so the VM
needs no extra packages to run it. fileio is the meaningful check: it exercises
open/write/read/stat/unlink through the FreeBSD leaf and layout, which the
static checks are blind to.
This commit is contained in:
parent
d48cbc64a0
commit
0ee32e51f5
76
.github/workflows/bootstrap.yml
vendored
76
.github/workflows/bootstrap.yml
vendored
|
|
@ -274,3 +274,79 @@ jobs:
|
|||
${{ steps.pre.outputs.dir }}/blaise_rtl.a
|
||||
retention-days: 30
|
||||
if-no-files-found: error
|
||||
|
||||
# ======================================================================
|
||||
# FreeBSD cross-compile verification under emulation
|
||||
# (docs/freebsd-x86_64-backend-design.adoc Step 8).
|
||||
#
|
||||
# The bootstrap job's TestRunner already runs the HOST-side static ELF
|
||||
# checks (EI_OSABI / ET_EXEC / no PT_INTERP via
|
||||
# TInternalLinkerE2ETests.TestCompile_FreeBSDTarget_EmitsStaticFreeBSDExe).
|
||||
# Those cannot catch a wrong FreeBSD struct-stat offset or syscall number —
|
||||
# only running the binary on a real FreeBSD kernel does. This job
|
||||
# cross-compiles the fixtures on the Linux runner, then boots a FreeBSD VM
|
||||
# (vmactions/freebsd-vm) and executes them there.
|
||||
#
|
||||
# The FreeBSD output is a static, freestanding raw-syscall ET_EXEC (no libc,
|
||||
# no rtld), so the VM needs no extra packages to run it.
|
||||
# ======================================================================
|
||||
freebsd-crosscheck:
|
||||
needs: bootstrap
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# The RTL is compiled from source by the -pre binary, so the compiler
|
||||
# needs no prebuilt blaise_rtl.a beside it — only the checked-out RTL
|
||||
# source (present after checkout). Pull the -pre binary the bootstrap
|
||||
# job just built and published.
|
||||
- name: Download -pre binary
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: blaise-pre-${{ github.sha }}
|
||||
path: .pre
|
||||
|
||||
- name: Cross-compile FreeBSD fixtures (Linux host)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
chmod +x .pre/blaise
|
||||
BLAISE="$PWD/.pre/blaise" scripts/freebsd-crosscheck-build.sh
|
||||
echo "Cross-built FreeBSD binaries:"
|
||||
ls -l target/freebsd-crosscheck
|
||||
|
||||
# Boot a FreeBSD VM and run the cross-compiled binaries there. The
|
||||
# workspace is rsync-mounted into the VM, so target/freebsd-crosscheck/*
|
||||
# is available inside. release pinned to a 14.x line: the struct-stat
|
||||
# offsets in rtl.platform.layout.freebsd are verified against FreeBSD 14
|
||||
# (stable since FreeBSD 12). Bump when 15.x is validated.
|
||||
- name: Run FreeBSD binaries under emulation
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "14.2"
|
||||
usesh: true
|
||||
copyback: false
|
||||
run: |
|
||||
set -e
|
||||
cd target/freebsd-crosscheck
|
||||
echo "=== uname ==="
|
||||
uname -a
|
||||
|
||||
echo "=== hello ==="
|
||||
OUT="$(./hello)"
|
||||
echo "$OUT"
|
||||
[ "$OUT" = "Hello" ] || { echo "FAIL: hello output '$OUT' != 'Hello'"; exit 1; }
|
||||
|
||||
echo "=== fileio ==="
|
||||
OUT="$(./fileio)"
|
||||
echo "$OUT"
|
||||
# Flatten to a single line (newlines -> '|') so the compare stays a
|
||||
# one-line shell string inside this YAML block scalar.
|
||||
GOT="$(printf '%s' "$OUT" | tr '\n' '|')"
|
||||
[ "$GOT" = "file-io-ok|exists-ok|done" ] || {
|
||||
echo "FAIL: fileio output '$GOT' != 'file-io-ok|exists-ok|done'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "FREEBSD_EMULATION_OK"
|
||||
|
|
|
|||
25
compiler/src/it/freebsd/fileio.pas
Normal file
25
compiler/src/it/freebsd/fileio.pas
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{ FreeBSD cross-compile emulation smoke test (docs/freebsd-x86_64-backend-design
|
||||
Step 8). Exercises the file path that STATIC ELF checks cannot catch: a wrong
|
||||
struct-stat offset or syscall number only shows up when the binary actually
|
||||
runs on FreeBSD. Write a file, read it back, and stat it (FileExists), then
|
||||
print the results. Expected output under FreeBSD:
|
||||
|
||||
file-io-ok
|
||||
exists-ok
|
||||
done
|
||||
|
||||
A wrong FreeBSD st_size / st_mode offset (they differ from Linux — see
|
||||
rtl.platform.layout.freebsd) would make ReadFile or FileExists misbehave. }
|
||||
program fileio;
|
||||
uses sysutils;
|
||||
var
|
||||
S: string;
|
||||
begin
|
||||
WriteFile('blaise_fbsd_it.txt', 'file-io-ok');
|
||||
S := ReadFile('blaise_fbsd_it.txt');
|
||||
WriteLn(S);
|
||||
if FileExists('blaise_fbsd_it.txt') then
|
||||
WriteLn('exists-ok');
|
||||
DeleteFile('blaise_fbsd_it.txt');
|
||||
WriteLn('done');
|
||||
end.
|
||||
9
compiler/src/it/freebsd/hello.pas
Normal file
9
compiler/src/it/freebsd/hello.pas
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{ FreeBSD cross-compile emulation smoke test (docs/freebsd-x86_64-backend-design
|
||||
Step 8). Cross-compiled with --target freebsd-x86_64 on the Linux CI runner,
|
||||
then executed inside a FreeBSD VM. A raw-syscall static ET_EXEC: printing
|
||||
Hello and exiting 0 proves _start, argv capture, write(2) and _exit(2) work
|
||||
under the real FreeBSD kernel. }
|
||||
program hello;
|
||||
begin
|
||||
WriteLn('Hello');
|
||||
end.
|
||||
73
scripts/freebsd-crosscheck-build.sh
Executable file
73
scripts/freebsd-crosscheck-build.sh
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Cross-compile the FreeBSD emulation-check fixtures (compiler/src/it/freebsd/*.pas)
|
||||
# with --target freebsd-x86_64 on a Linux host. The resulting static, freestanding
|
||||
# FreeBSD ET_EXEC binaries are then run inside a FreeBSD VM (see the freebsd-crosscheck
|
||||
# job in .github/workflows/bootstrap.yml) to validate the FreeBSD syscall leaf and
|
||||
# struct-stat layout that host-side static ELF checks cannot catch.
|
||||
#
|
||||
# Usage:
|
||||
# BLAISE=/path/to/blaise scripts/freebsd-crosscheck-build.sh [OUTDIR]
|
||||
#
|
||||
# BLAISE path to a native-backend blaise binary that supports --target
|
||||
# freebsd-x86_64 (defaults to compiler/target/blaise).
|
||||
# OUTDIR where to write the cross-compiled binaries (defaults to
|
||||
# target/freebsd-crosscheck).
|
||||
#
|
||||
# The binaries CANNOT run on the Linux host (FreeBSD syscall numbers/ABI); this
|
||||
# script only builds and sanity-checks the emitted ELF shape. Prints
|
||||
# FREEBSD_CROSSBUILD_OK on success.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
BLAISE="${BLAISE:-compiler/target/blaise}"
|
||||
OUTDIR="${1:-target/freebsd-crosscheck}"
|
||||
FIXDIR="compiler/src/it/freebsd"
|
||||
|
||||
if [ ! -x "$BLAISE" ]; then
|
||||
echo "error: blaise binary not found or not executable: $BLAISE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
# The compiler resolves blaise_rtl.a / the RTL source relative to its own binary;
|
||||
# the RTL source lives in the compiler tree. Both unit paths mirror the normal
|
||||
# native build.
|
||||
UNIT_ARGS=(
|
||||
--unit-path runtime/src/main/pascal
|
||||
--unit-path stdlib/src/main/pascal
|
||||
)
|
||||
|
||||
for src in "$FIXDIR"/*.pas; do
|
||||
name="$(basename "$src" .pas)"
|
||||
out="$OUTDIR/$name"
|
||||
echo "cross-compiling $src -> $out (freebsd-x86_64)"
|
||||
"$BLAISE" \
|
||||
--source "$src" \
|
||||
--backend native \
|
||||
--target freebsd-x86_64 \
|
||||
--output "$out" \
|
||||
"${UNIT_ARGS[@]}"
|
||||
|
||||
# Sanity-check the emitted ELF is a FreeBSD (EI_OSABI=9) ET_EXEC. readelf is
|
||||
# part of binutils, always present on the Linux runner.
|
||||
osabi="$(readelf -hW "$out" | awk -F: '/OS\/ABI/ {print $2}' | xargs)"
|
||||
etype="$(readelf -hW "$out" | awk -F: '/Type:/ {print $2}' | awk '{print $1}')"
|
||||
case "$osabi" in
|
||||
*FreeBSD*) : ;;
|
||||
*) echo "error: $out is not a FreeBSD binary (OS/ABI=$osabi)" >&2; exit 1 ;;
|
||||
esac
|
||||
if [ "$etype" != "EXEC" ]; then
|
||||
echo "error: $out is not a static ET_EXEC (Type=$etype)" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Drop the .o intermediates the compiler leaves beside each output, so the VM
|
||||
# job can run every executable in OUTDIR without tripping over object files.
|
||||
rm -f "$OUTDIR"/*.o
|
||||
|
||||
echo "FREEBSD_CROSSBUILD_OK"
|
||||
Loading…
Reference in a new issue