New debug monitor

The new monitor component at os/src/monitor is the designated successor
of the gdb_monitor. This initial version, however, implements only the
subset needed to inspect the memory of the monitored component(s).

In contrast to the gdb_monitor, the new component supports the monitoring
of multiple components, leveraging the sandbox API. It can therefore be
used as a drop-in replacement for the init component. Like the gdb_monitor,
the new monitor speaks the GDB protocol over Genode's terminal session.
But the protocol implementation does not re-use any gdbserver code,
sidestepping the complexities of POSIX.

There exist two run scripts illustrating the new component. The
os/run/monitor.run script exercises memory inspection via the 'm' command
by letting a test program monitor itself. The os/run/monitor_gdb.run
script allows for the interactive use of GDB to interact with monitored
components.

Issue #4917
This commit is contained in:
Norman Feske 2023-05-08 16:57:57 +02:00
parent 65f65073e6
commit 6a57683e52
36 changed files with 3641 additions and 0 deletions

View File

@ -0,0 +1,117 @@
/*
* \brief GDB packet parser
* \author Norman Feske
* \date 2023-05-11
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITOR__GDB_PACKET_H_
#define _MONITOR__GDB_PACKET_H_
#include <util/string.h>
namespace Genode { template <size_t> struct Gdb_packet; }
template <Genode::size_t MAX_SIZE>
struct Genode::Gdb_packet
{
enum class State {
IDLE, INCOMPLETE,
EXPECT_CHECKSUM1, EXPECT_CHECKSUM2,
COMPLETE, CORRUPT
};
State state { State::IDLE };
unsigned cursor = 0;
struct Checksum
{
uint8_t accumulated;
uint8_t expected;
bool matches() const { return (accumulated == expected); }
};
Checksum checksum { };
char buf[MAX_SIZE] { };
void reset()
{
state = State::IDLE;
cursor = 0;
checksum = { };
}
enum class Append_result { OK, COMPLETE, OVERFLOW, CORRUPT };
Append_result append(char const c)
{
if (cursor >= sizeof(buf))
return Append_result::OVERFLOW;
auto interpret = [&]
{
auto is_hex_digit = [] (char c) { return is_digit(c, true); };
auto hex_digit = [] (char c) { return digit (c, true); };
if (state == State::EXPECT_CHECKSUM1 || state == State::EXPECT_CHECKSUM2)
if (!is_hex_digit(c))
return State::CORRUPT;
switch (state) {
case State::IDLE:
return (c == '$') ? State::INCOMPLETE
: State::IDLE;
case State::INCOMPLETE:
return (c == '#') ? State::EXPECT_CHECKSUM1
: State::INCOMPLETE;
case State::EXPECT_CHECKSUM1:
checksum.expected = uint8_t(hex_digit(c) << 4u);
return State::EXPECT_CHECKSUM2;
case State::EXPECT_CHECKSUM2:
checksum.expected |= uint8_t(hex_digit(c));
return checksum.matches() ? State::COMPLETE
: State::CORRUPT;
case State::COMPLETE:
case State::CORRUPT:
break; /* expect call of 'reset' */
};
return state;
};
State const orig_state = state;
state = interpret();
/* capture only the command payload between '$' and '#' */
if ((orig_state == State::INCOMPLETE) && (state == State::INCOMPLETE)) {
buf[cursor++] = c;
checksum.accumulated = uint8_t(checksum.accumulated + c);
}
return (state == State::COMPLETE) ? Append_result::COMPLETE :
(state == State::CORRUPT) ? Append_result::CORRUPT :
Append_result::OK;
}
bool complete() const { return state == State::COMPLETE; }
void with_command(auto const &fn) const
{
if (complete())
fn(Const_byte_range_ptr { buf, cursor });
}
};
#endif /* _MONITOR__GDB_PACKET_H_ */

View File

@ -0,0 +1,61 @@
/*
* \brief Output utilities
* \author Norman Feske
* \date 2023-06-06
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITOR__OUTPUT_H_
#define _MONITOR__OUTPUT_H_
#include <base/output.h>
namespace Genode {
struct Gdb_hex;
struct Gdb_checksummed_output;
}
struct Genode::Gdb_hex : Hex
{
template <typename T>
explicit Gdb_hex(T value) : Hex(value, OMIT_PREFIX, PAD) { }
};
struct Genode::Gdb_checksummed_output : Output
{
Output &_output;
uint8_t _accumulated = 0;
Gdb_checksummed_output(Output &output) : _output(output)
{
print(_output, "$");
}
~Gdb_checksummed_output()
{
print(_output, "#", Gdb_hex(_accumulated));
}
void out_char(char c) override { out_string(&c, 1); }
void out_string(char const *str, size_t n) override
{
n = (n == ~0UL) ? strlen(str) : n;
for (unsigned i = 0; i < n; i++)
_accumulated = uint8_t(_accumulated + str[i]);
_output.out_string(str, n);
}
};
#endif /* _MONITOR__OUTPUT_H_ */

View File

@ -0,0 +1,76 @@
/*
* \brief Parsing utilities
* \author Norman Feske
* \date 2023-06-06
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITOR__STRING_H_
#define _MONITOR__STRING_H_
#include <util/string.h>
namespace Genode {
static void with_max_bytes(Const_byte_range_ptr const &bytes,
size_t const max, auto const &fn)
{
fn(Const_byte_range_ptr { bytes.start, min(max, bytes.num_bytes) });
}
static void with_skipped_bytes(Const_byte_range_ptr const &bytes,
size_t const n, auto const &fn)
{
if (bytes.num_bytes < n)
return;
Const_byte_range_ptr const remainder { bytes.start + n,
bytes.num_bytes - n };
fn(remainder);
}
static void with_skipped_prefix(Const_byte_range_ptr const &bytes,
Const_byte_range_ptr const &prefix,
auto const &fn)
{
if (bytes.num_bytes < prefix.num_bytes)
return;
if (strcmp(prefix.start, bytes.start, prefix.num_bytes) != 0)
return;
with_skipped_bytes(bytes, prefix.num_bytes, fn);
}
static void with_skipped_prefix(Const_byte_range_ptr const &bytes,
char const *prefix, auto const &fn)
{
with_skipped_prefix(bytes, Const_byte_range_ptr { prefix, strlen(prefix) }, fn);
}
template <size_t N>
static void with_skipped_prefix(Const_byte_range_ptr const &bytes,
String<N> const &prefix, auto const &fn)
{
with_skipped_prefix(bytes, prefix.string(), fn);
}
/**
* Return true if 'bytes' equals the null-terminated string 'str'
*/
static inline bool equal(Const_byte_range_ptr const &bytes, char const *str)
{
size_t const len = strlen(str);
return (len == bytes.num_bytes) && (strcmp(bytes.start, str, len) == 0);
}
}
#endif /* _MONITOR__STRING_H_ */

View File

@ -0,0 +1,5 @@
SRC_BIN = gdb_target.xml
SRC_CC = gdb_arch.cc
INC_DIR += $(REP_DIR)/src/monitor
vpath % $(REP_DIR)/src/monitor/spec/x86_64

View File

@ -0,0 +1,8 @@
content: include/monitor LICENSE
include/monitor:
$(mirror_from_rep_dir)
LICENSE:
cp $(GENODE_DIR)/LICENSE $@

View File

@ -0,0 +1 @@
2023-06-09 132782703fc58b9aa089970fc4ae971c96f37fac

View File

@ -0,0 +1,9 @@
SRC_DIR = src/monitor
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
MIRROR_FROM_REP_DIR += lib/mk/spec/x86_64/monitor_gdb_arch.mk
content: $(MIRROR_FROM_REP_DIR)
$(MIRROR_FROM_REP_DIR):
$(mirror_from_rep_dir)

View File

@ -0,0 +1 @@
2023-06-09 0d4af26c7b3dfb7f197f46bda6ef09f90150666f

View File

@ -0,0 +1,7 @@
base
sandbox
os
monitor
report_session
timer_session
terminal_session

80
repos/os/run/monitor.run Normal file
View File

@ -0,0 +1,80 @@
proc platform_supported { } {
if {[have_spec x86_64] && [have_board pc]} {
if {![have_spec linux] && ![have_spec foc] && ![have_spec sel4]} {
return 1 } }
return 0
}
if {![platform_supported]} {
puts "Run script is not supported on this platform"
exit 0
}
build { core lib/ld init timer monitor test/monitor server/terminal_crosslink }
create_boot_directory
install_config {
<config>
<parent-provides>
<service name="LOG"/>
<service name="PD"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="CPU"/>
<service name="ROM"/>
<service name="RM"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Timer"/> </provides>
</start>
<start name="terminal_crosslink" caps="100">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Terminal"/> </provides>
<config/>
</start>
<start name="monitor" caps="1000">
<resource name="RAM" quantum="100M"/>
<config>
<parent-provides>
<service name="LOG"/>
<service name="PD"/>
<service name="CPU"/>
<service name="ROM"/>
<service name="RM"/>
<service name="Timer"/>
<service name="Terminal"/>
</parent-provides>
<default caps="100"/>
<monitor>
<policy label="test-monitor" stop="no" wx="yes" />
</monitor>
<start name="test-monitor">
<resource name="RAM" quantum="32M"/>
<route>
<service name="PD"> <local/> </service>
<service name="CPU"> <local/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</config>
</start>
</config>
}
build_boot_image [build_artifacts]
append qemu_args "-nographic "
run_genode_until {\[init -> monitor\] child "test-monitor" exited with exit value 0.*\n} 30

View File

@ -0,0 +1,120 @@
proc platform_supported { } {
if {[have_spec x86_64] && [have_board pc]} {
if {![have_spec linux] && ![have_spec foc] && ![have_spec sel4]} {
return 1 } }
return 0
}
if {![platform_supported]} {
puts "Run script is not supported on this platform"
exit 0
}
build { core lib/ld init timer monitor drivers/uart test/log }
create_boot_directory
install_config {
<config>
<parent-provides>
<service name="LOG"/>
<service name="PD"/>
<service name="CPU"/>
<service name="ROM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="RM"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Timer"/> </provides>
</start>
<start name="pc_uart_drv">
<resource name="RAM" quantum="2M"/>
<provides>
<service name="Terminal"/>
<service name="Uart"/>
</provides>
<config>
<policy label_prefix="monitor" uart="1"/>
</config>
</start>
<start name="monitor" caps="1000">
<resource name="RAM" quantum="100M"/>
<config>
<parent-provides>
<service name="LOG"/>
<service name="PD"/>
<service name="CPU"/>
<service name="ROM"/>
</parent-provides>
<default caps="100"/>
<monitor>
<policy label="first-test-log" stop="no"/>
</monitor>
<start name="first-test-log">
<resource name="RAM" quantum="2M"/>
<binary name="test-log"/>
<route>
<service name="PD"> <local/> </service>
<service name="CPU"> <local/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="second-test-log">
<resource name="RAM" quantum="2M"/>
<binary name="test-log"/>
<route>
<service name="PD"> <local/> </service>
<service name="CPU"> <local/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</config>
</start>
</config>
}
build_boot_image [build_artifacts]
set local_port 5555
# qemu config
append qemu_args " -display none "
# connect comport 0 to stdio
append qemu_args " -serial stdio "
# connect comport 1 with TCP port $local_port
append qemu_args " -serial chardev:uart "
append qemu_args " -chardev socket,id=uart,port=$local_port,host=localhost,server,nowait,ipv4 "
run_genode_until {.*\[init -> monitor -> first-test-log\].*} 30
set genode_id [output_spawn_id]
# sequence of GDB commands to execute at startup
set gdb_cmds ""
append gdb_cmds {-ex "target remote localhost:$local_port" }
append gdb_cmds {-ex "set style enabled off" }
# test for dumping memory
append gdb_cmds {-ex "x/x 0x1003e8b" }
# run GDB
eval spawn [gdb] debug/ld.lib.so -n $gdb_cmds
set gdb_id [list $spawn_id $genode_id]
# show output of both GDB and Genode, supply user input to GDB
interact -i $gdb_id $genode_id

View File

@ -0,0 +1,75 @@
The monitor component is a drop-in replacement for init that allows for the
inspection and debugging of child components. Its configuration mirrors the
configuration of init.
In contrast to init, the monitor requests the 'platform_info' ROM to detect
the used kernel at runtime. This is needed because low-level mechanisms for
interacting with threads differ between the various kernels supported by
Genode. Note that Linux is not supported by the monitor.
A component can be selected for monitoring by routing its PD and CPU sessions
to the monitor's local PD and CPU services instead of the routing those
sessions to the parent. For example, the following start node selects the
'test-log' component to be monitored.
! <start name="first-test-log">
! <resource name="RAM" quantum="2M"/>
! <binary name="test-log"/>
! <route>
! <service name="PD"> <local/> </service>
! <service name="CPU"> <local/> </service>
! <any-service> <parent/> </any-service>
! </route>
! </start>
If monitor's configuration features a <monitor> sub node, the monitor creates
a terminal session that offers access to the monitored components via the GDB
protocol. Each component appears as a distinct GDB inferior. The following
GDB commands are useful:
* List all monitored components:
! (gdb) info inferiors
* List all threads:
! (gdb) info threads
* Select a component for inspection, specifying the component's inferior ID:
! (gdb) inferior 2
* Show 10 words of the component's memory at virtual address 0x100000:
! x/10x 0x100000
The <monitor> configuration can host optional <policy> nodes referring to
inferiors by their respective labels. For example:
! <monitor>
! <policy label="first-test-log" wait="no" stop="yes" wx="no"/>
! </monitor>
;;; XXX not implemented, uncomment once completed
;
; By setting the 'wait' attribute to "yes", the execution of the inferior is
; delayed until the monitor receives the GDB command for continuing execution.
; This is useful for inspecting the startup phase of a component. By default,
; inferiors don't wait.
;
; The 'stop' attribute defines the behavior of an inferior when GDB connects
; to the monitor. By default, all inferiors are stopped. By setting the
; attribute to "no", a GDB connection does not interfere with the execution of
; the inferiors while memory can be inspected.
The enabling of the 'wx' attribute prompts the monitor to map the executable
code of the monitored component as writeable memory, allowing the patching of
text segment by GDB, which is needed for using breakpoints.
RAM wiping
----------
As a secondary functionality, the monitor wipes all RAM dataspaces allocated
by the monitored components when the RAM is freed. This is useful as a possible
defense against cold-boot attacks. Note however that only RAM allocated by
monitored components is subjected to the wiping. Shared-memory buffers
obtained from a service - think of a GUI server - are freed by the service,
not the client. If the service is not monitored, client information could
prevail in such a shared-memory buffer.

View File

@ -0,0 +1,26 @@
/*
* \brief Architecture-specific GDB protocol support
* \author Norman Feske
* \date 2023-05-15
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _GDB_ARCH_H_
#define _GDB_ARCH_H_
#include <cpu/cpu_state.h>
#include <gdb_response.h>
#include <types.h>
namespace Monitor { namespace Gdb {
void print_registers(Output &out, Cpu_state const &cpu);
} }
#endif /* _GDB_ARCH_H_ */

View File

@ -0,0 +1,187 @@
/*
* \brief Interfaces for providing GDB commands
* \author Norman Feske
* \date 2023-05-11
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _GDB_COMMAND_H_
#define _GDB_COMMAND_H_
#include <base/registry.h>
#include <monitor/string.h>
#include <types.h>
namespace Monitor { namespace Gdb {
struct State;
struct Command;
struct Command_with_separator;
struct Command_without_separator;
using Commands = Registry<Command>;
} }
struct Monitor::Gdb::Command : private Commands::Element, Interface
{
using Name = String<32>;
Name const name;
Command(Commands &commands, Name const &name)
:
Commands::Element(commands, *this), name(name)
{ }
void _match_name(Const_byte_range_ptr const &bytes, auto const &match_remainder_fn) const
{
with_skipped_prefix(bytes, name, match_remainder_fn);
}
struct With_args_fn : Interface
{
virtual void call(Const_byte_range_ptr const &args) const = 0;
};
virtual void _with_args(Const_byte_range_ptr const &, With_args_fn const &) const = 0;
void with_args(Const_byte_range_ptr const &command_bytes, auto const &fn) const
{
using Fn = typeof(fn);
struct Impl : With_args_fn
{
Fn const &_fn;
Impl(Fn const &fn) : _fn(fn) { }
void call(Const_byte_range_ptr const &args) const override { _fn(args); }
};
_with_args(command_bytes, Impl(fn));
}
virtual void execute(State &, Const_byte_range_ptr const &args, Output &) const = 0;
/**
* Argument-separating character
*/
struct Sep { char value; };
/**
* Call 'fn' for each semicolon-separated argument
*/
static void for_each_argument(Const_byte_range_ptr const &args, Sep sep, auto const &fn)
{
char const *start = args.start;
size_t num_bytes = args.num_bytes;
for (; num_bytes > 0;) {
auto field_len = [] (char sep, Const_byte_range_ptr const &arg)
{
size_t n = 0;
for ( ; (n < arg.num_bytes) && (arg.start[n] != sep); n++);
return n;
};
size_t const arg_len = field_len(sep.value, { start, num_bytes });
fn(Const_byte_range_ptr(start, arg_len));
auto skip = [&] (size_t n)
{
if (num_bytes >= n) {
start += n;
num_bytes -= n;
}
};
skip(arg_len); /* argument */
skip(1); /* separating semicolon */
}
}
/**
* Call 'fn' with the Nth semicolon-separated argument
*/
static void with_argument(Const_byte_range_ptr const &args, Sep const sep,
unsigned const n, auto const &fn)
{
unsigned i = 0;
for_each_argument(args, sep, [&] (Const_byte_range_ptr const &arg) {
if (n == i++)
fn(arg); });
}
/**
* Call 'fn' with pointer to 'arg' as null-terminated string
*
* Note that the argument length is limited by the bounds of the locally
* allocated buffer, which is dimensioned for parsing number arguments.
*/
static void with_null_terminated(Const_byte_range_ptr const &arg, auto const &fn)
{
char null_terminated[20] { };
memcpy(null_terminated, arg.start,
min(sizeof(null_terminated) - 1, arg.num_bytes));
fn(const_cast<char const *>(null_terminated));
}
/**
* Return Nth comma-separated hexadecimal number from args
*/
template <typename T>
static T comma_separated_hex_value(Const_byte_range_ptr const &args,
unsigned const n, T const default_value)
{
T result { default_value };
with_argument(args, Sep {','}, n, [&] (Const_byte_range_ptr const &arg) {
with_null_terminated(arg, [&] (char const *str) {
ascii_to_unsigned<T>(str, result, 16); }); });
return result;
}
};
struct Monitor::Gdb::Command_with_separator : Command
{
using Command::Command;
void _match_separator(Const_byte_range_ptr const &bytes, auto const &match_remainder_fn) const
{
if (bytes.num_bytes == 0)
return;
char const c = bytes.start[0];
if ((c == ',') || (c == ';') || (c || ':'))
with_skipped_bytes(bytes, 1, match_remainder_fn);
}
void _with_args(Const_byte_range_ptr const &bytes,
With_args_fn const &fn) const override
{
_match_name(bytes, [&] (Const_byte_range_ptr const &bytes) {
_match_separator(bytes, [&] (Const_byte_range_ptr const &args) {
fn.call(args); }); });
}
};
struct Monitor::Gdb::Command_without_separator : Command
{
using Command::Command;
void _with_args(Const_byte_range_ptr const &bytes,
With_args_fn const &fn) const override
{
_match_name(bytes, [&] (Const_byte_range_ptr const &args) {
fn.call(args); });
}
};
#endif /* _GDB_COMMAND_H_ */

View File

@ -0,0 +1,78 @@
/*
* \brief GDB packet handler
* \author Norman Feske
* \date 2023-05-11
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _GDB_PACKET_HANDLER_H_
#define _GDB_PACKET_HANDLER_H_
#include <monitor/gdb_packet.h>
/* local includes */
#include <gdb_command.h>
namespace Monitor { namespace Gdb { struct Packet_handler; } }
struct Monitor::Gdb::Packet_handler
{
using Gdb_packet = Genode::Gdb_packet<GDB_PACKET_MAX_SIZE>;
Gdb_packet _packet { };
bool execute(State &state,
Commands const &commands,
Const_byte_range_ptr const &input,
Output &output)
{
bool progress = false;
for (unsigned i = 0; i < input.num_bytes; i++) {
using Result = Gdb_packet::Append_result;
switch (_packet.append(input.start[i])) {
case Result::COMPLETE:
_packet.with_command([&] (Const_byte_range_ptr const &bytes) {
bool handled = false;
commands.for_each([&] (Command const &command) {
command.with_args(bytes, [&] (Const_byte_range_ptr const &args) {
print(output, "+"); /* ack */
command.execute(state, args, output);
handled = true; }); });
if (!handled)
warning("unhandled GDB command: ",
Cstring(bytes.start, bytes.num_bytes));
});
_packet.reset();
break;
case Result::OVERFLOW:
error("received unexpectedly large GDB command");
_packet.reset();
break;
case Result::CORRUPT:
error("received GDB command that could not be parsed");
_packet.reset();
break;
case Result::OK:
break;
}
progress = true;
}
return progress;
}
};
#endif /* _GDB_PACKET_HANDLER_H_ */

View File

@ -0,0 +1,57 @@
/*
* \brief Utilities for generating responses for the GDB protocol
* \author Norman Feske
* \date 2023-05-12
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _GDB_RESPONSE_H_
#define _GDB_RESPONSE_H_
#include <monitor/string.h>
#include <monitor/output.h>
namespace Genode {
void gdb_response(Output &, auto const &fn);
static inline void gdb_ok (Output &);
static inline void gdb_error(Output &, uint8_t);
}
/**
* Calls 'fn' with an output interface that wraps the date into a GDB packet
*/
void Genode::gdb_response(Output &output, auto const &fn)
{
Gdb_checksummed_output checksummed_output { output };
fn(checksummed_output);
};
/**
* Generate OK response
*/
static inline void Genode::gdb_ok(Output &output)
{
gdb_response(output, [&] (Output &out) { print(out, "OK"); });
}
/**
* Generate error respones
*/
static inline void Genode::gdb_error(Output &output, uint8_t errno)
{
gdb_response(output, [&] (Output &out) { print(out, "E", Gdb_hex(errno)); });
}
#endif /* _GDB_RESPONSE_H_ */

View File

@ -0,0 +1,535 @@
/*
* \brief GDB stub
* \author Norman Feske
* \date 2023-05-11
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _GDB_STUB_H_
#define _GDB_STUB_H_
#include <inferior_cpu.h>
#include <memory_accessor.h>
#include <gdb_command.h>
#include <gdb_response.h>
#include <gdb_arch.h>
namespace Monitor { namespace Gdb { struct State; } }
struct Monitor::Gdb::State : Noncopyable
{
Inferiors &inferiors;
struct Thread_list
{
char _buf[1024*16] { };
size_t _len = 0;
Thread_list(Inferiors const &inferiors)
{
Xml_generator xml(_buf, sizeof(_buf), "threads", [&] {
inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
inferior.for_each_thread([&] (Monitored_thread const &thread) {
xml.node("thread", [&] {
String<32> const id("p", inferior.id(), ".", thread.id());
xml.attribute("id", id);
xml.attribute("core", 0);
xml.attribute("name", thread._name); }); }); }); });
_len = strlen(_buf);
}
void with_bytes(auto const &fn) const
{
Const_byte_range_ptr const ptr { _buf, _len };
fn(ptr);
}
};
Memory_accessor &_memory_accessor;
struct Current : Noncopyable
{
Inferior_pd &pd;
struct Thread
{
Monitored_thread &thread;
Thread(Monitored_thread &thread) : thread(thread) { }
};
Constructible<Thread> thread { };
Current(Inferior_pd &pd) : pd(pd) { }
};
Constructible<Current> _current { };
void flush(Inferior_pd &pd)
{
if (_current.constructed() && _current->pd.id() == pd.id())
_current.destruct();
}
size_t read_memory(Memory_accessor::Virt_addr at, Byte_range_ptr const &dst)
{
if (_current.constructed())
return _memory_accessor.read(_current->pd, at, dst);
warning("attempt to read memory without a current target");
return 0;
}
bool current_defined() const { return _current.constructed(); }
void current(Inferiors::Id pid, Threads::Id tid)
{
_current.destruct();
inferiors.for_each<Inferior_pd &>([&] (Inferior_pd &inferior) {
if (inferior.id() != pid.value)
return;
_current.construct(inferior);
inferior._threads.for_each<Monitored_thread &>([&] (Monitored_thread &thread) {
if (thread.id() == tid.value)
_current->thread.construct(thread); });
});
}
void with_current_thread_state(auto const &fn)
{
Thread_state thread_state { };
if (_current.constructed() && _current->thread.constructed()) {
try {
thread_state = _current->thread->thread._real.call<Cpu_thread::Rpc_get_state>();
} catch (Cpu_thread::State_access_failed) {
warning("unable to access state of thread ", _current->thread->thread.id());
}
}
fn(thread_state);
};
State(Inferiors &inferiors, Memory_accessor &memory_accessor)
:
inferiors(inferiors), _memory_accessor(memory_accessor)
{ }
};
/*
* The command types within the 'Gdb::Cmd' namespace deliberately do not follow
* Genode's coding style regarding the use of upper/lower case letters.
*
* The types are named precisely atfer the corresponding commands of the GDB
* prototol, which are case sensitive.
*/
namespace Monitor { namespace Gdb { namespace Cmd {
/**
* Protocol negotiation
*/
struct qSupported : Command_with_separator
{
qSupported(Commands &c) : Command_with_separator(c, "qSupported") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
/* check for expected GDB features */
bool gdb_supports_multiprocess = false,
gdb_supports_vcont = false;
for_each_argument(args, Sep {';'}, [&] (Const_byte_range_ptr const &arg) {
if (equal(arg, "multiprocess+")) gdb_supports_multiprocess = true;
if (equal(arg, "vContSupported+")) gdb_supports_vcont = true;
});
if (!gdb_supports_multiprocess) warning("GDB lacks multi-process support");
if (!gdb_supports_vcont) warning("GDB lacks vcont support");
/* tell GDB about our features */
gdb_response(out, [&] (Output &out) {
print(out, "PacketSize=", Gdb_hex(GDB_PACKET_MAX_SIZE), ";");
print(out, "vContSupported+;");
print(out, "qXfer:features:read+;"); /* XML target descriptions */
print(out, "qXfer:threads:read+;");
print(out, "multiprocess+;");
print(out, "QNonStop+;");
});
}
};
extern "C" char _binary_gdb_target_xml_start[];
extern "C" char _binary_gdb_target_xml_end[];
/**
* Query XML-based information
*/
struct qXfer : Command_with_separator
{
qXfer(Commands &c) : Command_with_separator(c, "qXfer") { }
struct Raw_data_ptr : Const_byte_range_ptr
{
Raw_data_ptr(char const *start, char const *end)
:
Const_byte_range_ptr(start, end - start)
{ }
};
struct Window
{
size_t offset, len;
static Window from_args(Const_byte_range_ptr const &args)
{
return { .offset = comma_separated_hex_value(args, 0, 0UL),
.len = comma_separated_hex_value(args, 1, 0UL) };
}
};
static void _send_window(Output &out, Const_byte_range_ptr const &total_bytes, Window const window)
{
with_skipped_bytes(total_bytes, window.offset, [&] (Const_byte_range_ptr const &bytes) {
with_max_bytes(bytes, window.len, [&] (Const_byte_range_ptr const &bytes) {
gdb_response(out, [&] (Output &out) {
char const *prefix = (window.offset + window.len < total_bytes.num_bytes)
? "m" : "l"; /* 'last' marker */
print(out, prefix, Cstring(bytes.start, bytes.num_bytes)); }); }); });
}
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
bool handled = false;
with_skipped_prefix(args, "features:read:target.xml:", [&] (Const_byte_range_ptr const &args) {
Raw_data_ptr const total_bytes { _binary_gdb_target_xml_start, _binary_gdb_target_xml_end };
_send_window(out, total_bytes, Window::from_args(args));
handled = true;
});
with_skipped_prefix(args, "threads:read::", [&] (Const_byte_range_ptr const &args) {
State::Thread_list const thread_list(state.inferiors);
thread_list.with_bytes([&] (Const_byte_range_ptr const &bytes) {
_send_window(out, bytes, Window::from_args(args)); });
handled = true;
});
if (!handled)
warning("GDB ", name, " command unsupported: ", Cstring(args.start, args.num_bytes));
}
};
struct vMustReplyEmpty : Command_without_separator
{
vMustReplyEmpty(Commands &c) : Command_without_separator(c, "vMustReplyEmpty") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_response(out, [&] (Output &) { });
}
};
/**
* Set current thread
*/
struct H : Command_without_separator
{
H(Commands &commands) : Command_without_separator(commands, "H") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
log("H command args: ", Cstring(args.start, args.num_bytes));
/* 'g' for other operations, 'p' as prefix of thread-id syntax */
with_skipped_prefix(args, "gp", [&] (Const_byte_range_ptr const &args) {
auto dot_separated_arg_value = [&] (unsigned i, auto &value)
{
with_argument(args, Sep { '.' }, i, [&] (Const_byte_range_ptr const &arg) {
with_null_terminated(arg, [&] (char const * const str) {
ascii_to(str, value); }); });
};
unsigned pid = 0, tid = 0;
dot_separated_arg_value(0, pid);
dot_separated_arg_value(1, tid);
/*
* GDB initially sends a Hgp0.0 command but assumes that inferior 1
* is current. Avoid losing the default current inferior as set by
* 'Main::_create_session'.
*/
if (pid > 0)
state.current(Inferiors::Id { pid }, Threads::Id { tid });
gdb_ok(out);
});
with_skipped_prefix(args, "c-", [&] (Const_byte_range_ptr const &) {
gdb_error(out, 1); });
}
};
/**
* Enable/disable non-stop mode
*/
struct QNonStop : Command_with_separator
{
QNonStop(Commands &commands) : Command_with_separator(commands, "QNonStop") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("QNonStop command args: ", Cstring(args.start, args.num_bytes));
gdb_ok(out);
}
};
/**
* Symbol-lookup prototol (not used)
*/
struct qSymbol : Command_with_separator
{
qSymbol(Commands &commands) : Command_with_separator(commands, "qSymbol") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_ok(out);
}
};
/**
* Query trace status
*/
struct qTStatus : Command_without_separator
{
qTStatus(Commands &commands) : Command_without_separator(commands, "qTStatus") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_response(out, [&] (Output &) { });
}
};
/**
* Query current thread ID
*/
struct qC : Command_without_separator
{
qC(Commands &commands) : Command_without_separator(commands, "qC") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("qC: ", Cstring(args.start, args.num_bytes));
gdb_response(out, [&] (Output &) { });
}
};
/**
* Query attached state
*/
struct qAttached : Command_without_separator
{
qAttached(Commands &commands) : Command_without_separator(commands, "qAttached") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_response(out, [&] (Output &out) {
print(out, "1"); });
}
};
/**
* Query text/data segment offsets
*/
struct qOffsets : Command_without_separator
{
qOffsets(Commands &commands) : Command_without_separator(commands, "qOffsets") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("qOffsets: ", Cstring(args.start, args.num_bytes));
gdb_response(out, [&] (Output &) { });
}
};
/**
* Query halt reason
*/
struct ask : Command_without_separator
{
ask(Commands &c) : Command_without_separator(c, "?") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("? command args: ", Cstring(args.start, args.num_bytes));
gdb_response(out, [&] (Output &out) {
print(out, "T05"); });
}
};
/**
* Read registers
*/
struct g : Command_without_separator
{
g(Commands &c) : Command_without_separator(c, "g") { }
void execute(State &state, Const_byte_range_ptr const &, Output &out) const override
{
log("-> execute g");
gdb_response(out, [&] (Output &out) {
state.with_current_thread_state([&] (Thread_state const &thread_state) {
print_registers(out, thread_state); }); });
}
};
/**
* Read memory
*/
struct m : Command_without_separator
{
m(Commands &c) : Command_without_separator(c, "m") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
addr_t const addr = comma_separated_hex_value(args, 0, addr_t(0));
size_t const len = comma_separated_hex_value(args, 1, 0UL);
gdb_response(out, [&] (Output &out) {
for (size_t pos = 0; pos < len; ) {
char chunk[16*1024] { };
size_t const remain_len = len - pos;
size_t const num_bytes = min(sizeof(chunk), remain_len);
size_t const read_len =
state.read_memory(Memory_accessor::Virt_addr { addr + pos },
Byte_range_ptr(chunk, num_bytes));
for (unsigned i = 0; i < read_len; i++)
print(out, Gdb_hex(chunk[i]));
pos += read_len;
if (read_len < num_bytes)
break;
}
});
}
};
/**
* Query liveliness of thread ID
*/
struct T : Command_without_separator
{
T(Commands &c) : Command_without_separator(c, "T") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("T command args: ", Cstring(args.start, args.num_bytes));
gdb_ok(out);
}
};
/**
* Disconnect
*/
struct D : Command_with_separator
{
D(Commands &c) : Command_with_separator(c, "D") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_ok(out);
}
};
} /* namespace Cmd */ } /* namespace Gdb */ } /* namespace Monitor */
/*
* Registry of all supported commands
*/
namespace Monitor { namespace Gdb { struct Supported_commands; } }
struct Monitor::Gdb::Supported_commands : Commands
{
template <typename...>
struct Instances;
template <typename LAST>
struct Instances<LAST>
{
LAST _last;
Instances(Commands &registry) : _last(registry) { };
};
template <typename HEAD, typename... TAIL>
struct Instances<HEAD, TAIL...>
{
HEAD _head;
Instances<TAIL...> _tail;
Instances(Commands &registry) : _head(registry), _tail(registry) { }
};
Instances<
Cmd::qSupported,
Cmd::qXfer,
Cmd::vMustReplyEmpty,
Cmd::H,
Cmd::QNonStop,
Cmd::qSymbol,
Cmd::qTStatus,
Cmd::qC,
Cmd::qAttached,
Cmd::qOffsets,
Cmd::g,
Cmd::m,
Cmd::D,
Cmd::T,
Cmd::ask
> _instances { *this };
};
#endif /* _GDB_STUB_H_ */

View File

@ -0,0 +1,101 @@
/*
* \brief CPU session of monitored child PD
* \author Norman Feske
* \date 2023-05-08
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _INFERIOR_CPU_H_
#define _INFERIOR_CPU_H_
/* local includes */
#include <monitored_cpu.h>
namespace Monitor { struct Inferior_cpu; }
struct Monitor::Inferior_cpu : Monitored_cpu_session
{
Allocator &_alloc;
Constructible<Monitored_native_cpu_nova> _native_cpu_nova { };
enum class Kernel { GENERIC, NOVA };
void init_native_cpu(Kernel kernel)
{
if (kernel == Kernel::NOVA) {
Capability<Native_cpu_nova> native_cpu_cap =
reinterpret_cap_cast<Native_cpu_nova>(_real.call<Rpc_native_cpu>());
_native_cpu_nova.construct(_ep, native_cpu_cap, "");
}
}
Inferior_cpu(Entrypoint &ep, Capability<Cpu_session> real,
Name const &name, Allocator &alloc)
:
Monitored_cpu_session(ep, real, name), _alloc(alloc)
{ }
/***************************
** Cpu_session interface **
***************************/
Thread_capability
create_thread(Capability<Pd_session> pd, Cpu_session::Name const &name,
Affinity::Location affinity, Weight weight, addr_t utcb) override
{
Thread_capability result { };
Inferior_pd::with_inferior_pd(_ep, pd,
[&] (Inferior_pd &inferior_pd) {
Capability<Cpu_thread> real_thread =
_real.call<Rpc_create_thread>(inferior_pd._real,
name, affinity, weight, utcb);
Monitored_thread &monitored_thread = *new (_alloc)
Monitored_thread(_ep, real_thread, name, inferior_pd._threads,
inferior_pd.alloc_thread_id());
result = monitored_thread.cap();
},
[&] {
result = _real.call<Rpc_create_thread>(pd, name, affinity, weight, utcb);
});
return result;
}
void kill_thread(Thread_capability thread) override {
_real.call<Rpc_kill_thread>(thread); }
void exception_sigh(Signal_context_capability sigh) override {
_real.call<Rpc_exception_sigh>(sigh); }
Affinity::Space affinity_space() const override {
return _real.call<Rpc_affinity_space>(); }
Dataspace_capability trace_control() override {
return _real.call<Rpc_trace_control>(); }
Quota quota() override { return _real.call<Rpc_quota>(); }
Capability<Native_cpu> native_cpu() override
{
if (_native_cpu_nova.constructed()) {
Untyped_capability cap = _native_cpu_nova->cap();
return reinterpret_cap_cast<Native_cpu>(cap);
}
return _real.call<Rpc_native_cpu>();
}
};
#endif /* _INFERIOR_CPU_H_ */

View File

@ -0,0 +1,246 @@
/*
* \brief Inferior is a monitored child PD
* \author Norman Feske
* \date 2023-05-08
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _INFERIOR_PD_H_
#define _INFERIOR_PD_H_
/* Genode includes */
#include <base/attached_dataspace.h>
#include <os/session_policy.h>
/* local includes */
#include <monitored_pd.h>
namespace Monitor { struct Inferior_pd; }
struct Monitor::Inferior_pd : Monitored_pd_session
{
Inferiors::Element _inferiors_elem;
Monitored_region_map _address_space {
_ep, _real.call<Pd_session::Rpc_address_space>(), "address space" };
Monitored_region_map _stack_area {
_ep, _real.call<Pd_session::Rpc_stack_area>(), "stack area" };
Monitored_region_map _linker_area {
_ep, _real.call<Pd_session::Rpc_linker_area>(), "linker area" };
Threads _threads { };
Threads::Id _last_thread_id { };
Io_signal_handler<Inferior_pd> _page_fault_handler;
Region_map &_local_rm; /* for wiping RAM dataspaces on free */
Allocator &_alloc; /* used for allocating 'Ram_ds' objects */
Ram_allocator &_wx_ram; /* RAM used for writeable text segments */
struct Policy
{
bool wait; /* wait for GDB continue command */
bool stop; /* stop execution when GDB connects */
bool wx; /* make text segments writeable */
static Policy from_xml(Xml_node const &policy)
{
return { .wait = policy.attribute_value("wait", false),
.stop = policy.attribute_value("stop", true),
.wx = policy.attribute_value("wx", false) };
}
static Policy default_policy() { return from_xml("<empty/>"); }
};
Policy _policy = Policy::default_policy();
unsigned _page_fault_count = 0;
void _handle_page_fault() { _page_fault_count++; }
/**
* Keep track of allocated RAM dataspaces for wiping when freed
*/
struct Ram_ds : Id_space<Ram_ds>::Element
{
static Id_space<Ram_ds>::Id id(Dataspace_capability const &cap)
{
return { (unsigned long)cap.local_name() };
}
Ram_ds(Id_space<Ram_ds> &id_space, Dataspace_capability cap)
:
Id_space<Ram_ds>::Element(*this, id_space, id(cap)), cap(cap)
{ }
Dataspace_capability cap;
};
Id_space<Ram_ds> _ram_dataspaces { };
void _wipe_ram_ds(Ram_ds &ram_ds)
{
{
Attached_dataspace ds(_local_rm, ram_ds.cap);
memset(ds.local_addr<void>(), 0, ds.size());
}
destroy(_alloc, &ram_ds);
}
Inferior_pd(Entrypoint &ep, Capability<Pd_session> real, Name const &name,
Inferiors &inferiors, Inferiors::Id id, Region_map &local_rm,
Allocator &alloc, Ram_allocator &wx_ram)
:
Monitored_pd_session(ep, real, name),
_inferiors_elem(*this, inferiors, id),
_page_fault_handler(ep, *this, &Inferior_pd::_handle_page_fault),
_local_rm(local_rm), _alloc(alloc), _wx_ram(wx_ram)
{
_address_space._real.call<Region_map::Rpc_fault_handler>(_page_fault_handler);
_stack_area ._real.call<Region_map::Rpc_fault_handler>(_page_fault_handler);
_linker_area ._real.call<Region_map::Rpc_fault_handler>(_page_fault_handler);
}
~Inferior_pd()
{
while (_ram_dataspaces.apply_any<Ram_ds &>([&] (Ram_ds &ram_ds) {
_wipe_ram_ds(ram_ds); }));
while (_threads.apply_any<Monitored_thread &>([&] (Monitored_thread &thread) {
destroy(_alloc, &thread); }));
}
void apply_monitor_config(Xml_node const &monitor)
{
with_matching_policy(_name, monitor,
[&] (Xml_node const policy) { _policy = Policy::from_xml(policy); },
[&] { _policy = Policy::default_policy(); });
_address_space.writeable_text_segments(_alloc, _wx_ram, _local_rm);
_linker_area .writeable_text_segments(_alloc, _wx_ram, _local_rm);
}
long unsigned id() const { return _inferiors_elem.id().value; }
unsigned page_fault_count() const { return _page_fault_count; }
Threads::Id alloc_thread_id()
{
_last_thread_id.value++;
return _last_thread_id;
}
void for_each_thread(auto const &fn) const
{
_threads.for_each<Monitored_thread const &>(fn);
}
static void with_inferior_pd(Entrypoint &ep, Capability<Pd_session> pd_cap,
auto const &monitored_fn, auto const &direct_fn)
{
with_monitored<Inferior_pd>(ep, pd_cap, monitored_fn, direct_fn);
}
/**************************
** Pd_session interface **
**************************/
void assign_parent(Capability<Parent> parent) override {
_real.call<Rpc_assign_parent>(parent); }
bool assign_pci(addr_t pci_config_memory_address, uint16_t bdf) override {
return _real.call<Rpc_assign_pci>(pci_config_memory_address, bdf); }
void map(addr_t virt, addr_t size) override {
_real.call<Rpc_map>(virt, size); }
Signal_source_capability alloc_signal_source() override {
return _real.call<Rpc_alloc_signal_source>(); }
void free_signal_source(Signal_source_capability cap) override {
_real.call<Rpc_free_signal_source>(cap); }
Signal_context_capability alloc_context(Signal_source_capability source,
unsigned long imprint) override {
return _real.call<Rpc_alloc_context>(source, imprint); }
void free_context(Signal_context_capability cap) override {
_real.call<Rpc_free_context>(cap); }
void submit(Signal_context_capability receiver, unsigned cnt = 1) override {
_real.call<Rpc_submit>(receiver, cnt); }
Native_capability alloc_rpc_cap(Native_capability ep) override {
return _real.call<Rpc_alloc_rpc_cap>(ep); }
void free_rpc_cap(Native_capability cap) override {
_real.call<Rpc_free_rpc_cap>(cap); }
Capability<Region_map> address_space() override { return _address_space.cap(); }
Capability<Region_map> stack_area() override { return _stack_area.cap(); }
Capability<Region_map> linker_area() override { return _linker_area.cap(); }
Cap_quota cap_quota() const override {
return _real.call<Rpc_cap_quota>(); }
Cap_quota used_caps() const override {
return _real.call<Rpc_used_caps>(); }
Alloc_result try_alloc(size_t size, Cache cache = CACHED) override
{
return _real.call<Rpc_try_alloc>(size, cache).convert<Alloc_result>(
[&] (Ram_dataspace_capability cap) -> Alloc_result {
new (_alloc) Ram_ds(_ram_dataspaces, cap);
return cap;
},
[&] (Alloc_error e) -> Alloc_result { return e; });
}
void free(Ram_dataspace_capability ds) override
{
_ram_dataspaces.apply<Ram_ds &>(Ram_ds::id(ds), [&] (Ram_ds &ram_ds) {
_wipe_ram_ds(ram_ds); });
_real.call<Rpc_free>(ds);
}
size_t dataspace_size(Ram_dataspace_capability ds) const override
{
return ds.valid() ? Dataspace_client(ds).size() : 0;
}
Ram_quota ram_quota() const override {
return _real.call<Rpc_ram_quota>(); }
Ram_quota used_ram() const override {
return _real.call<Rpc_used_ram>(); }
Capability<Native_pd> native_pd() override {
return _real.call<Rpc_native_pd>(); }
Managing_system_state managing_system(Managing_system_state const & state) override {
return _real.call<Rpc_managing_system>(state); }
addr_t dma_addr(Ram_dataspace_capability ds) override {
return _real.call<Rpc_dma_addr>(ds); }
Attach_dma_result attach_dma(Dataspace_capability ds, addr_t at) override {
return _real.call<Rpc_attach_dma>(ds, at); }
};
#endif /* _INFERIOR_PD_H_ */

View File

@ -0,0 +1,345 @@
/*
* \brief Init component with builtin debug monitor
* \author Norman Feske
* \date 2023-05-08
*/
/*
* Copyright (C) 2023 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/component.h>
#include <base/attached_rom_dataspace.h>
#include <region_map/client.h>
#include <sandbox/sandbox.h>
#include <os/reporter.h>
#include <terminal_session/connection.h>
/* local includes */
#include <pd_intrinsics.h>
#include <gdb_stub.h>
#include <gdb_packet_handler.h>
namespace Monitor {
template <typename CONNECTION>
struct Local_session_base : Noncopyable
{
CONNECTION _connection;
Local_session_base(Env &env, Session::Label const &label)
:
_connection(env, label)
{ };
};
template <typename CONNECTION, typename MONITORED_SESSION>
struct Local_session : private Local_session_base<CONNECTION>,
public MONITORED_SESSION
{
using Local_session_base<CONNECTION>::_connection;
template <typename... ARGS>
Local_session(Env &env, Session::Label const &label, ARGS &&... args)
:
Local_session_base<CONNECTION>(env, label),
MONITORED_SESSION(env.ep(), _connection.cap(), label, args...)
{ }
void upgrade(Session::Resources const &resources)
{
_connection.upgrade(resources);
}
};
}
namespace Monitor { struct Main; }
struct Monitor::Main : Sandbox::State_handler
{
using Local_pd_session = Local_session<Pd_connection, Inferior_pd>;
using Local_cpu_session = Local_session<Cpu_connection, Inferior_cpu>;
using Pd_service = Sandbox::Local_service<Local_pd_session>;
using Cpu_service = Sandbox::Local_service<Local_cpu_session>;
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
Pd_intrinsics _pd_intrinsics { _env };
Sandbox _sandbox { _env, *this, _pd_intrinsics };
Inferior_cpu::Kernel _detect_kernel()
{
Attached_rom_dataspace info { _env, "platform_info" };
Inferior_cpu::Kernel result = Inferior_cpu::Kernel::GENERIC;
info.xml().with_optional_sub_node("kernel", [&] (Xml_node const &kernel) {
if (kernel.attribute_value("name", String<10>()) == "nova")
result = Inferior_cpu::Kernel::NOVA; });
return result;
}
Inferior_cpu::Kernel const _kernel = _detect_kernel();
Attached_rom_dataspace _config { _env, "config" };
Inferiors::Id _last_inferior_id { }; /* counter for unique inferior IDs */
Inferiors _inferiors { };
struct Gdb_stub
{
Env &_env;
Terminal::Connection _terminal { _env };
Signal_handler<Gdb_stub> _terminal_read_avail_handler {
_env.ep(), *this, &Gdb_stub::_handle_terminal_read_avail };
Memory_accessor _memory_accessor { _env };
Gdb::Packet_handler _packet_handler { };
Gdb::State _state;
Gdb::Supported_commands _commands { };
struct Terminal_output
{
struct Write_fn
{
Terminal::Connection &_terminal;
void operator () (char const *str)
{
for (;;) {
size_t const num_bytes = strlen(str);
size_t const written_bytes = _terminal.write(str, num_bytes);
if (written_bytes == num_bytes)
break;
str = str + written_bytes;
}
}
} _write_fn;
Buffered_output<1024, Write_fn> buffered { _write_fn };
};
void _handle_terminal_read_avail()
{
Terminal_output output { ._write_fn { _terminal } };
for (;;) {
char buffer[1024] { };
size_t const num_bytes = _terminal.read(buffer, sizeof(buffer));
if (!num_bytes)
return;
_packet_handler.execute(_state, _commands,
Const_byte_range_ptr { buffer, num_bytes },
output.buffered);
}
}
void flush(Inferior_pd &pd)
{
_state.flush(pd);
_memory_accessor.flush();
}
Gdb_stub(Env &env, Inferiors &inferiors)
:
_env(env), _state(inferiors, _memory_accessor)
{
_terminal.read_avail_sigh(_terminal_read_avail_handler);
_handle_terminal_read_avail();
}
};
Constructible<Gdb_stub> _gdb_stub { };
void _handle_resource_avail() { }
Signal_handler<Main> _resource_avail_handler {
_env.ep(), *this, &Main::_handle_resource_avail };
Constructible<Reporter> _reporter { };
size_t _report_buffer_size = 0;
template <typename SERVICE>
void _handle_service(SERVICE &);
void _handle_pd_service() { _handle_service<Pd_service> (_pd_service); }
void _handle_cpu_service() { _handle_service<Cpu_service>(_cpu_service); }
struct Service_handler : Sandbox::Local_service_base::Wakeup
{
Main &_main;
using Member = void (Main::*) ();
Member _member;
void wakeup_local_service() override { (_main.*_member)(); }
Service_handler(Main &main, Member member)
: _main(main), _member(member) { }
};
Service_handler _pd_handler { *this, &Main::_handle_pd_service };
Service_handler _cpu_handler { *this, &Main::_handle_cpu_service };
Pd_service _pd_service { _sandbox, _pd_handler };
Cpu_service _cpu_service { _sandbox, _cpu_handler };
Local_pd_session &_create_session(Pd_service &, Session::Label const &label)
{
_last_inferior_id.value++;
Inferiors::Id const id = _last_inferior_id;
Local_pd_session &session = *new (_heap)
Local_pd_session(_env, label, _inferiors, id, _env.rm(), _heap, _env.ram());
_apply_monitor_config_to_inferiors();
/* set first monitored PD as current inferior */
if (_gdb_stub.constructed() && !_gdb_stub->_state.current_defined())
_gdb_stub->_state.current(id, Threads::Id { });
return session;
}
Local_cpu_session &_create_session(Cpu_service &, Session::Label const &label)
{
Local_cpu_session &session = *new (_heap) Local_cpu_session(_env, label, _heap);
session.init_native_cpu(_kernel);
return session;
}
void _destroy_session(Local_pd_session &session)
{
if (_gdb_stub.constructed())
_gdb_stub->flush(session);
destroy(_heap, &session);
}
void _destroy_session(Local_cpu_session &session)
{
destroy(_heap, &session);
}
void _apply_monitor_config_to_inferiors()
{
_config.xml().with_sub_node("monitor",
[&] (Xml_node const monitor) {
_inferiors.for_each<Inferior_pd>([&] (Inferior_pd &pd) {
pd.apply_monitor_config(monitor); }); },
[&] {
_inferiors.for_each<Inferior_pd>([&] (Inferior_pd &pd) {
pd.apply_monitor_config("<monitor/>"); }); });
}
void _handle_config()
{
_config.update();
Xml_node const config = _config.xml();
bool reporter_enabled = false;
config.with_optional_sub_node("report", [&] (Xml_node report) {
reporter_enabled = true;
/* (re-)construct reporter whenever the buffer size is changed */
Number_of_bytes const buffer_size =
report.attribute_value("buffer", Number_of_bytes(4096));
if (buffer_size != _report_buffer_size || !_reporter.constructed()) {
_report_buffer_size = buffer_size;
_reporter.construct(_env, "state", "state", _report_buffer_size);
}
});
if (_reporter.constructed())
_reporter->enabled(reporter_enabled);
_gdb_stub.conditional(config.has_sub_node("monitor"), _env, _inferiors);
_apply_monitor_config_to_inferiors();
_sandbox.apply_config(config);
}
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
/**
* Sandbox::State_handler interface
*/
void handle_sandbox_state() override
{
try {
Reporter::Xml_generator xml(*_reporter, [&] () {
_sandbox.generate_state_report(xml); });
}
catch (Xml_generator::Buffer_exceeded) {
error("state report exceeds maximum size");
/* try to reflect the error condition as state report */
try {
Reporter::Xml_generator xml(*_reporter, [&] () {
xml.attribute("error", "report buffer exceeded"); });
}
catch (...) { }
}
}
Main(Env &env) : _env(env)
{
_config.sigh(_config_handler);
/* prevent blocking for resource upgrades (never satisfied by core) */
_env.parent().resource_avail_sigh(_resource_avail_handler);
_handle_config();
}
};
template <typename SERVICE>
void Monitor::Main::_handle_service(SERVICE &service)
{
using Local_session = typename SERVICE::Local_session;
service.for_each_requested_session([&] (typename SERVICE::Request &request) {
request.deliver_session(_create_session(service, request.label));
});
service.for_each_upgraded_session([&] (Local_session &session,
Session::Resources const &amount) {
session.upgrade(amount);
return SERVICE::Upgrade_response::CONFIRMED;
});
service.for_each_session_to_close([&] (Local_session &session) {
_destroy_session(session);
return SERVICE::Close_response::CLOSED;
});
}
void Component::construct(Genode::Env &env) { static Monitor::Main main(env); }

View File

@ -0,0 +1,246 @@
/*
* \brief Mechanism for accessing the virtual memory of inferiors
* \author Norman Feske
* \date 2023-05-24
*
* The 'Memory_accessor' uses a dedicated "probe" thread of accessing the
* inferior's memory. It keeps a window of the address space locally attached
* and guards the access of this window by the probe thread by watching out for
* page faults. The sacrifice of the probe thread shields the monitor from the
* effects of accessing empty regions of the inferior's address space.
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MEMORY_ACCESSOR_H_
#define _MEMORY_ACCESSOR_H_
/* Genode includes */
#include <rm_session/connection.h>
#include <timer_session/connection.h>
/* local includes */
#include <inferior_pd.h>
#include <types.h>
namespace Monitor { struct Memory_accessor; }
class Monitor::Memory_accessor : Noncopyable
{
public:
struct Virt_addr { addr_t value; };
private:
Env &_env;
static constexpr size_t WINDOW_SIZE_LOG2 = 24, /* 16 MiB */
WINDOW_SIZE = 1 << WINDOW_SIZE_LOG2;
struct Curr_view
{
Region_map &_local_rm;
Inferior_pd &_pd;
addr_t const _offset;
struct { uint8_t const *_local_ptr; };
Curr_view(Region_map &local_rm, Inferior_pd &pd, addr_t offset)
:
_local_rm(local_rm), _pd(pd), _offset(offset),
_local_ptr(_local_rm.attach(pd._address_space.dataspace(),
WINDOW_SIZE, offset))
{ }
~Curr_view() { _local_rm.detach(_local_ptr); }
bool _in_curr_range(Virt_addr at) const
{
return (at.value >= _offset) && (at.value < _offset + WINDOW_SIZE);
}
bool contains(Inferior_pd &pd, Virt_addr at) const
{
return (pd.id() == _pd.id()) && _in_curr_range(at);
}
};
Constructible<Curr_view> _curr_view { };
using Probe_response_handler = Io_signal_handler<Memory_accessor>;
Io_signal_handler<Memory_accessor> _probe_response_handler {
_env.ep(), *this, &Memory_accessor::_handle_probe_response };
void _handle_probe_response() { }
struct Probe : Thread
{
Probe_response_handler &_probe_response_handler;
Blockade _blockade { };
/* exists only temporary during a 'Memory_accessor::read' call */
struct Read_job
{
Curr_view &curr_view;
Virt_addr at;
Byte_range_ptr const &dst;
size_t pos = 0; /* number of completed bytes */
bool done = 0; /* true if 'execute_may_fault' survived */
Read_job(Curr_view &curr_view, Virt_addr at, Byte_range_ptr const &dst)
:
curr_view(curr_view), at(at), dst(dst)
{ }
/* called only once */
void execute_may_fault()
{
/* offset from the start of the window */
addr_t const window_pos = at.value - curr_view._offset;
addr_t const num_bytes_at_window_pos = WINDOW_SIZE - window_pos;
size_t const len = min(dst.num_bytes, num_bytes_at_window_pos);
uint8_t const * const src_ptr = curr_view._local_ptr;
for (pos = 0; pos < len; pos++)
dst.start[pos] = src_ptr[window_pos + pos];
done = true;
}
};
Constructible<Read_job> _read_job { };
/**
* Thread interface
*/
void entry() override
{
for (;;) {
_blockade.block();
if (_read_job.constructed()) {
_read_job->execute_may_fault();
/* wakeup 'Memory_accessor::read' via I/O signal */
_probe_response_handler.local_submit();
}
}
}
Probe(Env &env, Probe_response_handler &probe_response_handler)
:
Thread(env, "probe", 16*1024),
_probe_response_handler(probe_response_handler)
{
start();
}
size_t read(Curr_view &curr_view,
Virt_addr at, Byte_range_ptr const &dst, auto const &block_fn)
{
_read_job.construct(curr_view, at, dst);
_blockade.wakeup();
/* block until read is done or a page fault occurred */
while (!_read_job->done && block_fn());
size_t const result = _read_job->pos;
_read_job.destruct();
return result;
}
};
Constructible<Probe> _probe { };
Io_signal_handler<Memory_accessor> _timeout_handler {
_env.ep(), *this, &Memory_accessor::_handle_timeout };
Timer::Connection _watchdog_timer { _env };
unsigned _timeout_count = 0;
void _handle_timeout() { _timeout_count++; }
public:
Memory_accessor(Env &env) : _env(env)
{
_watchdog_timer.sigh(_timeout_handler);
}
void flush() { _curr_view.destruct(); }
/**
* Read memory from inferior 'pd' at address 'at' into buffer 'dst'
*
* The 'dst.num_bytes' value denotes the number of bytes to read.
*
* \return number of successfully read bytes, which can be smaller
* than 'dst.num_bytes' when encountering the bounds of
* mapped memory in the inferior's address space.
*/
size_t read(Inferior_pd &pd, Virt_addr at, Byte_range_ptr const &dst)
{
if (_curr_view.constructed() && !_curr_view->contains(pd, at))
_curr_view.destruct();
if (!_curr_view.constructed()) {
addr_t const offset = at.value & ~(WINDOW_SIZE - 1);
try { _curr_view.construct(_env.rm(), pd, offset); }
catch (Region_map::Region_conflict) {
warning("attempt to read outside the virtual address space: ",
Hex(at.value));
return 0;
}
}
/* give up after 100 milliseconds */
_watchdog_timer.trigger_once(1000*100);
/* drain pending signals to avoid spurious watchdog timeouts */
while (_env.ep().dispatch_pending_io_signal());
unsigned const orig_page_fault_count = pd.page_fault_count();
unsigned const orig_timeout_count = _timeout_count;
if (!_probe.constructed())
_probe.construct(_env, _probe_response_handler);
auto fault_or_timeout_occurred = [&] {
return (orig_page_fault_count != pd.page_fault_count())
|| (orig_timeout_count != _timeout_count); };
auto block_fn = [&]
{
if (fault_or_timeout_occurred())
return false; /* cancel read */
_env.ep().wait_and_dispatch_one_io_signal();
return true; /* keep trying */
};
size_t const read_num_bytes =
_probe->read(*_curr_view, at, dst, block_fn);
/* wind down the faulted probe thread, spawn a fresh one on next call */
if (fault_or_timeout_occurred())
_probe.destruct();
return read_num_bytes;
}
};
#endif /* _MEMORY_ACCESSOR_H_ */

View File

@ -0,0 +1,74 @@
/*
* \brief Monitored CPU session
* \author Norman Feske
* \date 2023-05-09
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITORED_CPU_H_
#define _MONITORED_CPU_H_
/* Genode includes */
#include <base/rpc_client.h>
#include <cpu_session/connection.h>
/* local includes */
#include <inferior_pd.h>
#include <monitored_native_cpu.h>
namespace Monitor { struct Monitored_cpu_session; }
struct Monitor::Monitored_cpu_session : Monitored_rpc_object<Cpu_session>
{
using Monitored_rpc_object::Monitored_rpc_object;
void _with_cpu_arg(Capability<Cpu_session> cpu_cap,
auto const &monitored_fn, auto const &direct_fn)
{
if (cpu_cap == cap()) {
error("attempt to pass invoked capability as RPC argument");
return;
}
with_monitored<Monitored_cpu_session>(_ep, cpu_cap, monitored_fn, direct_fn);
}
/***************************
** Cpu_session interface **
***************************/
int ref_account(Cpu_session_capability cpu_cap) override
{
int result = 0;
_with_cpu_arg(cpu_cap,
[&] (Monitored_cpu_session &monitored_cpu) {
result = _real.call<Rpc_ref_account>(monitored_cpu._real); },
[&] {
result = _real.call<Rpc_ref_account>(cpu_cap); });
return result;
}
int transfer_quota(Cpu_session_capability cpu_cap, size_t amount) override
{
int result = 0;
_with_cpu_arg(cpu_cap,
[&] (Monitored_cpu_session &monitored_cpu) {
result = _real.call<Rpc_transfer_quota>(monitored_cpu._real, amount); },
[&] {
result = _real.call<Rpc_transfer_quota>(cpu_cap, amount); });
return result;
}
};
#endif /* _MONITORED_CPU_H_ */

View File

@ -0,0 +1,44 @@
/*
* \brief Monitored kernel-specific native CPU interface
* \author Norman Feske
* \date 2023-05-16
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITORED_NATIVE_CPU_H_
#define _MONITORED_NATIVE_CPU_H_
/* Genode includes */
#include <base/rpc_client.h>
#include <cpu_thread/client.h>
/* local includes */
#include <types.h>
#include <monitored_thread.h>
#include <native_cpu_nova.h>
namespace Monitor { struct Monitored_native_cpu_nova; }
struct Monitor::Monitored_native_cpu_nova : Monitored_rpc_object<Native_cpu_nova>
{
using Monitored_rpc_object::Monitored_rpc_object;
void thread_type(Capability<Cpu_thread> cap, Thread_type type,
Exception_base exc) override
{
Monitored_thread::with_thread(_ep, cap,
[&] (Monitored_thread &monitored_thread) {
_real.call<Rpc_thread_type>(monitored_thread._real, type, exc); },
[&] /* thread not intercepted */ {
_real.call<Rpc_thread_type>(cap, type, exc); });
}
};
#endif /* _MONITORED_NATIVE_CPU_H_ */

View File

@ -0,0 +1,73 @@
/*
* \brief Monitored PD session
* \author Norman Feske
* \date 2023-05-08
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITORED_PD_H_
#define _MONITORED_PD_H_
/* Genode includes */
#include <base/rpc_client.h>
#include <pd_session/connection.h>
/* local includes */
#include <monitored_region_map.h>
#include <monitored_thread.h>
namespace Monitor { struct Monitored_pd_session; }
struct Monitor::Monitored_pd_session : Monitored_rpc_object<Pd_session>
{
using Monitored_rpc_object::Monitored_rpc_object;
void _with_pd_arg(Capability<Pd_session> pd_cap,
auto const &monitored_fn, auto const &direct_fn)
{
if (pd_cap == cap()) {
error("attempt to pass invoked capability as RPC argument");
return;
}
with_monitored<Monitored_pd_session>(_ep, pd_cap, monitored_fn, direct_fn);
}
/**************************
** Pd_session interface **
**************************/
void ref_account(Capability<Pd_session> pd_cap) override
{
_with_pd_arg(pd_cap,
[&] (Monitored_pd_session &pd) { _real.call<Rpc_ref_account>(pd._real); },
[&] { _real.call<Rpc_ref_account>(pd_cap); });
}
void transfer_quota(Capability<Pd_session> pd_cap, Cap_quota amount) override
{
_with_pd_arg(pd_cap,
[&] (Monitored_pd_session &pd) {
_real.call<Rpc_transfer_cap_quota>(pd._real, amount); },
[&] {
_real.call<Rpc_transfer_cap_quota>(pd_cap, amount); });
}
void transfer_quota(Pd_session_capability pd_cap, Ram_quota amount) override
{
_with_pd_arg(pd_cap,
[&] (Monitored_pd_session &pd) {
_real.call<Rpc_transfer_ram_quota>(pd._real, amount); },
[&] {
_real.call<Rpc_transfer_ram_quota>(pd_cap, amount); });
}
};
#endif /* _MONITORED_PD_H_ */

View File

@ -0,0 +1,145 @@
/*
* \brief Monitored region map
* \author Norman Feske
* \date 2023-05-09
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITORED_REGION_MAP_H_
#define _MONITORED_REGION_MAP_H_
/* Genode includes */
#include <base/rpc_client.h>
#include <base/attached_ram_dataspace.h>
#include <region_map/region_map.h>
/* local includes */
#include <types.h>
namespace Monitor { struct Monitored_region_map; }
struct Monitor::Monitored_region_map : Monitored_rpc_object<Region_map>
{
using Monitored_rpc_object::Monitored_rpc_object;
/* see the comment in base/include/region_map/client.h */
Dataspace_capability _rm_ds_cap { };
struct Writeable_text_segments
{
Allocator &_alloc;
Ram_allocator &_ram;
Region_map &_local_rm;
struct Ram_ds : Registry<Ram_ds>::Element
{
Attached_ram_dataspace ds;
Ram_ds(Registry<Ram_ds> &registry, Ram_allocator &ram, Region_map &local_rm,
Const_byte_range_ptr const &content)
:
Registry<Ram_ds>::Element(registry, *this),
ds(ram, local_rm, content.num_bytes)
{
memcpy(ds.local_addr<void>(), content.start, content.num_bytes);
}
};
Registry<Ram_ds> _dataspaces { };
Writeable_text_segments(Allocator &alloc,
Ram_allocator &ram,
Region_map &local_rm)
:
_alloc(alloc), _ram(ram), _local_rm(local_rm)
{ }
~Writeable_text_segments()
{
_dataspaces.for_each([&] (Ram_ds &ram_ds) {
destroy(_alloc, &ram_ds); });
}
Dataspace_capability create_writable_copy(Dataspace_capability orig_ds,
off_t offset, size_t size)
{
Attached_dataspace ds { _local_rm, orig_ds };
if (size_t(offset) >= ds.size())
return Dataspace_capability();
Const_byte_range_ptr const
content_ptr(ds.local_addr<char>() + offset,
min(size, ds.size() - size_t(offset)));
Ram_ds &ram_ds = *new (_alloc)
Ram_ds(_dataspaces, _ram, _local_rm, content_ptr);
return ram_ds.ds.cap();
}
};
Constructible<Writeable_text_segments> _writeable_text_segments { };
void writeable_text_segments(Allocator &alloc,
Ram_allocator &ram,
Region_map &local_rm)
{
if (!_writeable_text_segments.constructed())
_writeable_text_segments.construct(alloc, ram, local_rm);
}
/**************************
** Region_map interface **
**************************/
Local_addr attach(Dataspace_capability ds, size_t size = 0,
off_t offset = 0, bool use_local_addr = false,
Local_addr local_addr = (void *)0,
bool executable = false,
bool writeable = true) override
{
if (executable && !writeable && _writeable_text_segments.constructed()) {
ds = _writeable_text_segments->create_writable_copy(ds, offset, size);
offset = 0;
writeable = true;
}
return _real.call<Rpc_attach>(ds, size, offset, use_local_addr, local_addr,
executable, writeable);
}
void detach(Local_addr local_addr) override
{
_real.call<Rpc_detach>(local_addr);
}
void fault_handler(Signal_context_capability) override
{
warning("Monitored_region_map: ignoring custom fault_handler for ", _name);
}
State state() override
{
return _real.call<Rpc_state>();
}
Dataspace_capability dataspace() override
{
if (!_rm_ds_cap.valid())
_rm_ds_cap = _real.call<Rpc_dataspace>();
return _rm_ds_cap;
}
};
#endif /* _MONITORED_REGION_MAP_H_ */

View File

@ -0,0 +1,84 @@
/*
* \brief Monitored CPU thread
* \author Norman Feske
* \date 2023-05-16
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITORED_THREAD_H_
#define _MONITORED_THREAD_H_
/* Genode includes */
#include <base/rpc_client.h>
#include <cpu_thread/client.h>
/* local includes */
#include <types.h>
namespace Monitor { struct Monitored_thread; }
struct Monitor::Monitored_thread : Monitored_rpc_object<Cpu_thread>
{
static void with_thread(Entrypoint &ep, Capability<Cpu_thread> cap,
auto const &monitored_fn, auto const &direct_fn)
{
with_monitored<Monitored_thread>(ep, cap, monitored_fn, direct_fn);
}
Threads::Element _threads_elem;
using Monitored_rpc_object::Monitored_rpc_object;
Monitored_thread(Entrypoint &ep, Capability<Cpu_thread> real, Name const &name,
Threads &threads, Threads::Id id)
:
Monitored_rpc_object(ep, real, name), _threads_elem(*this, threads, id)
{ }
long unsigned id() const { return _threads_elem.id().value; }
Dataspace_capability utcb() override {
return _real.call<Rpc_utcb>(); }
void start(addr_t ip, addr_t sp) override {
_real.call<Rpc_start>(ip, sp); }
void pause() override {
_real.call<Rpc_pause>(); }
void resume() override {
_real.call<Rpc_resume>(); }
Thread_state state() override {
return _real.call<Rpc_get_state>(); }
void state(Thread_state const &state) override {
_real.call<Rpc_set_state>(state); }
void exception_sigh(Signal_context_capability handler) override {
_real.call<Rpc_exception_sigh>(handler); }
void single_step(bool enabled) override {
_real.call<Rpc_single_step>(enabled); }
void affinity(Affinity::Location location) override {
_real.call<Rpc_affinity>(location); }
unsigned trace_control_index() override {
return _real.call<Rpc_trace_control_index>(); }
Dataspace_capability trace_buffer() override {
return _real.call<Rpc_trace_buffer>(); }
Dataspace_capability trace_policy() override {
return _real.call<Rpc_trace_policy>(); }
};
#endif /* _MONITORED_THREAD_H_ */

View File

@ -0,0 +1,49 @@
/*
* \brief NOVA-specific part of the CPU session interface
* \author Norman Feske
* \date 2022-06-02
*
* Mirrored from 'base-nova/include/nova_native_cpu/nova_native_cpu.h'.
*/
/*
* Copyright (C) 2016-2023 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.
*/
#ifndef _NATIVE_CPU_NOVA_H_
#define _NATIVE_CPU_NOVA_H_
#include <base/rpc.h>
/* local includes */
#include <types.h>
namespace Monitor { struct Native_cpu_nova; }
struct Monitor::Native_cpu_nova : Interface
{
enum Thread_type { GLOBAL, LOCAL, VCPU };
/*
* Exception base of thread in caller protection domain - not in core!
*/
struct Exception_base { addr_t exception_base; };
virtual void thread_type(Thread_capability, Thread_type, Exception_base) = 0;
/*********************
** RPC declaration **
*********************/
GENODE_RPC(Rpc_thread_type, void, thread_type, Thread_capability,
Thread_type, Exception_base );
GENODE_RPC_INTERFACE(Rpc_thread_type);
};
#endif /* _NATIVE_CPU_NOVA_H_ */

View File

@ -0,0 +1,143 @@
/*
* \brief Sandbox::Pd_intrinsics for intercepting the PD access of children
* \author Norman Feske
* \date 2023-05-10
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _PD_INTRINSICS_H_
#define _PD_INTRINSICS_H_
/* Genode includes */
#include <pd_session/connection.h>
/* local includes */
#include <monitored_cpu.h>
namespace Monitor { struct Pd_intrinsics; }
struct Monitor::Pd_intrinsics : Sandbox::Pd_intrinsics
{
Env &_env;
/*
* The sandbox interacts with the 'Monitored_ref_pd' only for quota
* transfers to the children, in particular during the child creation.
*/
struct Monitored_ref_pd : Monitored_pd_session
{
using Monitored_pd_session::Monitored_pd_session;
using Sig_src_cap = Signal_source_capability;
using Sig_ctx_cap = Signal_context_capability;
using Ram_ds_cap = Ram_dataspace_capability;
using Mng_sys_state = Managing_system_state;
void assign_parent(Capability<Parent>) override { never_called(__func__); };
bool assign_pci(addr_t, uint16_t) override { never_called(__func__); };
void map(addr_t, addr_t) override { never_called(__func__); };
Sig_src_cap alloc_signal_source() override { never_called(__func__); };
void free_signal_source(Sig_src_cap) override { never_called(__func__); };
Sig_ctx_cap alloc_context(Sig_src_cap, unsigned long) override { never_called(__func__); };
void free_context(Sig_ctx_cap) override { never_called(__func__); };
void submit(Sig_ctx_cap, unsigned) override { never_called(__func__); };
Native_capability alloc_rpc_cap(Native_capability) override { never_called(__func__); };
void free_rpc_cap(Native_capability) override { never_called(__func__); };
Capability<Region_map> address_space() override { never_called(__func__); };
Capability<Region_map> stack_area() override { never_called(__func__); };
Capability<Region_map> linker_area() override { never_called(__func__); };
Cap_quota cap_quota() const override { never_called(__func__); };
Cap_quota used_caps() const override { never_called(__func__); };
Alloc_result try_alloc(size_t, Cache) override { never_called(__func__); };
void free(Ram_ds_cap) override { never_called(__func__); };
size_t dataspace_size(Ram_ds_cap) const override { never_called(__func__); };
Ram_quota ram_quota() const override { never_called(__func__); };
Ram_quota used_ram() const override { never_called(__func__); };
Capability<Native_pd> native_pd() override { never_called(__func__); };
Mng_sys_state managing_system(Mng_sys_state const &) override { never_called(__func__); };
addr_t dma_addr(Ram_ds_cap) override { never_called(__func__); };
Attach_dma_result attach_dma(Dataspace_capability, addr_t) override { never_called(__func__); };
} _monitored_ref_pd { _env.ep(), _env.pd_session_cap(), Session::Label { } };
struct Monitored_ref_cpu : Monitored_cpu_session
{
using Monitored_cpu_session::Monitored_cpu_session;
using Sig_ctx_cap = Signal_context_capability;
Thread_capability
create_thread(Capability<Pd_session>, Cpu_session::Name const &,
Affinity::Location, Weight, addr_t) override { never_called(__func__); }
void kill_thread(Thread_capability) override { never_called(__func__); }
void exception_sigh(Sig_ctx_cap) override { never_called(__func__); }
Affinity::Space affinity_space() const override { never_called(__func__); }
Dataspace_capability trace_control() override { never_called(__func__); }
Quota quota() override { never_called(__func__); }
Capability<Native_cpu> native_cpu() override { never_called(__func__); }
} _monitored_ref_cpu { _env.ep(), _env.cpu_session_cap(), Session::Label { } };
void with_intrinsics(Capability<Pd_session> pd_cap, Pd_session &pd, Fn const &fn) override
{
/*
* Depending on the presence of the PD session in our local entrypoint,
* we know whether the child is to be monitored or not.
*
* If not monitored, we provide the default PD intrinsics as done by
* init, using the 'Env' as reference PD session and accessing the
* child's address space via RPC. For such childen, the monitor
* functions exactly like init with no indirection.
*
* For monitored childen, we provide the '_monitored_ref_pd' and
* '_monitored_ref_cpu' to get hold of all interactions between the
* child and its reference PD. Furthermore, the sandbox' interplay with
* the child's address space is redirected to the locally implemented
* 'Monitored_region_map'.
*/
Inferior_pd::with_inferior_pd(_env.ep(), pd_cap,
[&] (Inferior_pd &inferior_pd) {
Intrinsics intrinsics { .ref_pd = _monitored_ref_pd,
.ref_pd_cap = _monitored_ref_pd.cap(),
.ref_cpu = _monitored_ref_cpu,
.ref_cpu_cap = _monitored_ref_cpu.cap(),
.address_space = inferior_pd._address_space };
fn.call(intrinsics);
},
[&] /* PD session not intercepted */ {
Region_map_client region_map(pd.address_space());
Intrinsics intrinsics { .ref_pd = _env.pd(),
.ref_pd_cap = _env.pd_session_cap(),
.ref_cpu = _env.cpu(),
.ref_cpu_cap = _env.cpu_session_cap(),
.address_space = region_map };
fn.call(intrinsics);
}
);
}
void start_initial_thread(Capability<Cpu_thread> cap, addr_t ip) override
{
Monitored_thread::with_thread(_env.ep(), cap,
[&] (Monitored_thread &monitored_thread) {
monitored_thread.start(ip, 0);
},
[&] /* PD session not intercepted */ {
Cpu_thread_client(cap).start(ip, 0);
});
}
Pd_intrinsics(Env &env) : _env(env) { }
};
#endif /* _PD_INTRINSICS_H_ */

View File

@ -0,0 +1,37 @@
/*
* \brief Architecture-specific GDB protocol support
* \author Norman Feske
* \date 2023-05-15
*/
/*
* Copyright (C) 2023 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.
*/
#include <util/endian.h>
#include <gdb_arch.h>
using namespace Monitor;
void Monitor::Gdb::print_registers(Output &out, Cpu_state const &cpu)
{
uint64_t const values_64bit[] = {
cpu.rax, cpu.rbx, cpu.rcx, cpu.rdx, cpu.rsi, cpu.rdi, cpu.rbp, cpu.sp,
cpu.r8, cpu.r9, cpu.r10, cpu.r11, cpu.r12, cpu.r13, cpu.r14, cpu.r15,
cpu.ip };
for (uint64_t value : values_64bit)
print(out, Gdb_hex(host_to_big_endian(value)));
uint32_t const values_32bit[] = {
uint32_t(cpu.eflags), uint32_t(cpu.cs), uint32_t(cpu.ss),
0 /* es */, 0 /* fs */, /* gs */ };
for (uint32_t value : values_32bit)
print(out, Gdb_hex(host_to_big_endian(value)));
}

View File

@ -0,0 +1,118 @@
<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target>
<architecture>i386:x86-64</architecture>
<feature name="org.gnu.gdb.i386.core">
<flags id="i386_eflags" size="4">
<field name="CF" start="0" end="0" type="bool"/>
<field name="" start="1" end="1" type="bool"/>
<field name="PF" start="2" end="2" type="bool"/>
<field name="AF" start="4" end="4" type="bool"/>
<field name="ZF" start="6" end="6" type="bool"/>
<field name="SF" start="7" end="7" type="bool"/>
<field name="TF" start="8" end="8" type="bool"/>
<field name="IF" start="9" end="9" type="bool"/>
<field name="DF" start="10" end="10" type="bool"/>
<field name="OF" start="11" end="11" type="bool"/>
<field name="NT" start="14" end="14" type="bool"/>
<field name="RF" start="16" end="16" type="bool"/>
<field name="VM" start="17" end="17" type="bool"/>
<field name="AC" start="18" end="18" type="bool"/>
<field name="VIF" start="19" end="19" type="bool"/>
<field name="VIP" start="20" end="20" type="bool"/>
<field name="ID" start="21" end="21" type="bool"/>
</flags>
<reg name="rax" bitsize="64" type="int64" regnum="0"/>
<reg name="rbx" bitsize="64" type="int64" regnum="1"/>
<reg name="rcx" bitsize="64" type="int64" regnum="2"/>
<reg name="rdx" bitsize="64" type="int64" regnum="3"/>
<reg name="rsi" bitsize="64" type="int64" regnum="4"/>
<reg name="rdi" bitsize="64" type="int64" regnum="5"/>
<reg name="rbp" bitsize="64" type="data_ptr" regnum="6"/>
<reg name="rsp" bitsize="64" type="data_ptr" regnum="7"/>
<reg name="r8" bitsize="64" type="int64" regnum="8"/>
<reg name="r9" bitsize="64" type="int64" regnum="9"/>
<reg name="r10" bitsize="64" type="int64" regnum="10"/>
<reg name="r11" bitsize="64" type="int64" regnum="11"/>
<reg name="r12" bitsize="64" type="int64" regnum="12"/>
<reg name="r13" bitsize="64" type="int64" regnum="13"/>
<reg name="r14" bitsize="64" type="int64" regnum="14"/>
<reg name="r15" bitsize="64" type="int64" regnum="15"/>
<reg name="rip" bitsize="64" type="code_ptr" regnum="16"/>
<reg name="eflags" bitsize="32" type="i386_eflags" regnum="17"/>
<reg name="cs" bitsize="32" type="int32" regnum="18"/>
<reg name="ss" bitsize="32" type="int32" regnum="19"/>
<reg name="ds" bitsize="32" type="int32" regnum="20"/>
<reg name="es" bitsize="32" type="int32" regnum="21"/>
<reg name="fs" bitsize="32" type="int32" regnum="22"/>
<reg name="gs" bitsize="32" type="int32" regnum="23"/>
<reg name="st0" bitsize="80" type="i387_ext" regnum="24"/>
<reg name="st1" bitsize="80" type="i387_ext" regnum="25"/>
<reg name="st2" bitsize="80" type="i387_ext" regnum="26"/>
<reg name="st3" bitsize="80" type="i387_ext" regnum="27"/>
<reg name="st4" bitsize="80" type="i387_ext" regnum="28"/>
<reg name="st5" bitsize="80" type="i387_ext" regnum="29"/>
<reg name="st6" bitsize="80" type="i387_ext" regnum="30"/>
<reg name="st7" bitsize="80" type="i387_ext" regnum="31"/>
<reg name="fctrl" bitsize="32" type="int" regnum="32" group="float"/>
<reg name="fstat" bitsize="32" type="int" regnum="33" group="float"/>
<reg name="ftag" bitsize="32" type="int" regnum="34" group="float"/>
<reg name="fiseg" bitsize="32" type="int" regnum="35" group="float"/>
<reg name="fioff" bitsize="32" type="int" regnum="36" group="float"/>
<reg name="foseg" bitsize="32" type="int" regnum="37" group="float"/>
<reg name="fooff" bitsize="32" type="int" regnum="38" group="float"/>
<reg name="fop" bitsize="32" type="int" regnum="39" group="float"/>
</feature>
<feature name="org.gnu.gdb.i386.sse">
<vector id="v8bf16" type="bfloat16" count="8"/>
<vector id="v4f" type="ieee_single" count="4"/>
<vector id="v2d" type="ieee_double" count="2"/>
<vector id="v16i8" type="int8" count="16"/>
<vector id="v8i16" type="int16" count="8"/>
<vector id="v4i32" type="int32" count="4"/>
<vector id="v2i64" type="int64" count="2"/>
<union id="vec128">
<field name="v8_bfloat16" type="v8bf16"/>
<field name="v4_float" type="v4f"/>
<field name="v2_double" type="v2d"/>
<field name="v16_int8" type="v16i8"/>
<field name="v8_int16" type="v8i16"/>
<field name="v4_int32" type="v4i32"/>
<field name="v2_int64" type="v2i64"/>
<field name="uint128" type="uint128"/>
</union>
<flags id="i386_mxcsr" size="4">
<field name="IE" start="0" end="0" type="bool"/>
<field name="DE" start="1" end="1" type="bool"/>
<field name="ZE" start="2" end="2" type="bool"/>
<field name="OE" start="3" end="3" type="bool"/>
<field name="UE" start="4" end="4" type="bool"/>
<field name="PE" start="5" end="5" type="bool"/>
<field name="DAZ" start="6" end="6" type="bool"/>
<field name="IM" start="7" end="7" type="bool"/>
<field name="DM" start="8" end="8" type="bool"/>
<field name="ZM" start="9" end="9" type="bool"/>
<field name="OM" start="10" end="10" type="bool"/>
<field name="UM" start="11" end="11" type="bool"/>
<field name="PM" start="12" end="12" type="bool"/>
<field name="FZ" start="15" end="15" type="bool"/>
</flags>
<reg name="xmm0" bitsize="128" type="vec128" regnum="40"/>
<reg name="xmm1" bitsize="128" type="vec128" regnum="41"/>
<reg name="xmm2" bitsize="128" type="vec128" regnum="42"/>
<reg name="xmm3" bitsize="128" type="vec128" regnum="43"/>
<reg name="xmm4" bitsize="128" type="vec128" regnum="44"/>
<reg name="xmm5" bitsize="128" type="vec128" regnum="45"/>
<reg name="xmm6" bitsize="128" type="vec128" regnum="46"/>
<reg name="xmm7" bitsize="128" type="vec128" regnum="47"/>
<reg name="xmm8" bitsize="128" type="vec128" regnum="48"/>
<reg name="xmm9" bitsize="128" type="vec128" regnum="49"/>
<reg name="xmm10" bitsize="128" type="vec128" regnum="50"/>
<reg name="xmm11" bitsize="128" type="vec128" regnum="51"/>
<reg name="xmm12" bitsize="128" type="vec128" regnum="52"/>
<reg name="xmm13" bitsize="128" type="vec128" regnum="53"/>
<reg name="xmm14" bitsize="128" type="vec128" regnum="54"/>
<reg name="xmm15" bitsize="128" type="vec128" regnum="55"/>
<reg name="mxcsr" bitsize="32" type="i386_mxcsr" regnum="56" group="vector"/>
</feature>
</target>

View File

@ -0,0 +1,4 @@
TARGET = monitor
SRC_CC = main.cc
LIBS = base sandbox monitor_gdb_arch
INC_DIR += $(PRG_DIR)

View File

@ -0,0 +1,90 @@
/*
* \brief Common types used within monitor component
* \author Norman Feske
* \date 2023-05-08
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _TYPES_H_
#define _TYPES_H_
#include <base/id_space.h>
#include <base/allocator.h>
#include <base/entrypoint.h>
#include <base/sleep.h>
#include <session/session.h>
namespace Monitor {
using namespace Genode;
template <typename> struct Monitored_rpc_object;
constexpr static uint16_t GDB_PACKET_MAX_SIZE = 16*1024;
struct Inferior_pd;
using Inferiors = Id_space<Inferior_pd>;
struct Monitored_thread;
using Threads = Id_space<Monitored_thread>;
static inline void never_called(char const *) __attribute__((noreturn));
static inline void never_called(char const *method_name)
{
error("unexpected call of ", method_name);
sleep_forever();
}
/**
* Call 'monitored_fn' with local RPC object that belongs to 'cap', or
* 'direct_fn' if 'cap' does not belong to any local RPC object of type
* 'OBJ'.
*/
template <typename OBJ>
static void with_monitored(Entrypoint &ep, Capability<typename OBJ::Interface> cap,
auto const &monitored_fn, auto const &direct_fn)
{
OBJ *monitored_obj_ptr = nullptr;
ep.rpc_ep().apply(cap, [&] (OBJ *ptr) {
monitored_obj_ptr = ptr; });
if (monitored_obj_ptr)
monitored_fn(*monitored_obj_ptr);
else
direct_fn();
}
}
template <typename IF>
struct Monitor::Monitored_rpc_object : Rpc_object<IF>
{
Entrypoint &_ep;
using Name = String<Session::Label::capacity()>;
Name const _name;
using Interface = IF;
Capability<IF> _real;
Monitored_rpc_object(Entrypoint &ep, Capability<IF> real, Name const &name)
:
_ep(ep), _name(name), _real(real)
{
_ep.manage(*this);
}
~Monitored_rpc_object() { _ep.dissolve(*this); }
};
#endif /* _TYPES_H_ */

View File

@ -0,0 +1,202 @@
/*
* \brief Test for accessing memory using the monitor runtime
* \author Norman Feske
* \date 2023-06-06
*
* This test exercises the memory-access functionality of the monitor component
* by acting as both the monitored inferior and the observer at the same time.
*/
/*
* Copyright (C) 2023 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/component.h>
#include <base/sleep.h>
#include <base/attached_ram_dataspace.h>
#include <rm_session/connection.h>
#include <timer_session/connection.h>
#include <region_map/client.h>
#include <monitor_controller.h>
namespace Test {
using namespace Genode;
struct Main;
}
struct Test::Main
{
Env &_env;
Monitor::Controller _monitor { _env };
static void assert(bool condition, auto &&... error_message)
{
if (!condition) {
error(error_message...);
sleep_forever();
}
}
void test_query_threads()
{
log("-- test_query_threads --");
using Thread_info = Monitor::Controller::Thread_info;
bool expected_inferior_detected = false;
_monitor.for_each_thread([&] (Thread_info const &info) {
if (info.name == "test-monitor" && info.pid == 1 && info.tid == 1)
expected_inferior_detected = true;
log("thread inferior:", info.pid, " tid:", info.tid, " name:", info.name);
});
assert(expected_inferior_detected, "failed to detect myself as inferior");
}
void test_survive_nonexisting_memory_access()
{
log("-- test_survive_nonexisting_memory_access --");
char buffer[32] { };
size_t const read_bytes =
_monitor.read_memory(0x10, Byte_range_ptr(buffer, sizeof(buffer)));
assert(read_bytes == 0,
"unexpected read of ", read_bytes, " from nonexisting memory");
}
void test_read_memory()
{
log("-- test_read_memory --");
char const * const s = "Trying to read back this pattern";
size_t const num_bytes = strlen(s);
char buffer[num_bytes] { };
size_t const read_bytes =
_monitor.read_memory((addr_t)s, Byte_range_ptr(buffer, sizeof(buffer)));
assert(read_bytes == num_bytes,
"unable to read string of ", num_bytes, " bytes");
assert(equal(Const_byte_range_ptr(buffer, read_bytes), s),
"read bytes don't match expected pattern");
}
void test_truncated_mapping()
{
log("-- test_truncated_mapping --");
/*
* Attach 4 KiB of RAM at at the beginning of a managed dataspace of 8
* KiB while leaving second 4 KiB unmapped.
*/
Rm_connection rm_connection { _env };
Region_map_client rm { rm_connection.create(8*1024) };
Attached_ram_dataspace ram_ds { _env.ram(), _env.rm(), 4*1024 };
rm.attach_at(ram_ds.cap(), 0);
Attached_dataspace managed_ds { _env.rm(), rm.dataspace() };
/* try to read 100 bytes at page boundary, expect to stop after 50 bytes */
char buffer[100] { };
addr_t const at = (addr_t)managed_ds.local_addr<char>()
+ 4*1024 - 50;
size_t const read_bytes =
_monitor.read_memory(at, Byte_range_ptr(buffer, sizeof(buffer)));
assert(read_bytes == 50, "failed to read from truncated mapping");
}
void test_bench()
{
log("-- test_bench --");
Timer::Connection timer { _env };
Attached_ram_dataspace large_ram_ds { _env.ram(), _env.rm(), 8*1024*1024 };
memset(large_ram_ds.local_addr<char>(), 1, large_ram_ds.size());
/*
* Dimensioning of the buffer for one round trip:
*
* The terminal_crosslink component uses a buffer of 4 KiB.
* GDB's 'm' command encodes memory as hex, two characters per byte.
* Hence, a dump of max. 2 KiB fits into the terminal-crosslink buffer.
* The GDB command, packet header, and checksum also take a few bytes.
*
* The most effective way to optimize the throughput would be to
* increase the terminal-crosslink's buffer size, reducing the number
* of round trips.
*/
char buffer[2*1024 - 16] { };
uint64_t const start_us = timer.elapsed_us();
uint64_t now_us = start_us;
uint64_t total_bytes = 0;
addr_t offset = 0;
for (;;) {
addr_t const at = (addr_t)large_ram_ds.local_addr<char>() + offset;
size_t const read_bytes =
_monitor.read_memory(at, Byte_range_ptr(buffer, sizeof(buffer)));
assert(read_bytes == sizeof(buffer),
"failed to read memory during benchmark");
total_bytes += read_bytes;
/* slide read window over large dataspace, wrap at the end */
offset = offset + sizeof(buffer);
if (offset + sizeof(buffer) >= large_ram_ds.size())
offset = 0;
now_us = timer.elapsed_us();
if (now_us - start_us > 3*1024*1024)
break;
}
double const seconds = double(now_us - start_us)/double(1000*1000);
double const rate_kib = (double(total_bytes)/1024) / seconds;
log("read ", total_bytes, " bytes at rate of ", rate_kib, " KiB/s");
}
void test_writeable_text_segment()
{
log("-- test_writeable_text_segment --");
/*
* Test the 'wx' attribute of the <monitor> <policy>, that converts
* executable text segments into writeable RAM.
*/
char *code_ptr = (char *)&Component::construct;
char const * const pattern = "risky";
copy_cstring(code_ptr, pattern, strlen(pattern) + 1);
assert(strcmp(code_ptr, pattern, strlen(pattern)) == 0,
"unexpected content at patched address");
}
Main(Env &env) : _env(env)
{
test_query_threads();
test_survive_nonexisting_memory_access();
test_read_memory();
test_truncated_mapping();
test_writeable_text_segment();
test_bench();
_env.parent().exit(0);
}
};
void Component::construct(Genode::Env &env) { static Test::Main main(env); }

View File

@ -0,0 +1,192 @@
/*
* \brief Utility for interacting with the monitor runtime
* \author Norman Feske
* \date 2023-06-06
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _MONITOR_CONTROLLER_H_
#define _MONITOR_CONTROLLER_H_
/* Genode includes */
#include <terminal_session/connection.h>
#include <monitor/gdb_packet.h>
#include <monitor/output.h>
#include <monitor/string.h>
namespace Monitor {
using namespace Genode;
class Controller;
}
/**
* Utility for the synchronous interaction with a GDB stub over a terminal
*
* Note that requests and responses are limited to GDB_PACKET_MAX_SIZE.
*/
class Monitor::Controller : Noncopyable
{
private:
static constexpr uint16_t GDB_PACKET_MAX_SIZE = 16*1024;
Env &_env;
Terminal::Connection _terminal { _env };
char _buffer[GDB_PACKET_MAX_SIZE] { };
Io_signal_handler<Controller> _terminal_read_avail_handler {
_env.ep(), *this, &Controller::_handle_terminal_read_avail };
void _handle_terminal_read_avail() { }
void _request(auto &&... args)
{
struct Terminal_output
{
struct Write_fn
{
Terminal::Connection &_terminal;
void operator () (char const *str) {
_terminal.write(str, strlen(str)); }
} _write_fn;
Buffered_output<1024, Write_fn> buffered { _write_fn };
};
Terminal_output output { ._write_fn { _terminal } };
Gdb_checksummed_output checksummed_output { output.buffered };
print(checksummed_output, args...);
};
void _with_response(auto const &fn)
{
using Gdb_packet = Genode::Gdb_packet<GDB_PACKET_MAX_SIZE>;
Gdb_packet _packet { };
while (!_packet.complete()) {
size_t const read_num_bytes = _terminal.read(_buffer, sizeof(_buffer));
for (unsigned i = 0; i < read_num_bytes; i++) {
using Result = Gdb_packet::Append_result;
switch (_packet.append(_buffer[i])) {
case Result::COMPLETE:
fn(Const_byte_range_ptr(_packet.buf, _packet.cursor));
_terminal.write("+", 1); /* ack */
return;
case Result::OVERFLOW:
error("received unexpectedly large GDB response");
break;
case Result::CORRUPT:
error("received GDB response that could not be parsed");
break;
case Result::OK:
break;
}
}
if (read_num_bytes == 0)
_env.ep().wait_and_dispatch_one_io_signal();
}
}
public:
Controller(Env &env) : _env(env)
{
_terminal.read_avail_sigh(_terminal_read_avail_handler);
}
struct Thread_info
{
using Name = String<64>;
Name name;
unsigned pid; /* inferior ID */
unsigned tid; /* thread ID */
static Thread_info from_xml(Xml_node const &node)
{
using Id = String<16>;
Id const id = node.attribute_value("id", Id());
char const *s = id.string();
/* parse GDB's thread ID format, e.g., 'p1.2' */
unsigned pid = 0, tid = 0;
if (*s == 'p') s++;
s = s + ascii_to(s, pid);
if (*s == '.') s++;
ascii_to(s, tid);
return { .name = node.attribute_value("name", Name()),
.pid = pid,
.tid = tid };
}
};
/**
* Call 'fn' for each thread with the 'Thread_info' as argument
*/
void for_each_thread(auto const &fn)
{
_request("qXfer:threads:read::0,1000");
_with_response([&] (Const_byte_range_ptr const &response) {
with_skipped_prefix(response, "l", [&] (Const_byte_range_ptr const &payload) {
Xml_node node(payload.start, payload.num_bytes);
node.for_each_sub_node("thread", [&] (Xml_node const &thread) {
fn(Thread_info::from_xml(thread)); }); }); });
}
/**
* Read memory 'at' from current inferior
*/
size_t read_memory(addr_t at, Byte_range_ptr const &dst)
{
/*
* Memory dump is in hex format (two digits per byte), also
* account for the protocol overhead (checksum, prefix).
*/
size_t num_bytes = min(dst.num_bytes, size_t(GDB_PACKET_MAX_SIZE)/2 - 16);
_request("m", Gdb_hex(at), ",", Gdb_hex(num_bytes));
size_t read_bytes = 0;
_with_response([&] (Const_byte_range_ptr const &response) {
read_bytes = min(response.num_bytes/2, dst.num_bytes);
/* convert ASCII hex to bytes */
char const *s = response.start;
uint8_t *d = (uint8_t *)dst.start;
for (unsigned i = 0; i < read_bytes; i++) {
char const hi = *s++;
char const lo = *s++;
*d++ = uint8_t(digit(hi, true) << 4)
| uint8_t(digit(lo, true));
}
});
return read_bytes;
}
};
#endif /* _MONITOR_CONTROLLER_H_ */

View File

@ -0,0 +1,4 @@
TARGET := test-monitor
SRC_CC := main.cc
LIBS += base
INC_DIR += $(PRG_DIR)

View File

@ -32,6 +32,7 @@ lx_hybrid_exception
lx_hybrid_pthread_ipc
microcode
migrate
monitor
netperf_lwip
netperf_lwip_bridge
netperf_lwip_usb