mirror of
https://github.com/genodelabs/genode.git
synced 2025-01-29 15:44:02 +00:00
parent
8b7f959451
commit
7000fb8642
@ -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()
|
||||
|
@ -1,12 +1,12 @@
|
||||
/*
|
||||
* \brief Convert between host endianess and big endian.
|
||||
* \brief Convert between host endianess (little endian) and big endian.
|
||||
* \author Stebastian Sumpf
|
||||
* \author Stefan Kalkowski
|
||||
* \date 2010-08-04
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2010-2017 Genode Labs GmbH
|
||||
* Copyright (C) 2010-2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
@ -17,7 +17,7 @@
|
||||
#include <base/stdint.h>
|
||||
|
||||
template <typename T>
|
||||
inline T host_to_big_endian(T x)
|
||||
inline T swap_bytes(T x)
|
||||
{
|
||||
Genode::uint8_t v[sizeof(T)];
|
||||
|
||||
@ -25,9 +25,21 @@ inline T host_to_big_endian(T x)
|
||||
|
||||
unsigned const shift = ((unsigned)sizeof(T) - i - 1) * 8;
|
||||
|
||||
v[i] = (Genode::uint8_t)((x & (0xffu << shift)) >> shift);
|
||||
v[i] = (Genode::uint8_t)((x & ((T)0xff << shift)) >> shift);
|
||||
}
|
||||
return *(T *)v;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T host_to_big_endian(T x)
|
||||
{
|
||||
return swap_bytes(x);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T big_endian_to_host(T x)
|
||||
{
|
||||
return swap_bytes(x);
|
||||
}
|
||||
|
||||
#endif /* _UTIL__ENDIAN_H_ */
|
||||
|
5
repos/os/lib/mk/spec/arm_64/monitor_gdb_arch.mk
Normal file
5
repos/os/lib/mk/spec/arm_64/monitor_gdb_arch.mk
Normal file
@ -0,0 +1,5 @@
|
||||
SRC_BIN = gdb_target.xml
|
||||
SRC_CC = gdb_arch.cc
|
||||
INC_DIR += $(REP_DIR)/src/monitor
|
||||
|
||||
vpath % $(REP_DIR)/src/monitor/spec/arm_64
|
@ -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)
|
||||
|
||||
|
@ -1,7 +1,12 @@
|
||||
proc platform_supported { } {
|
||||
if {[have_spec x86_64] && [have_board pc]} {
|
||||
if {![have_spec linux] && ![have_spec foc] && ![have_spec sel4]} {
|
||||
return 1 } }
|
||||
if {[have_spec nova] || [have_spec hw]} {
|
||||
return 1 }
|
||||
} elseif {[have_spec arm_v8a] && [have_board rpi3] &&
|
||||
[have_include power_on/qemu]} {
|
||||
if {[have_spec hw]} {
|
||||
return 1 }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -10,11 +15,30 @@ if {![platform_supported]} {
|
||||
exit 0
|
||||
}
|
||||
|
||||
build { core lib/ld init timer monitor drivers/uart test/log }
|
||||
|
||||
create_boot_directory
|
||||
|
||||
install_config {
|
||||
import_from_depot [depot_user]/src/[base_src] \
|
||||
[depot_user]/src/init \
|
||||
[depot_user]/src/sandbox \
|
||||
[depot_user]/src/monitor
|
||||
|
||||
set build_components { test/monitor_gdb test/log }
|
||||
|
||||
if {[have_include power_on/qemu]} {
|
||||
append build_components { drivers/uart }
|
||||
} else {
|
||||
import_from_depot [depot_user]/pkg/[drivers_nic_pkg] \
|
||||
[depot_user]/src/nic_router \
|
||||
[depot_user]/src/vfs \
|
||||
[depot_user]/src/vfs_lwip \
|
||||
[depot_user]/src/vfs_pipe
|
||||
|
||||
append build_components { server/tcp_terminal }
|
||||
}
|
||||
|
||||
build $build_components
|
||||
|
||||
append config {
|
||||
<config>
|
||||
<parent-provides>
|
||||
<service name="LOG"/>
|
||||
@ -35,17 +59,105 @@ install_config {
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides> <service name="Timer"/> </provides>
|
||||
</start>
|
||||
}
|
||||
|
||||
<start name="pc_uart_drv">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides>
|
||||
<service name="Terminal"/>
|
||||
<service name="Uart"/>
|
||||
</provides>
|
||||
<config>
|
||||
<policy label_prefix="monitor" uart="1"/>
|
||||
</config>
|
||||
</start>
|
||||
if { [have_include power_on/qemu] } {
|
||||
|
||||
append_if [have_board pc] config {
|
||||
<start name="terminal">
|
||||
<binary name="pc_uart_drv"/>
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides>
|
||||
<service name="Terminal"/>
|
||||
<service name="Uart"/>
|
||||
</provides>
|
||||
<config>
|
||||
<policy label_prefix="monitor" uart="1"/>
|
||||
</config>
|
||||
</start>
|
||||
}
|
||||
|
||||
append_if [have_board rpi3] config {
|
||||
<start name="terminal">
|
||||
<binary name="rpi3_uart_drv"/>
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides>
|
||||
<service name="Terminal"/>
|
||||
<service name="Uart"/>
|
||||
</provides>
|
||||
<config>
|
||||
<policy label_prefix="monitor" uart="0"/>
|
||||
</config>
|
||||
</start>
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
append config {
|
||||
<start name="drivers" caps="1000" managing_system="yes">
|
||||
<resource name="RAM" quantum="32M"/>
|
||||
<binary name="init"/>
|
||||
<route>
|
||||
<service name="ROM" label="config"> <parent label="drivers.config"/> </service>
|
||||
<service name="Timer"> <child name="timer"/> </service>
|
||||
<service name="Uplink"> <child name="nic_router"/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="nic_router" caps="200">
|
||||
<resource name="RAM" quantum="10M"/>
|
||||
<provides>
|
||||
<service name="Nic"/>
|
||||
<service name="Uplink"/>
|
||||
</provides>
|
||||
<config verbose_domain_state="yes">
|
||||
|
||||
<policy label_prefix="terminal" domain="downlink"/>
|
||||
<policy label_prefix="drivers" domain="uplink"/>
|
||||
|
||||
<domain name="uplink">
|
||||
|
||||
<nat domain="downlink"
|
||||
tcp-ports="1"
|
||||
udp-ports="1"
|
||||
icmp-ids="1"/>
|
||||
|
||||
<tcp-forward port="5555" domain="downlink" to="10.0.3.2"/>
|
||||
|
||||
</domain>
|
||||
|
||||
<domain name="downlink" interface="10.0.3.1/24">
|
||||
|
||||
<dhcp-server ip_first="10.0.3.2" ip_last="10.0.3.2"/>
|
||||
|
||||
<tcp dst="0.0.0.0/0"><permit-any domain="uplink" /></tcp>
|
||||
|
||||
</domain>
|
||||
|
||||
</config>
|
||||
</start>
|
||||
<start name="terminal" caps="200">
|
||||
<binary name="tcp_terminal"/>
|
||||
<resource name="RAM" quantum="8M"/>
|
||||
<provides> <service name="Terminal"/> </provides>
|
||||
<config>
|
||||
<policy label_prefix="monitor" port="5555"/>
|
||||
<vfs>
|
||||
<dir name="dev"> <log/> </dir>
|
||||
<dir name="socket"> <lwip dhcp="yes"/> </dir>
|
||||
<dir name="pipe"> <pipe/> </dir>
|
||||
</vfs>
|
||||
<libc stdout="/dev/log" socket="/socket" pipe="/pipe"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="Nic"> <child name="nic_router"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
}
|
||||
}
|
||||
|
||||
append config {
|
||||
|
||||
<start name="monitor" caps="1000">
|
||||
<resource name="RAM" quantum="100M"/>
|
||||
@ -59,47 +171,78 @@ install_config {
|
||||
<default caps="100"/>
|
||||
|
||||
<monitor>
|
||||
<policy label="first-test-log" wx="yes"/>
|
||||
<policy label="test-monitor_gdb" wait="yes" wx="yes"/>
|
||||
</monitor>
|
||||
|
||||
<start name="first-test-log">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<binary name="test-log"/>
|
||||
<start name="test-monitor_gdb" caps="300">
|
||||
<resource name="RAM" quantum="10M"/>
|
||||
<config>
|
||||
<vfs> <dir name="dev"> <log/> </dir> </vfs>
|
||||
<libc stdout="/dev/log" stderr="/dev/log"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="PD"> <local/> </service>
|
||||
<service name="CPU"> <local/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
<start name="second-test-log">
|
||||
<start name="test-log">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<binary name="test-log"/>
|
||||
<route>
|
||||
<service name="PD"> <local/> </service>
|
||||
<service name="CPU"> <local/> </service>
|
||||
<any-service> <parent/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
</config>
|
||||
</start>
|
||||
</config>
|
||||
}
|
||||
|
||||
install_config $config
|
||||
|
||||
build_boot_image [build_artifacts]
|
||||
|
||||
set local_port 5555
|
||||
set port 5555
|
||||
|
||||
# qemu config
|
||||
append qemu_args " -display none "
|
||||
if {[have_include power_on/qemu]} {
|
||||
|
||||
# connect comport 0 to stdio
|
||||
append qemu_args " -serial stdio "
|
||||
set host "localhost"
|
||||
|
||||
# connect comport 1 with TCP port $local_port
|
||||
append qemu_args " -serial chardev:uart "
|
||||
append qemu_args " -chardev socket,id=uart,port=$local_port,host=localhost,server,nowait,ipv4 "
|
||||
# qemu config
|
||||
append qemu_args " -display none "
|
||||
|
||||
if {[have_board rpi3]} {
|
||||
# connect comport 0 with TCP port $port
|
||||
append qemu_args " -serial chardev:uart "
|
||||
# connect comport 1 to stdio
|
||||
append qemu_args " -serial stdio "
|
||||
} else {
|
||||
# connect comport 0 to stdio
|
||||
append qemu_args " -serial stdio "
|
||||
# connect comport 1 with TCP port $port
|
||||
append qemu_args " -serial chardev:uart "
|
||||
}
|
||||
|
||||
append qemu_args " -chardev socket,id=uart,port=$port,host=$host,server,nowait,ipv4 "
|
||||
|
||||
run_genode_until {.*monitor ready*} 30
|
||||
|
||||
} else {
|
||||
|
||||
set match_string "nic_router. .uplink. dynamic IP config: interface .*\n"
|
||||
|
||||
run_genode_until $match_string 30
|
||||
|
||||
regexp $match_string $output host
|
||||
regexp {[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+} $host host
|
||||
}
|
||||
|
||||
run_genode_until {.*\[init -> monitor -> first-test-log\].*} 30
|
||||
set genode_id [output_spawn_id]
|
||||
|
||||
# GDB loads symbols from 'debug/ld.lib.so'
|
||||
if { [have_spec nova] } {
|
||||
exec ln -sf ld-nova.lib.so debug/ld.lib.so
|
||||
} elseif { [have_spec hw] } {
|
||||
exec ln -sf ld-hw.lib.so debug/ld.lib.so
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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\'" &
|
||||
|
@ -49,13 +49,15 @@ inferiors by their respective labels. For example:
|
||||
! <policy label="first-test-log" wait="no" stop="yes" wx="no"/>
|
||||
! </monitor>
|
||||
|
||||
By setting the 'wait' attribute to "yes", the execution of the inferior is
|
||||
delayed until the monitor receives the GDB command for continuing execution.
|
||||
This is useful for inspecting the startup phase of a component. By default,
|
||||
inferiors don't wait. If this attribute is set, the 'wx' attribute needs to
|
||||
be set as well because a software breakpoint is used to stop the inferior
|
||||
at the first instruction.
|
||||
|
||||
;;; XXX not implemented, uncomment once completed
|
||||
;
|
||||
; By setting the 'wait' attribute to "yes", the execution of the inferior is
|
||||
; delayed until the monitor receives the GDB command for continuing execution.
|
||||
; This is useful for inspecting the startup phase of a component. By default,
|
||||
; inferiors don't wait.
|
||||
;
|
||||
; The 'stop' attribute defines the behavior of an inferior when GDB connects
|
||||
; to the monitor. By default, all inferiors are stopped. By setting the
|
||||
; attribute to "no", a GDB connection does not interfere with the execution of
|
||||
|
@ -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_ */
|
||||
|
@ -31,6 +31,8 @@ namespace Monitor { namespace Gdb {
|
||||
|
||||
struct Monitor::Gdb::Command : private Commands::Element, Interface
|
||||
{
|
||||
static constexpr bool _verbose = false;
|
||||
|
||||
using Name = String<32>;
|
||||
|
||||
Name const name;
|
||||
@ -144,6 +146,31 @@ struct Monitor::Gdb::Command : private Commands::Element, Interface
|
||||
ascii_to_unsigned<T>(str, result, 16); }); });
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode "ppid.tid" thread-id string
|
||||
*/
|
||||
static void thread_id(Const_byte_range_ptr const &args, int &pid, int &tid)
|
||||
{
|
||||
with_skipped_prefix(args, "p", [&] (Const_byte_range_ptr const &args) {
|
||||
|
||||
auto dot_separated_arg_value = [&] (unsigned i, auto &value)
|
||||
{
|
||||
with_argument(args, Sep { '.' }, i, [&] (Const_byte_range_ptr const &arg) {
|
||||
with_null_terminated(arg, [&] (char const * const str) {
|
||||
if (strcmp(str, "-1") == 0)
|
||||
value = -1;
|
||||
else
|
||||
ascii_to_unsigned(str, value, 16);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
dot_separated_arg_value(0, pid);
|
||||
dot_separated_arg_value(1, tid);
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -73,12 +73,38 @@ struct Monitor::Gdb::State : Noncopyable
|
||||
|
||||
Constructible<Current> _current { };
|
||||
|
||||
/**
|
||||
* Only one stop notification is sent directly, then
|
||||
* additional stop replies are sent as response to 'vStopped'.
|
||||
*/
|
||||
bool notification_in_progress { false };
|
||||
|
||||
bool gdb_connected { false };
|
||||
|
||||
void flush(Inferior_pd &pd)
|
||||
{
|
||||
if (_current.constructed() && _current->pd.id() == pd.id())
|
||||
_current.destruct();
|
||||
}
|
||||
|
||||
void flush(Monitored_thread &thread)
|
||||
{
|
||||
if (_current.constructed() &&
|
||||
_current->thread.constructed() &&
|
||||
(&_current->thread->thread == &thread))
|
||||
_current->thread.destruct();
|
||||
}
|
||||
|
||||
size_t read_memory(Inferior_pd &pd, Memory_accessor::Virt_addr at, Byte_range_ptr const &dst)
|
||||
{
|
||||
return _memory_accessor.read(pd, at, dst);
|
||||
}
|
||||
|
||||
size_t write_memory(Inferior_pd &pd, Memory_accessor::Virt_addr at, Const_byte_range_ptr const &src)
|
||||
{
|
||||
return _memory_accessor.write(pd, at, src);
|
||||
}
|
||||
|
||||
size_t read_memory(Memory_accessor::Virt_addr at, Byte_range_ptr const &dst)
|
||||
{
|
||||
if (_current.constructed())
|
||||
@ -99,19 +125,44 @@ struct Monitor::Gdb::State : Noncopyable
|
||||
|
||||
bool current_defined() const { return _current.constructed(); }
|
||||
|
||||
/**
|
||||
* Select current inferior and thread (id == 0 means any).
|
||||
*
|
||||
* GDB initially sends a Hgp0.0 command but assumes that inferior 1
|
||||
* is current. Avoid losing the default current inferior as set by
|
||||
* 'Main::_create_session' by keeping the previously chosen inferior.
|
||||
*/
|
||||
void current(Inferiors::Id pid, Threads::Id tid)
|
||||
{
|
||||
if ((pid.value == 0) && _current.constructed()) {
|
||||
|
||||
pid.value = _current->pd.id();
|
||||
|
||||
if ((tid.value == 0) && _current->thread.constructed()) {
|
||||
/* keep the current thread */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_current.destruct();
|
||||
|
||||
inferiors.for_each<Inferior_pd &>([&] (Inferior_pd &inferior) {
|
||||
if (inferior.id() != pid.value)
|
||||
|
||||
if ((_current.constructed() &&
|
||||
_current->thread.constructed()) ||
|
||||
((pid.value > 0) && (inferior.id() != pid.value)))
|
||||
return;
|
||||
|
||||
_current.construct(inferior);
|
||||
|
||||
inferior._threads.for_each<Monitored_thread &>([&] (Monitored_thread &thread) {
|
||||
if (thread.id() == tid.value)
|
||||
_current->thread.construct(thread); });
|
||||
|
||||
if (_current->thread.constructed() ||
|
||||
((tid.value > 0) && (thread.id() != tid.value)))
|
||||
return;
|
||||
|
||||
_current->thread.construct(thread);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -130,6 +181,19 @@ struct Monitor::Gdb::State : Noncopyable
|
||||
fn(thread_state);
|
||||
};
|
||||
|
||||
bool current_thread_state(Thread_state const &thread_state)
|
||||
{
|
||||
if (_current.constructed() && _current->thread.constructed()) {
|
||||
try {
|
||||
_current->thread->thread._real.call<Cpu_thread::Rpc_set_state>(thread_state);
|
||||
return true;
|
||||
} catch (Cpu_thread::State_access_failed) {
|
||||
warning("unable to set state of thread ", _current->thread->thread.id());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
State(Inferiors &inferiors, Memory_accessor &memory_accessor)
|
||||
:
|
||||
inferiors(inferiors), _memory_accessor(memory_accessor)
|
||||
@ -177,6 +241,7 @@ struct qSupported : Command_with_separator
|
||||
print(out, "qXfer:threads:read+;");
|
||||
print(out, "multiprocess+;");
|
||||
print(out, "QNonStop+;");
|
||||
print(out, "swbreak+;");
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -265,30 +330,22 @@ struct H : Command_without_separator
|
||||
|
||||
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
log("H command args: ", Cstring(args.start, args.num_bytes));
|
||||
if (_verbose)
|
||||
log("H command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
/* 'g' for other operations, 'p' as prefix of thread-id syntax */
|
||||
with_skipped_prefix(args, "gp", [&] (Const_byte_range_ptr const &args) {
|
||||
/* 'g' for other operations */
|
||||
with_skipped_prefix(args, "g", [&] (Const_byte_range_ptr const &args) {
|
||||
|
||||
auto dot_separated_arg_value = [&] (unsigned i, auto &value)
|
||||
{
|
||||
with_argument(args, Sep { '.' }, i, [&] (Const_byte_range_ptr const &arg) {
|
||||
with_null_terminated(arg, [&] (char const * const str) {
|
||||
ascii_to(str, value); }); });
|
||||
};
|
||||
int pid = 0, tid = 0;
|
||||
thread_id(args, pid, tid);
|
||||
|
||||
unsigned pid = 0, tid = 0;
|
||||
if ((pid == -1) || (tid == -1)) {
|
||||
gdb_error(out, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
dot_separated_arg_value(0, pid);
|
||||
dot_separated_arg_value(1, tid);
|
||||
|
||||
/*
|
||||
* GDB initially sends a Hgp0.0 command but assumes that inferior 1
|
||||
* is current. Avoid losing the default current inferior as set by
|
||||
* 'Main::_create_session'.
|
||||
*/
|
||||
if (pid > 0)
|
||||
state.current(Inferiors::Id { pid }, Threads::Id { tid });
|
||||
state.current(Inferiors::Id { (unsigned)pid },
|
||||
Threads::Id { (unsigned)tid });
|
||||
|
||||
gdb_ok(out);
|
||||
});
|
||||
@ -300,7 +357,7 @@ struct H : Command_without_separator
|
||||
|
||||
|
||||
/**
|
||||
* Enable/disable non-stop mode
|
||||
* Enable/disable non-stop mode (only non-stop mode is supported)
|
||||
*/
|
||||
struct QNonStop : Command_with_separator
|
||||
{
|
||||
@ -308,9 +365,17 @@ struct QNonStop : Command_with_separator
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
log("QNonStop command args: ", Cstring(args.start, args.num_bytes));
|
||||
if (_verbose)
|
||||
log("QNonStop command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
gdb_ok(out);
|
||||
with_null_terminated(args, [&] (char const * const str) {
|
||||
unsigned non_stop_mode { 0 };
|
||||
ascii_to(str, non_stop_mode);
|
||||
if (non_stop_mode)
|
||||
gdb_ok(out);
|
||||
else
|
||||
gdb_error(out, 1);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -352,7 +417,8 @@ struct qC : Command_without_separator
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
log("qC: ", Cstring(args.start, args.num_bytes));
|
||||
if (_verbose)
|
||||
log("qC command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
gdb_response(out, [&] (Output &) { });
|
||||
}
|
||||
@ -383,7 +449,8 @@ struct qOffsets : Command_without_separator
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
log("qOffsets: ", Cstring(args.start, args.num_bytes));
|
||||
if (_verbose)
|
||||
log("qOffsets command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
gdb_response(out, [&] (Output &) { });
|
||||
}
|
||||
@ -397,12 +464,45 @@ struct ask : Command_without_separator
|
||||
{
|
||||
ask(Commands &c) : Command_without_separator(c, "?") { }
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
log("? command args: ", Cstring(args.start, args.num_bytes));
|
||||
if (_verbose)
|
||||
log("? command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
gdb_response(out, [&] (Output &out) {
|
||||
print(out, "T05"); });
|
||||
state.gdb_connected = true;
|
||||
|
||||
bool handled = false;
|
||||
|
||||
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
|
||||
inferior.for_each_thread([&] (Monitored_thread &thread) {
|
||||
|
||||
if (handled)
|
||||
return;
|
||||
|
||||
using Stop_state = Monitored_thread::Stop_state;
|
||||
using Stop_reply_signal = Monitored_thread::Stop_reply_signal;
|
||||
|
||||
if (thread.stop_state == Stop_state::RUNNING)
|
||||
return;
|
||||
|
||||
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
|
||||
|
||||
long unsigned int pid = inferior.id();
|
||||
long unsigned int tid = thread.id();
|
||||
|
||||
gdb_response(out, [&] (Output &out) {
|
||||
print(out, "T", Gdb_hex((uint8_t)thread.stop_reply_signal),
|
||||
"thread:p", Gdb_hex(pid), ".", Gdb_hex(tid), ";");
|
||||
if (thread.stop_reply_signal == Stop_reply_signal::TRAP)
|
||||
print(out, "swbreak:;");
|
||||
});
|
||||
|
||||
handled = true;
|
||||
});
|
||||
});
|
||||
|
||||
if (!handled)
|
||||
gdb_ok(out);
|
||||
}
|
||||
};
|
||||
|
||||
@ -416,7 +516,8 @@ struct g : Command_without_separator
|
||||
|
||||
void execute(State &state, Const_byte_range_ptr const &, Output &out) const override
|
||||
{
|
||||
log("-> execute g");
|
||||
if (_verbose)
|
||||
log("-> execute g");
|
||||
|
||||
gdb_response(out, [&] (Output &out) {
|
||||
state.with_current_thread_state([&] (Thread_state const &thread_state) {
|
||||
@ -465,31 +566,58 @@ struct m : Command_without_separator
|
||||
|
||||
/**
|
||||
* Write memory (binary data)
|
||||
*
|
||||
* Not supported in favor of the 'M' command which is easier
|
||||
* to parse (no escaped special characters or ':' interpreted
|
||||
* as separator character) and readable in the log when
|
||||
* debugging.
|
||||
*/
|
||||
struct X : Command_without_separator
|
||||
{
|
||||
X(Commands &c) : Command_without_separator(c, "X") { }
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
|
||||
{
|
||||
gdb_response(out, [&] (Output &) { });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write memory (hex data)
|
||||
*/
|
||||
struct M : Command_without_separator
|
||||
{
|
||||
M(Commands &c) : Command_without_separator(c, "M") { }
|
||||
|
||||
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
addr_t const addr = comma_separated_hex_value(args, 0, addr_t(0));
|
||||
size_t const len = comma_separated_hex_value(args, 1, 0UL);
|
||||
|
||||
if (len == 0) {
|
||||
/* packet support probing */
|
||||
gdb_ok(out);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t written_num_bytes { 0 };
|
||||
|
||||
with_argument(args, Sep {':'}, 1, [&] (Const_byte_range_ptr const &arg) {
|
||||
|
||||
if (arg.num_bytes != len)
|
||||
if (arg.num_bytes != len * 2)
|
||||
return;
|
||||
|
||||
char buf[len];
|
||||
|
||||
for (size_t i = 0; i < len; i++)
|
||||
with_skipped_bytes(arg, i * 2,
|
||||
[&] (Const_byte_range_ptr const &arg) {
|
||||
with_max_bytes(arg, 2,
|
||||
[&] (Const_byte_range_ptr const &arg) {
|
||||
with_null_terminated(arg, [&] (char const * const str) {
|
||||
ascii_to_unsigned(str, buf[i], 16);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
written_num_bytes =
|
||||
state.write_memory(Memory_accessor::Virt_addr { addr }, arg);
|
||||
state.write_memory(Memory_accessor::Virt_addr { addr },
|
||||
Const_byte_range_ptr(buf, len));
|
||||
});
|
||||
|
||||
if (written_num_bytes == len)
|
||||
@ -509,7 +637,8 @@ struct T : Command_without_separator
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
log("T command args: ", Cstring(args.start, args.num_bytes));
|
||||
if (_verbose)
|
||||
log("T command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
gdb_ok(out);
|
||||
}
|
||||
@ -523,13 +652,347 @@ struct D : Command_with_separator
|
||||
{
|
||||
D(Commands &c) : Command_with_separator(c, "D") { }
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
|
||||
void execute(State &state, Const_byte_range_ptr const &, Output &out) const override
|
||||
{
|
||||
state.gdb_connected = false;
|
||||
gdb_ok(out);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable extended mode
|
||||
*/
|
||||
struct bang : Command_without_separator
|
||||
{
|
||||
bang(Commands &c) : Command_without_separator(c, "!") { }
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("! command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
gdb_ok(out);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Report stopped threads in non-stop mode
|
||||
*/
|
||||
struct vStopped : Command_without_separator
|
||||
{
|
||||
vStopped(Commands &c) : Command_without_separator(c, "vStopped") { }
|
||||
|
||||
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("vStopped command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
using Stop_state = Monitored_thread::Stop_state;
|
||||
using Stop_reply_signal = Monitored_thread::Stop_reply_signal;
|
||||
|
||||
/* mark previous stop reply as acked */
|
||||
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
|
||||
inferior.for_each_thread([&] (Monitored_thread &thread) {
|
||||
if (thread.stop_state == Stop_state::STOPPED_REPLY_SENT)
|
||||
thread.stop_state = Stop_state::STOPPED_REPLY_ACKED;
|
||||
});
|
||||
});
|
||||
|
||||
bool handled = false;
|
||||
|
||||
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
|
||||
inferior.for_each_thread([&] (Monitored_thread &thread) {
|
||||
|
||||
if (handled)
|
||||
return;
|
||||
|
||||
if (thread.stop_state != Stop_state::STOPPED_REPLY_PENDING)
|
||||
return;
|
||||
|
||||
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
|
||||
|
||||
long unsigned int pid = inferior.id();
|
||||
long unsigned int tid = thread.id();
|
||||
|
||||
gdb_response(out, [&] (Output &out) {
|
||||
print(out, "T", Gdb_hex((uint8_t)thread.stop_reply_signal),
|
||||
"thread:p", Gdb_hex(pid), ".", Gdb_hex(tid), ";");
|
||||
if (thread.stop_reply_signal == Stop_reply_signal::TRAP)
|
||||
print(out, "swbreak:;");
|
||||
});
|
||||
|
||||
handled = true;
|
||||
});
|
||||
});
|
||||
|
||||
if (!handled) {
|
||||
state.notification_in_progress = false;
|
||||
gdb_ok(out);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resume the inferior
|
||||
*/
|
||||
struct vCont : Command_without_separator
|
||||
{
|
||||
vCont(Commands &c) : Command_without_separator(c, "vCont") { }
|
||||
|
||||
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("vCont command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
bool handled = false;
|
||||
|
||||
with_skipped_prefix(args, "?", [&] (Const_byte_range_ptr const &) {
|
||||
|
||||
gdb_response(out, [&] (Output &out) {
|
||||
print(out, "vCont;c;s;t"); });
|
||||
|
||||
handled = true;
|
||||
});
|
||||
|
||||
with_skipped_prefix(args, ";", [&] (Const_byte_range_ptr const &args) {
|
||||
|
||||
for_each_argument(args, Sep { ';' }, [&] (Const_byte_range_ptr const &arg) {
|
||||
|
||||
auto with_vcont_target_thread = [&] (Const_byte_range_ptr const &arg, auto const &fn)
|
||||
{
|
||||
handled = true;
|
||||
|
||||
int pid = -1;
|
||||
int tid = -1;
|
||||
|
||||
with_skipped_prefix(arg, ":", [&] (Const_byte_range_ptr const &arg) {
|
||||
thread_id(arg, pid, tid); });
|
||||
|
||||
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
|
||||
|
||||
if (pid == 0)
|
||||
pid = (int)inferior.id();
|
||||
|
||||
if ((pid != -1) && ((int)inferior.id() != pid))
|
||||
return;
|
||||
|
||||
inferior.for_each_thread([&] (Monitored_thread &thread) {
|
||||
|
||||
if (tid == 0)
|
||||
tid = (int)thread.id();
|
||||
|
||||
if ((tid != -1) && ((int)thread.id() != tid))
|
||||
return;
|
||||
|
||||
fn(inferior, thread);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
using Stop_state = Monitored_thread::Stop_state;
|
||||
|
||||
with_skipped_prefix(arg, "t", [&] (Const_byte_range_ptr const &arg) {
|
||||
|
||||
with_vcont_target_thread(arg, [&] (Inferior_pd const &inferior,
|
||||
Monitored_thread &thread) {
|
||||
|
||||
if (thread.stop_state == Stop_state::RUNNING) {
|
||||
thread.pause();
|
||||
if (!state.notification_in_progress) {
|
||||
state.notification_in_progress = true;
|
||||
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
|
||||
gdb_notification(out, [&] (Output &out) {
|
||||
print(out, "Stop:T",
|
||||
Gdb_hex((uint8_t)thread.stop_reply_signal),
|
||||
"thread:p",
|
||||
Gdb_hex(inferior.id()),
|
||||
".",
|
||||
Gdb_hex(thread.id()),
|
||||
";");
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
with_skipped_prefix(arg, "c", [&] (Const_byte_range_ptr const &arg) {
|
||||
|
||||
with_vcont_target_thread(arg, [&] (Inferior_pd const &,
|
||||
Monitored_thread &thread) {
|
||||
|
||||
if (thread.stop_state == Stop_state::STOPPED_REPLY_ACKED) {
|
||||
thread.single_step(false);
|
||||
thread.resume();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
with_skipped_prefix(arg, "s", [&] (Const_byte_range_ptr const &arg) {
|
||||
|
||||
with_vcont_target_thread(arg, [&] (Inferior_pd const &,
|
||||
Monitored_thread &thread) {
|
||||
|
||||
if (thread.stop_state == Stop_state::STOPPED_REPLY_ACKED) {
|
||||
thread.single_step(true);
|
||||
thread.resume();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (handled) {
|
||||
gdb_ok(out);
|
||||
return;
|
||||
}
|
||||
|
||||
warning("GDB ", name, " command unsupported: ", Cstring(args.start, args.num_bytes));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Read value of register
|
||||
*/
|
||||
struct p : Command_without_separator
|
||||
{
|
||||
p(Commands &c) : Command_without_separator(c, "p") { }
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("p command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
/* currently not supported */
|
||||
gdb_response(out, [&] (Output &) { });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write value of register
|
||||
*/
|
||||
struct P : Command_without_separator
|
||||
{
|
||||
P(Commands &c) : Command_without_separator(c, "P") { }
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("P command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
/* currently not supported */
|
||||
gdb_response(out, [&] (Output &) { });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stop thread(s)
|
||||
*/
|
||||
struct vCtrlC : Command_without_separator
|
||||
{
|
||||
vCtrlC(Commands &c) : Command_without_separator(c, "vCtrlC") { }
|
||||
|
||||
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("vCtrlC command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
if (state._current.constructed() &&
|
||||
state._current->thread.constructed()) {
|
||||
|
||||
Inferior_pd &inferior = state._current->pd;
|
||||
Monitored_thread &thread = state._current->thread->thread;
|
||||
|
||||
using Stop_state = Monitored_thread::Stop_state;
|
||||
|
||||
if (thread.stop_state == Stop_state::RUNNING) {
|
||||
thread.pause();
|
||||
if (!state.notification_in_progress) {
|
||||
state.notification_in_progress = true;
|
||||
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
|
||||
gdb_notification(out, [&] (Output &out) {
|
||||
print(out, "Stop:T",
|
||||
Gdb_hex((uint8_t)thread.stop_reply_signal),
|
||||
"thread:p",
|
||||
Gdb_hex(inferior.id()),
|
||||
".",
|
||||
Gdb_hex(thread.id()),
|
||||
";");
|
||||
});
|
||||
}
|
||||
}
|
||||
gdb_ok(out);
|
||||
return;
|
||||
}
|
||||
|
||||
gdb_error(out, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* File operations
|
||||
*/
|
||||
struct vFile : Command_without_separator
|
||||
{
|
||||
vFile(Commands &c) : Command_without_separator(c, "vFile") { }
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("vFile command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
/* currently not supported */
|
||||
gdb_response(out, [&] (Output &) { });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set breakpoint
|
||||
*/
|
||||
struct Z : Command_without_separator
|
||||
{
|
||||
Z(Commands &c) : Command_without_separator(c, "Z") { }
|
||||
|
||||
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("Z command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
/* currently not supported */
|
||||
gdb_response(out, [&] (Output &) { });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Write registers
|
||||
*/
|
||||
struct G : Command_without_separator
|
||||
{
|
||||
G(Commands &c) : Command_without_separator(c, "G") { }
|
||||
|
||||
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
|
||||
{
|
||||
if (_verbose)
|
||||
log("G command args: ", Cstring(args.start, args.num_bytes));
|
||||
|
||||
Thread_state thread_state;
|
||||
|
||||
parse_registers(args, thread_state);
|
||||
|
||||
if (state.current_thread_state(thread_state))
|
||||
gdb_ok(out);
|
||||
else
|
||||
gdb_error(out, 1);
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace Cmd */ } /* namespace Gdb */ } /* namespace Monitor */
|
||||
|
||||
|
||||
@ -575,7 +1038,17 @@ struct Monitor::Gdb::Supported_commands : Commands
|
||||
Cmd::D,
|
||||
Cmd::T,
|
||||
Cmd::ask,
|
||||
Cmd::X
|
||||
Cmd::X,
|
||||
Cmd::M,
|
||||
Cmd::bang,
|
||||
Cmd::vStopped,
|
||||
Cmd::vCont,
|
||||
Cmd::p,
|
||||
Cmd::P,
|
||||
Cmd::vCtrlC,
|
||||
Cmd::vFile,
|
||||
Cmd::Z,
|
||||
Cmd::G
|
||||
> _instances { *this };
|
||||
};
|
||||
|
||||
|
@ -22,7 +22,8 @@ namespace Monitor { struct Inferior_cpu; }
|
||||
|
||||
struct Monitor::Inferior_cpu : Monitored_cpu_session
|
||||
{
|
||||
Allocator &_alloc;
|
||||
Allocator &_alloc;
|
||||
Thread_monitor &_thread_monitor;
|
||||
|
||||
Constructible<Monitored_native_cpu_nova> _native_cpu_nova { };
|
||||
|
||||
@ -38,9 +39,11 @@ struct Monitor::Inferior_cpu : Monitored_cpu_session
|
||||
}
|
||||
|
||||
Inferior_cpu(Entrypoint &ep, Capability<Cpu_session> real,
|
||||
Name const &name, Allocator &alloc)
|
||||
Name const &name, Allocator &alloc,
|
||||
Thread_monitor &thread_monitor)
|
||||
:
|
||||
Monitored_cpu_session(ep, real, name), _alloc(alloc)
|
||||
Monitored_cpu_session(ep, real, name), _alloc(alloc),
|
||||
_thread_monitor(thread_monitor)
|
||||
{ }
|
||||
|
||||
|
||||
@ -61,9 +64,14 @@ struct Monitor::Inferior_cpu : Monitored_cpu_session
|
||||
_real.call<Rpc_create_thread>(inferior_pd._real,
|
||||
name, affinity, weight, utcb);
|
||||
|
||||
Threads::Id thread_id { inferior_pd.alloc_thread_id() };
|
||||
bool wait = inferior_pd._policy.wait &&
|
||||
(thread_id == Threads::Id { 1 });
|
||||
|
||||
Monitored_thread &monitored_thread = *new (_alloc)
|
||||
Monitored_thread(_ep, real_thread, name, inferior_pd._threads,
|
||||
inferior_pd.alloc_thread_id());
|
||||
Monitored_thread(_ep, real_thread, name,
|
||||
inferior_pd._threads, thread_id,
|
||||
pd, _thread_monitor, wait);
|
||||
|
||||
result = monitored_thread.cap();
|
||||
},
|
||||
|
@ -67,7 +67,38 @@ struct Monitor::Inferior_pd : Monitored_pd_session
|
||||
|
||||
unsigned _page_fault_count = 0;
|
||||
|
||||
void _handle_page_fault() { _page_fault_count++; }
|
||||
void _handle_page_fault()
|
||||
{
|
||||
bool thread_found = false;
|
||||
|
||||
for_each_thread([&] (Monitored_thread &thread) {
|
||||
|
||||
if (thread.stop_state != Monitored_thread::Stop_state::RUNNING)
|
||||
return;
|
||||
|
||||
try {
|
||||
Thread_state thread_state = thread.state();
|
||||
if (thread_state.unresolved_page_fault) {
|
||||
thread.handle_page_fault();
|
||||
thread_found = true;
|
||||
}
|
||||
} catch (Cpu_thread::State_access_failed) {
|
||||
/* this exception occurs for running threads */
|
||||
}
|
||||
});
|
||||
|
||||
if (!thread_found) {
|
||||
/*
|
||||
* Fault caused by memory accessor
|
||||
*
|
||||
* If both an inferior thread and the memory accessor
|
||||
* caused a page fault, this is not detected here and
|
||||
* the watchdog timeout of the memory accessor will
|
||||
* trigger instead.
|
||||
*/
|
||||
_page_fault_count++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track of allocated RAM dataspaces for wiping when freed
|
||||
@ -146,7 +177,7 @@ struct Monitor::Inferior_pd : Monitored_pd_session
|
||||
|
||||
void for_each_thread(auto const &fn) const
|
||||
{
|
||||
_threads.for_each<Monitored_thread const &>(fn);
|
||||
_threads.for_each<Monitored_thread &>(fn);
|
||||
}
|
||||
|
||||
static void with_inferior_pd(Entrypoint &ep, Capability<Pd_session> pd_cap,
|
||||
|
@ -45,7 +45,8 @@ namespace Monitor {
|
||||
|
||||
namespace Monitor { struct Main; }
|
||||
|
||||
struct Monitor::Main : Sandbox::State_handler
|
||||
struct Monitor::Main : Sandbox::State_handler,
|
||||
Thread_monitor
|
||||
{
|
||||
struct Local_pd_session : Connection<Pd_connection>, Inferior_pd
|
||||
{
|
||||
@ -58,10 +59,13 @@ struct Monitor::Main : Sandbox::State_handler
|
||||
|
||||
struct Local_cpu_session : Connection<Cpu_connection>, Inferior_cpu
|
||||
{
|
||||
Local_cpu_session(Env &env, Session::Label const &label, Priority priority, Allocator &alloc)
|
||||
Local_cpu_session(Env &env, Session::Label const &label,
|
||||
Priority priority, Allocator &alloc,
|
||||
Thread_monitor &thread_monitor)
|
||||
:
|
||||
Connection<Cpu_connection>(env, label, priority.value),
|
||||
Inferior_cpu(env.ep(), _connection.cap(), label, alloc)
|
||||
Inferior_cpu(env.ep(), _connection.cap(), label, alloc,
|
||||
thread_monitor)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -165,6 +169,37 @@ struct Monitor::Main : Sandbox::State_handler
|
||||
_memory_accessor.flush();
|
||||
}
|
||||
|
||||
void flush(Monitored_thread &thread)
|
||||
{
|
||||
_state.flush(thread);
|
||||
}
|
||||
|
||||
void thread_stopped(Inferior_pd &inferior, Monitored_thread &thread)
|
||||
{
|
||||
if (_state.gdb_connected && !_state.notification_in_progress) {
|
||||
|
||||
_state.notification_in_progress = true;
|
||||
|
||||
using Stop_state = Monitored_thread::Stop_state;
|
||||
using Stop_reply_signal = Monitored_thread::Stop_reply_signal;
|
||||
|
||||
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
|
||||
|
||||
Terminal_output output { ._write_fn { _terminal } };
|
||||
gdb_notification(output.buffered, [&] (Output &out) {
|
||||
print(out, "Stop:T",
|
||||
Gdb_hex((uint8_t)thread.stop_reply_signal),
|
||||
"thread:p",
|
||||
Gdb_hex(inferior.id()),
|
||||
".",
|
||||
Gdb_hex(thread.id()),
|
||||
";");
|
||||
if (thread.stop_reply_signal == Stop_reply_signal::TRAP)
|
||||
print(out, "swbreak:;");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Gdb_stub(Env &env, Inferiors &inferiors)
|
||||
:
|
||||
_env(env), _state(inferiors, _memory_accessor)
|
||||
@ -243,7 +278,8 @@ struct Monitor::Main : Sandbox::State_handler
|
||||
Local_cpu_session &_create_session(Cpu_service &, Session_request const &request)
|
||||
{
|
||||
Local_cpu_session &session = *new (_heap)
|
||||
Local_cpu_session(_env, request.label, _priority_from_args(request.args), _heap);
|
||||
Local_cpu_session(_env, request.label,
|
||||
_priority_from_args(request.args), _heap, *this);
|
||||
|
||||
session.init_native_cpu(_kernel);
|
||||
|
||||
@ -349,6 +385,76 @@ struct Monitor::Main : Sandbox::State_handler
|
||||
_env.parent().resource_avail_sigh(_resource_avail_handler);
|
||||
|
||||
_handle_config();
|
||||
|
||||
/* cue for run scripts to start GDB */
|
||||
log("monitor ready");
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread_monitor interface
|
||||
*/
|
||||
|
||||
void set_initial_breakpoint(Capability<Pd_session> pd,
|
||||
addr_t addr,
|
||||
char original_instruction[]) override
|
||||
{
|
||||
if (!_gdb_stub.constructed()) {
|
||||
Genode::error("set_initial_breakpoint() called without monitor config");
|
||||
return;
|
||||
}
|
||||
|
||||
Inferior_pd::with_inferior_pd(_env.ep(), pd,
|
||||
[&] (Inferior_pd &inferior) {
|
||||
_gdb_stub->_state.read_memory(inferior,
|
||||
Memory_accessor::Virt_addr { addr },
|
||||
Byte_range_ptr { original_instruction,
|
||||
Gdb::breakpoint_instruction_len() });
|
||||
|
||||
_gdb_stub->_state.write_memory(inferior,
|
||||
Memory_accessor::Virt_addr { addr },
|
||||
Const_byte_range_ptr { Gdb::breakpoint_instruction(),
|
||||
Gdb::breakpoint_instruction_len() });
|
||||
}, [] { });
|
||||
}
|
||||
|
||||
void remove_initial_breakpoint(Capability<Pd_session> pd,
|
||||
addr_t addr,
|
||||
char const original_instruction[]) override
|
||||
{
|
||||
if (!_gdb_stub.constructed()) {
|
||||
Genode::error("remove_initial_breakpoint() called without monitor config");
|
||||
return;
|
||||
}
|
||||
|
||||
Inferior_pd::with_inferior_pd(_env.ep(), pd,
|
||||
[&] (Inferior_pd &inferior) {
|
||||
_gdb_stub->_state.write_memory(inferior,
|
||||
Memory_accessor::Virt_addr { addr },
|
||||
Const_byte_range_ptr { original_instruction,
|
||||
Gdb::breakpoint_instruction_len() });
|
||||
}, [] { });
|
||||
}
|
||||
|
||||
void flush(Monitored_thread &thread) override
|
||||
{
|
||||
if (!_gdb_stub.constructed()) {
|
||||
Genode::error("flush_thread() called without monitor config");
|
||||
return;
|
||||
}
|
||||
|
||||
_gdb_stub->flush(thread);
|
||||
}
|
||||
|
||||
void thread_stopped(Capability<Pd_session> pd, Monitored_thread &thread) override
|
||||
{
|
||||
if (!_gdb_stub.constructed()) {
|
||||
Genode::error("thread_stopped() called without monitor config");
|
||||
return;
|
||||
}
|
||||
Inferior_pd::with_inferior_pd(_env.ep(), pd,
|
||||
[&] (Inferior_pd &inferior) {
|
||||
_gdb_stub->thread_stopped(inferior, thread);
|
||||
}, [] { });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -19,9 +19,34 @@
|
||||
#include <cpu_thread/client.h>
|
||||
|
||||
/* local includes */
|
||||
#include <gdb_arch.h>
|
||||
#include <types.h>
|
||||
|
||||
namespace Monitor { struct Monitored_thread; }
|
||||
namespace Monitor {
|
||||
struct Monitored_thread;
|
||||
struct Thread_monitor;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Interface for the interaction of the monitored thread
|
||||
* with the monitor.
|
||||
*/
|
||||
struct Monitor::Thread_monitor : Interface
|
||||
{
|
||||
virtual void set_initial_breakpoint(Capability<Pd_session> pd,
|
||||
addr_t addr,
|
||||
char original_instruction[]) = 0;
|
||||
|
||||
virtual void remove_initial_breakpoint(Capability<Pd_session> pd,
|
||||
addr_t addr,
|
||||
char const original_instruction[]) = 0;
|
||||
|
||||
virtual void flush(Monitored_thread &thread) = 0;
|
||||
|
||||
virtual void thread_stopped(Capability<Pd_session> pd,
|
||||
Monitored_thread &thread) = 0;
|
||||
};
|
||||
|
||||
|
||||
struct Monitor::Monitored_thread : Monitored_rpc_object<Cpu_thread>
|
||||
@ -32,29 +57,100 @@ struct Monitor::Monitored_thread : Monitored_rpc_object<Cpu_thread>
|
||||
with_monitored<Monitored_thread>(ep, cap, monitored_fn, direct_fn);
|
||||
}
|
||||
|
||||
Threads::Element _threads_elem;
|
||||
Threads::Element _threads_elem;
|
||||
Capability<Pd_session> _pd;
|
||||
Thread_monitor &_thread_monitor;
|
||||
bool _wait;
|
||||
|
||||
addr_t _first_instruction_addr { };
|
||||
char _original_first_instruction[Gdb::max_breakpoint_instruction_len] { };
|
||||
|
||||
Signal_handler<Monitored_thread> _exception_handler;
|
||||
|
||||
/* stop reply signal values as expected by GDB */
|
||||
enum Stop_reply_signal {
|
||||
STOP = 0,
|
||||
ILL = 4,
|
||||
TRAP = 5,
|
||||
FPE = 8,
|
||||
SEGV = 11
|
||||
};
|
||||
|
||||
Stop_reply_signal stop_reply_signal { STOP };
|
||||
|
||||
enum Stop_state {
|
||||
RUNNING,
|
||||
STOPPED_REPLY_PENDING,
|
||||
STOPPED_REPLY_SENT,
|
||||
STOPPED_REPLY_ACKED
|
||||
};
|
||||
|
||||
Stop_state stop_state { RUNNING };
|
||||
|
||||
void _handle_exception();
|
||||
|
||||
void handle_page_fault()
|
||||
{
|
||||
/*
|
||||
* On NOVA 'pause()' must be called to get the
|
||||
* complete register state.
|
||||
*/
|
||||
pause();
|
||||
|
||||
stop_state = Stop_state::STOPPED_REPLY_PENDING;
|
||||
stop_reply_signal = Stop_reply_signal::SEGV;
|
||||
|
||||
_thread_monitor.thread_stopped(_pd, *this);
|
||||
}
|
||||
|
||||
using Monitored_rpc_object::Monitored_rpc_object;
|
||||
|
||||
Monitored_thread(Entrypoint &ep, Capability<Cpu_thread> real, Name const &name,
|
||||
Threads &threads, Threads::Id id)
|
||||
Threads &threads, Threads::Id id,
|
||||
Capability<Pd_session> pd,
|
||||
Thread_monitor &thread_monitor, bool wait)
|
||||
:
|
||||
Monitored_rpc_object(ep, real, name), _threads_elem(*this, threads, id)
|
||||
{ }
|
||||
Monitored_rpc_object(ep, real, name),
|
||||
_threads_elem(*this, threads, id),
|
||||
_pd(pd), _thread_monitor(thread_monitor), _wait(wait),
|
||||
_exception_handler(ep, *this, &Monitored_thread::_handle_exception)
|
||||
{
|
||||
_real.call<Rpc_exception_sigh>(_exception_handler);
|
||||
|
||||
}
|
||||
|
||||
~Monitored_thread()
|
||||
{
|
||||
_thread_monitor.flush(*this);
|
||||
}
|
||||
|
||||
long unsigned id() const { return _threads_elem.id().value; }
|
||||
|
||||
Dataspace_capability utcb() override {
|
||||
return _real.call<Rpc_utcb>(); }
|
||||
|
||||
void start(addr_t ip, addr_t sp) override {
|
||||
_real.call<Rpc_start>(ip, sp); }
|
||||
void start(addr_t ip, addr_t sp) override
|
||||
{
|
||||
if (_wait) {
|
||||
_first_instruction_addr = ip;
|
||||
_thread_monitor.set_initial_breakpoint(_pd, ip,
|
||||
_original_first_instruction);
|
||||
}
|
||||
|
||||
void pause() override {
|
||||
_real.call<Rpc_pause>(); }
|
||||
_real.call<Rpc_start>(ip, sp);
|
||||
}
|
||||
|
||||
void pause() override
|
||||
{
|
||||
_real.call<Rpc_pause>();
|
||||
stop_state = Stop_state::STOPPED_REPLY_PENDING;
|
||||
stop_reply_signal = Stop_reply_signal::STOP;
|
||||
}
|
||||
|
||||
void resume() override {
|
||||
_real.call<Rpc_resume>(); }
|
||||
stop_state = Stop_state::RUNNING;
|
||||
_real.call<Rpc_resume>();
|
||||
}
|
||||
|
||||
Thread_state state() override {
|
||||
return _real.call<Rpc_get_state>(); }
|
||||
|
88
repos/os/src/monitor/spec/arm_64/gdb_arch.cc
Normal file
88
repos/os/src/monitor/spec/arm_64/gdb_arch.cc
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* \brief Architecture-specific GDB protocol support
|
||||
* \author Christian Prochaska
|
||||
* \date 2023-07-31
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <util/endian.h>
|
||||
|
||||
/* monitor includes */
|
||||
#include <gdb_arch.h>
|
||||
#include <monitored_thread.h>
|
||||
|
||||
using namespace Monitor;
|
||||
|
||||
|
||||
/* BRK */
|
||||
char const *Monitor::Gdb::breakpoint_instruction() { return "\x20\x00\x20\xd4"; }
|
||||
size_t Monitor::Gdb::breakpoint_instruction_len() { return 4; }
|
||||
|
||||
|
||||
void Monitor::Gdb::print_registers(Output &out, Cpu_state const &cpu)
|
||||
{
|
||||
for (addr_t r : cpu.r)
|
||||
print(out, Gdb_hex(host_to_big_endian(r)));
|
||||
|
||||
print(out, Gdb_hex(host_to_big_endian(cpu.sp)));
|
||||
print(out, Gdb_hex(host_to_big_endian(cpu.ip)));
|
||||
}
|
||||
|
||||
|
||||
void Monitor::Gdb::parse_registers(Const_byte_range_ptr const &in, Cpu_state &cpu)
|
||||
{
|
||||
for (size_t i = 0; i < 33; i++) {
|
||||
with_skipped_bytes(in, i * sizeof(addr_t) * 2,
|
||||
[&] (Const_byte_range_ptr const &in) {
|
||||
with_max_bytes(in, sizeof(addr_t) * 2, [&] (Const_byte_range_ptr const &in) {
|
||||
char null_terminated[sizeof(addr_t) * 2 + 1] { };
|
||||
memcpy(null_terminated, in.start,
|
||||
min(sizeof(null_terminated) - 1, in.num_bytes));
|
||||
addr_t value = 0;
|
||||
ascii_to_unsigned(null_terminated, value, 16);
|
||||
if (i < 31) {
|
||||
cpu.r[i] = big_endian_to_host(value);
|
||||
} else if (i == 31) {
|
||||
cpu.sp = big_endian_to_host(value);
|
||||
} else if (i == 32) {
|
||||
cpu.ip = big_endian_to_host(value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Monitor::Monitored_thread::_handle_exception()
|
||||
{
|
||||
stop_state = Stop_state::STOPPED_REPLY_PENDING;
|
||||
|
||||
Thread_state thread_state = _real.call<Rpc_get_state>();
|
||||
|
||||
if (_wait) {
|
||||
_wait = false;
|
||||
_thread_monitor.remove_initial_breakpoint(_pd, _first_instruction_addr,
|
||||
_original_first_instruction);
|
||||
stop_reply_signal = Stop_reply_signal::STOP;
|
||||
} else {
|
||||
switch(thread_state.ec) {
|
||||
case Cpu_state::Cpu_exception::SOFTWARE_STEP:
|
||||
stop_reply_signal = Stop_reply_signal::TRAP;
|
||||
break;
|
||||
case Cpu_state::Cpu_exception::BREAKPOINT:
|
||||
stop_reply_signal = Stop_reply_signal::TRAP;
|
||||
break;
|
||||
default:
|
||||
stop_reply_signal = Stop_reply_signal::TRAP;
|
||||
}
|
||||
}
|
||||
|
||||
_thread_monitor.thread_stopped(_pd, *this);
|
||||
}
|
183
repos/os/src/monitor/spec/arm_64/gdb_target.xml
Normal file
183
repos/os/src/monitor/spec/arm_64/gdb_target.xml
Normal file
@ -0,0 +1,183 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target>
|
||||
<architecture>aarch64</architecture>
|
||||
<feature name="org.gnu.gdb.aarch64.core">
|
||||
<flags id="cpsr_flags" size="4">
|
||||
<field name="SP" start="0" end="0" type="bool"/>
|
||||
<field name="EL" start="2" end="3" type="uint32"/>
|
||||
<field name="nRW" start="4" end="4" type="bool"/>
|
||||
<field name="F" start="6" end="6" type="bool"/>
|
||||
<field name="I" start="7" end="7" type="bool"/>
|
||||
<field name="A" start="8" end="8" type="bool"/>
|
||||
<field name="D" start="9" end="9" type="bool"/>
|
||||
<field name="BTYPE" start="10" end="11" type="uint32"/>
|
||||
<field name="SSBS" start="12" end="12" type="bool"/>
|
||||
<field name="IL" start="20" end="20" type="bool"/>
|
||||
<field name="SS" start="21" end="21" type="bool"/>
|
||||
<field name="PAN" start="22" end="22" type="bool"/>
|
||||
<field name="UAO" start="23" end="23" type="bool"/>
|
||||
<field name="DIT" start="24" end="24" type="bool"/>
|
||||
<field name="TCO" start="25" end="25" type="bool"/>
|
||||
<field name="V" start="28" end="28" type="bool"/>
|
||||
<field name="C" start="29" end="29" type="bool"/>
|
||||
<field name="Z" start="30" end="30" type="bool"/>
|
||||
<field name="N" start="31" end="31" type="bool"/>
|
||||
</flags>
|
||||
<reg name="x0" bitsize="64" type="int" regnum="0"/>
|
||||
<reg name="x1" bitsize="64" type="int" regnum="1"/>
|
||||
<reg name="x2" bitsize="64" type="int" regnum="2"/>
|
||||
<reg name="x3" bitsize="64" type="int" regnum="3"/>
|
||||
<reg name="x4" bitsize="64" type="int" regnum="4"/>
|
||||
<reg name="x5" bitsize="64" type="int" regnum="5"/>
|
||||
<reg name="x6" bitsize="64" type="int" regnum="6"/>
|
||||
<reg name="x7" bitsize="64" type="int" regnum="7"/>
|
||||
<reg name="x8" bitsize="64" type="int" regnum="8"/>
|
||||
<reg name="x9" bitsize="64" type="int" regnum="9"/>
|
||||
<reg name="x10" bitsize="64" type="int" regnum="10"/>
|
||||
<reg name="x11" bitsize="64" type="int" regnum="11"/>
|
||||
<reg name="x12" bitsize="64" type="int" regnum="12"/>
|
||||
<reg name="x13" bitsize="64" type="int" regnum="13"/>
|
||||
<reg name="x14" bitsize="64" type="int" regnum="14"/>
|
||||
<reg name="x15" bitsize="64" type="int" regnum="15"/>
|
||||
<reg name="x16" bitsize="64" type="int" regnum="16"/>
|
||||
<reg name="x17" bitsize="64" type="int" regnum="17"/>
|
||||
<reg name="x18" bitsize="64" type="int" regnum="18"/>
|
||||
<reg name="x19" bitsize="64" type="int" regnum="19"/>
|
||||
<reg name="x20" bitsize="64" type="int" regnum="20"/>
|
||||
<reg name="x21" bitsize="64" type="int" regnum="21"/>
|
||||
<reg name="x22" bitsize="64" type="int" regnum="22"/>
|
||||
<reg name="x23" bitsize="64" type="int" regnum="23"/>
|
||||
<reg name="x24" bitsize="64" type="int" regnum="24"/>
|
||||
<reg name="x25" bitsize="64" type="int" regnum="25"/>
|
||||
<reg name="x26" bitsize="64" type="int" regnum="26"/>
|
||||
<reg name="x27" bitsize="64" type="int" regnum="27"/>
|
||||
<reg name="x28" bitsize="64" type="int" regnum="28"/>
|
||||
<reg name="x29" bitsize="64" type="int" regnum="29"/>
|
||||
<reg name="x30" bitsize="64" type="int" regnum="30"/>
|
||||
<reg name="sp" bitsize="64" type="data_ptr" regnum="31"/>
|
||||
<reg name="pc" bitsize="64" type="code_ptr" regnum="32"/>
|
||||
<reg name="cpsr" bitsize="32" type="cpsr_flags" regnum="33"/>
|
||||
</feature>
|
||||
<feature name="org.gnu.gdb.aarch64.fpu">
|
||||
<vector id="v2d" type="ieee_double" count="2"/>
|
||||
<vector id="v2u" type="uint64" count="2"/>
|
||||
<vector id="v2i" type="int64" count="2"/>
|
||||
<vector id="v4f" type="ieee_single" count="4"/>
|
||||
<vector id="v4u" type="uint32" count="4"/>
|
||||
<vector id="v4i" type="int32" count="4"/>
|
||||
<vector id="v8f" type="ieee_half" count="8"/>
|
||||
<vector id="v8u" type="uint16" count="8"/>
|
||||
<vector id="v8i" type="int16" count="8"/>
|
||||
<vector id="v8bf16" type="bfloat16" count="8"/>
|
||||
<vector id="v16u" type="uint8" count="16"/>
|
||||
<vector id="v16i" type="int8" count="16"/>
|
||||
<vector id="v1u" type="uint128" count="1"/>
|
||||
<vector id="v1i" type="int128" count="1"/>
|
||||
<union id="vnd">
|
||||
<field name="f" type="v2d"/>
|
||||
<field name="u" type="v2u"/>
|
||||
<field name="s" type="v2i"/>
|
||||
</union>
|
||||
<union id="vns">
|
||||
<field name="f" type="v4f"/>
|
||||
<field name="u" type="v4u"/>
|
||||
<field name="s" type="v4i"/>
|
||||
</union>
|
||||
<union id="vnh">
|
||||
<field name="bf" type="v8bf16"/>
|
||||
<field name="f" type="v8f"/>
|
||||
<field name="u" type="v8u"/>
|
||||
<field name="s" type="v8i"/>
|
||||
</union>
|
||||
<union id="vnb">
|
||||
<field name="u" type="v16u"/>
|
||||
<field name="s" type="v16i"/>
|
||||
</union>
|
||||
<union id="vnq">
|
||||
<field name="u" type="v1u"/>
|
||||
<field name="s" type="v1i"/>
|
||||
</union>
|
||||
<union id="aarch64v">
|
||||
<field name="d" type="vnd"/>
|
||||
<field name="s" type="vns"/>
|
||||
<field name="h" type="vnh"/>
|
||||
<field name="b" type="vnb"/>
|
||||
<field name="q" type="vnq"/>
|
||||
</union>
|
||||
<flags id="fpsr_flags" size="4">
|
||||
<field name="IOC" start="0" end="0" type="bool"/>
|
||||
<field name="DZC" start="1" end="1" type="bool"/>
|
||||
<field name="OFC" start="2" end="2" type="bool"/>
|
||||
<field name="UFC" start="3" end="3" type="bool"/>
|
||||
<field name="IXC" start="4" end="4" type="bool"/>
|
||||
<field name="IDC" start="7" end="7" type="bool"/>
|
||||
<field name="QC" start="27" end="27" type="bool"/>
|
||||
<field name="V" start="28" end="28" type="bool"/>
|
||||
<field name="C" start="29" end="29" type="bool"/>
|
||||
<field name="Z" start="30" end="30" type="bool"/>
|
||||
<field name="N" start="31" end="31" type="bool"/>
|
||||
</flags>
|
||||
<flags id="fpcr_flags" size="4">
|
||||
<field name="FIZ" start="0" end="0" type="bool"/>
|
||||
<field name="AH" start="1" end="1" type="bool"/>
|
||||
<field name="NEP" start="2" end="2" type="bool"/>
|
||||
<field name="IOE" start="8" end="8" type="bool"/>
|
||||
<field name="DZE" start="9" end="9" type="bool"/>
|
||||
<field name="OFE" start="10" end="10" type="bool"/>
|
||||
<field name="UFE" start="11" end="11" type="bool"/>
|
||||
<field name="IXE" start="12" end="12" type="bool"/>
|
||||
<field name="EBF" start="13" end="13" type="bool"/>
|
||||
<field name="IDE" start="15" end="15" type="bool"/>
|
||||
<field name="Len" start="16" end="18" type="uint32"/>
|
||||
<field name="FZ16" start="19" end="19" type="bool"/>
|
||||
<field name="Stride" start="20" end="21" type="uint32"/>
|
||||
<field name="RMode" start="22" end="23" type="uint32"/>
|
||||
<field name="FZ" start="24" end="24" type="bool"/>
|
||||
<field name="DN" start="25" end="25" type="bool"/>
|
||||
<field name="AHP" start="26" end="26" type="bool"/>
|
||||
</flags>
|
||||
<reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
|
||||
<reg name="v1" bitsize="128" type="aarch64v" regnum="35"/>
|
||||
<reg name="v2" bitsize="128" type="aarch64v" regnum="36"/>
|
||||
<reg name="v3" bitsize="128" type="aarch64v" regnum="37"/>
|
||||
<reg name="v4" bitsize="128" type="aarch64v" regnum="38"/>
|
||||
<reg name="v5" bitsize="128" type="aarch64v" regnum="39"/>
|
||||
<reg name="v6" bitsize="128" type="aarch64v" regnum="40"/>
|
||||
<reg name="v7" bitsize="128" type="aarch64v" regnum="41"/>
|
||||
<reg name="v8" bitsize="128" type="aarch64v" regnum="42"/>
|
||||
<reg name="v9" bitsize="128" type="aarch64v" regnum="43"/>
|
||||
<reg name="v10" bitsize="128" type="aarch64v" regnum="44"/>
|
||||
<reg name="v11" bitsize="128" type="aarch64v" regnum="45"/>
|
||||
<reg name="v12" bitsize="128" type="aarch64v" regnum="46"/>
|
||||
<reg name="v13" bitsize="128" type="aarch64v" regnum="47"/>
|
||||
<reg name="v14" bitsize="128" type="aarch64v" regnum="48"/>
|
||||
<reg name="v15" bitsize="128" type="aarch64v" regnum="49"/>
|
||||
<reg name="v16" bitsize="128" type="aarch64v" regnum="50"/>
|
||||
<reg name="v17" bitsize="128" type="aarch64v" regnum="51"/>
|
||||
<reg name="v18" bitsize="128" type="aarch64v" regnum="52"/>
|
||||
<reg name="v19" bitsize="128" type="aarch64v" regnum="53"/>
|
||||
<reg name="v20" bitsize="128" type="aarch64v" regnum="54"/>
|
||||
<reg name="v21" bitsize="128" type="aarch64v" regnum="55"/>
|
||||
<reg name="v22" bitsize="128" type="aarch64v" regnum="56"/>
|
||||
<reg name="v23" bitsize="128" type="aarch64v" regnum="57"/>
|
||||
<reg name="v24" bitsize="128" type="aarch64v" regnum="58"/>
|
||||
<reg name="v25" bitsize="128" type="aarch64v" regnum="59"/>
|
||||
<reg name="v26" bitsize="128" type="aarch64v" regnum="60"/>
|
||||
<reg name="v27" bitsize="128" type="aarch64v" regnum="61"/>
|
||||
<reg name="v28" bitsize="128" type="aarch64v" regnum="62"/>
|
||||
<reg name="v29" bitsize="128" type="aarch64v" regnum="63"/>
|
||||
<reg name="v30" bitsize="128" type="aarch64v" regnum="64"/>
|
||||
<reg name="v31" bitsize="128" type="aarch64v" regnum="65"/>
|
||||
<reg name="fpsr" bitsize="32" type="fpsr_flags" regnum="66"/>
|
||||
<reg name="fpcr" bitsize="32" type="fpcr_flags" regnum="67"/>
|
||||
</feature>
|
||||
<feature name="org.gnu.gdb.aarch64.pauth">
|
||||
<reg name="pauth_dmask" bitsize="64" type="int" regnum="68"/>
|
||||
<reg name="pauth_cmask" bitsize="64" type="int" regnum="69"/>
|
||||
</feature>
|
||||
<feature name="org.gnu.gdb.aarch64.tls">
|
||||
<reg name="tpidr" bitsize="64" type="data_ptr" regnum="70"/>
|
||||
<reg name="tpidr2" bitsize="64" type="data_ptr" regnum="71"/>
|
||||
</feature>
|
||||
</target>
|
@ -11,12 +11,20 @@
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <util/endian.h>
|
||||
|
||||
/* monitor includes */
|
||||
#include <gdb_arch.h>
|
||||
#include <monitored_thread.h>
|
||||
|
||||
using namespace Monitor;
|
||||
|
||||
|
||||
char const *Monitor::Gdb::breakpoint_instruction() { return "\xcc"; }
|
||||
size_t Monitor::Gdb::breakpoint_instruction_len() { return 1; }
|
||||
|
||||
|
||||
void Monitor::Gdb::print_registers(Output &out, Cpu_state const &cpu)
|
||||
{
|
||||
uint64_t const values_64bit[] = {
|
||||
@ -29,9 +37,89 @@ void Monitor::Gdb::print_registers(Output &out, Cpu_state const &cpu)
|
||||
|
||||
uint32_t const values_32bit[] = {
|
||||
uint32_t(cpu.eflags), uint32_t(cpu.cs), uint32_t(cpu.ss),
|
||||
0 /* es */, 0 /* fs */, /* gs */ };
|
||||
0 /* ds */, 0 /* es */, 0 /* fs */, 0 /* gs */ };
|
||||
|
||||
for (uint32_t value : values_32bit)
|
||||
print(out, Gdb_hex(host_to_big_endian(value)));
|
||||
}
|
||||
|
||||
|
||||
void Monitor::Gdb::parse_registers(Const_byte_range_ptr const &in, Cpu_state &cpu)
|
||||
{
|
||||
addr_t * const values_64bit[] = {
|
||||
&cpu.rax, &cpu.rbx, &cpu.rcx, &cpu.rdx, &cpu.rsi, &cpu.rdi, &cpu.rbp, &cpu.sp,
|
||||
&cpu.r8, &cpu.r9, &cpu.r10, &cpu.r11, &cpu.r12, &cpu.r13, &cpu.r14, &cpu.r15,
|
||||
&cpu.ip };
|
||||
|
||||
for (size_t i = 0; i < sizeof(values_64bit) / sizeof(addr_t); i++) {
|
||||
with_skipped_bytes(in, i * sizeof(addr_t) * 2,
|
||||
[&] (Const_byte_range_ptr const &in) {
|
||||
with_max_bytes(in, sizeof(addr_t) * 2, [&] (Const_byte_range_ptr const &in) {
|
||||
char null_terminated[sizeof(addr_t) * 2 + 1] { };
|
||||
memcpy(null_terminated, in.start,
|
||||
min(sizeof(null_terminated) - 1, in.num_bytes));
|
||||
addr_t value = 0;
|
||||
ascii_to_unsigned(null_terminated, value, 16);
|
||||
*values_64bit[i] = big_endian_to_host(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addr_t * const values_32bit[] = { &cpu.eflags, &cpu.cs, &cpu.ss };
|
||||
|
||||
for (size_t i = 0; i < sizeof(values_32bit) / sizeof(addr_t); i++) {
|
||||
with_skipped_bytes(in, (sizeof(values_64bit) * 2) + (i * sizeof(uint32_t) * 2),
|
||||
[&] (Const_byte_range_ptr const &in) {
|
||||
with_max_bytes(in, sizeof(uint32_t) * 2, [&] (Const_byte_range_ptr const &in) {
|
||||
char null_terminated[sizeof(uint32_t) * 2 + 1] { };
|
||||
memcpy(null_terminated, in.start,
|
||||
min(sizeof(null_terminated) - 1, in.num_bytes));
|
||||
uint32_t value = 0;
|
||||
ascii_to_unsigned(null_terminated, value, 16);
|
||||
*values_32bit[i] = big_endian_to_host(value);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Monitor::Monitored_thread::_handle_exception()
|
||||
{
|
||||
stop_state = Stop_state::STOPPED_REPLY_PENDING;
|
||||
|
||||
Thread_state thread_state = _real.call<Rpc_get_state>();
|
||||
|
||||
if (thread_state.trapno == Cpu_state::Cpu_exception::BREAKPOINT) {
|
||||
thread_state.ip -= Gdb::breakpoint_instruction_len();
|
||||
_real.call<Rpc_set_state>(thread_state);
|
||||
}
|
||||
|
||||
if (_wait) {
|
||||
_wait = false;
|
||||
_thread_monitor.remove_initial_breakpoint(_pd, _first_instruction_addr,
|
||||
_original_first_instruction);
|
||||
stop_reply_signal = Stop_reply_signal::STOP;
|
||||
} else {
|
||||
switch(thread_state.trapno) {
|
||||
case Cpu_state::Cpu_exception::DIVIDE_ERROR:
|
||||
stop_reply_signal = Stop_reply_signal::FPE;
|
||||
break;
|
||||
case Cpu_state::Cpu_exception::DEBUG:
|
||||
stop_reply_signal = Stop_reply_signal::TRAP;
|
||||
break;
|
||||
case Cpu_state::Cpu_exception::BREAKPOINT:
|
||||
stop_reply_signal = Stop_reply_signal::TRAP;
|
||||
break;
|
||||
case Cpu_state::Cpu_exception::UNDEFINED_INSTRUCTION:
|
||||
stop_reply_signal = Stop_reply_signal::ILL;
|
||||
break;
|
||||
case Cpu_state::Cpu_exception::GENERAL_PROTECTION:
|
||||
stop_reply_signal = Stop_reply_signal::SEGV;
|
||||
break;
|
||||
default:
|
||||
stop_reply_signal = Stop_reply_signal::TRAP;
|
||||
}
|
||||
}
|
||||
|
||||
_thread_monitor.thread_stopped(_pd, *this);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
91
repos/os/src/test/monitor_gdb/main.cc
Normal file
91
repos/os/src/test/monitor_gdb/main.cc
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* \brief Test for the debug monitor with GDB
|
||||
* \author Christian Prochaska
|
||||
* \date 2023-07-21
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2023 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU Affero General Public License version 3.
|
||||
*/
|
||||
|
||||
#include <base/component.h>
|
||||
#include <base/thread.h>
|
||||
#include <cpu/cache.h>
|
||||
|
||||
using namespace Genode;
|
||||
|
||||
|
||||
/* a variable to be modified with GDB */
|
||||
int test_var = 1;
|
||||
|
||||
|
||||
struct Test_thread : Thread
|
||||
{
|
||||
void test_step()
|
||||
{
|
||||
/* nothing */
|
||||
}
|
||||
|
||||
void test_sigsegv()
|
||||
{
|
||||
*(int *)0 = 42;
|
||||
}
|
||||
|
||||
Test_thread(Genode::Env &env) : Thread(env, "thread", 8192) { }
|
||||
|
||||
void entry() override
|
||||
{
|
||||
test_step();
|
||||
test_sigsegv();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* This function returns the current value of 'test_var' + 1 and can be called from
|
||||
* GDB using the 'call' or 'print' commands
|
||||
*/
|
||||
int test_var_func()
|
||||
{
|
||||
return test_var + 1;
|
||||
}
|
||||
|
||||
void func2()
|
||||
{
|
||||
/*
|
||||
* Set the first breakpoint here in 'Genode::cache_coherent' to test the
|
||||
* 'backtrace' command for a thread which is not in a syscall and executes
|
||||
* code in a shared library.
|
||||
*/
|
||||
Genode::cache_coherent(0, 0);
|
||||
|
||||
/* call 'test_var_func()', so the compiler does not throw the function away */
|
||||
log("test_var_func() returned ", test_var_func());
|
||||
}
|
||||
|
||||
|
||||
void func1()
|
||||
{
|
||||
func2();
|
||||
}
|
||||
|
||||
|
||||
struct Main
|
||||
{
|
||||
Main(Genode::Env &env)
|
||||
{
|
||||
func1();
|
||||
|
||||
Test_thread test_thread(env);
|
||||
test_thread.start();
|
||||
test_thread.join();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void Component::construct(Env &env)
|
||||
{
|
||||
static Main main { env };
|
||||
}
|
6
repos/os/src/test/monitor_gdb/target.mk
Normal file
6
repos/os/src/test/monitor_gdb/target.mk
Normal file
@ -0,0 +1,6 @@
|
||||
TARGET = test-monitor_gdb
|
||||
SRC_CC = main.cc
|
||||
|
||||
LIBS = base
|
||||
|
||||
CC_OLEVEL = -O0
|
Loading…
x
Reference in New Issue
Block a user