mirror of
https://github.com/genodelabs/genode.git
synced 2025-06-05 09:00:55 +00:00
parent
b39c124628
commit
d7c4265089
@ -720,6 +720,7 @@ set default_test_pkgs {
|
|||||||
test-tls
|
test-tls
|
||||||
test-token
|
test-token
|
||||||
test-trace
|
test-trace
|
||||||
|
test-trace_buffer
|
||||||
test-trace_logger
|
test-trace_logger
|
||||||
test-utf8
|
test-utf8
|
||||||
test-vfs_block
|
test-vfs_block
|
||||||
|
1
repos/os/recipes/pkg/test-trace_buffer/README
Normal file
1
repos/os/recipes/pkg/test-trace_buffer/README
Normal file
@ -0,0 +1 @@
|
|||||||
|
Low-level test of the trace buffer.
|
2
repos/os/recipes/pkg/test-trace_buffer/archives
Normal file
2
repos/os/recipes/pkg/test-trace_buffer/archives
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
_/src/init
|
||||||
|
_/src/test-trace_buffer
|
1
repos/os/recipes/pkg/test-trace_buffer/hash
Normal file
1
repos/os/recipes/pkg/test-trace_buffer/hash
Normal file
@ -0,0 +1 @@
|
|||||||
|
2022-02-17 f1783a34eb594210102d15d5aa8c75baf3922198
|
34
repos/os/recipes/pkg/test-trace_buffer/runtime
Normal file
34
repos/os/recipes/pkg/test-trace_buffer/runtime
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<runtime ram="90M" caps="1000" binary="init">
|
||||||
|
|
||||||
|
<events>
|
||||||
|
<timeout meaning="failed" sec="30" />
|
||||||
|
<log meaning="succeeded">child "test-trace_buffer" exited with exit value 0</log>
|
||||||
|
<log meaning="failed">Error: </log>
|
||||||
|
</events>
|
||||||
|
|
||||||
|
<content>
|
||||||
|
<rom label="ld.lib.so"/>
|
||||||
|
<rom label="test-trace_buffer"/>
|
||||||
|
</content>
|
||||||
|
|
||||||
|
<config>
|
||||||
|
<parent-provides>
|
||||||
|
<service name="ROM"/>
|
||||||
|
<service name="IRQ"/>
|
||||||
|
<service name="IO_MEM"/>
|
||||||
|
<service name="IO_PORT"/>
|
||||||
|
<service name="PD"/>
|
||||||
|
<service name="RM"/>
|
||||||
|
<service name="CPU"/>
|
||||||
|
<service name="LOG"/>
|
||||||
|
<service name="Timer"/>
|
||||||
|
</parent-provides>
|
||||||
|
<default-route>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</default-route>
|
||||||
|
<default caps="200"/>
|
||||||
|
<start name="test-trace_buffer">
|
||||||
|
<resource name="RAM" quantum="2M"/>
|
||||||
|
</start>
|
||||||
|
</config>
|
||||||
|
</runtime>
|
2
repos/os/recipes/src/test-trace_buffer/content.mk
Normal file
2
repos/os/recipes/src/test-trace_buffer/content.mk
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
SRC_DIR = src/test/trace_buffer include/trace
|
||||||
|
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
|
1
repos/os/recipes/src/test-trace_buffer/hash
Normal file
1
repos/os/recipes/src/test-trace_buffer/hash
Normal file
@ -0,0 +1 @@
|
|||||||
|
2022-02-17 0a60c331146d0d478b553a6e08b1ed323a398911
|
2
repos/os/recipes/src/test-trace_buffer/used_apis
Normal file
2
repos/os/recipes/src/test-trace_buffer/used_apis
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
base
|
||||||
|
os
|
344
repos/os/src/test/trace_buffer/main.cc
Normal file
344
repos/os/src/test/trace_buffer/main.cc
Normal file
@ -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 <base/trace/buffer.h>
|
||||||
|
#include <trace/trace_buffer.h>
|
||||||
|
#include <base/attached_ram_dataspace.h>
|
||||||
|
#include <base/component.h>
|
||||||
|
#include <timer_session/connection.h>
|
||||||
|
|
||||||
|
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<Entry>(dst, _next_value);
|
||||||
|
|
||||||
|
_next_value++;
|
||||||
|
|
||||||
|
return sizeof(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate(Trace::Buffer::Entry const &entry, bool print_error=true)
|
||||||
|
{
|
||||||
|
Entry const ¤t { *reinterpret_cast<const Entry*>(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<const Entry*>(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<Entry>(dst, _next_value, len);
|
||||||
|
|
||||||
|
_next();
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate(Trace::Buffer::Entry const &entry, bool print_error=true)
|
||||||
|
{
|
||||||
|
Entry const ¤t { *reinterpret_cast<const Entry*>(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<const Entry*>(entry.data())->value[0];
|
||||||
|
_next_length = entry.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(Output &out) const { Genode::print(out, "variable entry size"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
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 <typename T>
|
||||||
|
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 <typename T>
|
||||||
|
class Test_tracing
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
size_t _trace_buffer_sz;
|
||||||
|
Attached_ram_dataspace _buffer_ds;
|
||||||
|
Trace::Buffer *_buffer { _buffer_ds.local_addr<Trace::Buffer>() };
|
||||||
|
unsigned long long *_canary { (unsigned long long*)(_buffer_ds.local_addr<char>()
|
||||||
|
+ _trace_buffer_sz) };
|
||||||
|
Test_thread<T> _thread;
|
||||||
|
Trace_buffer_monitor<T> _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_tracing<Generator1>> test_1 { };
|
||||||
|
Constructible<Test_tracing<Generator2>> 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); }
|
3
repos/os/src/test/trace_buffer/target.mk
Normal file
3
repos/os/src/test/trace_buffer/target.mk
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
TARGET = test-trace_buffer
|
||||||
|
SRC_CC = main.cc
|
||||||
|
LIBS += base
|
Loading…
x
Reference in New Issue
Block a user