Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ jobs:
bash examples/build-image.sh "$arch" "out/$arch/libmathlib.okso" examples/bind/mathlib/image/mathlib.c
# 8 MiB of the file's bytes, and a sleep: what stays resident once it is loaded.
bash examples/build-image.sh "$arch" "out/$arch/resident.oke" examples/resident/app.c
bash examples/build-image.sh "$arch" "out/$arch/prof.oke" examples/prof/app.c
done
"$OKLD" keygen "$RUNNER_TEMP/seed" > out/key.hex
for arch in x86_64 aarch64; do
Expand Down Expand Up @@ -177,6 +178,9 @@ jobs:
echo "OKRUN=$okrun" >> "$GITHUB_ENV"
okld=$(find "$PWD/target" -path '*bin*' \( -name okld -o -name okld.exe \) -type f | head -1)
echo "OKLD_HOST=$okld" >> "$GITHUB_ENV"
okprof=$(find "$PWD/target" -path '*bin*' \( -name okprof -o -name okprof.exe \) -type f | head -1)
[ -n "$okprof" ] || { echo "::error::okprof did not build"; exit 1; }
echo "OKPROF=$okprof" >> "$GITHUB_ENV"
- name: The same bytes
run: |
a='${{ matrix.arch }}'
Expand All @@ -193,6 +197,26 @@ jobs:
w=$("$OKRUN" "out/$a/weak.oke" | tr -d '\r')
echo "$w"
grep -q "weak: openkal.net bound" <<<"$w" || { echo "::error::a weak import the implementation provides was not bound"; exit 1; }
# A MEASUREMENT AND THE NAME OF AN ADDRESS (0.2). Unmeasured, the
# image is told so and the loader names its function; measured, the
# report holds its calls, its counter and its phase.
p=$("$OKRUN" "out/$a/prof.oke" | tr -d '\r')
echo "$p"
grep -q "prof: not measured" <<<"$p" || { echo "::error::an unmeasured image was told it is measured"; exit 1; }
grep -q "debug: prof_named_function" <<<"$p" || { echo "::error::the loader did not name the image's function"; exit 1; }
rm -rf prof && mkdir prof
# A directory relative to the working one: under Git Bash `$(pwd)` is
# an MSYS spelling (/d/a/...), which a Windows program does not open.
"$OKRUN" "--prof=calls,counters:prof" "out/$a/prof.oke" > prof.out
grep -q "prof: measured" prof.out || { echo "::error::a measured image was told it is not"; exit 1; }
report=$(ls prof/okprof-*.json | head -1)
[ -n "$report" ] || { echo "::error::a measured image wrote no report"; exit 1; }
cat "$report"
grep -q '"example.widgets", "value": 42' "$report" || { echo "::error::the counter was not read"; exit 1; }
grep -q '"example.phase", "count": 2' "$report" || { echo "::error::the phase was not counted"; exit 1; }
grep -q '"kal_stream_write", "count": ' "$report" || { echo "::error::the calls were not counted"; exit 1; }
"$OKPROF" report prof | tee okprof.out
grep -q "openkal.stream" okprof.out || { echo "::error::okprof report did not group the calls by interface"; exit 1; }
- name: The refusals
run: |
a='${{ matrix.arch }}'
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ architecture that has a runner.
|---|---|---|
| `openkal-ld` | `libokld` | The format's definition. A reader of bytes, a checker, a signer (Ed25519) and a loader. Freestanding: no C library, no exceptions. Its users are libokrun, `okld`, and a C library's `dlopen`. |
| `openkal-run` | `libokrun` | The runner's core. It binds an image's names to this process's one openkal implementation, in the image's calling convention, then enters the image. |
| `openkal-tools` | `okrun`, `okld` | `okrun [--require-signature] [--trust <key>] [--debugger] image\|okb args...` runs an image or an okb. `okld check \| note \| sign \| keygen \| inspect \| note-source \| bundle \| bundle check` works on images and okbs on a build machine. |
| `openkal-tools` | `okrun`, `okld`, `okprof` | `okrun [--require-signature] [--trust <key>] [--debugger] [--prof=<what>[:dir]] image\|okb args...` runs an image or an okb. `okld check \| note \| sign \| keygen \| inspect \| note-source \| bundle \| bundle check` works on images and okbs on a build machine. `okprof record \| report \| diff` measures what an image did across openkal. |
| `openkal-bind` | `mcpp.mcpplibs.openkal_bind` | A build plugin. A library published as an image (`.okso`) and a C header is called from a program of Linux, macOS or Windows through a binding generated from the header ([docs/bind.md](docs/bind.md)). |

`libokld` is the loader at run time and `okld` is the tool on the build machine. They share a
Expand Down Expand Up @@ -44,6 +44,37 @@ The images an image needs are found in the okb, then beside it, in `lib/<arch>/`
in `OKRUN_PATH`. A program that carries its images gives their archive to
`okrun_run_okb_bytes`, which runs it from memory and writes nothing to disk.

## A measurement of an image

The runner binds every name an image calls across openkal and read the image's file, so it
measures what the image did there and names its addresses (openkal SPEC 12.3, 0.20):

```sh
okprof record -o prof -- okrun app.okb arguments... # or a program that starts an okrun
okprof report prof # calls by interface and by name; counters; phases
okprof diff before after # the same names, ordered by how their time moved
```

`okprof record` sets `OKRUN_PROF` (`okrun --prof=` says the same), so every image an okrun
beneath the program runs writes `okprof-<id>.json` when it ends:

| what | measured |
|---|---|
| `calls` | each name the image imports, bound through an entry that counts its calls and their time; an image that is not measured is bound as before and pays nothing |
| `counters` | the counters and phases the program states through `openkal.prof` (`kal_prof_counter`, `kal_prof_mark`); `kal_prof_active` answers zero when nothing measures |
| `perfmap` | `/tmp/perf-<pid>.map` on Linux, where `perf` reads the names of an image's addresses; the runner does not sample, the system's profiler does |

`openkal.debug` (`kal_debug_symbolize`) names an address from the symbol table of the image
that holds it, which the runner reads from the image's file again when it is first asked; the
table is never placed in memory by loading.

**An okb's admission is remembered.** The first start of an okb compares the images it runs
with the digests its manifest states; the comparison is then recorded per user (`okrun/admitted`
in the user's cache directory), keyed by the manifest's SHA-256 and the member, with the file's
identity, length and modification time. A later start of the same file admits the member by its
shape alone; a file replaced or changed is compared again. A language server that starts a
worker per module no longer reads its 146 MB okb at each start.

## A library from a program of any system

`okrun_open_library`, `okrun_symbol` and `okrun_close` load an image into a program of this
Expand Down
2 changes: 1 addition & 1 deletion bind/mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-bind"
version = "0.1.9"
version = "0.2.0"
# `mcpp::xpkg_request', `mcpp::decision' (protocol 15).
mcpp = ">=2026.10.1.3"
description = "A build plugin: a library published as an ok image (.okso) and a C header is called from a program of any system, through a binding generated from the header and libokrun."
Expand Down
5 changes: 3 additions & 2 deletions docs/bind.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ sources = []
include_dirs = ["include"]

[target.'cfg(not(os = "openkal"))'.dependencies]
openkal-run = "0.1.9"
openkal-run = "0.2.0"

[build-dependencies]
openkal-bind = "0.1.9"
openkal-bind = "0.2.0"
```

```cpp
Expand Down Expand Up @@ -104,4 +104,5 @@ llvm toolchain's clang converts the call.
| 0.1.6 | No change of its own; released with the archive, whose runner releases the file it ran once the image is loaded, and whose loader digests on the processor's SHA-256 instructions and writes each segment once. |
| 0.1.7 | No change of its own; released with the archive, whose loader also digests on ARMv8's SHA-256 instructions on Linux where the processor states them. |
| 0.1.8 | No change of its own; released with the archive, built against openkal 0.19.0 (the runner binds `kal_task_cores` and `kal_task_set_priority`), whose okb carries chunk digests the runner verifies on several contexts at once. |
| 0.2.0 | No change of its own; released with the archive, whose runner measures an image (okprof) and remembers an okb's admission. |
| 0.1.9 | No change of its own; released with the archive, whose runner on macOS is built over openkal-macos 0.17.1 (`kal_task_current` without a system call). |
41 changes: 41 additions & 0 deletions examples/prof/app.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* What a program states to a measurement, and the name of one of its own
* addresses (openkal.prof and openkal.debug, a loader's interfaces, SPEC 12.3;
* openkal-image 0.2). The image registers a counter, counts into it, marks a
* phase twice, and asks the loader for the name of a function of its own, which
* the loader reads from the image's symbol table --- a table loading never
* places in memory. The entry is `int ok_main(void)' (SPEC 12.2). */
typedef unsigned long kal_uintptr;
typedef long kal_intptr;
typedef unsigned long long kal_u64;
struct kal_stream { kal_uintptr h; };

struct kal_stream kal_stdout(void);
kal_intptr kal_stream_write(struct kal_stream, const void*, kal_uintptr);
int kal_prof_active(void);
int kal_prof_counter(const char* name, kal_uintptr len, const volatile kal_u64* cell);
void kal_prof_mark(const char* name, kal_uintptr len, int begin);
kal_intptr kal_debug_symbolize(kal_uintptr address, char* out, kal_uintptr cap, kal_uintptr* start);

static void put(const char* s, kal_uintptr n) { kal_stream_write(kal_stdout(), s, n); }
static void say(const char* s) { kal_uintptr n = 0; while (s[n]) ++n; put(s, n); }

static volatile kal_u64 widgets;

__attribute__((noinline, visibility("default"))) int prof_named_function(int x) { return x * 7 + 3; }

__attribute__((visibility("default"))) int ok_main(void) {
say(kal_prof_active() ? "prof: measured\n" : "prof: not measured\n");
kal_prof_counter("example.widgets", 15, &widgets);
for (int round = 0; round < 2; ++round) {
kal_prof_mark("example.phase", 13, 1);
for (int i = 0; i < 21; ++i) widgets = widgets + 1;
kal_prof_mark("example.phase", 13, 0);
}
char name[128];
kal_uintptr start = 0;
int (*volatile fn)(int) = &prof_named_function;
const kal_intptr n = kal_debug_symbolize((kal_uintptr)fn + 1, name, sizeof name, &start);
if (n > 0 && n < (kal_intptr)sizeof name) { say("debug: "); put(name, (kal_uintptr)n); say("\n"); }
else say("debug: no name\n");
return prof_named_function(0) == 3 ? 0 : 1;
}
11 changes: 11 additions & 0 deletions ld/include/okld.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ void okld_imports(const void* bytes, okld_size size, void (*each)(const char* na
void okld_required_imports(const void* bytes, okld_size size, void (*each)(const char* name, void* ctx), void* ctx);
void okld_exports(const void* bytes, okld_size size, void (*each)(const char* name, void* ctx), void* ctx);

/* THE SYMBOLS OF AN IMAGE'S CODE AND DATA (0.2): every function and object
* symbol its symbol table states (the section table's `.symtab', else its
* dynamic symbols), as `value' (an address of the image, unrelocated: add the
* module's base), `size' and name, in the table's order. The table lies in the
* file and is never placed in memory by loading, which is why a program that
* names an address in itself asks the party holding the file (openkal.debug,
* SPEC 12.3). Returns how many were given. */
okld_u64 okld_symbols(const void* bytes, okld_size size,
void (*each)(okld_u64 value, okld_u64 symbol_size, const char* name, void* ctx),
void* ctx);

/* The `KAL_IFACE_*' positions a set of imported names requires: each `kal_'
* name belongs to the interface its prefix names. */
okld_u64 okld_interface_of(const char* kal_name);
Expand Down
2 changes: 1 addition & 1 deletion ld/mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-ld"
version = "0.1.9"
version = "0.2.0"
description = "libokld: the ok format (openkal image format) --- its definition, a reader of bytes, a checker, a signer, and a loader. Freestanding: no C library, no exceptions."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand Down
54 changes: 54 additions & 0 deletions ld/src/read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ extern "C" okld_u64 okld_interface_of(const char* n) {
{ "kal_env_", 3 }, { "kal_time_", 4 }, { "kal_random_", 5 }, { "kal_fs_", 6 },
{ "kal_process_", 7 }, { "kal_task_", 8 }, { "kal_exec_", 9 }, { "kal_terminal_", 10 },
{ "kal_net_", 11 }, { "kal_datagram_", 12 }, { "kal_space_", 13 }, { "kal_timeout_", 14 },
// openkal 0.18 and 0.20. openkal.map was missing here until 0.2, so an
// image that imported it required nothing for it in its note.
{ "kal_map_", 15 }, { "kal_region_", 16 }, { "kal_prof_", 17 }, { "kal_debug_", 18 },
};
for (const auto& e : table) if (starts(n, e.prefix)) return 1ull << e.bit;
return 0; // kal_version, kal_interfaces: the specification's self-description
Expand Down Expand Up @@ -254,3 +257,54 @@ extern "C" int okld_set_requirements(void* bytes, okld_size n, okld_u64 interfac
copy(d + 8, &interfaces, 8);
return 0;
}

// --- the symbols of an image's code and data (0.2) -----------------------------
extern "C" okld_u64 okld_symbols(const void* bytes, okld_size size,
void (*each)(okld_u64, okld_u64, const char*, void*), void* ctx) {
using namespace okld;
view v;
char why[8];
if (!bytes || !each || !open(bytes, size, v, why, sizeof why)) return 0;
// The section table's symbol table, with its string table; else the
// dynamic one, whose names are the exports alone.
const elf::sym* syms = nullptr;
elf::u64 count = 0;
const char* strings = nullptr;
elf::u64 strings_size = 0;
if (v.eh->shoff && v.eh->shentsize == sizeof(elf::shdr)) {
const auto* sh = v.at<elf::shdr>(v.eh->shoff, v.eh->shnum);
for (unsigned i = 0; sh && i < v.eh->shnum && !syms; ++i) {
if (sh[i].type != elf::sht_symtab || sh[i].link >= v.eh->shnum) continue;
const auto& str = sh[sh[i].link];
const auto n = sh[i].size / sizeof(elf::sym);
const auto* t = v.at<elf::sym>(sh[i].offset, n);
const auto* s = v.at<char>(str.offset, str.size);
if (t && s) { syms = t; count = n; strings = s; strings_size = str.size; }
}
}
if (!syms) {
syms = v.symbols();
count = syms ? v.symbol_count() : 0;
}
okld_u64 given = 0;
for (elf::u64 k = 0; k < count; ++k) {
const auto& s = syms[k];
const unsigned type = s.info & 0xf;
if ((type != 1 /* STT_OBJECT */ && type != 2 /* STT_FUNC */) || s.value == 0
|| s.shndx == elf::shn_undef || s.shndx == elf::shn_abs) continue;
const char* name = nullptr;
if (strings) {
if (s.name >= strings_size) continue;
name = strings + s.name;
bool ended = false;
for (elf::u64 i = s.name; i < strings_size; ++i) if (strings[i] == 0) { ended = true; break; }
if (!ended) continue;
} else {
name = v.string(s.name);
}
if (!name || !*name) continue;
each(s.value, s.size, name, ctx);
++given;
}
return given;
}
18 changes: 14 additions & 4 deletions run/build.mcpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,32 @@ int main() {
}
// The arguments and the image's own name are the runner's to answer (SPEC
// 12.3); a context's entry is entered in the image's convention through a
// box; every other name is the implementation's.
const std::set<std::string> own{ "kal_env_arg_count", "kal_env_arg", "kal_env_image", "kal_env_loader" };
// box; every other name is the implementation's. Since 0.2 the runner
// also answers openkal.prof and openkal.debug, which are a loader's (SPEC
// 12.3), the word of interfaces (the implementation's and those two), and
// the program's end (where a measurement records what it measured).
std::set<std::string> own{ "kal_env_arg_count", "kal_env_arg", "kal_env_image", "kal_env_loader",
"kal_interfaces", "kal_exit", "kal_abort" };
const std::set<std::string> callbacks{ "kal_task_start", "kal_task_start_sized" };
std::vector<std::string> main_names, space_names;
std::string group, line;
while (std::getline(in, line)) {
if (line.rfind("# openkal.", 0) == 0) { group = line.substr(2, line.find_first_of(" \t", 2) - 2); continue; }
if (line.rfind("kal_", 0) != 0) continue;
while (!line.empty() && (line.back() == '\r' || line.back() == ' ')) line.pop_back();
if (group == "openkal.prof" || group == "openkal.debug") own.insert(line);
(group == "openkal.space" ? space_names : main_names).push_back(line);
}
auto emit = [&](const char* file, const std::vector<std::string>& names) {
std::ofstream o(std::filesystem::path(out) / file);
o << "// Generated by openkal-run's build.mcpp from " << surface.generic_string() << ".\n";
for (const auto& n : names)
o << (own.contains(n) ? "OKRUN_OWN(" : callbacks.contains(n) ? "OKRUN_CALLBACK(" : "OKRUN_BIND(") << n << ")\n";
// The second argument is the name's position, by which a measurement
// of the calls an image makes (okprof, 0.2) keeps its counts.
for (std::size_t i = 0; i < names.size(); ++i) {
const auto& n = names[i];
o << (own.contains(n) ? "OKRUN_OWN(" : callbacks.contains(n) ? "OKRUN_CALLBACK(" : "OKRUN_BIND(")
<< n << ", " << i << ")\n";
}
};
emit("surface.inc", main_names);
emit("surface_space.inc", space_names);
Expand Down
10 changes: 10 additions & 0 deletions run/include/okrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ void* okrun_symbol(struct okrun_library* library, const char* name);
* nothing else holds. */
void okrun_close(struct okrun_library* library);

/* --- a measurement (0.2) --------------------------------------------------- */

/* What the next image run is measured for, as OKRUN_PROF would say it: any of
* `calls' (each name an image imports is counted with its time), `counters'
* (the counters and phases the program registers through openkal.prof) and
* `perfmap' (/tmp/perf-<pid>.map, for this system's profiler), separated by
* commas, then `:<directory>' for the report (`okprof-<id>.json'). Empty or
* null measures nothing. Without a call, OKRUN_PROF is read. */
void okrun_profile(const char* spec);

/* The names this runner binds (the specification's surface), `count' of them. */
const char* const* okrun_names(unsigned long* count);

Expand Down
12 changes: 6 additions & 6 deletions run/mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-run"
version = "0.1.9"
version = "0.2.0"
description = "libokrun: the runner's core --- an ok image's names bound to this process's one openkal implementation, in the image's calling convention, and its entry entered."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand All @@ -11,23 +11,23 @@ repo = "https://github.com/mcpplibs/openkal-image"
kind = "lib"

[dependencies]
openkal = "0.19.0"
openkal = "0.20.0"
openkal-ld = { path = "../ld" }

[target.linux.dependencies]
openkal-linux = "0.20.0"
openkal-linux = "0.21.0"

[target.macos.dependencies]
openkal-macos = "0.17.1"
openkal-macos = "0.18.0"

[target.windows.dependencies]
openkal-windows = "0.15.0"
openkal-windows = "0.16.0"

# The interfaces each system's implementation provides decide one unit: where
# openkal.space is provided its names are bound, and elsewhere an image that
# requires it is refused at load, naming it.
[build]
sources = ["src/okrun.cppm", "src/run.cpp", "src/bindings.cpp"]
sources = ["src/okrun.cppm", "src/run.cpp", "src/bindings.cpp", "src/prof.cpp"]
include_dirs = ["include"]
flags = [
{ glob = "src/*.cpp", cxxflags = ["-fno-exceptions", "-fno-rtti"] },
Expand Down
Loading
Loading