From 7000fb86421a412022e4ac41cca2eeb876d588a9 Mon Sep 17 00:00:00 2001 From: Christian Prochaska Date: Tue, 8 Aug 2023 14:17:36 +0200 Subject: [PATCH] monitor: add more debugging features Fixes #4977 --- repos/os/include/monitor/output.h | 7 +- repos/os/include/util/endian.h | 20 +- .../os/lib/mk/spec/arm_64/monitor_gdb_arch.mk | 5 + repos/os/recipes/src/monitor/content.mk | 3 +- repos/os/run/monitor_gdb.inc | 207 ++++++- repos/os/run/monitor_gdb.run | 217 ++++++- repos/os/run/monitor_gdb_interactive.run | 3 +- repos/os/src/monitor/README | 12 +- repos/os/src/monitor/gdb_arch.h | 7 + repos/os/src/monitor/gdb_command.h | 27 + repos/os/src/monitor/gdb_response.h | 18 +- repos/os/src/monitor/gdb_stub.h | 561 ++++++++++++++++-- repos/os/src/monitor/inferior_cpu.h | 18 +- repos/os/src/monitor/inferior_pd.h | 35 +- repos/os/src/monitor/main.cc | 114 +++- repos/os/src/monitor/monitored_thread.h | 116 +++- repos/os/src/monitor/spec/arm_64/gdb_arch.cc | 88 +++ .../os/src/monitor/spec/arm_64/gdb_target.xml | 183 ++++++ repos/os/src/monitor/spec/x86_64/gdb_arch.cc | 90 ++- .../os/src/test/monitor/monitor_controller.h | 13 +- repos/os/src/test/monitor_gdb/main.cc | 91 +++ repos/os/src/test/monitor_gdb/target.mk | 6 + 22 files changed, 1705 insertions(+), 136 deletions(-) create mode 100644 repos/os/lib/mk/spec/arm_64/monitor_gdb_arch.mk create mode 100644 repos/os/src/monitor/spec/arm_64/gdb_arch.cc create mode 100644 repos/os/src/monitor/spec/arm_64/gdb_target.xml create mode 100644 repos/os/src/test/monitor_gdb/main.cc create mode 100644 repos/os/src/test/monitor_gdb/target.mk 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