Cross-Compiling Pyvorin Edge Kernels for ARM64
Build ARM64-native shared objects from an x86 machine using Docker buildx, verify artifacts with the file command, and automate everything in GitHub Actions.
Published Jun 2, 2026
Introduction
Most Pyvorin Edge deployments target ARM64 Linux devices (Raspberry Pi, NVIDIA Jetson, custom boards), but CI/CD often runs on x86_64. Cross-compilation lets you build .so artifacts on an x86 host and ship them to the edge without requiring native ARM64 build nodes.
Toolchain Setup
On Ubuntu/Debian install the cross-compiler and standard library headers for ARM64:
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross
Verify the compiler is on PATH:
aarch64-linux-gnu-gcc --version
# aarch64-linux-gnu-gcc (Ubuntu 13.2.0) 13.2.0
Cross-Compiling a Kernel
Use the same flags as a native build, but swap the compiler prefix. The -march flag should match your target SoC. For a generic ARMv8-A device:
aarch64-linux-gnu-gcc \
-shared -fPIC -O3 -march=armv8-a+fp+simd \
-o libvecadd_arm64.so vec_add_neon.c
For a Cortex-A72 specific optimization (Raspberry Pi 4, Jetson Nano):
aarch64-linux-gnu-gcc \
-shared -fPIC -O3 -mcpu=cortex-a72+fp+simd -mtune=cortex-a72 \
-o libvecadd_arm64.so vec_add_neon.c
Verifying Compiled Artifacts
Always confirm the architecture of the output before deployment. The file utility reads the ELF header and reports the target machine.
file libvecadd_arm64.so
# libvecadd_arm64.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked
Also inspect exported symbols to ensure the ABI contract matches:
aarch64-linux-gnu-nm -D libvecadd_arm64.so | grep vec_add_f32
# 0000000000000730 T vec_add_f32
Docker buildx with --platform linux/arm64
Docker buildx is the most reliable way to cross-compile in CI. You write a multi-stage Dockerfile that builds inside an ARM64 container running under QEMU, or you use a native cross-compiler image.
Fast Cross-Build Dockerfile (using cross-compiler)
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM debian:bookworm-slim AS builder
RUN apt-get update && apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross
WORKDIR /src
COPY vec_add_neon.c .
RUN aarch64-linux-gnu-gcc \
-shared -fPIC -O3 -march=armv8-a+fp+simd \
-o /out/libvecadd_arm64.so vec_add_neon.c
FROM scratch AS export
COPY --from=builder /out/libvecadd_arm64.so /
Build and Extract
docker buildx build \
--platform linux/arm64 \
--target export \
--output type=local,dest=./artifacts \
.
Manifest Verification After Cross-Compile
After cross-compilation, generate a manifest with CompilerBridge and include the SHA-256 hash of the .so. The edge runtime verifies this hash before loading.
import hashlib
import json
from pathlib import Path
from pyvorin_edge.compiler_bridge import CompilerBridge
so_path = Path("./artifacts/libvecadd_arm64.so")
# Compute hash
hasher = hashlib.sha256()
with open(so_path, "rb") as fh:
while chunk := fh.read(8192):
hasher.update(chunk)
so_hash = hasher.hexdigest()
# Generate ABI + manifest
bridge = CompilerBridge()
abi = bridge.generate_abi(so_path, "vec_add_f32")
manifest = {
"version": "1.0.0",
"platform": "linux/arm64",
"modules": [
{
"name": "vec_add_f32",
"path": str(so_path.name),
"abi": abi.to_dict(),
"sha256": so_hash,
}
],
}
with open("manifest.json", "w") as fh:
json.dump(manifest, fh, indent=2)
print(json.dumps(manifest, indent=2))
CI/CD Pipeline Example (GitHub Actions)
The workflow below cross-compiles on every push, verifies the ELF architecture, generates the manifest, and uploads artifacts.
name: Cross-Compile ARM64 Kernels
on:
push:
branches: [main]
paths:
- "kernels/**/*.c"
- ".github/workflows/cross-compile.yml"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cross-compile with buildx
run: |
docker buildx build \
--platform linux/arm64 \
--target export \
--output type=local,dest=./artifacts \
-f kernels/Dockerfile .
- name: Verify ELF architecture
run: |
for so in ./artifacts/*.so; do
echo "Checking $so"
file "$so" | grep -q "ARM aarch64" || exit 1
done
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install -e ./edge_sdk
- name: Generate manifest
run: python kernels/generate_manifest.py
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: arm64-kernels
path: |
artifacts/*.so
manifest.json
Runtime Verification on the Edge Device
When the edge agent boots, it loads the manifest and verifies each module before calling ModuleLoader.load(). The verify_signature() method in module_host/loader.py performs the SHA-256 check.
from pyv_edge_agent.module_host.loader import ModuleLoader
import json
manifest = json.load(open("manifest.json"))
loader = ModuleLoader()
for mod in manifest["modules"]:
loader.load(mod["path"], ABIContract.from_manifest(mod["abi"]))
if not loader.verify_signature(mod["sha256"]):
raise RuntimeError(f"Integrity check failed for {mod['name']}")
print(f"{mod['name']} verified and loaded.")
Summary
Cross-compilation for ARM64 is straightforward with aarch64-linux-gnu-gcc or Docker buildx. Always verify the ELF architecture with file, generate a manifest with SHA-256 hashes, and check those hashes at runtime via ModuleLoader.verify_signature(). Automate the entire flow in GitHub Actions for reproducible edge deployments.