diff --git a/repos/os/include/monitor/output.h b/repos/os/include/monitor/output.h
index 72cb67a72e..f16853d2fa 100644
--- a/repos/os/include/monitor/output.h
+++ b/repos/os/include/monitor/output.h
@@ -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()
diff --git a/repos/os/include/util/endian.h b/repos/os/include/util/endian.h
index 5455303545..4affc30710 100644
--- a/repos/os/include/util/endian.h
+++ b/repos/os/include/util/endian.h
@@ -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
template
-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
+inline T host_to_big_endian(T x)
+{
+ return swap_bytes(x);
+}
+
+template
+inline T big_endian_to_host(T x)
+{
+ return swap_bytes(x);
+}
+
#endif /* _UTIL__ENDIAN_H_ */
diff --git a/repos/os/lib/mk/spec/arm_64/monitor_gdb_arch.mk b/repos/os/lib/mk/spec/arm_64/monitor_gdb_arch.mk
new file mode 100644
index 0000000000..10d0eac678
--- /dev/null
+++ b/repos/os/lib/mk/spec/arm_64/monitor_gdb_arch.mk
@@ -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
diff --git a/repos/os/recipes/src/monitor/content.mk b/repos/os/recipes/src/monitor/content.mk
index e83b06950e..57fa88607e 100644
--- a/repos/os/recipes/src/monitor/content.mk
+++ b/repos/os/recipes/src/monitor/content.mk
@@ -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)
diff --git a/repos/os/run/monitor_gdb.inc b/repos/os/run/monitor_gdb.inc
index 5d75820a83..900021bca6 100644
--- a/repos/os/run/monitor_gdb.inc
+++ b/repos/os/run/monitor_gdb.inc
@@ -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 {
@@ -35,17 +59,105 @@ install_config {
+}
-
-
-
-
-
-
-
-
-
-
+if { [have_include power_on/qemu] } {
+
+ append_if [have_board pc] config {
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+ append_if [have_board rpi3] config {
+
+
+
+
+
+
+
+
+
+
+
+ }
+
+} else {
+
+ append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+}
+
+append config {
@@ -59,47 +171,78 @@ install_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
+}
diff --git a/repos/os/run/monitor_gdb.run b/repos/os/run/monitor_gdb.run
index 922159ddf9..eeb4bbaa5b 100644
--- a/repos/os/run/monitor_gdb.run
+++ b/repos/os/run/monitor_gdb.run
@@ -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"
-}
diff --git a/repos/os/run/monitor_gdb_interactive.run b/repos/os/run/monitor_gdb_interactive.run
index a8cd93e975..f7d21418a9 100644
--- a/repos/os/run/monitor_gdb_interactive.run
+++ b/repos/os/run/monitor_gdb_interactive.run
@@ -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\'" &
diff --git a/repos/os/src/monitor/README b/repos/os/src/monitor/README
index eee2118d9f..578dfebaeb 100644
--- a/repos/os/src/monitor/README
+++ b/repos/os/src/monitor/README
@@ -49,13 +49,15 @@ inferiors by their respective labels. For example:
!
!
+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
diff --git a/repos/os/src/monitor/gdb_arch.h b/repos/os/src/monitor/gdb_arch.h
index 97a3c55aca..74e4a74f1c 100644
--- a/repos/os/src/monitor/gdb_arch.h
+++ b/repos/os/src/monitor/gdb_arch.h
@@ -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_ */
diff --git a/repos/os/src/monitor/gdb_command.h b/repos/os/src/monitor/gdb_command.h
index 6790e4181e..f72b5cfa65 100644
--- a/repos/os/src/monitor/gdb_command.h
+++ b/repos/os/src/monitor/gdb_command.h
@@ -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(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);
+ });
+ };
+
};
diff --git a/repos/os/src/monitor/gdb_response.h b/repos/os/src/monitor/gdb_response.h
index 06b27d8803..44314c9e42 100644
--- a/repos/os/src/monitor/gdb_response.h
+++ b/repos/os/src/monitor/gdb_response.h
@@ -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);
};
diff --git a/repos/os/src/monitor/gdb_stub.h b/repos/os/src/monitor/gdb_stub.h
index 3cb978cbca..bde38df1b0 100644
--- a/repos/os/src/monitor/gdb_stub.h
+++ b/repos/os/src/monitor/gdb_stub.h
@@ -73,12 +73,38 @@ struct Monitor::Gdb::State : Noncopyable
Constructible _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) {
- 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 &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(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) {
+ 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) {
+ 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) {
+ 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) {
+
+ 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 };
};
diff --git a/repos/os/src/monitor/inferior_cpu.h b/repos/os/src/monitor/inferior_cpu.h
index 1ed1d349e8..d0ab6b2672 100644
--- a/repos/os/src/monitor/inferior_cpu.h
+++ b/repos/os/src/monitor/inferior_cpu.h
@@ -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 _native_cpu_nova { };
@@ -38,9 +39,11 @@ struct Monitor::Inferior_cpu : Monitored_cpu_session
}
Inferior_cpu(Entrypoint &ep, Capability 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(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();
},
diff --git a/repos/os/src/monitor/inferior_pd.h b/repos/os/src/monitor/inferior_pd.h
index fcd840053a..b685e9dabb 100644
--- a/repos/os/src/monitor/inferior_pd.h
+++ b/repos/os/src/monitor/inferior_pd.h
@@ -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(fn);
+ _threads.for_each(fn);
}
static void with_inferior_pd(Entrypoint &ep, Capability pd_cap,
diff --git a/repos/os/src/monitor/main.cc b/repos/os/src/monitor/main.cc
index ecb3d802ca..2eb74c46e4 100644
--- a/repos/os/src/monitor/main.cc
+++ b/repos/os/src/monitor/main.cc
@@ -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, Inferior_pd
{
@@ -58,10 +59,13 @@ struct Monitor::Main : Sandbox::State_handler
struct Local_cpu_session : 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(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,
+ 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,
+ 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, 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);
+ }, [] { });
}
};
diff --git a/repos/os/src/monitor/monitored_thread.h b/repos/os/src/monitor/monitored_thread.h
index 01c5c4fe8c..27db83dbe1 100644
--- a/repos/os/src/monitor/monitored_thread.h
+++ b/repos/os/src/monitor/monitored_thread.h
@@ -19,9 +19,34 @@
#include
/* local includes */
+#include
#include
-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,
+ addr_t addr,
+ char original_instruction[]) = 0;
+
+ virtual void remove_initial_breakpoint(Capability pd,
+ addr_t addr,
+ char const original_instruction[]) = 0;
+
+ virtual void flush(Monitored_thread &thread) = 0;
+
+ virtual void thread_stopped(Capability pd,
+ Monitored_thread &thread) = 0;
+};
struct Monitor::Monitored_thread : Monitored_rpc_object
@@ -32,29 +57,100 @@ struct Monitor::Monitored_thread : Monitored_rpc_object
with_monitored(ep, cap, monitored_fn, direct_fn);
}
- Threads::Element _threads_elem;
+ Threads::Element _threads_elem;
+ Capability _pd;
+ Thread_monitor &_thread_monitor;
+ bool _wait;
+
+ addr_t _first_instruction_addr { };
+ char _original_first_instruction[Gdb::max_breakpoint_instruction_len] { };
+
+ Signal_handler _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 real, Name const &name,
- Threads &threads, Threads::Id id)
+ Threads &threads, Threads::Id id,
+ Capability 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(_exception_handler);
+
+ }
+
+ ~Monitored_thread()
+ {
+ _thread_monitor.flush(*this);
+ }
long unsigned id() const { return _threads_elem.id().value; }
Dataspace_capability utcb() override {
return _real.call(); }
- void start(addr_t ip, addr_t sp) override {
- _real.call(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(); }
+ _real.call(ip, sp);
+ }
+
+ void pause() override
+ {
+ _real.call();
+ stop_state = Stop_state::STOPPED_REPLY_PENDING;
+ stop_reply_signal = Stop_reply_signal::STOP;
+ }
void resume() override {
- _real.call(); }
+ stop_state = Stop_state::RUNNING;
+ _real.call();
+ }
Thread_state state() override {
return _real.call(); }
diff --git a/repos/os/src/monitor/spec/arm_64/gdb_arch.cc b/repos/os/src/monitor/spec/arm_64/gdb_arch.cc
new file mode 100644
index 0000000000..5f381305ac
--- /dev/null
+++ b/repos/os/src/monitor/spec/arm_64/gdb_arch.cc
@@ -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
+
+/* monitor includes */
+#include
+#include
+
+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();
+
+ 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);
+}
diff --git a/repos/os/src/monitor/spec/arm_64/gdb_target.xml b/repos/os/src/monitor/spec/arm_64/gdb_target.xml
new file mode 100644
index 0000000000..9e4b6ce2ff
--- /dev/null
+++ b/repos/os/src/monitor/spec/arm_64/gdb_target.xml
@@ -0,0 +1,183 @@
+
+
+
+ aarch64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/repos/os/src/monitor/spec/x86_64/gdb_arch.cc b/repos/os/src/monitor/spec/x86_64/gdb_arch.cc
index 0d7ae61eb2..72c231e32a 100644
--- a/repos/os/src/monitor/spec/x86_64/gdb_arch.cc
+++ b/repos/os/src/monitor/spec/x86_64/gdb_arch.cc
@@ -11,12 +11,20 @@
* under the terms of the GNU Affero General Public License version 3.
*/
+/* Genode includes */
#include
+
+/* monitor includes */
#include
+#include
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();
+
+ if (thread_state.trapno == Cpu_state::Cpu_exception::BREAKPOINT) {
+ thread_state.ip -= Gdb::breakpoint_instruction_len();
+ _real.call(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);
+}
diff --git a/repos/os/src/test/monitor/monitor_controller.h b/repos/os/src/test/monitor/monitor_controller.h
index f1ed38fc7d..a2829d4774 100644
--- a/repos/os/src/test/monitor/monitor_controller.h
+++ b/repos/os/src/test/monitor/monitor_controller.h
@@ -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();
}
diff --git a/repos/os/src/test/monitor_gdb/main.cc b/repos/os/src/test/monitor_gdb/main.cc
new file mode 100644
index 0000000000..eafc9102ab
--- /dev/null
+++ b/repos/os/src/test/monitor_gdb/main.cc
@@ -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
+#include
+#include
+
+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 };
+}
diff --git a/repos/os/src/test/monitor_gdb/target.mk b/repos/os/src/test/monitor_gdb/target.mk
new file mode 100644
index 0000000000..64a80be838
--- /dev/null
+++ b/repos/os/src/test/monitor_gdb/target.mk
@@ -0,0 +1,6 @@
+TARGET = test-monitor_gdb
+SRC_CC = main.cc
+
+LIBS = base
+
+CC_OLEVEL = -O0