mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-20 09:46:20 +00:00
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:
parent
65f65073e6
commit
6a57683e52
117
repos/os/include/monitor/gdb_packet.h
Normal file
117
repos/os/include/monitor/gdb_packet.h
Normal 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_ */
|
61
repos/os/include/monitor/output.h
Normal file
61
repos/os/include/monitor/output.h
Normal 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_ */
|
76
repos/os/include/monitor/string.h
Normal file
76
repos/os/include/monitor/string.h
Normal 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_ */
|
5
repos/os/lib/mk/spec/x86_64/monitor_gdb_arch.mk
Normal file
5
repos/os/lib/mk/spec/x86_64/monitor_gdb_arch.mk
Normal 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
|
8
repos/os/recipes/api/monitor/content.mk
Normal file
8
repos/os/recipes/api/monitor/content.mk
Normal file
@ -0,0 +1,8 @@
|
||||
content: include/monitor LICENSE
|
||||
|
||||
include/monitor:
|
||||
$(mirror_from_rep_dir)
|
||||
|
||||
LICENSE:
|
||||
cp $(GENODE_DIR)/LICENSE $@
|
||||
|
1
repos/os/recipes/api/monitor/hash
Normal file
1
repos/os/recipes/api/monitor/hash
Normal file
@ -0,0 +1 @@
|
||||
2023-06-09 132782703fc58b9aa089970fc4ae971c96f37fac
|
9
repos/os/recipes/src/monitor/content.mk
Normal file
9
repos/os/recipes/src/monitor/content.mk
Normal 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)
|
1
repos/os/recipes/src/monitor/hash
Normal file
1
repos/os/recipes/src/monitor/hash
Normal file
@ -0,0 +1 @@
|
||||
2023-06-09 0d4af26c7b3dfb7f197f46bda6ef09f90150666f
|
7
repos/os/recipes/src/monitor/used_apis
Normal file
7
repos/os/recipes/src/monitor/used_apis
Normal file
@ -0,0 +1,7 @@
|
||||
base
|
||||
sandbox
|
||||
os
|
||||
monitor
|
||||
report_session
|
||||
timer_session
|
||||
terminal_session
|
80
repos/os/run/monitor.run
Normal file
80
repos/os/run/monitor.run
Normal 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
|
120
repos/os/run/monitor_gdb.run
Normal file
120
repos/os/run/monitor_gdb.run
Normal 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
|
75
repos/os/src/monitor/README
Normal file
75
repos/os/src/monitor/README
Normal 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.
|
26
repos/os/src/monitor/gdb_arch.h
Normal file
26
repos/os/src/monitor/gdb_arch.h
Normal 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_ */
|
187
repos/os/src/monitor/gdb_command.h
Normal file
187
repos/os/src/monitor/gdb_command.h
Normal 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_ */
|
78
repos/os/src/monitor/gdb_packet_handler.h
Normal file
78
repos/os/src/monitor/gdb_packet_handler.h
Normal 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_ */
|
57
repos/os/src/monitor/gdb_response.h
Normal file
57
repos/os/src/monitor/gdb_response.h
Normal 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_ */
|
535
repos/os/src/monitor/gdb_stub.h
Normal file
535
repos/os/src/monitor/gdb_stub.h
Normal 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 ®istry) : _last(registry) { };
|
||||
};
|
||||
|
||||
template <typename HEAD, typename... TAIL>
|
||||
struct Instances<HEAD, TAIL...>
|
||||
{
|
||||
HEAD _head;
|
||||
Instances<TAIL...> _tail;
|
||||
Instances(Commands ®istry) : _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_ */
|
101
repos/os/src/monitor/inferior_cpu.h
Normal file
101
repos/os/src/monitor/inferior_cpu.h
Normal 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_ */
|
246
repos/os/src/monitor/inferior_pd.h
Normal file
246
repos/os/src/monitor/inferior_pd.h
Normal 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_ */
|
345
repos/os/src/monitor/main.cc
Normal file
345
repos/os/src/monitor/main.cc
Normal 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); }
|
||||
|
246
repos/os/src/monitor/memory_accessor.h
Normal file
246
repos/os/src/monitor/memory_accessor.h
Normal 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_ */
|
74
repos/os/src/monitor/monitored_cpu.h
Normal file
74
repos/os/src/monitor/monitored_cpu.h
Normal 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_ */
|
44
repos/os/src/monitor/monitored_native_cpu.h
Normal file
44
repos/os/src/monitor/monitored_native_cpu.h
Normal 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_ */
|
73
repos/os/src/monitor/monitored_pd.h
Normal file
73
repos/os/src/monitor/monitored_pd.h
Normal 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_ */
|
145
repos/os/src/monitor/monitored_region_map.h
Normal file
145
repos/os/src/monitor/monitored_region_map.h
Normal 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> ®istry, 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_ */
|
84
repos/os/src/monitor/monitored_thread.h
Normal file
84
repos/os/src/monitor/monitored_thread.h
Normal 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_ */
|
49
repos/os/src/monitor/native_cpu_nova.h
Normal file
49
repos/os/src/monitor/native_cpu_nova.h
Normal 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_ */
|
143
repos/os/src/monitor/pd_intrinsics.h
Normal file
143
repos/os/src/monitor/pd_intrinsics.h
Normal 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_ */
|
37
repos/os/src/monitor/spec/x86_64/gdb_arch.cc
Normal file
37
repos/os/src/monitor/spec/x86_64/gdb_arch.cc
Normal 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)));
|
||||
}
|
||||
|
118
repos/os/src/monitor/spec/x86_64/gdb_target.xml
Normal file
118
repos/os/src/monitor/spec/x86_64/gdb_target.xml
Normal 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>
|
4
repos/os/src/monitor/target.mk
Normal file
4
repos/os/src/monitor/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = monitor
|
||||
SRC_CC = main.cc
|
||||
LIBS = base sandbox monitor_gdb_arch
|
||||
INC_DIR += $(PRG_DIR)
|
90
repos/os/src/monitor/types.h
Normal file
90
repos/os/src/monitor/types.h
Normal 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_ */
|
202
repos/os/src/test/monitor/main.cc
Normal file
202
repos/os/src/test/monitor/main.cc
Normal 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); }
|
192
repos/os/src/test/monitor/monitor_controller.h
Normal file
192
repos/os/src/test/monitor/monitor_controller.h
Normal 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_ */
|
4
repos/os/src/test/monitor/target.mk
Normal file
4
repos/os/src/test/monitor/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET := test-monitor
|
||||
SRC_CC := main.cc
|
||||
LIBS += base
|
||||
INC_DIR += $(PRG_DIR)
|
@ -32,6 +32,7 @@ lx_hybrid_exception
|
||||
lx_hybrid_pthread_ipc
|
||||
microcode
|
||||
migrate
|
||||
monitor
|
||||
netperf_lwip
|
||||
netperf_lwip_bridge
|
||||
netperf_lwip_usb
|
||||
|
Loading…
x
Reference in New Issue
Block a user