Backend API

Header: zbc_backend.h

Backends provide the actual implementation of semihosting operations (file I/O, console, time). The host library dispatches requests to a backend vtable.

Backend Vtable

struct zbc_backend_t

Backend vtable defining semihosting operations.

Implement the functions you need, set unused operations to NULL. The host library returns an error to the guest for any NULL operation.

Return value conventions:

  • open: file descriptor (>=0) on success, -1 on error

  • close, seek, remove, rename: 0 on success, -1 on error

  • read, write: bytes NOT transferred (0 = complete), -1 on error

  • flen: file length on success, -1 on error

  • clock: centiseconds since start, -1 on error

  • time: seconds since epoch, -1 on error

  • tmpnam: 0 on success (fills buf), -1 on error

Public Members

int (*open)(void *ctx, const char *path, size_t path_len, int mode)

Open a file.

Path is NOT null-terminated; use path_len. Mode is SH_OPEN_*.

int (*close)(void *ctx, int fd)

Close a file descriptor.

int (*read)(void *ctx, int fd, void *buf, size_t count)

Read up to count bytes.

Returns bytes NOT read (0 = all read).

int (*write)(void *ctx, int fd, const void *buf, size_t count)

Write count bytes.

Returns bytes NOT written (0 = all written).

int (*seek)(void *ctx, int fd, int pos)

Seek to absolute position.

intmax_t (*flen)(void *ctx, int fd)

Return file length.

int (*remove)(void *ctx, const char *path, size_t path_len)

Delete a file.

Path is NOT null-terminated.

int (*rename)(void *ctx, const char *old_path, size_t old_len, const char *new_path, size_t new_len)

Rename a file.

Paths are NOT null-terminated.

int (*tmpnam)(void *ctx, char *buf, size_t buf_size, int id)

Generate temporary filename.

Write to buf, return 0 on success.

void (*writec)(void *ctx, char c)

Write a single character to console.

void (*write0)(void *ctx, const char *str)

Write a null-terminated string to console.

int (*readc)(void *ctx)

Read a character from console (blocking).

Return char or -1.

int (*iserror)(void *ctx, int status)

Check if status indicates error.

Return 1 if error, 0 otherwise.

int (*istty)(void *ctx, int fd)

Check if fd is a TTY.

Return 1 if TTY, 0 otherwise.

int (*clock)(void *ctx)

Return centiseconds since program start.

int (*time)(void *ctx)

Return seconds since Unix epoch.

int (*elapsed)(void *ctx, unsigned int *lo, unsigned int *hi)

Return 64-bit tick count in *lo and *hi.

int (*tickfreq)(void *ctx)

Return ticks per second.

int (*do_system)(void *ctx, const char *cmd, size_t cmd_len)

Execute a shell command.

Return exit code.

int (*get_cmdline)(void *ctx, char *buf, size_t buf_size)

Get command line.

Write to buf, return 0 on success.

int (*heapinfo)(void *ctx, uintptr_t *heap_base, uintptr_t *heap_limit, uintptr_t *stack_base, uintptr_t *stack_limit)

Get heap/stack info.

Fill all four values, return 0 on success.

void (*do_exit)(void *ctx, unsigned int reason, unsigned int subcode)

Guest is exiting.

Handle as appropriate (stop emulation, etc.).

int (*get_errno)(void *ctx)

Return last errno value.

int (*timer_config)(void *ctx, unsigned int rate_hz)

Configure periodic timer.

rate_hz=0 disables. Returns 0 on success.

int (*stat)(void *ctx, const char *path, size_t path_len, void *stat_buf)

Stat a path.

Writes the SH_STAT_BUF_SIZE-byte response struct (ino[8] mode[4] nlink[4] size[8] mtime[8] atime[8] ctime[8], all little-endian) into stat_buf. Returns 0 on success, -1 on error.

int (*opendir)(void *ctx, const char *path, size_t path_len)

Open a directory for enumeration.

Returns a non-negative dir handle on success, -1 on error. The handle is host-defined; the guest treats it as opaque and only passes it to readdir/closedir.

int (*readdir)(void *ctx, int dir_handle, void *buf, size_t buf_size)

Read one directory entry.

Writes a single entry in SYS_READDIR wire layout (d_ino[8] d_type[1] d_namlen[1] d_name[d_namlen+1]) into buf. Returns the number of bytes written (> 0), 0 at end of directory, or -1 on error.

int (*closedir)(void *ctx, int dir_handle)

Release a dir handle from opendir.

Returns 0 on success, -1 on error (e.g. handle was never opened or already closed).

int (*readc_poll)(void *ctx)

Non-blocking console char read.

Returns 0-255 if a character is available, -1 if none. -1 is not an error — it just means the caller should poll again later. Hosts implement this with select()/poll() on stdin with a zero timeout.

int (*fstat)(void *ctx, int fd, void *stat_buf)

Stat an open file by descriptor.

Writes the same 48-byte little-endian struct as stat(). Returns 0 on success, -1 on error.

int (*mkdir)(void *ctx, const char *path, size_t path_len, int mode)

Create a directory at path.

mode is passed through (may be filtered by host umask or ignored on platforms without POSIX permission bits). Returns 0 on success, -1 on error.

int (*rmdir)(void *ctx, const char *path, size_t path_len)

Remove an empty directory at path.

Returns 0 on success, -1 on error (including ENOTEMPTY).

int (*ftruncate)(void *ctx, int fd, uint64_t length)

Truncate an open file to length bytes.

Length is unsigned 64-bit so a 16-bit guest can still describe sizes beyond its address space. Returns 0 on success, -1 on error.

int (*fsync)(void *ctx, int fd)

Flush dirty buffers for an open file to storage.

Returns 0 on success, -1 on error.

int (*link)(void *ctx, const char *old_path, size_t old_len, const char *new_path, size_t new_len)

Create a hard link new_path -> old_path.

Returns 0 on success, -1 on error.

int (*symlink)(void *ctx, const char *target, size_t target_len, const char *linkpath, size_t linkpath_len)

Create a symbolic link at linkpath that points to target.

Returns 0 on success, -1 on error.

int (*readlink)(void *ctx, const char *path, size_t path_len, void *buf, size_t buf_size)

Read the target of a symbolic link at path.

Writes up to buf_size bytes (NOT NUL-terminated). Returns the number of bytes written on success, -1 on error (matches POSIX readlink(2)).

int (*lstat)(void *ctx, const char *path, size_t path_len, void *stat_buf)

Stat a path without following the terminal symlink.

Same 48-byte response layout as stat(). Returns 0 on success, -1 on error.

Return Value Conventions

Operation

Success

Error

open

file descriptor (>=0)

-1

close, seek, remove, rename

0

-1

read, write

bytes NOT transferred (0 = complete)

-1

flen

file length

-1

clock

centiseconds since start

-1

time

seconds since epoch

-1

tmpnam

0 (fills buf)

-1

Set unused operations to NULL. The host library returns an error to the guest for any NULL operation.

Built-in Backends

Secure ANSI Backend

const zbc_backend_t *zbc_backend_ansi(void)

Get the secure (sandboxed) ANSI backend.

The secure backend restricts file access to a sandbox directory. Guest code cannot escape the sandbox or access arbitrary host files. Use with zbc_ansi_state_t from zbc_backend_ansi.h.

See also

zbc_ansi_init

Returns:

Pointer to backend vtable

State initialization (from zbc_backend_ansi.h):

#include "zbc_backend_ansi.h"

static zbc_ansi_state_t state;

zbc_ansi_init(&state, "/path/to/sandbox/");

Configuration:

/* Add additional allowed paths */
zbc_ansi_add_path(&state, "/usr/share/data/", 0);  /* read-only */
zbc_ansi_add_path(&state, "/tmp/output/", 1);      /* read-write */

/* Set flags */
state.flags |= ZBC_ANSI_FLAG_ALLOW_SYSTEM;  /* enable system() */
state.flags |= ZBC_ANSI_FLAG_READ_ONLY;     /* block all writes */

/* Set callbacks for violations, exit, and timer config */
zbc_ansi_set_callbacks(&state, violation_handler, exit_handler,
                       timer_handler, ctx);

Cleanup:

zbc_ansi_cleanup(&state);

Insecure ANSI Backend

const zbc_backend_t *zbc_backend_ansi_insecure(void)

Get the insecure (unrestricted) ANSI backend.

The insecure backend provides unrestricted access to the host filesystem. Guest code can read, write, and delete any file the host process can access. Use only for trusted code. Use with zbc_ansi_insecure_state_t from zbc_backend_ansi.h.

Returns:

Pointer to backend vtable

Use only for trusted code (e.g., your own test programs).

State initialization (from zbc_backend_ansi.h):

#include "zbc_backend_ansi.h"

static zbc_ansi_insecure_state_t state;

zbc_ansi_insecure_init(&state);

Cleanup:

zbc_ansi_insecure_cleanup(&state);

Dummy Backend

const zbc_backend_t *zbc_backend_dummy(void)

Get the dummy (no-op) backend.

All operations succeed with no side effects. Useful for testing the host processing logic without actual I/O. No state required — pass NULL as backend_ctx.

Returns:

Pointer to backend vtable

Implementing Custom Backends

To implement a custom backend:

  1. Define your context structure (if needed)

  2. Implement the vtable functions you need

  3. Create a static zbc_backend_t with your function pointers

  4. Use NULL for operations you don’t support

Example:

typedef struct {
    int console_fd;
    /* ... */
} my_backend_ctx_t;

static int my_open(void *ctx, const char *path, size_t len, int mode)
{
    my_backend_ctx_t *my = ctx;
    /* Implementation */
    return fd;
}

static void my_writec(void *ctx, char c)
{
    my_backend_ctx_t *my = ctx;
    write(my->console_fd, &c, 1);
}

static const zbc_backend_t my_backend = {
    .open = my_open,
    .writec = my_writec,
    /* Other operations NULL - returns error to guest */
};

/* Usage */
my_backend_ctx_t my_ctx = { .console_fd = STDOUT_FILENO };
zbc_host_init(&host, &mem_ops, NULL, &my_backend, &my_ctx,
              work_buf, sizeof(work_buf));

ANSI Backend Types

Header: zbc_backend_ansi.h

Secure Backend State

struct zbc_ansi_state_t

Secure ANSI backend state (caller-allocated).

Initialize with zbc_ansi_init() before use.

Public Members

char sandbox_dir[ZBC_ANSI_SANDBOX_DIR_MAX]

Primary sandbox directory.

size_t sandbox_dir_len

Length of sandbox_dir.

unsigned int flags

ZBC_ANSI_FLAG_*.

zbc_ansi_path_rule_t path_rules[ZBC_ANSI_MAX_PATH_RULES]

Path rules.

int path_rule_count

Number of path rules.

const zbc_ansi_policy_t *policy

NULL = use built-in policy.

void *policy_ctx

Context for policy callbacks.

void (*on_violation)(void *ctx, int type, const char *detail)

Violation callback.

void (*on_exit)(void *ctx, unsigned int reason, unsigned int subcode)

Exit callback.

int (*on_timer_config)(void *ctx, unsigned int rate_hz)

Timer config callback.

Return 0 on success, non-zero if the rate is not achievable; a non-zero return is reported to the guest as -1 with errno EINVAL (per spec SYS_TIMER_CONFIG).

void *callback_ctx

Context for callbacks.

uint64_t start_clock

Stored as uint64_t to avoid time.h.

void zbc_ansi_init(zbc_ansi_state_t *state, const char *sandbox_dir)

Initialize secure ANSI backend.

The sandbox_dir is copied into state, so the original can be freed.

Parameters:
  • state – Caller-allocated state structure

  • sandbox_dir – Directory to sandbox file operations to (required). All file paths must start with this prefix. Should end with ‘/’ (will be added if missing).

int zbc_ansi_add_path(zbc_ansi_state_t *state, const char *prefix, int allow_write)

Add an additional allowed path.

The prefix string must remain valid for the lifetime of state.

Parameters:
  • state – Initialized state

  • prefix – Path prefix to allow (e.g., “/usr/lib/”)

  • allow_write – 0 for read-only, 1 for read-write

Returns:

0 on success, -1 if path_rules array is full

void zbc_ansi_set_policy(zbc_ansi_state_t *state, const zbc_ansi_policy_t *policy, void *ctx)

Set custom security policy.

Parameters:
  • state – Initialized state

  • policy – Policy vtable (NULL to use built-in)

  • ctx – Context passed to policy callbacks

void zbc_ansi_set_callbacks(zbc_ansi_state_t *state, void (*on_violation)(void *ctx, int type, const char *detail), void (*on_exit)(void *ctx, unsigned int reason, unsigned int subcode), int (*on_timer_config)(void *ctx, unsigned int rate_hz), void *ctx)

Set callbacks for security events.

Parameters:
  • state – Initialized state

  • on_violation – Called when operation is blocked (may be NULL)

  • on_exit – Called when exit() is intercepted (may be NULL)

  • on_timer_config – Called when timer is configured (may be NULL). Return 0 on success, non-zero if the rate is not achievable (guest receives -1 with errno EINVAL).

  • ctx – Context passed to callbacks

void zbc_ansi_cleanup(zbc_ansi_state_t *state)

Clean up secure ANSI backend state.

Closes all open files. State can be reused after calling zbc_ansi_init().

Parameters:

state – Initialized state

Insecure Backend State

struct zbc_ansi_insecure_state_t

Insecure ANSI backend state (caller-allocated).

Initialize with zbc_ansi_insecure_init() before use.

Public Members

uint64_t start_clock

Stored as uint64_t to avoid time.h.

char path_buf[ZBC_ANSI_PATH_BUF_MAX]

For null-terminating paths.

void zbc_ansi_insecure_init(zbc_ansi_insecure_state_t *state)

Initialize insecure ANSI backend.

Warning

This provides unrestricted filesystem access! Guest code can read/write/delete any file the host process can access. Only use for trusted guest code or debugging.

Parameters:

state – Caller-allocated state structure

void zbc_ansi_insecure_cleanup(zbc_ansi_insecure_state_t *state)

Clean up insecure ANSI backend state.

Closes all open files. State can be reused after calling zbc_ansi_insecure_init().

Parameters:

state – Initialized state

Policy Vtable

struct zbc_ansi_policy_t

Custom policy vtable (optional, for OS-specific sandboxing).

Public Members

int (*validate_path)(void *ctx, const char *path, size_t path_len, int for_write, char *resolved, size_t resolved_size, size_t *resolved_len)

Validate and resolve a path.

Called before any file operation.

Param ctx:

Policy context (from zbc_ansi_set_policy)

Param path:

Original path from guest

Param path_len:

Length of path

Param for_write:

1 if operation will modify file, 0 for read-only

Param resolved:

Buffer to write resolved path

Param resolved_size:

Size of resolved buffer

Param resolved_len:

[out] Length of resolved path

Return:

0 on success (path allowed), non-zero to deny (EACCES)

int (*validate_system)(void *ctx, const char *cmd, size_t cmd_len)

Validate a system() command.

Only called if ZBC_ANSI_FLAG_ALLOW_SYSTEM is set.

Param ctx:

Policy context

Param cmd:

Command string

Param cmd_len:

Length of command

Return:

0 to allow execution, non-zero to deny

int (*handle_exit)(void *ctx, unsigned int reason, unsigned int subcode)

Handle exit() request.

Only called if ZBC_ANSI_FLAG_ALLOW_EXIT is NOT set.

Param ctx:

Policy context

Param reason:

Exit reason code

Param subcode:

Exit subcode

Return:

0 to allow exit, non-zero to block