gen-secure
Generate NSC veneer binary and NS interface modules for S/NS programs
Name
gen-secure – generate NSC veneer binary and NS interface modules for TrustZone S/NS programs
Synopsis
gen-secure s_map_file [module …] [--nsc-addr ADDR] [--cfg-file PATH] [--const-leaf NAME] [--nsc-dir DIR] [--ns-dir DIR] [-v]
Description
gen-secure reads an Astrobe linker .map file of a Secure program
and the .lst files of the specified modules to generate everything
the Non-secure side needs to call Secure procedures through TrustZone
veneers.
The tool generates:
-
NSC.bin – a combined Non-secure Callable veneer binary containing SG (Secure Gateway) sequences for all exported procedures across all specified modules. Padded to a 32-byte boundary.
-
NSC.alst – a listing of the veneer code, formatted as an absolute assembly listing for use with gen-rdb and make-elf for veneer-level debugging (stepping through SG sequences in a debugger).
-
NS_*.mod – Oberon interface modules for each exposed Secure module, plus dependency modules. Each NS_ module contains the CONST, TYPE, and stub PROCEDURE definitions that the NS compiler needs to generate calls through the veneers.
The tool implements the ARM TrustZone calling convention for Cortex-M processors with the Security Extension (Armv8-M).
Prerequisites
Before running gen-secure, the Secure program must be compiled and linked. The following files must be present:
- the Secure linker
.mapfile (passed as the positional argument) - all
.lstfiles for the modules to be exposed (referenced by the.mapfile) - the
.lstfiles of any modules that provide types or constants used by the exposed modules (resolved automatically via dependency walking)
Configuration File
Configuration can be given via command line arguments, a configuration
file, or both. The default configuration file gen-secure.cfg in the
current working directory is loaded automatically if present. An
alternative file can be specified with --cfg-file.
The file uses INI format with a single
[gen-secure] section:
[gen-secure]
nsc-addr = 00C0FE000H
ns-dir = nonsec/ns_
nsc-dir = sec
const-leaf = MCU
modules =
S0
Kernel
TextIO
Supported keys:
- nsc-addr: NSC veneer base address (hex; bare, 0x prefix, or trailing H accepted)
- ns-dir: output directory for NS_*.mod files
- nsc-dir: output directory for NSC.bin and NSC.alst
- const-leaf: CONST leaf module name
- modules: exposed Secure module names, one per indented continuation line
Command line arguments override configuration file values. For modules, the rule is either/or: if any module is given on the command line, the configuration file's modules list is ignored entirely.
Comments are supported: lines starting with # or ; are ignored.
Options
- s_map_file
- Path to the Secure program
.mapfile (positional, required). The tool reads module addresses and file paths from this file. - module
- One or more Secure module names to expose to NS (positional,
optional if given in a configuration file). Module names can be
given as bare names (
S0) or with.modsuffix (S0.mod). - --nsc-addr ADDR / --nsc-addr=ADDR
- Base address of the NSC veneer region in memory (hex). This is the
address where the first SG instruction will be placed. Accepts bare
hex (
C0FE000), 0x prefix (0xC0FE000), or trailing H (00C0FE000H). Required unless given in the configuration file. - --cfg-file PATH / --cfg-file=PATH
- Path to a configuration file. If not given,
gen-secure.cfgin the current working directory is loaded automatically if it exists. - --const-leaf NAME / --const-leaf=NAME
- Name of the CONST leaf module. Dependency walking stops at this
module – its own dependencies are not followed. Typically the
MCU definition module (eg.
MCU). - --nsc-dir DIR / --nsc-dir=DIR
- Output directory for NSC.bin and NSC.alst. Default: the
.mapfile's parent directory. The directory must exist. - --ns-dir DIR / --ns-dir=DIR
- Output directory for NS_*.mod files. Required unless given in the configuration file. The directory must exist.
- -v, --verbose
- Verbose output. Print module classifications, skipped dependencies, and additional detail.
Output Files
NSC Veneer Binary (NSC.bin)
A raw binary containing one 16-byte veneer entry per exported procedure, across all exposed modules. Each entry consists of:
- SG (Secure Gateway) instruction (4 bytes)
ldr.w r11,[pc,#4]– load target address (4 bytes)bx r11+nop– branch to Secure procedure (4 bytes)- target address with Thumb bit set (4 bytes)
The binary is padded to a 32-byte boundary. Veneer entries are ordered by module (in the order specified), then by procedure declaration order within each module.
This binary is typically combined with the Secure program binary to form the Secure ELF (see make-elf).
NSC Listing (NSC.alst)
A listing of the veneer code in the .alst format used by
gen-rdb. Each veneer is
shown as a procedure with absolute addresses, opcodes, and mnemonics.
Including this file in the rdb/ directory (via gen-rdb's --nsc-dir
option) enables veneer-level debugging – stepping through SG sequences
in a debugger.
NS Interface Modules (NS_*.mod)
One Oberon source module per resolved dependency. Each module is named
NS_<Module>.mod and is generated with one of three classifications:
-
exposed: full interface – CONST, TYPE, and stub PROCEDUREs that branch to the NSC veneers. One per module specified on the command line or in the configuration file.
-
type_only: TYPE and CONST definitions only (no procedures). Generated when an exposed module's types reference types from another Secure module.
-
const_only: CONST definitions only. Generated when an exposed module's constants reference constants from another Secure module.
All qualified references in the generated modules are rewritten to use
NS_ prefixed names (eg. S0.TwoBits becomes NS_S0.TwoBits).
Only exported types are included. All fields of exported RECORD types are emitted (including non-exported fields), because the NS compiler needs the full layout for correct code generation.
Dependency Resolution
Starting from the exposed modules, gen-secure walks type and constant dependencies to discover which additional Secure modules need NS_ counterparts.
For each exposed module:
- Extract CONST and TYPE sections from the
.lstfile - Scan for qualified references (eg.
S0.TwoBits,MCU.GPIO_BASE) - Resolve aliases via the module's IMPORT list
- If the referenced module is in the
.mapfile, add it as a dependency
Dependencies are classified as type_only or const_only based on
where the reference appears. A module referenced by both types and
constants is classified as type_only.
The --const-leaf option stops the walk at the named module. Without
it, the walker would follow the leaf module's own imports, pulling in
low-level modules that are irrelevant for the NS side.
Generation Order
Modules are generated in dependency order:
- CONST leaf module (if any)
- Other
const_onlyandtype_onlydependency modules - Exposed modules (in the order specified)
This ensures that each NS_ module can import from modules already generated.
Secure Procedure Requirements
The current version of Astrobe does not yet support Secure compilation,
so the compiler emits a standard pop {pc} return for every procedure.
Each exported Secure procedure must have a Non-secure return epilogue
inserted before its END statement, replacing the compiler's return
sequence with stack deallocation, POP.W {LR}, and BXNS LR. Without
this epilogue, the return would fault because the processor remains in
Secure state.
Use sec-epilogueo
insert the epilogues automatically. The tool analyses each procedure's
stack frame from the .lst file and generates the correct
SYSTEM.EMIT/SYSTEM.EMITH sequence, including optional register
sanitisation for the adversarial security model.
Build Workflow
A complete Secure/Non-secure build follows this sequence:
- Compile the Secure program with Astrobe (produces
.lst,.map,.bin) - Run sec-epilogue
to insert Secure return epilogues into the
.modfiles - Recompile the Secure program
- Run gen-secure to generate NSC.bin, NSC.alst, and NS_*.mod files
- Compile the Non-secure program with Astrobe (imports the generated NS_*.mod files)
- Run gen-rdb for both S
and NS programs (use
--nsc-dirfor the S side to include NSC.alst) - Run make-elf to create ELF files for debugging
Examples
Using a configuration file (gen-secure.cfg in the project directory):
python gen-secure.py sec/S.map
All settings (NSC address, NS directory, modules) come from the configuration file.
Using command line arguments only:
python gen-secure.py sec/S.map --nsc-addr C0FE000 --ns-dir nonsec --nsc-dir sec --const-leaf MCU S0
Mixed (configuration file provides defaults, command line overrides modules):
python gen-secure.py sec/S.map S0 S1
Diagnostics
The tool exits with status 0 on success and status 1 on any error. Common error conditions include:
.mapfile not found- exposed module not found in
.mapfile .lstfile not found for an exposed module- NSC address not given (neither command line nor configuration file)
- NS output directory not given or does not exist
- NSC output directory does not exist
- no exposed modules specified
- configuration file specified with
--cfg-filenot found - configuration file missing
[gen-secure]section
With -v, the tool prints each module's classification and any skipped dependencies to standard output.
Compatibility
Developed and tested with Astrobe for RP2350 version 10. The .map and
.lst file formats may be specific to the Astrobe compiler and linker.
Tested targets:
- STM32U585 (Cortex-M33, TrustZone)
- RP2350 (Cortex-M33, TrustZone)
Notes
The stub procedures in NS_ exposed modules use SYSTEM.EMIT and
SYSTEM.DATA to generate inline machine code that branches to the
NSC veneer address. The SYSTEM.EMITH instruction that adjusts the
stack pointer compensates for the push instruction that the compiler
inserts at the procedure entry.
Module names on the command line accept bare names (S0) or names
with .mod suffix (S0.mod). File paths are not accepted – module
names are resolved via the .map file.
The tool uses the elfdata library to parse .lst files for type
and constant information.
See Also
gen-rdb, make-elf, sec-epilogue, elfdata
Last updated: 25 March 2026