Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/emulation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
# SPDX-License-Identifier: Apache-2.0

name: Sentry emulator end-to-end

on:
push:
pull_request:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
emulator:
defaults:
run:
shell: bash
runs-on: ubuntu-latest
container:
image: mesonbuild/ubuntu-rolling
steps:
- name: XXX git permission quirk XXX
run: |
git config --global --add safe.directory $GITHUB_WORKSPACE
- uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
set-safe-directory: true
- name: Clone cross-files
uses: actions/checkout@v6
with:
ref: 'main'
repository: 'camelot-os/meson-cross-files'
path: crossfiles
- name: Deploy cross-files
run: |
mkdir -p $HOME/.local/share/meson/cross
cp -a $GITHUB_WORKSPACE/crossfiles/*.ini $HOME/.local/share/meson/cross
echo "MESON_CROSS_FILES=$HOME/.local/share/meson/cross" >> $GITHUB_ENV
- name: install prerequisites pkg
uses: camelot-os/action-install-pkg@v1
with:
packages: 'dtc|device-tree-compiler,libssh2-1|libssh2,curl,lld'
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: 1.86.0
targets: thumbv7m-none-eabi,thumbv7em-none-eabi,thumbv7em-none-eabihf,thumbv8m.base-none-eabi,thumbv8m.main-none-eabi,thumbv8m.main-none-eabihf,x86_64-unknown-linux-gnu
components: clippy,rustfmt
- name: Setup C toolchain
uses: camelot-os/action-setup-compiler@v1
with:
compiler: gcc
triple: arm-none-eabi
ref: '12.3.Rel1'
workspace: $GITHUB_WORKSPACE
- name: deploy local deps
run: |
pip install -r requirements.txt
- name: defconfig
run: |
defconfig configs/stm32f429i_disc1_debug_defconfig
- name: Meson setup and compile
uses: camelot-os/action-meson@v1
with:
cross_files: ${{ format('{0}/{1}', env.MESON_CROSS_FILES, 'cm4-none-eabi-gcc.ini') }}
actions: '["prefetch", "setup", "compile"]'
options: '-Dconfig=.config -Ddts=dts/examples/stm32f429i_disc1_debug.dts -Ddts-include-dirs=dts -Dwith_doc=false -Dwith_tests=false -Dwith_proof=false -Dwith_kernel=false -Dwith_uapi=true -Dwith_idle=false -Dwith_tools=true -Dwith_emulator=true -Dwith_sbom=false -Dwith-install-crates=false'
- name: Run emulator e2e test suite
run: |
cd builddir
meson test --suite emulator --verbose
- name: Meson postcheck
if: failure()
run: |
cat builddir/meson-logs/meson-log.txt || true
cat builddir/meson-logs/testlog.txt || true
33 changes: 33 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,36 @@ path = ["uapi/Cargo.lock", "uapi/rust_target.in", "uapi/rustargs.in"]
SPDX-FileCopyrightText = "2023-2024 Ledger SAS"
SPDX-License-Identifier = "Apache-2.0"

# local build and analysis artifacts
[[annotations]]
path = [
".config",
".config.*",
".ivette",
".vscode/settings.json",
".sonar/**",
"bw_output/**",
]
SPDX-FileCopyrightText = "2026 H2Lab Development Team"
SPDX-License-Identifier = "Apache-2.0"

[[annotations]]
path = [
"config",
"coverage.xml",
"cscope.out",
"cscope.out.in",
"cscope.out.po",
"subprojects.toml",
"results/log.html",
"results/output.xml",
"results/report.html",
"kernel/proof/proof_composition/proof_zlib/.meson.build.swp",
"kernel/src/managers/memory/.memory_mpu.c.swp",
"kernel/proof/tools/gencoverage.py.2",
"kernel/proof/tools/gencoverage.py.metrics",
"kernel/proof/tools/sonarqube.tests.schema",
]
SPDX-FileCopyrightText = "2026 H2Lab Development Team"
SPDX-License-Identifier = "Apache-2.0"

1 change: 1 addition & 0 deletions meson.options
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ option('with_kernel', type: 'boolean', value: true, description: 'Sentry kernel
option('with_uapi', type: 'boolean', value: true, description: 'Sentry UAPI library is built')
option('with_idle', type: 'boolean', value: true, description: 'Sentry Idle task is built')
option('with_tools', type: 'boolean', value: true, description: 'compile naitive tools')
option('with_emulator', type: 'boolean', value: false, description: 'build Python Sentry emulator daemon')
option('with_sbom', type: 'boolean', value: false, description: 'generate SBOMs for python and subprojects')
option('config', type: 'string', description: 'Configuration file to use', yield: true)
option('dts', type: 'string', description: 'Top level DTS file', yield: true)
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ weasyprint
lief
GitPython>=3.1.0
cyclonedx-bom
build
grpcio>=1.80
protobuf>=6.31
26 changes: 26 additions & 0 deletions tools/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,29 @@ endif

subdir('robot')
subdir('genmetadata')

if get_option('with_emulator')
subdir('sentry-emulator')

emulator_start_app_one = '@0@,label=7'.format(sample_rust_app_one_path)
emulator_start_app_two = '@0@,label=8'.format(sample_rust_app_two_path)

test(
'sentry-emulator-rust-apps-e2e',
py3,
args: [
'-c',
'from camelot.sentry_emulator.cli import main; raise SystemExit(main())',
'--log-level',
'ERROR',
'--start',
emulator_start_app_one,
'--start',
emulator_start_app_two,
],
env: {'PYTHONPATH': meson.project_source_root() / 'tools/sentry-emulator/src'},
suite: 'emulator',
timeout: 30,
depends: [sample_rust_build],
)
endif
21 changes: 21 additions & 0 deletions tools/sentry-emulator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
# SPDX-License-Identifier: Apache-2.0

# Python caches and bytecode
__pycache__/
*.py[cod]
*.pyo

# Virtual environments and test tooling
.venv/
.tox/
.mypy_cache/
.pytest_cache/

# Build artifacts
build/
dist/
*.egg-info/

# Rust sample app build artifacts
sample-rust-app/target/
53 changes: 53 additions & 0 deletions tools/sentry-emulator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!-- SPDX-FileCopyrightText: 2026 H2Lab Development Team -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# Camelot Sentry Emulator

This package provides a gRPC daemon used by the POSIX implementation of the `sentry-uapi` crate.

## Features

- listens on gRPC (`127.0.0.1:44044` by default)
- receives syscall commands emitted by the POSIX Rust backend
- protobuf-based serialization/deserialization for syscall payloads
- traces each deserialized command to daemon stderr logs
- validates incoming commands and sorts them by syscall name
- starts applications with labeled contexts (`--start app.elf,label=<u32>`)
- assigns one unique context handle per started application

## Run

```sh
sentry-emulator --host 127.0.0.1 --port 44044
```

To start applications and register their contexts:

```sh
sentry-emulator --start ./build/my-app,label=7 --start ./build/my-other-app,label=12
```

Each started app receives `SENTRY_APP_LABEL`, `SENTRY_EMULATOR_HOST`, and
`SENTRY_EMULATOR_PORT` in its environment.

## Sample Rust tasks

The sample Rust project now builds two binaries with the same validation flow:

- `sample-app-one`
- `sample-app-two`

They are produced under `sample-rust-app/target/debug/` when building the sample
project.

## Local build

```sh
python -m build
```

## Tox workflow

```sh
tox
```
59 changes: 59 additions & 0 deletions tools/sentry-emulator/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
# SPDX-License-Identifier: Apache-2.0


emulator_pyproject = files('pyproject.toml')
emulator_tox = files('tox.ini')

rustmod = import('rust')

emulator_build = custom_target(
'sentry-emulator-build',
input: [emulator_pyproject, emulator_tox],
output: 'sentry-emulator-build.stamp',
command: [
py3,
'-c',
'import pathlib, subprocess, sys; out = pathlib.Path(sys.argv[1]); src = pathlib.Path(sys.argv[2]); dist = pathlib.Path(sys.argv[3]); dist.mkdir(parents=True, exist_ok=True); subprocess.run([sys.executable, "-m", "build", "--sdist", "--wheel", "--outdir", str(dist), str(src)], check=True); out.write_text("ok\\n", encoding="utf-8")',
'@OUTPUT@',
meson.current_source_dir(),
meson.current_build_dir() / 'dist',
],
build_by_default: true,
)

cargo = find_program('cargo', required: true)

sample_rust_manifest = files('sample-rust-app/Cargo.toml')
sample_rust_sources = files([
'sample-rust-app/src/lib.rs',
'sample-rust-app/src/bin/sample-app-one.rs',
'sample-rust-app/src/bin/sample-app-two.rs',
])
sample_rust_target_dir = meson.current_build_dir() / 'sample-rust-target'
sample_rust_app_one_path = sample_rust_target_dir / 'debug' / 'sample-app-one'
sample_rust_app_two_path = sample_rust_target_dir / 'debug' / 'sample-app-two'

sample_rust_build = custom_target(
'sentry-emulator-sample-rust-app-build',
input: [sample_rust_manifest, sample_rust_sources],
output: 'sentry-emulator-sample-rust-app-build.stamp',
command: [
py3,
'-c',
'import pathlib, subprocess, sys; out = pathlib.Path(sys.argv[1]); manifest = pathlib.Path(sys.argv[2]); target = pathlib.Path(sys.argv[3]); subprocess.run(["cargo", "build", "--manifest-path", str(manifest), "--target-dir", str(target)], check=True); out.write_text("ok\\n", encoding="utf-8")',
'@OUTPUT@',
sample_rust_manifest[0 ].full_path(),
sample_rust_target_dir,
],
build_by_default: true,
)

summary(
{
'sentry emulator artifacts': meson.current_build_dir() / 'dist',
'sentry emulator sample rust app one': sample_rust_app_one_path,
'sentry emulator sample rust app two': sample_rust_app_two_path,
},
section: 'Tools',
)
23 changes: 23 additions & 0 deletions tools/sentry-emulator/proto/emulator.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2026 H2Lab Development Team
// SPDX-License-Identifier: Apache-2.0

syntax = "proto3";

package camelot.sentry.emulator;

service Emulator {
rpc Dispatch(DispatchRequest) returns (DispatchResponse);
}

message DispatchRequest {
string syscall = 1;
repeated sint64 args = 2;
uint32 label = 3;
bytes payload = 4;
}

message DispatchResponse {
sint32 status = 1;
string detail = 2;
bytes payload = 3;
}
66 changes: 66 additions & 0 deletions tools/sentry-emulator/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
# SPDX-License-Identifier: Apache-2.0

[build-system]
requires = [
"setuptools>=69",
"setuptools-scm[toml]>=8",
"wheel",
]
build-backend = "setuptools.build_meta"

[project]
name = "camelot.sentry-emulator"
description = "gRPC daemon that emulates Sentry kernel syscalls in POSIX mode"
readme = "README.md"
requires-python = ">=3.11"
license = "Apache-2.0"
authors = [
{ name = "H2Lab Development Team" },
]
keywords = ["sentry", "emulator", "grpc", "kernel", "camelot"]
dependencies = [
"grpcio>=1.80",
"protobuf>=6.31",
]
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Embedded Systems",
]
dynamic = ["version"]

[project.urls]
Homepage = "https://github.com/camelot-os/sentry-kernel"
Repository = "https://github.com/camelot-os/sentry-kernel"

[project.scripts]
sentry-emulator = "camelot.sentry_emulator.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

[tool.setuptools_scm]
root = "../.."
fallback_version = "0.0.0+unknown"

[tool.pytest.ini_options]
pythonpath = ["src"]
addopts = "-q"
testpaths = ["tests"]
markers = [
"emulator: end-to-end tests that launch the emulator with real startup apps",
]

[tool.mypy]
python_version = "3.11"
strict = true
warn_unused_configs = true
exclude = "(^build/|^dist/|src/camelot/sentry_emulator/grpc/)"
packages = ["camelot.sentry_emulator"]

[[tool.mypy.overrides]]
module = ["camelot.sentry_emulator.grpc.*"]
ignore_errors = true
Loading
Loading