monitor: add more debugging features

Fixes #4977
This commit is contained in:
Christian Prochaska 2023-08-08 14:17:36 +02:00 committed by Christian Helmuth
parent 8b7f959451
commit 7000fb8642
22 changed files with 1705 additions and 136 deletions

View File

@ -35,9 +35,12 @@ struct Genode::Gdb_checksummed_output : Output
Output &_output;
uint8_t _accumulated = 0;
Gdb_checksummed_output(Output &output) : _output(output)
Gdb_checksummed_output(Output &output, bool notification) : _output(output)
{
print(_output, "$");
if (notification)
print(_output, "%");
else
print(_output, "$");
}
~Gdb_checksummed_output()

View File

@ -1,12 +1,12 @@
/*
* \brief Convert between host endianess and big endian.
* \brief Convert between host endianess (little endian) and big endian.
* \author Stebastian Sumpf
* \author Stefan Kalkowski
* \date 2010-08-04
*/
/*
* Copyright (C) 2010-2017 Genode Labs GmbH
* Copyright (C) 2010-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.
@ -17,7 +17,7 @@
#include <base/stdint.h>
template <typename T>
inline T host_to_big_endian(T x)
inline T swap_bytes(T x)
{
Genode::uint8_t v[sizeof(T)];
@ -25,9 +25,21 @@ inline T host_to_big_endian(T x)
unsigned const shift = ((unsigned)sizeof(T) - i - 1) * 8;
v[i] = (Genode::uint8_t)((x & (0xffu << shift)) >> shift);
v[i] = (Genode::uint8_t)((x & ((T)0xff << shift)) >> shift);
}
return *(T *)v;
}
template <typename T>
inline T host_to_big_endian(T x)
{
return swap_bytes(x);
}
template <typename T>
inline T big_endian_to_host(T x)
{
return swap_bytes(x);
}
#endif /* _UTIL__ENDIAN_H_ */

View File

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

View File

@ -1,7 +1,8 @@
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
MIRROR_FROM_REP_DIR += lib/mk/spec/arm_64/monitor_gdb_arch.mk \
lib/mk/spec/x86_64/monitor_gdb_arch.mk
content: $(MIRROR_FROM_REP_DIR)

View File

@ -1,7 +1,12 @@
proc platform_supported { } {
if {[have_spec x86_64] && [have_board pc]} {
if {![have_spec linux] && ![have_spec foc] && ![have_spec sel4]} {
return 1 } }
if {[have_spec nova] || [have_spec hw]} {
return 1 }
} elseif {[have_spec arm_v8a] && [have_board rpi3] &&
[have_include power_on/qemu]} {
if {[have_spec hw]} {
return 1 }
}
return 0
}
@ -10,11 +15,30 @@ if {![platform_supported]} {
exit 0
}
build { core lib/ld init timer monitor drivers/uart test/log }
create_boot_directory
install_config {
import_from_depot [depot_user]/src/[base_src] \
[depot_user]/src/init \
[depot_user]/src/sandbox \
[depot_user]/src/monitor
set build_components { test/monitor_gdb test/log }
if {[have_include power_on/qemu]} {
append build_components { drivers/uart }
} else {
import_from_depot [depot_user]/pkg/[drivers_nic_pkg] \
[depot_user]/src/nic_router \
[depot_user]/src/vfs \
[depot_user]/src/vfs_lwip \
[depot_user]/src/vfs_pipe
append build_components { server/tcp_terminal }
}
build $build_components
append config {
<config>
<parent-provides>
<service name="LOG"/>
@ -35,17 +59,105 @@ install_config {
<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>
if { [have_include power_on/qemu] } {
append_if [have_board pc] config {
<start name="terminal">
<binary 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>
}
append_if [have_board rpi3] config {
<start name="terminal">
<binary name="rpi3_uart_drv"/>
<resource name="RAM" quantum="2M"/>
<provides>
<service name="Terminal"/>
<service name="Uart"/>
</provides>
<config>
<policy label_prefix="monitor" uart="0"/>
</config>
</start>
}
} else {
append config {
<start name="drivers" caps="1000" managing_system="yes">
<resource name="RAM" quantum="32M"/>
<binary name="init"/>
<route>
<service name="ROM" label="config"> <parent label="drivers.config"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="Uplink"> <child name="nic_router"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="nic_router" caps="200">
<resource name="RAM" quantum="10M"/>
<provides>
<service name="Nic"/>
<service name="Uplink"/>
</provides>
<config verbose_domain_state="yes">
<policy label_prefix="terminal" domain="downlink"/>
<policy label_prefix="drivers" domain="uplink"/>
<domain name="uplink">
<nat domain="downlink"
tcp-ports="1"
udp-ports="1"
icmp-ids="1"/>
<tcp-forward port="5555" domain="downlink" to="10.0.3.2"/>
</domain>
<domain name="downlink" interface="10.0.3.1/24">
<dhcp-server ip_first="10.0.3.2" ip_last="10.0.3.2"/>
<tcp dst="0.0.0.0/0"><permit-any domain="uplink" /></tcp>
</domain>
</config>
</start>
<start name="terminal" caps="200">
<binary name="tcp_terminal"/>
<resource name="RAM" quantum="8M"/>
<provides> <service name="Terminal"/> </provides>
<config>
<policy label_prefix="monitor" port="5555"/>
<vfs>
<dir name="dev"> <log/> </dir>
<dir name="socket"> <lwip dhcp="yes"/> </dir>
<dir name="pipe"> <pipe/> </dir>
</vfs>
<libc stdout="/dev/log" socket="/socket" pipe="/pipe"/>
</config>
<route>
<service name="Nic"> <child name="nic_router"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
}
}
append config {
<start name="monitor" caps="1000">
<resource name="RAM" quantum="100M"/>
@ -59,47 +171,78 @@ install_config {
<default caps="100"/>
<monitor>
<policy label="first-test-log" wx="yes"/>
<policy label="test-monitor_gdb" wait="yes" wx="yes"/>
</monitor>
<start name="first-test-log">
<resource name="RAM" quantum="2M"/>
<binary name="test-log"/>
<start name="test-monitor_gdb" caps="300">
<resource name="RAM" quantum="10M"/>
<config>
<vfs> <dir name="dev"> <log/> </dir> </vfs>
<libc stdout="/dev/log" stderr="/dev/log"/>
</config>
<route>
<service name="PD"> <local/> </service>
<service name="CPU"> <local/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="second-test-log">
<start name="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>
}
install_config $config
build_boot_image [build_artifacts]
set local_port 5555
set port 5555
# qemu config
append qemu_args " -display none "
if {[have_include power_on/qemu]} {
# connect comport 0 to stdio
append qemu_args " -serial stdio "
set host "localhost"
# 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 "
# qemu config
append qemu_args " -display none "
if {[have_board rpi3]} {
# connect comport 0 with TCP port $port
append qemu_args " -serial chardev:uart "
# connect comport 1 to stdio
append qemu_args " -serial stdio "
} else {
# connect comport 0 to stdio
append qemu_args " -serial stdio "
# connect comport 1 with TCP port $port
append qemu_args " -serial chardev:uart "
}
append qemu_args " -chardev socket,id=uart,port=$port,host=$host,server,nowait,ipv4 "
run_genode_until {.*monitor ready*} 30
} else {
set match_string "nic_router. .uplink. dynamic IP config: interface .*\n"
run_genode_until $match_string 30
regexp $match_string $output host
regexp {[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+} $host host
}
run_genode_until {.*\[init -> monitor -> first-test-log\].*} 30
set genode_id [output_spawn_id]
# GDB loads symbols from 'debug/ld.lib.so'
if { [have_spec nova] } {
exec ln -sf ld-nova.lib.so debug/ld.lib.so
} elseif { [have_spec hw] } {
exec ln -sf ld-hw.lib.so debug/ld.lib.so
}

View File

@ -2,36 +2,219 @@ source ${genode_dir}/repos/os/run/monitor_gdb.inc
# 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 non-stop on" }
append gdb_cmds {-ex "target extended-remote $host:$port" }
# avoid pagination prompts in autopilot test
append gdb_cmds {-ex "set pagination off" }
# avoid color escape sequences in autopilot test
append gdb_cmds {-ex "set style enabled off" }
# don't ask for y/n when loading a new symbol file
append gdb_cmds {-ex "set interactive-mode off" }
# set search path for shared libraries
append gdb_cmds {-ex "set solib-search-path debug" }
# set a breakpoint in the 'binary_ready_hook_for_gdb' function
append gdb_cmds {-ex "b binary_ready_hook_for_gdb" }
# continue execution until the breakpoint triggers
append gdb_cmds {-ex "c" }
# delete the 'binary_ready_hook_for_gdb' breakpoint
append gdb_cmds {-ex "delete 1" }
# switch to the 'ep' thread
append gdb_cmds {-ex "thread 2" }
# load the symbols of the test application
append gdb_cmds "-ex \"file debug/test-monitor_gdb\" "
# run GDB
eval spawn [gdb] debug/ld.lib.so -n $gdb_cmds
set gdb_id [list $spawn_id $genode_id]
puts ""
puts "----- test: dump memory -----"
puts "----- test: breakpoint in 'Main::Main()' -----"
puts ""
run_genode_until {\(gdb\)} 60 $gdb_id
send "x/x 0x1003e8b\n"
send "b Main::Main\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "c\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {0x1003e8b:\t0x6f636e6f} $output]} {
puts stderr "*** Error: Dumped memory is not as expected"
if {![regexp {Thread 1.2 "ep" hit Breakpoint 2, Main::Main} $output]} {
puts stderr "*** Error: Breakpoint in Main::Main() did not trigger"
exit -1
}
send "delete 2\n"
run_genode_until {\(gdb\)} 20 $gdb_id
puts "\n"
puts "----- test: breakpoint in shared library -----"
puts ""
send "b Genode::cache_coherent(unsigned long, unsigned long)\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "c\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {Breakpoint 3, Genode::cache_coherent ()} $output]} {
puts "*** Error: Breakpoint in shared library did not trigger"
exit -1
}
puts "\n"
puts "----- test: stack trace when not in syscall -----"
puts ""
send "bt\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {#0 Genode::cache_coherent ()} $output] ||
![regexp {in func2 ()} $output] ||
![regexp {in func1 ()} $output] ||
![regexp {in Main::Main} $output]} {
puts stderr "*** Error: Stack trace when not in syscall is not as expected"
exit -1
}
puts "\n"
puts "----- test: modification of a variable value -----"
puts ""
send "print test_var\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {\$1 = 1} $output]} {
puts stderr "*** Error: first 'print test_var' command didn't result in the expected output"
exit -1
}
send "set var test_var=2\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "print test_var\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {\$2 = 2} $output]} {
puts stderr "*** Error: second 'print test_var' command didn't result in the expected output"
exit -1
}
puts "\n"
puts "----- test: 'call' command -----"
puts ""
send "call test_var_func()\n"
run_genode_until {\(gdb\)} 60 $gdb_id
if {![regexp {\$3 = 3} $output]} {
puts stderr "*** Error: 'call' command didn't result in the expected output"
exit -1
}
puts "\n"
puts "----- test: thread info -----"
puts ""
send "b Test_thread::entry\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "c\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {Breakpoint 4, Test_thread::entry} $output]} {
puts stderr "*** Error: Breakpoint in test thread did not trigger"
exit -1
}
send "thread 4\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {[regexp {Unknown thread 1.4} $output]} {
# probably on a platform without signal handler thread
send "thread 3\n"
run_genode_until {\(gdb\)} 20 $gdb_id
}
send "info threads\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp { 1.1 Thread 1.1 "test-monitor_gdb" \(running\)} $output] ||
![regexp { 1.2 Thread 1.2 "ep" \(running\)} $output] ||
![regexp {"thread" Test_thread::entry} $output] ||
![regexp { 2.1 Thread 2.1 "test-log" \(running\)} $output] ||
![regexp { 2.2 Thread 2.2 "ep" \(running\)} $output]} {
puts stderr "*** Error: Thread info is not as expected"
exit -1
}
puts "\n"
puts "----- test: step into function -----"
puts ""
send "step\n"
run_genode_until {\(gdb\)} 30 $gdb_id
if {![regexp {Test_thread::test_step} $output]} {
puts stderr "*** Error: Step into function didn't result in the expected output"
exit -1
}
puts "\n"
puts "----- test: catching a segmentation fault -----"
puts ""
send "c\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {"thread" received signal SIGSEGV, Segmentation fault.} $output]} {
puts stderr "*** Error: Segmentation fault exception was not caught"
exit -1
}
puts "\n"
puts "----- test: stack trace when in syscall -----"
puts ""
send "thread 2\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "interrupt &\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "bt\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {Genode::Lock::lock} $output] ||
![regexp {Main::Main} $output] } {
puts stderr "*** Error: Stack trace when in syscall is not as expected"
exit -1
}
puts "\n"
puts "----- test: stack trace of second inferior -----"
puts ""
send "inferior 2\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "thread 1\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "interrupt\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "file debug/test-log\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "bt\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {Genode::Signal_receiver::block_for_signal} $output] } {
puts stderr "*** Error: Stack trace of second inferior is not as expected"
exit -1
}
puts ""
puts "----- test: write memory -----"
puts ""
send "set {int} 0x1003e8b = 0x12345678\n"
run_genode_until {\(gdb\)} 20 $gdb_id
send "x/x 0x1003e8b\n"
run_genode_until {\(gdb\)} 20 $gdb_id
if {![regexp {0x1003e8b:\t0x12345678} $output]} {
puts stderr "*** Error: Modified memory is not as expected"
}

View File

@ -2,7 +2,8 @@ source ${genode_dir}/repos/os/run/monitor_gdb.inc
# 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 non-stop on" }
append gdb_cmds "-ex \"target extended-remote $host:$port\" "
# run GDB
exec [terminal] -e "bash -lc \'[gdb] debug/ld.lib.so $gdb_cmds\'" &

View File

@ -49,13 +49,15 @@ inferiors by their respective labels. For example:
! <policy label="first-test-log" wait="no" stop="yes" wx="no"/>
! </monitor>
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. If this attribute is set, the 'wx' attribute needs to
be set as well because a software breakpoint is used to stop the inferior
at the first instruction.
;;; 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

View File

@ -20,7 +20,14 @@
namespace Monitor { namespace Gdb {
/* needed for the array size of the saved original instruction */
static constexpr size_t max_breakpoint_instruction_len = 8;
char const *breakpoint_instruction();
size_t breakpoint_instruction_len();
void print_registers(Output &out, Cpu_state const &cpu);
void parse_registers(Const_byte_range_ptr const &in, Cpu_state &cpu);
} }
#endif /* _GDB_ARCH_H_ */

View File

@ -31,6 +31,8 @@ namespace Monitor { namespace Gdb {
struct Monitor::Gdb::Command : private Commands::Element, Interface
{
static constexpr bool _verbose = false;
using Name = String<32>;
Name const name;
@ -144,6 +146,31 @@ struct Monitor::Gdb::Command : private Commands::Element, Interface
ascii_to_unsigned<T>(str, result, 16); }); });
return result;
}
/**
* Decode "ppid.tid" thread-id string
*/
static void thread_id(Const_byte_range_ptr const &args, int &pid, int &tid)
{
with_skipped_prefix(args, "p", [&] (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) {
if (strcmp(str, "-1") == 0)
value = -1;
else
ascii_to_unsigned(str, value, 16);
});
});
};
dot_separated_arg_value(0, pid);
dot_separated_arg_value(1, tid);
});
};
};

View File

@ -20,6 +20,7 @@
namespace Genode {
void gdb_response(Output &, auto const &fn);
void gdb_notification(Output &, auto const &fn);
static inline void gdb_ok (Output &);
static inline void gdb_error(Output &, uint8_t);
@ -27,11 +28,24 @@ namespace Genode {
/**
* Calls 'fn' with an output interface that wraps the date into a GDB packet
* Calls 'fn' with an output interface that wraps the data into a GDB response
* packet.
*/
void Genode::gdb_response(Output &output, auto const &fn)
{
Gdb_checksummed_output checksummed_output { output };
Gdb_checksummed_output checksummed_output { output, false};
fn(checksummed_output);
};
/**
* Calls 'fn' with an output interface that wraps the data into a GDB
* notification packet.
*/
void Genode::gdb_notification(Output &output, auto const &fn)
{
Gdb_checksummed_output checksummed_output { output, true};
fn(checksummed_output);
};

View File

@ -73,12 +73,38 @@ struct Monitor::Gdb::State : Noncopyable
Constructible<Current> _current { };
/**
* Only one stop notification is sent directly, then
* additional stop replies are sent as response to 'vStopped'.
*/
bool notification_in_progress { false };
bool gdb_connected { false };
void flush(Inferior_pd &pd)
{
if (_current.constructed() && _current->pd.id() == pd.id())
_current.destruct();
}
void flush(Monitored_thread &thread)
{
if (_current.constructed() &&
_current->thread.constructed() &&
(&_current->thread->thread == &thread))
_current->thread.destruct();
}
size_t read_memory(Inferior_pd &pd, Memory_accessor::Virt_addr at, Byte_range_ptr const &dst)
{
return _memory_accessor.read(pd, at, dst);
}
size_t write_memory(Inferior_pd &pd, Memory_accessor::Virt_addr at, Const_byte_range_ptr const &src)
{
return _memory_accessor.write(pd, at, src);
}
size_t read_memory(Memory_accessor::Virt_addr at, Byte_range_ptr const &dst)
{
if (_current.constructed())
@ -99,19 +125,44 @@ struct Monitor::Gdb::State : Noncopyable
bool current_defined() const { return _current.constructed(); }
/**
* Select current inferior and thread (id == 0 means any).
*
* 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' by keeping the previously chosen inferior.
*/
void current(Inferiors::Id pid, Threads::Id tid)
{
if ((pid.value == 0) && _current.constructed()) {
pid.value = _current->pd.id();
if ((tid.value == 0) && _current->thread.constructed()) {
/* keep the current thread */
return;
}
}
_current.destruct();
inferiors.for_each<Inferior_pd &>([&] (Inferior_pd &inferior) {
if (inferior.id() != pid.value)
if ((_current.constructed() &&
_current->thread.constructed()) ||
((pid.value > 0) && (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); });
if (_current->thread.constructed() ||
((tid.value > 0) && (thread.id() != tid.value)))
return;
_current->thread.construct(thread);
});
});
}
@ -130,6 +181,19 @@ struct Monitor::Gdb::State : Noncopyable
fn(thread_state);
};
bool current_thread_state(Thread_state const &thread_state)
{
if (_current.constructed() && _current->thread.constructed()) {
try {
_current->thread->thread._real.call<Cpu_thread::Rpc_set_state>(thread_state);
return true;
} catch (Cpu_thread::State_access_failed) {
warning("unable to set state of thread ", _current->thread->thread.id());
}
}
return false;
}
State(Inferiors &inferiors, Memory_accessor &memory_accessor)
:
inferiors(inferiors), _memory_accessor(memory_accessor)
@ -177,6 +241,7 @@ struct qSupported : Command_with_separator
print(out, "qXfer:threads:read+;");
print(out, "multiprocess+;");
print(out, "QNonStop+;");
print(out, "swbreak+;");
});
}
};
@ -265,30 +330,22 @@ struct H : Command_without_separator
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
log("H command args: ", Cstring(args.start, args.num_bytes));
if (_verbose)
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) {
/* 'g' for other operations */
with_skipped_prefix(args, "g", [&] (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); }); });
};
int pid = 0, tid = 0;
thread_id(args, pid, tid);
unsigned pid = 0, tid = 0;
if ((pid == -1) || (tid == -1)) {
gdb_error(out, 1);
return;
}
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 });
state.current(Inferiors::Id { (unsigned)pid },
Threads::Id { (unsigned)tid });
gdb_ok(out);
});
@ -300,7 +357,7 @@ struct H : Command_without_separator
/**
* Enable/disable non-stop mode
* Enable/disable non-stop mode (only non-stop mode is supported)
*/
struct QNonStop : Command_with_separator
{
@ -308,9 +365,17 @@ struct QNonStop : Command_with_separator
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("QNonStop command args: ", Cstring(args.start, args.num_bytes));
if (_verbose)
log("QNonStop command args: ", Cstring(args.start, args.num_bytes));
gdb_ok(out);
with_null_terminated(args, [&] (char const * const str) {
unsigned non_stop_mode { 0 };
ascii_to(str, non_stop_mode);
if (non_stop_mode)
gdb_ok(out);
else
gdb_error(out, 1);
});
}
};
@ -352,7 +417,8 @@ struct qC : Command_without_separator
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("qC: ", Cstring(args.start, args.num_bytes));
if (_verbose)
log("qC command args: ", Cstring(args.start, args.num_bytes));
gdb_response(out, [&] (Output &) { });
}
@ -383,7 +449,8 @@ struct qOffsets : Command_without_separator
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("qOffsets: ", Cstring(args.start, args.num_bytes));
if (_verbose)
log("qOffsets command args: ", Cstring(args.start, args.num_bytes));
gdb_response(out, [&] (Output &) { });
}
@ -397,12 +464,45 @@ 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
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
log("? command args: ", Cstring(args.start, args.num_bytes));
if (_verbose)
log("? command args: ", Cstring(args.start, args.num_bytes));
gdb_response(out, [&] (Output &out) {
print(out, "T05"); });
state.gdb_connected = true;
bool handled = false;
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
inferior.for_each_thread([&] (Monitored_thread &thread) {
if (handled)
return;
using Stop_state = Monitored_thread::Stop_state;
using Stop_reply_signal = Monitored_thread::Stop_reply_signal;
if (thread.stop_state == Stop_state::RUNNING)
return;
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
long unsigned int pid = inferior.id();
long unsigned int tid = thread.id();
gdb_response(out, [&] (Output &out) {
print(out, "T", Gdb_hex((uint8_t)thread.stop_reply_signal),
"thread:p", Gdb_hex(pid), ".", Gdb_hex(tid), ";");
if (thread.stop_reply_signal == Stop_reply_signal::TRAP)
print(out, "swbreak:;");
});
handled = true;
});
});
if (!handled)
gdb_ok(out);
}
};
@ -416,7 +516,8 @@ struct g : Command_without_separator
void execute(State &state, Const_byte_range_ptr const &, Output &out) const override
{
log("-> execute g");
if (_verbose)
log("-> execute g");
gdb_response(out, [&] (Output &out) {
state.with_current_thread_state([&] (Thread_state const &thread_state) {
@ -465,31 +566,58 @@ struct m : Command_without_separator
/**
* Write memory (binary data)
*
* Not supported in favor of the 'M' command which is easier
* to parse (no escaped special characters or ':' interpreted
* as separator character) and readable in the log when
* debugging.
*/
struct X : Command_without_separator
{
X(Commands &c) : Command_without_separator(c, "X") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_response(out, [&] (Output &) { });
}
};
/**
* Write memory (hex data)
*/
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);
if (len == 0) {
/* packet support probing */
gdb_ok(out);
return;
}
size_t written_num_bytes { 0 };
with_argument(args, Sep {':'}, 1, [&] (Const_byte_range_ptr const &arg) {
if (arg.num_bytes != len)
if (arg.num_bytes != len * 2)
return;
char buf[len];
for (size_t i = 0; i < len; i++)
with_skipped_bytes(arg, i * 2,
[&] (Const_byte_range_ptr const &arg) {
with_max_bytes(arg, 2,
[&] (Const_byte_range_ptr const &arg) {
with_null_terminated(arg, [&] (char const * const str) {
ascii_to_unsigned(str, buf[i], 16);
});
});
});
written_num_bytes =
state.write_memory(Memory_accessor::Virt_addr { addr }, arg);
state.write_memory(Memory_accessor::Virt_addr { addr },
Const_byte_range_ptr(buf, len));
});
if (written_num_bytes == len)
@ -509,7 +637,8 @@ struct T : Command_without_separator
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
log("T command args: ", Cstring(args.start, args.num_bytes));
if (_verbose)
log("T command args: ", Cstring(args.start, args.num_bytes));
gdb_ok(out);
}
@ -523,13 +652,347 @@ 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
void execute(State &state, Const_byte_range_ptr const &, Output &out) const override
{
state.gdb_connected = false;
gdb_ok(out);
}
};
/**
* Enable extended mode
*/
struct bang : Command_without_separator
{
bang(Commands &c) : Command_without_separator(c, "!") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("! command args: ", Cstring(args.start, args.num_bytes));
gdb_ok(out);
}
};
/**
* Report stopped threads in non-stop mode
*/
struct vStopped : Command_without_separator
{
vStopped(Commands &c) : Command_without_separator(c, "vStopped") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("vStopped command args: ", Cstring(args.start, args.num_bytes));
using Stop_state = Monitored_thread::Stop_state;
using Stop_reply_signal = Monitored_thread::Stop_reply_signal;
/* mark previous stop reply as acked */
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
inferior.for_each_thread([&] (Monitored_thread &thread) {
if (thread.stop_state == Stop_state::STOPPED_REPLY_SENT)
thread.stop_state = Stop_state::STOPPED_REPLY_ACKED;
});
});
bool handled = false;
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
inferior.for_each_thread([&] (Monitored_thread &thread) {
if (handled)
return;
if (thread.stop_state != Stop_state::STOPPED_REPLY_PENDING)
return;
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
long unsigned int pid = inferior.id();
long unsigned int tid = thread.id();
gdb_response(out, [&] (Output &out) {
print(out, "T", Gdb_hex((uint8_t)thread.stop_reply_signal),
"thread:p", Gdb_hex(pid), ".", Gdb_hex(tid), ";");
if (thread.stop_reply_signal == Stop_reply_signal::TRAP)
print(out, "swbreak:;");
});
handled = true;
});
});
if (!handled) {
state.notification_in_progress = false;
gdb_ok(out);
}
}
};
/**
* Resume the inferior
*/
struct vCont : Command_without_separator
{
vCont(Commands &c) : Command_without_separator(c, "vCont") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("vCont command args: ", Cstring(args.start, args.num_bytes));
bool handled = false;
with_skipped_prefix(args, "?", [&] (Const_byte_range_ptr const &) {
gdb_response(out, [&] (Output &out) {
print(out, "vCont;c;s;t"); });
handled = true;
});
with_skipped_prefix(args, ";", [&] (Const_byte_range_ptr const &args) {
for_each_argument(args, Sep { ';' }, [&] (Const_byte_range_ptr const &arg) {
auto with_vcont_target_thread = [&] (Const_byte_range_ptr const &arg, auto const &fn)
{
handled = true;
int pid = -1;
int tid = -1;
with_skipped_prefix(arg, ":", [&] (Const_byte_range_ptr const &arg) {
thread_id(arg, pid, tid); });
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
if (pid == 0)
pid = (int)inferior.id();
if ((pid != -1) && ((int)inferior.id() != pid))
return;
inferior.for_each_thread([&] (Monitored_thread &thread) {
if (tid == 0)
tid = (int)thread.id();
if ((tid != -1) && ((int)thread.id() != tid))
return;
fn(inferior, thread);
});
});
};
using Stop_state = Monitored_thread::Stop_state;
with_skipped_prefix(arg, "t", [&] (Const_byte_range_ptr const &arg) {
with_vcont_target_thread(arg, [&] (Inferior_pd const &inferior,
Monitored_thread &thread) {
if (thread.stop_state == Stop_state::RUNNING) {
thread.pause();
if (!state.notification_in_progress) {
state.notification_in_progress = true;
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
gdb_notification(out, [&] (Output &out) {
print(out, "Stop:T",
Gdb_hex((uint8_t)thread.stop_reply_signal),
"thread:p",
Gdb_hex(inferior.id()),
".",
Gdb_hex(thread.id()),
";");
});
}
}
});
});
with_skipped_prefix(arg, "c", [&] (Const_byte_range_ptr const &arg) {
with_vcont_target_thread(arg, [&] (Inferior_pd const &,
Monitored_thread &thread) {
if (thread.stop_state == Stop_state::STOPPED_REPLY_ACKED) {
thread.single_step(false);
thread.resume();
}
});
});
with_skipped_prefix(arg, "s", [&] (Const_byte_range_ptr const &arg) {
with_vcont_target_thread(arg, [&] (Inferior_pd const &,
Monitored_thread &thread) {
if (thread.stop_state == Stop_state::STOPPED_REPLY_ACKED) {
thread.single_step(true);
thread.resume();
}
});
});
});
});
if (handled) {
gdb_ok(out);
return;
}
warning("GDB ", name, " command unsupported: ", Cstring(args.start, args.num_bytes));
}
};
/**
* Read value of register
*/
struct p : Command_without_separator
{
p(Commands &c) : Command_without_separator(c, "p") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("p command args: ", Cstring(args.start, args.num_bytes));
/* currently not supported */
gdb_response(out, [&] (Output &) { });
}
};
/**
* Write value of register
*/
struct P : Command_without_separator
{
P(Commands &c) : Command_without_separator(c, "P") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("P command args: ", Cstring(args.start, args.num_bytes));
/* currently not supported */
gdb_response(out, [&] (Output &) { });
}
};
/**
* Stop thread(s)
*/
struct vCtrlC : Command_without_separator
{
vCtrlC(Commands &c) : Command_without_separator(c, "vCtrlC") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("vCtrlC command args: ", Cstring(args.start, args.num_bytes));
if (state._current.constructed() &&
state._current->thread.constructed()) {
Inferior_pd &inferior = state._current->pd;
Monitored_thread &thread = state._current->thread->thread;
using Stop_state = Monitored_thread::Stop_state;
if (thread.stop_state == Stop_state::RUNNING) {
thread.pause();
if (!state.notification_in_progress) {
state.notification_in_progress = true;
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
gdb_notification(out, [&] (Output &out) {
print(out, "Stop:T",
Gdb_hex((uint8_t)thread.stop_reply_signal),
"thread:p",
Gdb_hex(inferior.id()),
".",
Gdb_hex(thread.id()),
";");
});
}
}
gdb_ok(out);
return;
}
gdb_error(out, 1);
}
};
/**
* File operations
*/
struct vFile : Command_without_separator
{
vFile(Commands &c) : Command_without_separator(c, "vFile") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("vFile command args: ", Cstring(args.start, args.num_bytes));
/* currently not supported */
gdb_response(out, [&] (Output &) { });
}
};
/**
* Set breakpoint
*/
struct Z : Command_without_separator
{
Z(Commands &c) : Command_without_separator(c, "Z") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("Z command args: ", Cstring(args.start, args.num_bytes));
/* currently not supported */
gdb_response(out, [&] (Output &) { });
}
};
/**
* Write registers
*/
struct G : Command_without_separator
{
G(Commands &c) : Command_without_separator(c, "G") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("G command args: ", Cstring(args.start, args.num_bytes));
Thread_state thread_state;
parse_registers(args, thread_state);
if (state.current_thread_state(thread_state))
gdb_ok(out);
else
gdb_error(out, 1);
}
};
} /* namespace Cmd */ } /* namespace Gdb */ } /* namespace Monitor */
@ -575,7 +1038,17 @@ struct Monitor::Gdb::Supported_commands : Commands
Cmd::D,
Cmd::T,
Cmd::ask,
Cmd::X
Cmd::X,
Cmd::M,
Cmd::bang,
Cmd::vStopped,
Cmd::vCont,
Cmd::p,
Cmd::P,
Cmd::vCtrlC,
Cmd::vFile,
Cmd::Z,
Cmd::G
> _instances { *this };
};

View File

@ -22,7 +22,8 @@ namespace Monitor { struct Inferior_cpu; }
struct Monitor::Inferior_cpu : Monitored_cpu_session
{
Allocator &_alloc;
Allocator &_alloc;
Thread_monitor &_thread_monitor;
Constructible<Monitored_native_cpu_nova> _native_cpu_nova { };
@ -38,9 +39,11 @@ struct Monitor::Inferior_cpu : Monitored_cpu_session
}
Inferior_cpu(Entrypoint &ep, Capability<Cpu_session> real,
Name const &name, Allocator &alloc)
Name const &name, Allocator &alloc,
Thread_monitor &thread_monitor)
:
Monitored_cpu_session(ep, real, name), _alloc(alloc)
Monitored_cpu_session(ep, real, name), _alloc(alloc),
_thread_monitor(thread_monitor)
{ }
@ -61,9 +64,14 @@ struct Monitor::Inferior_cpu : Monitored_cpu_session
_real.call<Rpc_create_thread>(inferior_pd._real,
name, affinity, weight, utcb);
Threads::Id thread_id { inferior_pd.alloc_thread_id() };
bool wait = inferior_pd._policy.wait &&
(thread_id == Threads::Id { 1 });
Monitored_thread &monitored_thread = *new (_alloc)
Monitored_thread(_ep, real_thread, name, inferior_pd._threads,
inferior_pd.alloc_thread_id());
Monitored_thread(_ep, real_thread, name,
inferior_pd._threads, thread_id,
pd, _thread_monitor, wait);
result = monitored_thread.cap();
},

View File

@ -67,7 +67,38 @@ struct Monitor::Inferior_pd : Monitored_pd_session
unsigned _page_fault_count = 0;
void _handle_page_fault() { _page_fault_count++; }
void _handle_page_fault()
{
bool thread_found = false;
for_each_thread([&] (Monitored_thread &thread) {
if (thread.stop_state != Monitored_thread::Stop_state::RUNNING)
return;
try {
Thread_state thread_state = thread.state();
if (thread_state.unresolved_page_fault) {
thread.handle_page_fault();
thread_found = true;
}
} catch (Cpu_thread::State_access_failed) {
/* this exception occurs for running threads */
}
});
if (!thread_found) {
/*
* Fault caused by memory accessor
*
* If both an inferior thread and the memory accessor
* caused a page fault, this is not detected here and
* the watchdog timeout of the memory accessor will
* trigger instead.
*/
_page_fault_count++;
}
}
/**
* Keep track of allocated RAM dataspaces for wiping when freed
@ -146,7 +177,7 @@ struct Monitor::Inferior_pd : Monitored_pd_session
void for_each_thread(auto const &fn) const
{
_threads.for_each<Monitored_thread const &>(fn);
_threads.for_each<Monitored_thread &>(fn);
}
static void with_inferior_pd(Entrypoint &ep, Capability<Pd_session> pd_cap,

View File

@ -45,7 +45,8 @@ namespace Monitor {
namespace Monitor { struct Main; }
struct Monitor::Main : Sandbox::State_handler
struct Monitor::Main : Sandbox::State_handler,
Thread_monitor
{
struct Local_pd_session : Connection<Pd_connection>, Inferior_pd
{
@ -58,10 +59,13 @@ struct Monitor::Main : Sandbox::State_handler
struct Local_cpu_session : Connection<Cpu_connection>, Inferior_cpu
{
Local_cpu_session(Env &env, Session::Label const &label, Priority priority, Allocator &alloc)
Local_cpu_session(Env &env, Session::Label const &label,
Priority priority, Allocator &alloc,
Thread_monitor &thread_monitor)
:
Connection<Cpu_connection>(env, label, priority.value),
Inferior_cpu(env.ep(), _connection.cap(), label, alloc)
Inferior_cpu(env.ep(), _connection.cap(), label, alloc,
thread_monitor)
{ }
};
@ -165,6 +169,37 @@ struct Monitor::Main : Sandbox::State_handler
_memory_accessor.flush();
}
void flush(Monitored_thread &thread)
{
_state.flush(thread);
}
void thread_stopped(Inferior_pd &inferior, Monitored_thread &thread)
{
if (_state.gdb_connected && !_state.notification_in_progress) {
_state.notification_in_progress = true;
using Stop_state = Monitored_thread::Stop_state;
using Stop_reply_signal = Monitored_thread::Stop_reply_signal;
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
Terminal_output output { ._write_fn { _terminal } };
gdb_notification(output.buffered, [&] (Output &out) {
print(out, "Stop:T",
Gdb_hex((uint8_t)thread.stop_reply_signal),
"thread:p",
Gdb_hex(inferior.id()),
".",
Gdb_hex(thread.id()),
";");
if (thread.stop_reply_signal == Stop_reply_signal::TRAP)
print(out, "swbreak:;");
});
}
}
Gdb_stub(Env &env, Inferiors &inferiors)
:
_env(env), _state(inferiors, _memory_accessor)
@ -243,7 +278,8 @@ struct Monitor::Main : Sandbox::State_handler
Local_cpu_session &_create_session(Cpu_service &, Session_request const &request)
{
Local_cpu_session &session = *new (_heap)
Local_cpu_session(_env, request.label, _priority_from_args(request.args), _heap);
Local_cpu_session(_env, request.label,
_priority_from_args(request.args), _heap, *this);
session.init_native_cpu(_kernel);
@ -349,6 +385,76 @@ struct Monitor::Main : Sandbox::State_handler
_env.parent().resource_avail_sigh(_resource_avail_handler);
_handle_config();
/* cue for run scripts to start GDB */
log("monitor ready");
}
/**
* Thread_monitor interface
*/
void set_initial_breakpoint(Capability<Pd_session> pd,
addr_t addr,
char original_instruction[]) override
{
if (!_gdb_stub.constructed()) {
Genode::error("set_initial_breakpoint() called without monitor config");
return;
}
Inferior_pd::with_inferior_pd(_env.ep(), pd,
[&] (Inferior_pd &inferior) {
_gdb_stub->_state.read_memory(inferior,
Memory_accessor::Virt_addr { addr },
Byte_range_ptr { original_instruction,
Gdb::breakpoint_instruction_len() });
_gdb_stub->_state.write_memory(inferior,
Memory_accessor::Virt_addr { addr },
Const_byte_range_ptr { Gdb::breakpoint_instruction(),
Gdb::breakpoint_instruction_len() });
}, [] { });
}
void remove_initial_breakpoint(Capability<Pd_session> pd,
addr_t addr,
char const original_instruction[]) override
{
if (!_gdb_stub.constructed()) {
Genode::error("remove_initial_breakpoint() called without monitor config");
return;
}
Inferior_pd::with_inferior_pd(_env.ep(), pd,
[&] (Inferior_pd &inferior) {
_gdb_stub->_state.write_memory(inferior,
Memory_accessor::Virt_addr { addr },
Const_byte_range_ptr { original_instruction,
Gdb::breakpoint_instruction_len() });
}, [] { });
}
void flush(Monitored_thread &thread) override
{
if (!_gdb_stub.constructed()) {
Genode::error("flush_thread() called without monitor config");
return;
}
_gdb_stub->flush(thread);
}
void thread_stopped(Capability<Pd_session> pd, Monitored_thread &thread) override
{
if (!_gdb_stub.constructed()) {
Genode::error("thread_stopped() called without monitor config");
return;
}
Inferior_pd::with_inferior_pd(_env.ep(), pd,
[&] (Inferior_pd &inferior) {
_gdb_stub->thread_stopped(inferior, thread);
}, [] { });
}
};

View File

@ -19,9 +19,34 @@
#include <cpu_thread/client.h>
/* local includes */
#include <gdb_arch.h>
#include <types.h>
namespace Monitor { struct Monitored_thread; }
namespace Monitor {
struct Monitored_thread;
struct Thread_monitor;
}
/*
* Interface for the interaction of the monitored thread
* with the monitor.
*/
struct Monitor::Thread_monitor : Interface
{
virtual void set_initial_breakpoint(Capability<Pd_session> pd,
addr_t addr,
char original_instruction[]) = 0;
virtual void remove_initial_breakpoint(Capability<Pd_session> pd,
addr_t addr,
char const original_instruction[]) = 0;
virtual void flush(Monitored_thread &thread) = 0;
virtual void thread_stopped(Capability<Pd_session> pd,
Monitored_thread &thread) = 0;
};
struct Monitor::Monitored_thread : Monitored_rpc_object<Cpu_thread>
@ -32,29 +57,100 @@ struct Monitor::Monitored_thread : Monitored_rpc_object<Cpu_thread>
with_monitored<Monitored_thread>(ep, cap, monitored_fn, direct_fn);
}
Threads::Element _threads_elem;
Threads::Element _threads_elem;
Capability<Pd_session> _pd;
Thread_monitor &_thread_monitor;
bool _wait;
addr_t _first_instruction_addr { };
char _original_first_instruction[Gdb::max_breakpoint_instruction_len] { };
Signal_handler<Monitored_thread> _exception_handler;
/* stop reply signal values as expected by GDB */
enum Stop_reply_signal {
STOP = 0,
ILL = 4,
TRAP = 5,
FPE = 8,
SEGV = 11
};
Stop_reply_signal stop_reply_signal { STOP };
enum Stop_state {
RUNNING,
STOPPED_REPLY_PENDING,
STOPPED_REPLY_SENT,
STOPPED_REPLY_ACKED
};
Stop_state stop_state { RUNNING };
void _handle_exception();
void handle_page_fault()
{
/*
* On NOVA 'pause()' must be called to get the
* complete register state.
*/
pause();
stop_state = Stop_state::STOPPED_REPLY_PENDING;
stop_reply_signal = Stop_reply_signal::SEGV;
_thread_monitor.thread_stopped(_pd, *this);
}
using Monitored_rpc_object::Monitored_rpc_object;
Monitored_thread(Entrypoint &ep, Capability<Cpu_thread> real, Name const &name,
Threads &threads, Threads::Id id)
Threads &threads, Threads::Id id,
Capability<Pd_session> pd,
Thread_monitor &thread_monitor, bool wait)
:
Monitored_rpc_object(ep, real, name), _threads_elem(*this, threads, id)
{ }
Monitored_rpc_object(ep, real, name),
_threads_elem(*this, threads, id),
_pd(pd), _thread_monitor(thread_monitor), _wait(wait),
_exception_handler(ep, *this, &Monitored_thread::_handle_exception)
{
_real.call<Rpc_exception_sigh>(_exception_handler);
}
~Monitored_thread()
{
_thread_monitor.flush(*this);
}
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 start(addr_t ip, addr_t sp) override
{
if (_wait) {
_first_instruction_addr = ip;
_thread_monitor.set_initial_breakpoint(_pd, ip,
_original_first_instruction);
}
void pause() override {
_real.call<Rpc_pause>(); }
_real.call<Rpc_start>(ip, sp);
}
void pause() override
{
_real.call<Rpc_pause>();
stop_state = Stop_state::STOPPED_REPLY_PENDING;
stop_reply_signal = Stop_reply_signal::STOP;
}
void resume() override {
_real.call<Rpc_resume>(); }
stop_state = Stop_state::RUNNING;
_real.call<Rpc_resume>();
}
Thread_state state() override {
return _real.call<Rpc_get_state>(); }

View File

@ -0,0 +1,88 @@
/*
* \brief Architecture-specific GDB protocol support
* \author Christian Prochaska
* \date 2023-07-31
*/
/*
* 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 <util/endian.h>
/* monitor includes */
#include <gdb_arch.h>
#include <monitored_thread.h>
using namespace Monitor;
/* BRK */
char const *Monitor::Gdb::breakpoint_instruction() { return "\x20\x00\x20\xd4"; }
size_t Monitor::Gdb::breakpoint_instruction_len() { return 4; }
void Monitor::Gdb::print_registers(Output &out, Cpu_state const &cpu)
{
for (addr_t r : cpu.r)
print(out, Gdb_hex(host_to_big_endian(r)));
print(out, Gdb_hex(host_to_big_endian(cpu.sp)));
print(out, Gdb_hex(host_to_big_endian(cpu.ip)));
}
void Monitor::Gdb::parse_registers(Const_byte_range_ptr const &in, Cpu_state &cpu)
{
for (size_t i = 0; i < 33; i++) {
with_skipped_bytes(in, i * sizeof(addr_t) * 2,
[&] (Const_byte_range_ptr const &in) {
with_max_bytes(in, sizeof(addr_t) * 2, [&] (Const_byte_range_ptr const &in) {
char null_terminated[sizeof(addr_t) * 2 + 1] { };
memcpy(null_terminated, in.start,
min(sizeof(null_terminated) - 1, in.num_bytes));
addr_t value = 0;
ascii_to_unsigned(null_terminated, value, 16);
if (i < 31) {
cpu.r[i] = big_endian_to_host(value);
} else if (i == 31) {
cpu.sp = big_endian_to_host(value);
} else if (i == 32) {
cpu.ip = big_endian_to_host(value);
}
});
});
}
}
void Monitor::Monitored_thread::_handle_exception()
{
stop_state = Stop_state::STOPPED_REPLY_PENDING;
Thread_state thread_state = _real.call<Rpc_get_state>();
if (_wait) {
_wait = false;
_thread_monitor.remove_initial_breakpoint(_pd, _first_instruction_addr,
_original_first_instruction);
stop_reply_signal = Stop_reply_signal::STOP;
} else {
switch(thread_state.ec) {
case Cpu_state::Cpu_exception::SOFTWARE_STEP:
stop_reply_signal = Stop_reply_signal::TRAP;
break;
case Cpu_state::Cpu_exception::BREAKPOINT:
stop_reply_signal = Stop_reply_signal::TRAP;
break;
default:
stop_reply_signal = Stop_reply_signal::TRAP;
}
}
_thread_monitor.thread_stopped(_pd, *this);
}

View File

@ -0,0 +1,183 @@
<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target>
<architecture>aarch64</architecture>
<feature name="org.gnu.gdb.aarch64.core">
<flags id="cpsr_flags" size="4">
<field name="SP" start="0" end="0" type="bool"/>
<field name="EL" start="2" end="3" type="uint32"/>
<field name="nRW" start="4" end="4" type="bool"/>
<field name="F" start="6" end="6" type="bool"/>
<field name="I" start="7" end="7" type="bool"/>
<field name="A" start="8" end="8" type="bool"/>
<field name="D" start="9" end="9" type="bool"/>
<field name="BTYPE" start="10" end="11" type="uint32"/>
<field name="SSBS" start="12" end="12" type="bool"/>
<field name="IL" start="20" end="20" type="bool"/>
<field name="SS" start="21" end="21" type="bool"/>
<field name="PAN" start="22" end="22" type="bool"/>
<field name="UAO" start="23" end="23" type="bool"/>
<field name="DIT" start="24" end="24" type="bool"/>
<field name="TCO" start="25" end="25" type="bool"/>
<field name="V" start="28" end="28" type="bool"/>
<field name="C" start="29" end="29" type="bool"/>
<field name="Z" start="30" end="30" type="bool"/>
<field name="N" start="31" end="31" type="bool"/>
</flags>
<reg name="x0" bitsize="64" type="int" regnum="0"/>
<reg name="x1" bitsize="64" type="int" regnum="1"/>
<reg name="x2" bitsize="64" type="int" regnum="2"/>
<reg name="x3" bitsize="64" type="int" regnum="3"/>
<reg name="x4" bitsize="64" type="int" regnum="4"/>
<reg name="x5" bitsize="64" type="int" regnum="5"/>
<reg name="x6" bitsize="64" type="int" regnum="6"/>
<reg name="x7" bitsize="64" type="int" regnum="7"/>
<reg name="x8" bitsize="64" type="int" regnum="8"/>
<reg name="x9" bitsize="64" type="int" regnum="9"/>
<reg name="x10" bitsize="64" type="int" regnum="10"/>
<reg name="x11" bitsize="64" type="int" regnum="11"/>
<reg name="x12" bitsize="64" type="int" regnum="12"/>
<reg name="x13" bitsize="64" type="int" regnum="13"/>
<reg name="x14" bitsize="64" type="int" regnum="14"/>
<reg name="x15" bitsize="64" type="int" regnum="15"/>
<reg name="x16" bitsize="64" type="int" regnum="16"/>
<reg name="x17" bitsize="64" type="int" regnum="17"/>
<reg name="x18" bitsize="64" type="int" regnum="18"/>
<reg name="x19" bitsize="64" type="int" regnum="19"/>
<reg name="x20" bitsize="64" type="int" regnum="20"/>
<reg name="x21" bitsize="64" type="int" regnum="21"/>
<reg name="x22" bitsize="64" type="int" regnum="22"/>
<reg name="x23" bitsize="64" type="int" regnum="23"/>
<reg name="x24" bitsize="64" type="int" regnum="24"/>
<reg name="x25" bitsize="64" type="int" regnum="25"/>
<reg name="x26" bitsize="64" type="int" regnum="26"/>
<reg name="x27" bitsize="64" type="int" regnum="27"/>
<reg name="x28" bitsize="64" type="int" regnum="28"/>
<reg name="x29" bitsize="64" type="int" regnum="29"/>
<reg name="x30" bitsize="64" type="int" regnum="30"/>
<reg name="sp" bitsize="64" type="data_ptr" regnum="31"/>
<reg name="pc" bitsize="64" type="code_ptr" regnum="32"/>
<reg name="cpsr" bitsize="32" type="cpsr_flags" regnum="33"/>
</feature>
<feature name="org.gnu.gdb.aarch64.fpu">
<vector id="v2d" type="ieee_double" count="2"/>
<vector id="v2u" type="uint64" count="2"/>
<vector id="v2i" type="int64" count="2"/>
<vector id="v4f" type="ieee_single" count="4"/>
<vector id="v4u" type="uint32" count="4"/>
<vector id="v4i" type="int32" count="4"/>
<vector id="v8f" type="ieee_half" count="8"/>
<vector id="v8u" type="uint16" count="8"/>
<vector id="v8i" type="int16" count="8"/>
<vector id="v8bf16" type="bfloat16" count="8"/>
<vector id="v16u" type="uint8" count="16"/>
<vector id="v16i" type="int8" count="16"/>
<vector id="v1u" type="uint128" count="1"/>
<vector id="v1i" type="int128" count="1"/>
<union id="vnd">
<field name="f" type="v2d"/>
<field name="u" type="v2u"/>
<field name="s" type="v2i"/>
</union>
<union id="vns">
<field name="f" type="v4f"/>
<field name="u" type="v4u"/>
<field name="s" type="v4i"/>
</union>
<union id="vnh">
<field name="bf" type="v8bf16"/>
<field name="f" type="v8f"/>
<field name="u" type="v8u"/>
<field name="s" type="v8i"/>
</union>
<union id="vnb">
<field name="u" type="v16u"/>
<field name="s" type="v16i"/>
</union>
<union id="vnq">
<field name="u" type="v1u"/>
<field name="s" type="v1i"/>
</union>
<union id="aarch64v">
<field name="d" type="vnd"/>
<field name="s" type="vns"/>
<field name="h" type="vnh"/>
<field name="b" type="vnb"/>
<field name="q" type="vnq"/>
</union>
<flags id="fpsr_flags" size="4">
<field name="IOC" start="0" end="0" type="bool"/>
<field name="DZC" start="1" end="1" type="bool"/>
<field name="OFC" start="2" end="2" type="bool"/>
<field name="UFC" start="3" end="3" type="bool"/>
<field name="IXC" start="4" end="4" type="bool"/>
<field name="IDC" start="7" end="7" type="bool"/>
<field name="QC" start="27" end="27" type="bool"/>
<field name="V" start="28" end="28" type="bool"/>
<field name="C" start="29" end="29" type="bool"/>
<field name="Z" start="30" end="30" type="bool"/>
<field name="N" start="31" end="31" type="bool"/>
</flags>
<flags id="fpcr_flags" size="4">
<field name="FIZ" start="0" end="0" type="bool"/>
<field name="AH" start="1" end="1" type="bool"/>
<field name="NEP" start="2" end="2" type="bool"/>
<field name="IOE" start="8" end="8" type="bool"/>
<field name="DZE" start="9" end="9" type="bool"/>
<field name="OFE" start="10" end="10" type="bool"/>
<field name="UFE" start="11" end="11" type="bool"/>
<field name="IXE" start="12" end="12" type="bool"/>
<field name="EBF" start="13" end="13" type="bool"/>
<field name="IDE" start="15" end="15" type="bool"/>
<field name="Len" start="16" end="18" type="uint32"/>
<field name="FZ16" start="19" end="19" type="bool"/>
<field name="Stride" start="20" end="21" type="uint32"/>
<field name="RMode" start="22" end="23" type="uint32"/>
<field name="FZ" start="24" end="24" type="bool"/>
<field name="DN" start="25" end="25" type="bool"/>
<field name="AHP" start="26" end="26" type="bool"/>
</flags>
<reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
<reg name="v1" bitsize="128" type="aarch64v" regnum="35"/>
<reg name="v2" bitsize="128" type="aarch64v" regnum="36"/>
<reg name="v3" bitsize="128" type="aarch64v" regnum="37"/>
<reg name="v4" bitsize="128" type="aarch64v" regnum="38"/>
<reg name="v5" bitsize="128" type="aarch64v" regnum="39"/>
<reg name="v6" bitsize="128" type="aarch64v" regnum="40"/>
<reg name="v7" bitsize="128" type="aarch64v" regnum="41"/>
<reg name="v8" bitsize="128" type="aarch64v" regnum="42"/>
<reg name="v9" bitsize="128" type="aarch64v" regnum="43"/>
<reg name="v10" bitsize="128" type="aarch64v" regnum="44"/>
<reg name="v11" bitsize="128" type="aarch64v" regnum="45"/>
<reg name="v12" bitsize="128" type="aarch64v" regnum="46"/>
<reg name="v13" bitsize="128" type="aarch64v" regnum="47"/>
<reg name="v14" bitsize="128" type="aarch64v" regnum="48"/>
<reg name="v15" bitsize="128" type="aarch64v" regnum="49"/>
<reg name="v16" bitsize="128" type="aarch64v" regnum="50"/>
<reg name="v17" bitsize="128" type="aarch64v" regnum="51"/>
<reg name="v18" bitsize="128" type="aarch64v" regnum="52"/>
<reg name="v19" bitsize="128" type="aarch64v" regnum="53"/>
<reg name="v20" bitsize="128" type="aarch64v" regnum="54"/>
<reg name="v21" bitsize="128" type="aarch64v" regnum="55"/>
<reg name="v22" bitsize="128" type="aarch64v" regnum="56"/>
<reg name="v23" bitsize="128" type="aarch64v" regnum="57"/>
<reg name="v24" bitsize="128" type="aarch64v" regnum="58"/>
<reg name="v25" bitsize="128" type="aarch64v" regnum="59"/>
<reg name="v26" bitsize="128" type="aarch64v" regnum="60"/>
<reg name="v27" bitsize="128" type="aarch64v" regnum="61"/>
<reg name="v28" bitsize="128" type="aarch64v" regnum="62"/>
<reg name="v29" bitsize="128" type="aarch64v" regnum="63"/>
<reg name="v30" bitsize="128" type="aarch64v" regnum="64"/>
<reg name="v31" bitsize="128" type="aarch64v" regnum="65"/>
<reg name="fpsr" bitsize="32" type="fpsr_flags" regnum="66"/>
<reg name="fpcr" bitsize="32" type="fpcr_flags" regnum="67"/>
</feature>
<feature name="org.gnu.gdb.aarch64.pauth">
<reg name="pauth_dmask" bitsize="64" type="int" regnum="68"/>
<reg name="pauth_cmask" bitsize="64" type="int" regnum="69"/>
</feature>
<feature name="org.gnu.gdb.aarch64.tls">
<reg name="tpidr" bitsize="64" type="data_ptr" regnum="70"/>
<reg name="tpidr2" bitsize="64" type="data_ptr" regnum="71"/>
</feature>
</target>

View File

@ -11,12 +11,20 @@
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <util/endian.h>
/* monitor includes */
#include <gdb_arch.h>
#include <monitored_thread.h>
using namespace Monitor;
char const *Monitor::Gdb::breakpoint_instruction() { return "\xcc"; }
size_t Monitor::Gdb::breakpoint_instruction_len() { return 1; }
void Monitor::Gdb::print_registers(Output &out, Cpu_state const &cpu)
{
uint64_t const values_64bit[] = {
@ -29,9 +37,89 @@ void Monitor::Gdb::print_registers(Output &out, Cpu_state const &cpu)
uint32_t const values_32bit[] = {
uint32_t(cpu.eflags), uint32_t(cpu.cs), uint32_t(cpu.ss),
0 /* es */, 0 /* fs */, /* gs */ };
0 /* ds */, 0 /* es */, 0 /* fs */, 0 /* gs */ };
for (uint32_t value : values_32bit)
print(out, Gdb_hex(host_to_big_endian(value)));
}
void Monitor::Gdb::parse_registers(Const_byte_range_ptr const &in, Cpu_state &cpu)
{
addr_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 (size_t i = 0; i < sizeof(values_64bit) / sizeof(addr_t); i++) {
with_skipped_bytes(in, i * sizeof(addr_t) * 2,
[&] (Const_byte_range_ptr const &in) {
with_max_bytes(in, sizeof(addr_t) * 2, [&] (Const_byte_range_ptr const &in) {
char null_terminated[sizeof(addr_t) * 2 + 1] { };
memcpy(null_terminated, in.start,
min(sizeof(null_terminated) - 1, in.num_bytes));
addr_t value = 0;
ascii_to_unsigned(null_terminated, value, 16);
*values_64bit[i] = big_endian_to_host(value);
});
});
}
addr_t * const values_32bit[] = { &cpu.eflags, &cpu.cs, &cpu.ss };
for (size_t i = 0; i < sizeof(values_32bit) / sizeof(addr_t); i++) {
with_skipped_bytes(in, (sizeof(values_64bit) * 2) + (i * sizeof(uint32_t) * 2),
[&] (Const_byte_range_ptr const &in) {
with_max_bytes(in, sizeof(uint32_t) * 2, [&] (Const_byte_range_ptr const &in) {
char null_terminated[sizeof(uint32_t) * 2 + 1] { };
memcpy(null_terminated, in.start,
min(sizeof(null_terminated) - 1, in.num_bytes));
uint32_t value = 0;
ascii_to_unsigned(null_terminated, value, 16);
*values_32bit[i] = big_endian_to_host(value);
});
});
}
}
void Monitor::Monitored_thread::_handle_exception()
{
stop_state = Stop_state::STOPPED_REPLY_PENDING;
Thread_state thread_state = _real.call<Rpc_get_state>();
if (thread_state.trapno == Cpu_state::Cpu_exception::BREAKPOINT) {
thread_state.ip -= Gdb::breakpoint_instruction_len();
_real.call<Rpc_set_state>(thread_state);
}
if (_wait) {
_wait = false;
_thread_monitor.remove_initial_breakpoint(_pd, _first_instruction_addr,
_original_first_instruction);
stop_reply_signal = Stop_reply_signal::STOP;
} else {
switch(thread_state.trapno) {
case Cpu_state::Cpu_exception::DIVIDE_ERROR:
stop_reply_signal = Stop_reply_signal::FPE;
break;
case Cpu_state::Cpu_exception::DEBUG:
stop_reply_signal = Stop_reply_signal::TRAP;
break;
case Cpu_state::Cpu_exception::BREAKPOINT:
stop_reply_signal = Stop_reply_signal::TRAP;
break;
case Cpu_state::Cpu_exception::UNDEFINED_INSTRUCTION:
stop_reply_signal = Stop_reply_signal::ILL;
break;
case Cpu_state::Cpu_exception::GENERAL_PROTECTION:
stop_reply_signal = Stop_reply_signal::SEGV;
break;
default:
stop_reply_signal = Stop_reply_signal::TRAP;
}
}
_thread_monitor.thread_stopped(_pd, *this);
}

View File

@ -66,7 +66,7 @@ class Monitor::Controller : Noncopyable
Terminal_output output { ._write_fn { _terminal } };
Gdb_checksummed_output checksummed_output { output.buffered };
Gdb_checksummed_output checksummed_output { output.buffered, false };
print(checksummed_output, args...);
};
@ -121,17 +121,18 @@ class Monitor::Controller : Noncopyable
return ok;
}
struct Gdb_binary_buffer
/* convert binary buffer to ASCII hex characters */
struct Gdb_hex_buffer
{
Const_byte_range_ptr const &src;
Gdb_binary_buffer(Const_byte_range_ptr const &src)
Gdb_hex_buffer(Const_byte_range_ptr const &src)
: src(src) { }
void print(Output &output) const
{
for (size_t i = 0; i < src.num_bytes; i++)
Genode::print(output, Char(src.start[i]));
Genode::print(output, Gdb_hex(src.start[i]));
}
};
@ -219,8 +220,8 @@ class Monitor::Controller : Noncopyable
*/
bool write_memory(addr_t at, Const_byte_range_ptr const &src)
{
_request("X", Gdb_hex(at), ",", Gdb_hex(src.num_bytes), ":",
Gdb_binary_buffer(src));
_request("M", Gdb_hex(at), ",", Gdb_hex(src.num_bytes), ":",
Gdb_hex_buffer(src));
return _response_ok();
}

View File

@ -0,0 +1,91 @@
/*
* \brief Test for the debug monitor with GDB
* \author Christian Prochaska
* \date 2023-07-21
*/
/*
* 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 <base/component.h>
#include <base/thread.h>
#include <cpu/cache.h>
using namespace Genode;
/* a variable to be modified with GDB */
int test_var = 1;
struct Test_thread : Thread
{
void test_step()
{
/* nothing */
}
void test_sigsegv()
{
*(int *)0 = 42;
}
Test_thread(Genode::Env &env) : Thread(env, "thread", 8192) { }
void entry() override
{
test_step();
test_sigsegv();
}
};
/*
* This function returns the current value of 'test_var' + 1 and can be called from
* GDB using the 'call' or 'print' commands
*/
int test_var_func()
{
return test_var + 1;
}
void func2()
{
/*
* Set the first breakpoint here in 'Genode::cache_coherent' to test the
* 'backtrace' command for a thread which is not in a syscall and executes
* code in a shared library.
*/
Genode::cache_coherent(0, 0);
/* call 'test_var_func()', so the compiler does not throw the function away */
log("test_var_func() returned ", test_var_func());
}
void func1()
{
func2();
}
struct Main
{
Main(Genode::Env &env)
{
func1();
Test_thread test_thread(env);
test_thread.start();
test_thread.join();
}
};
void Component::construct(Env &env)
{
static Main main { env };
}

View File

@ -0,0 +1,6 @@
TARGET = test-monitor_gdb
SRC_CC = main.cc
LIBS = base
CC_OLEVEL = -O0