diff --git a/repos/gems/run/depot_autopilot.run b/repos/gems/run/depot_autopilot.run
index a03469ef7f..0ad2dba1c6 100644
--- a/repos/gems/run/depot_autopilot.run
+++ b/repos/gems/run/depot_autopilot.run
@@ -720,6 +720,7 @@ set default_test_pkgs {
test-tls
test-token
test-trace
+ test-trace_buffer
test-trace_logger
test-utf8
test-vfs_block
diff --git a/repos/os/recipes/pkg/test-trace_buffer/README b/repos/os/recipes/pkg/test-trace_buffer/README
new file mode 100644
index 0000000000..57ce9d22ff
--- /dev/null
+++ b/repos/os/recipes/pkg/test-trace_buffer/README
@@ -0,0 +1 @@
+Low-level test of the trace buffer.
diff --git a/repos/os/recipes/pkg/test-trace_buffer/archives b/repos/os/recipes/pkg/test-trace_buffer/archives
new file mode 100644
index 0000000000..2876652e79
--- /dev/null
+++ b/repos/os/recipes/pkg/test-trace_buffer/archives
@@ -0,0 +1,2 @@
+_/src/init
+_/src/test-trace_buffer
diff --git a/repos/os/recipes/pkg/test-trace_buffer/hash b/repos/os/recipes/pkg/test-trace_buffer/hash
new file mode 100644
index 0000000000..76b9f62b4b
--- /dev/null
+++ b/repos/os/recipes/pkg/test-trace_buffer/hash
@@ -0,0 +1 @@
+2022-02-17 f1783a34eb594210102d15d5aa8c75baf3922198
diff --git a/repos/os/recipes/pkg/test-trace_buffer/runtime b/repos/os/recipes/pkg/test-trace_buffer/runtime
new file mode 100644
index 0000000000..e307078008
--- /dev/null
+++ b/repos/os/recipes/pkg/test-trace_buffer/runtime
@@ -0,0 +1,34 @@
+
+
+
+
+ child "test-trace_buffer" exited with exit value 0
+ Error:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repos/os/recipes/src/test-trace_buffer/content.mk b/repos/os/recipes/src/test-trace_buffer/content.mk
new file mode 100644
index 0000000000..623e080fd9
--- /dev/null
+++ b/repos/os/recipes/src/test-trace_buffer/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR = src/test/trace_buffer include/trace
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/repos/os/recipes/src/test-trace_buffer/hash b/repos/os/recipes/src/test-trace_buffer/hash
new file mode 100644
index 0000000000..24710da932
--- /dev/null
+++ b/repos/os/recipes/src/test-trace_buffer/hash
@@ -0,0 +1 @@
+2022-02-17 0a60c331146d0d478b553a6e08b1ed323a398911
diff --git a/repos/os/recipes/src/test-trace_buffer/used_apis b/repos/os/recipes/src/test-trace_buffer/used_apis
new file mode 100644
index 0000000000..ec3bf565df
--- /dev/null
+++ b/repos/os/recipes/src/test-trace_buffer/used_apis
@@ -0,0 +1,2 @@
+base
+os
diff --git a/repos/os/src/test/trace_buffer/main.cc b/repos/os/src/test/trace_buffer/main.cc
new file mode 100644
index 0000000000..b4df442ac0
--- /dev/null
+++ b/repos/os/src/test/trace_buffer/main.cc
@@ -0,0 +1,344 @@
+/*
+ * \brief Low-level trace buffer test
+ * \author Johannes Schlatow
+ * \date 2022-02-15
+ */
+
+/*
+ * Copyright (C) 2022 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+
+using namespace Genode;
+
+
+/**
+ * Constant, word-sized entries
+ */
+struct Generator1
+{
+ size_t _next_value { 1 };
+
+ struct Entry {
+ size_t value;
+
+ Entry(size_t v) : value(v) { }
+
+ void print(Output &out) const { Genode::print(out, value); }
+ };
+
+ size_t max_len() { return sizeof(Entry); }
+
+ size_t generate(char *dst)
+ {
+ construct_at(dst, _next_value);
+
+ _next_value++;
+
+ return sizeof(Entry);
+ }
+
+ bool validate(Trace::Buffer::Entry const &entry, bool print_error=true)
+ {
+ Entry const ¤t { *reinterpret_cast(entry.data()) };
+ if (current.value != _next_value) {
+ if (print_error) {
+ error("expected entry: ", _next_value, ", but got: ", current);
+ }
+ return false;
+ }
+
+ _next_value++;
+ return true;
+ }
+
+ void value(Trace::Buffer::Entry const &entry) {
+ _next_value = reinterpret_cast(entry.data())->value; }
+
+ void print(Output &out) const { Genode::print(out, "constant entry size"); }
+};
+
+
+/**
+ * Variable-size entries to test wrap-around with padding
+ */
+struct Generator2
+{
+ unsigned char _next_value { 1 };
+ size_t _next_length { 10 };
+ size_t const _max_length { 200 };
+
+ struct Entry {
+ unsigned char value[0] { };
+
+ Entry(unsigned char v, size_t len) { memset(value, v, len); }
+
+ void print(Output &out) const { Genode::print(out, value[0]); }
+ };
+
+ size_t max_len() { return _max_length; }
+
+ void _next()
+ {
+ _next_value++;
+ _next_length = (_next_length + 10) % (_max_length+1);
+ }
+
+ size_t generate(char *dst)
+ {
+ const size_t len = _next_length;
+ construct_at(dst, _next_value, len);
+
+ _next();
+ return len;
+ }
+
+ bool validate(Trace::Buffer::Entry const &entry, bool print_error=true)
+ {
+ Entry const ¤t { *reinterpret_cast(entry.data()) };
+ if (current.value[0] != _next_value) {
+ if (print_error) {
+ error("expected entry: ", _next_value, ", but got: ", current);
+ }
+ return false;
+ }
+
+ if (entry.length() != _next_length) {
+ if (print_error) {
+ error("expected entry length: ", _next_length, ", but got: ", entry.length());
+ }
+ return false;
+ }
+
+ unsigned char last = current.value[entry.length()-1];
+ if (last != _next_value) {
+ if (print_error) {
+ error("corrupted entry, expected: ", _next_value, ", but got: ", last);
+ }
+ return false;
+ }
+
+ _next();
+ return true;
+ }
+
+ void value(Trace::Buffer::Entry const &entry)
+ {
+ _next_value = reinterpret_cast(entry.data())->value[0];
+ _next_length = entry.length();
+ }
+
+ void print(Output &out) const { Genode::print(out, "variable entry size"); }
+};
+
+
+template
+struct Test_thread : Thread
+{
+ Env &env;
+ Trace::Buffer &buffer;
+ unsigned delay;
+ Timer::Connection timer { env };
+ T generator { };
+ bool stop { false };
+
+ void entry() override
+ {
+ while (!stop) {
+ char *dst = buffer.reserve(generator.max_len());
+ buffer.commit(generator.generate(dst));
+
+ if (delay)
+ timer.usleep(delay);
+ }
+ }
+
+ Test_thread(Env &env, Trace::Buffer &buffer, unsigned delay)
+ : Thread(env, "producer", 1024 * sizeof(addr_t)),
+ env(env),
+ buffer(buffer),
+ delay(delay)
+ { }
+
+ ~Test_thread()
+ {
+ stop = true;
+ this->join();
+ }
+};
+
+
+template
+struct Trace_buffer_monitor
+{
+ Env &env;
+ Trace_buffer buffer;
+ unsigned delay;
+ Timer::Connection timer { env };
+ T generator { };
+
+ struct Failed : Genode::Exception { };
+
+ Trace_buffer_monitor(Env &env, Trace::Buffer &buffer, unsigned delay)
+ : env(env),
+ buffer(buffer),
+ delay(delay)
+ { }
+
+ void test_ok()
+ {
+ bool done = false;
+
+ while (!done) {
+ buffer.for_each_new_entry([&] (Trace::Buffer::Entry &entry) {
+ if (!entry.length() || !entry.data() || entry.length() > generator.max_len()) {
+ error("Got invalid entry from for_each_new_entry()");
+ throw Failed();
+ }
+
+ if (!generator.validate(entry))
+ throw Failed();
+
+ done = true;
+
+ if (delay)
+ timer.usleep(delay);
+
+ return true;
+ });
+ }
+ }
+
+ void test_lost()
+ {
+ /* read a single entry (which has unexpected value) and stop */
+ bool recalibrated = false;
+
+ while (!recalibrated) {
+ buffer.for_each_new_entry([&] (Trace::Buffer::Entry &entry) {
+ if (!entry.length() || !entry.data() || entry.length() > generator.max_len()) {
+ error("Got invalid entry from for_each_new_entry()");
+ throw Failed();
+ }
+
+ if (generator.validate(entry, false))
+ throw Failed();
+
+ /* reset generator value */
+ generator.value(entry);
+ recalibrated = true;
+
+ return false;
+ });
+ }
+ }
+};
+
+
+template
+class Test_tracing
+{
+ private:
+ size_t _trace_buffer_sz;
+ Attached_ram_dataspace _buffer_ds;
+ Trace::Buffer *_buffer { _buffer_ds.local_addr() };
+ unsigned long long *_canary { (unsigned long long*)(_buffer_ds.local_addr()
+ + _trace_buffer_sz) };
+ Test_thread _thread;
+ Trace_buffer_monitor _test_monitor;
+
+ /*
+ * Noncopyable
+ */
+ Test_tracing(Test_tracing const &);
+ Test_tracing &operator = (Test_tracing const &);
+
+ public:
+
+ struct Overflow : Genode::Exception { };
+ struct Starvation : Genode::Exception { };
+
+ Test_tracing(Env &env, size_t buffer_sz, unsigned producer_delay, unsigned consumer_delay)
+ : _trace_buffer_sz (buffer_sz),
+ _buffer_ds (env.ram(), env.rm(), _trace_buffer_sz + sizeof(_canary)),
+ _thread (env, *_buffer, producer_delay),
+ _test_monitor(env, *_buffer, consumer_delay)
+ {
+ /**
+ * The canary is placed right after the trace buffer. This allows us
+ * to detect buffer overflows. By filling the canary with a bogus
+ * length value, we can also detect out-of-bounds read accesses.
+ */
+ *_canary = ~0ULL;
+ _buffer->init(_trace_buffer_sz);
+
+ log("running ", _test_monitor.generator, " test");
+ _thread.start();
+
+ /* read until buffer wrapped once */
+ while (_buffer->wrapped() < 1)
+ _test_monitor.test_ok();
+
+ /* make sure to continue reading after buffer wrapped */
+ _test_monitor.test_ok();
+
+ /* wait for buffer to wrap twice */
+ size_t const wrapped = _buffer->wrapped();
+ while (_buffer->wrapped() < wrapped + 2);
+
+ /* read an unexpected value */
+ _test_monitor.test_lost();
+
+ /* read some more expected entries */
+ _test_monitor.test_ok();
+
+ if (*_canary != ~0ULL) {
+ error("Buffer overflow, canary was overwritten with ", Hex(*_canary));
+ throw Overflow();
+ }
+
+ log(_test_monitor.generator, " test succeeded\n");
+ }
+};
+
+
+struct Main
+{
+ Constructible> test_1 { };
+ Constructible> test_2 { };
+
+ Main(Env &env)
+ {
+ /* determine buffer size so that Generator1 entries fit perfectly */
+ enum { ENTRY_SIZE = sizeof(Trace::Buffer::Entry) + sizeof(Generator1::Entry) };
+ enum { BUFFER_SIZE = 32 * ENTRY_SIZE + sizeof(Trace::Buffer) };
+
+ /* consume as fast as possible */
+ test_1.construct(env, BUFFER_SIZE, 10000, 0);
+ test_1.destruct();
+
+ /* leave a word-sized padding at the end */
+ test_1.construct(env, BUFFER_SIZE+4, 10000, 0);
+ test_1.destruct();
+
+ /* XXX also test with slower consumer than producer */
+
+ /* variable-size test */
+ test_2.construct(env, BUFFER_SIZE, 10000, 0);
+ test_2.destruct();
+
+ env.parent().exit(0);
+ }
+};
+
+
+void Component::construct(Env &env) { static Main main(env); }
diff --git a/repos/os/src/test/trace_buffer/target.mk b/repos/os/src/test/trace_buffer/target.mk
new file mode 100644
index 0000000000..fa406c3bb6
--- /dev/null
+++ b/repos/os/src/test/trace_buffer/target.mk
@@ -0,0 +1,3 @@
+TARGET = test-trace_buffer
+SRC_CC = main.cc
+LIBS += base