Files
genode/repos/os/src/monitor/gdb_stub.h
Norman Feske 0105494223 Rework Region_map interface
- Remove exceptions
- Use 'Attr' struct for attach arguments
- Let 'attach' return 'Range' instead of 'Local_addr'
- Renamed 'Region_map::State' to 'Region_map::Fault'

Issue #5245
Fixes #5070
2024-07-02 11:59:16 +02:00

1208 lines
29 KiB
C++

/*
* \brief GDB stub
* \author Norman Feske
* \date 2023-05-11
*/
/*
* Copyright (C) 2023 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _GDB_STUB_H_
#define _GDB_STUB_H_
#include <inferior_cpu.h>
#include <memory_accessor.h>
#include <gdb_command.h>
#include <gdb_response.h>
#include <gdb_arch.h>
namespace Monitor { namespace Gdb { struct State; } }
struct Monitor::Gdb::State : Noncopyable
{
Inferiors &inferiors;
struct Thread_list
{
char _buf[1024*16] { };
size_t _len = 0;
Thread_list(Inferiors const &inferiors)
{
Xml_generator xml(_buf, sizeof(_buf), "threads", [&] {
inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
inferior.for_each_thread([&] (Monitored_thread const &thread) {
xml.node("thread", [&] {
String<32> const id("p", inferior.id(), ".", thread.id());
xml.attribute("id", id);
xml.attribute("core", 0);
xml.attribute("name", thread._name); }); }); }); });
_len = strlen(_buf);
}
void with_bytes(auto const &fn) const
{
Const_byte_range_ptr const ptr { _buf, _len };
fn(ptr);
}
};
struct Memory_map
{
char _buf[1024*16] { };
size_t _len = 0;
Memory_map(Inferior_pd &inferior)
{
typedef String<16> Value;
Xml_generator xml(_buf, sizeof(_buf), "memory-map", [&] {
inferior._address_space.for_each_region(
[&] (Monitored_region_map::Region const &region) {
if (region.cap == inferior._linker_area.dataspace()) {
inferior._linker_area.for_each_region(
[&] (Monitored_region_map::Region const
&linker_area_region) {
xml.node("memory", [&] {
xml.attribute("type",
linker_area_region.writeable ?
"ram" : "rom");
xml.attribute("start",
Value(Hex(region.range.start +
linker_area_region.range.start)));
xml.attribute("length",
Value(Hex(linker_area_region.range.num_bytes)));
});
});
return;
}
if (region.cap == inferior._stack_area.dataspace()) {
inferior._stack_area.for_each_region(
[&] (Monitored_region_map::Region const
&stack_area_region) {
xml.node("memory", [&] {
xml.attribute("type",
stack_area_region.writeable ?
"ram" : "rom");
xml.attribute("start",
Value(Hex(region.range.start +
stack_area_region.range.start)));
xml.attribute("length",
Value(Hex(stack_area_region.range.num_bytes)));
});
});
return;
}
xml.node("memory", [&] {
xml.attribute("type", region.writeable ? "ram" : "rom");
xml.attribute("start", Value(Hex(region.range.start)));
xml.attribute("length", Value(Hex(region.range.num_bytes)));
});
});
});
_len = strlen(_buf);
}
void with_bytes(auto const &fn) const
{
Const_byte_range_ptr const ptr { _buf, _len };
fn(ptr);
}
};
Memory_accessor &_memory_accessor;
struct Current : Noncopyable
{
Inferior_pd &pd;
struct Thread
{
Monitored_thread &thread;
Thread(Monitored_thread &thread) : thread(thread) { }
};
Constructible<Thread> thread { };
Current(Inferior_pd &pd) : pd(pd) { }
};
Constructible<Current> _current { };
/**
* 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 };
struct Max_response { size_t num_bytes; };
Max_response max_response;
/**
* Try to notify GDB about a vanished inferior
*/
void try_send_exit_notification(Output &out, int pid)
{
if (!notification_in_progress) {
notification_in_progress = true;
using Stop_reply_signal = Monitored_thread::Stop_reply_signal;
if (pid > 0)
gdb_notification(out, [&] (Output &out) {
print(out, "Stop:X",
Gdb_hex((uint8_t)Stop_reply_signal::KILL),
";process:", Gdb_hex(pid));
});
else
gdb_notification(out, [&] (Output &out) {
print(out, "Stop:X",
Gdb_hex((uint8_t)Stop_reply_signal::KILL));
});
} else {
/*
* Only one notification can be in flight and we don't want to
* keep exit information in the monitor for later because GDB
* might never consume it. There will be another notification
* attempt when GDB calls 'vCont' for the vanished inferior
* (again) in the future.
*/
}
}
void flush(Inferior_pd &pd, Output &out)
{
if (_current.constructed() && _current->pd.id() == pd.id())
_current.destruct();
try_send_exit_notification(out, (int)pd.id());
}
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())
return _memory_accessor.read(_current->pd, at, dst);
warning("attempt to read memory without a current target");
return 0;
}
size_t write_memory(Memory_accessor::Virt_addr at, Const_byte_range_ptr const &src)
{
if (_current.constructed())
return _memory_accessor.write(_current->pd, at, src);
warning("attempt to write memory without a current target");
return 0;
}
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 ((_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 (_current->thread.constructed() ||
((tid.value > 0) && (thread.id() != tid.value)))
return;
_current->thread.construct(thread);
});
});
}
void with_current_thread_state(auto const &fn)
{
if (!_current.constructed() || !_current->thread.constructed())
return;
Thread_state const thread_state = _current->thread->thread._real.call<Cpu_thread::Rpc_get_state>();
if (thread_state.state == Thread_state::State::UNAVAILABLE) {
warning("unable to access state of thread ", _current->thread->thread.id());
return;
}
fn(thread_state);
}
bool current_thread_state(Thread_state const &thread_state)
{
if (!_current.constructed() || !_current->thread.constructed())
return false;
_current->thread->thread._real.call<Cpu_thread::Rpc_set_state>(thread_state);
return true;
}
State(Inferiors &inferiors, Memory_accessor &memory_accessor,
Xml_node const &config)
:
inferiors(inferiors), _memory_accessor(memory_accessor),
max_response(config.sub_node("monitor").attribute_value("max_response",
Number_of_bytes(2048)))
{ }
};
/*
* The command types within the 'Gdb::Cmd' namespace deliberately do not follow
* Genode's coding style regarding the use of upper/lower case letters.
*
* The types are named precisely atfer the corresponding commands of the GDB
* prototol, which are case sensitive.
*/
namespace Monitor { namespace Gdb { namespace Cmd {
/**
* Protocol negotiation
*/
struct qSupported : Command_with_separator
{
qSupported(Commands &c) : Command_with_separator(c, "qSupported") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
/* check for expected GDB features */
bool gdb_supports_multiprocess = false,
gdb_supports_vcont = false;
for_each_argument(args, Sep {';'}, [&] (Const_byte_range_ptr const &arg) {
if (equal(arg, "multiprocess+")) gdb_supports_multiprocess = true;
if (equal(arg, "vContSupported+")) gdb_supports_vcont = true;
});
if (!gdb_supports_multiprocess) warning("GDB lacks multi-process support");
if (!gdb_supports_vcont) warning("GDB lacks vcont support");
/* tell GDB about our features */
gdb_response(out, [&] (Output &out) {
print(out, "PacketSize=", Gdb_hex(GDB_PACKET_MAX_SIZE), ";");
print(out, "vContSupported+;");
print(out, "qXfer:features:read+;"); /* XML target descriptions */
print(out, "qXfer:threads:read+;");
print(out, "qXfer:memory-map:read+;");
print(out, "multiprocess+;");
print(out, "QNonStop+;");
print(out, "swbreak+;");
});
}
};
extern "C" char _binary_gdb_target_xml_start[];
extern "C" char _binary_gdb_target_xml_end[];
/**
* Query XML-based information
*/
struct qXfer : Command_with_separator
{
qXfer(Commands &c) : Command_with_separator(c, "qXfer") { }
struct Raw_data_ptr : Const_byte_range_ptr
{
Raw_data_ptr(char const *start, char const *end)
:
Const_byte_range_ptr(start, end - start)
{ }
};
struct Window
{
size_t offset, len;
static Window from_args(Const_byte_range_ptr const &args,
State::Max_response max_response)
{
return { .offset = comma_separated_hex_value(args, 0, 0UL),
.len = min(comma_separated_hex_value(args, 1, 0UL),
max_response.num_bytes) };
}
};
static void _send_window(Output &out, Const_byte_range_ptr const &total_bytes, Window const window)
{
with_skipped_bytes(total_bytes, window.offset, [&] (Const_byte_range_ptr const &bytes) {
with_max_bytes(bytes, window.len, [&] (Const_byte_range_ptr const &bytes) {
gdb_response(out, [&] (Output &out) {
char const *prefix = (window.offset + window.len < total_bytes.num_bytes)
? "m" : "l"; /* 'last' marker */
print(out, prefix, Cstring(bytes.start, bytes.num_bytes)); }); }); });
}
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
bool handled = false;
with_skipped_prefix(args, "features:read:target.xml:", [&] (Const_byte_range_ptr const &args) {
Raw_data_ptr const total_bytes { _binary_gdb_target_xml_start, _binary_gdb_target_xml_end };
_send_window(out, total_bytes, Window::from_args(args, state.max_response));
handled = true;
});
with_skipped_prefix(args, "threads:read::", [&] (Const_byte_range_ptr const &args) {
State::Thread_list const thread_list(state.inferiors);
thread_list.with_bytes([&] (Const_byte_range_ptr const &bytes) {
_send_window(out, bytes, Window::from_args(args, state.max_response)); });
handled = true;
});
with_skipped_prefix(args, "memory-map:read::", [&] (Const_byte_range_ptr const &args) {
if (state.current_defined()) {
State::Memory_map const memory_map(state._current->pd);
memory_map.with_bytes([&] (Const_byte_range_ptr const &bytes) {
_send_window(out, bytes, Window::from_args(args, state.max_response)); });
} else
gdb_response(out, [&] (Output &out) {
print(out, "l"); });
handled = true;
});
if (!handled)
warning("GDB ", name, " command unsupported: ", Cstring(args.start, args.num_bytes));
}
};
struct vMustReplyEmpty : Command_without_separator
{
vMustReplyEmpty(Commands &c) : Command_without_separator(c, "vMustReplyEmpty") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_response(out, [&] (Output &) { });
}
};
/**
* Set current thread
*/
struct H : Command_without_separator
{
H(Commands &commands) : Command_without_separator(commands, "H") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("H command args: ", Cstring(args.start, args.num_bytes));
/* 'g' for other operations */
with_skipped_prefix(args, "g", [&] (Const_byte_range_ptr const &args) {
int pid = 0, tid = 0;
thread_id(args, pid, tid);
if ((pid == -1) || (tid == -1)) {
gdb_error(out, 1);
return;
}
state.current(Inferiors::Id { (unsigned)pid },
Threads::Id { (unsigned)tid });
gdb_ok(out);
});
with_skipped_prefix(args, "c-", [&] (Const_byte_range_ptr const &) {
gdb_error(out, 1); });
}
};
/**
* Enable/disable non-stop mode (only non-stop mode is supported)
*/
struct QNonStop : Command_with_separator
{
QNonStop(Commands &commands) : Command_with_separator(commands, "QNonStop") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("QNonStop command args: ", Cstring(args.start, args.num_bytes));
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);
});
}
};
/**
* Symbol-lookup prototol (not used)
*/
struct qSymbol : Command_with_separator
{
qSymbol(Commands &commands) : Command_with_separator(commands, "qSymbol") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_ok(out);
}
};
/**
* Query trace status
*/
struct qTStatus : Command_without_separator
{
qTStatus(Commands &commands) : Command_without_separator(commands, "qTStatus") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_response(out, [&] (Output &) { });
}
};
/**
* Query current thread ID
*/
struct qC : Command_without_separator
{
qC(Commands &commands) : Command_without_separator(commands, "qC") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("qC command args: ", Cstring(args.start, args.num_bytes));
gdb_response(out, [&] (Output &) { });
}
};
/**
* Query attached state
*/
struct qAttached : Command_without_separator
{
qAttached(Commands &commands) : Command_without_separator(commands, "qAttached") { }
void execute(State &, Const_byte_range_ptr const &, Output &out) const override
{
gdb_response(out, [&] (Output &out) {
print(out, "1"); });
}
};
/**
* Query text/data segment offsets
*/
struct qOffsets : Command_without_separator
{
qOffsets(Commands &commands) : Command_without_separator(commands, "qOffsets") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("qOffsets command args: ", Cstring(args.start, args.num_bytes));
gdb_response(out, [&] (Output &) { });
}
};
/**
* Query halt reason
*/
struct ask : Command_without_separator
{
ask(Commands &c) : Command_without_separator(c, "?") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("? command args: ", Cstring(args.start, args.num_bytes));
state.gdb_connected = true;
bool stop_reply_sent = false;
state.inferiors.for_each<Inferior_pd const &>([&] (Inferior_pd const &inferior) {
inferior.for_each_thread([&] (Monitored_thread &thread) {
using Stop_state = Monitored_thread::Stop_state;
using Stop_reply_signal = Monitored_thread::Stop_reply_signal;
if (thread.stop_state == Stop_state::RUNNING)
return;
/* found a stopped thread */
if (!stop_reply_sent) {
/* send a stop reply for one stopped thread */
state.notification_in_progress = true;
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:;");
});
thread.stop_state = Stop_state::STOPPED_REPLY_SENT;
stop_reply_sent = true;
} else {
/* report other stopped threads on 'vStopped' */
thread.stop_state = Stop_state::STOPPED_REPLY_PENDING;
}
});
});
if (!stop_reply_sent) {
/* all threads are running */
state.notification_in_progress = false;
gdb_ok(out);
}
}
};
/**
* Read registers
*/
struct g : Command_without_separator
{
g(Commands &c) : Command_without_separator(c, "g") { }
void execute(State &state, Const_byte_range_ptr const &, Output &out) const override
{
if (_verbose)
log("-> execute g");
gdb_response(out, [&] (Output &out) {
state.with_current_thread_state([&] (Thread_state const &thread_state) {
print_registers(out, thread_state.cpu); }); });
}
};
/**
* Read memory
*/
struct m : Command_without_separator
{
m(Commands &c) : Command_without_separator(c, "m") { }
void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override
{
addr_t const addr = comma_separated_hex_value(args, 0, addr_t(0));
/* GDB's 'm' command encodes memory as hex, two characters per byte. */
size_t const len = min(comma_separated_hex_value(args, 1, 0UL),
state.max_response.num_bytes / 2);
gdb_response(out, [&] (Output &out) {
for (size_t pos = 0; pos < len; ) {
char chunk[min(16*1024UL, len)] { };
size_t const remain_len = len - pos;
size_t const num_bytes = min(sizeof(chunk), remain_len);
size_t const read_len =
state.read_memory(Memory_accessor::Virt_addr { addr + pos },
Byte_range_ptr(chunk, num_bytes));
for (unsigned i = 0; i < read_len; i++)
print(out, Gdb_hex(chunk[i]));
pos += read_len;
if (read_len < num_bytes)
break;
}
});
}
};
/**
* 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);
size_t written_num_bytes { 0 };
with_argument(args, Sep {':'}, 1, [&] (Const_byte_range_ptr const &arg) {
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 },
Const_byte_range_ptr(buf, len));
});
if (written_num_bytes == len)
gdb_ok(out);
else
gdb_error(out, 1);
}
};
/**
* Query liveliness of thread ID
*/
struct T : Command_without_separator
{
T(Commands &c) : Command_without_separator(c, "T") { }
void execute(State &, Const_byte_range_ptr const &args, Output &out) const override
{
if (_verbose)
log("T command args: ", Cstring(args.start, args.num_bytes));
gdb_ok(out);
}
};
/**
* Disconnect
*/
struct D : Command_with_separator
{
D(Commands &c) : Command_with_separator(c, "D") { }
void execute(State &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;
});
if (handled)
return;
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;
bool inferior_found = false;
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_found = true;
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);
});
});
if (!inferior_found)
state.try_send_exit_notification(out, pid);
};
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.cpu);
if (state.current_thread_state(thread_state))
gdb_ok(out);
else
gdb_error(out, 1);
}
};
} /* namespace Cmd */ } /* namespace Gdb */ } /* namespace Monitor */
/*
* Registry of all supported commands
*/
namespace Monitor { namespace Gdb { struct Supported_commands; } }
struct Monitor::Gdb::Supported_commands : Commands
{
template <typename...>
struct Instances;
template <typename LAST>
struct Instances<LAST>
{
LAST _last;
Instances(Commands &registry) : _last(registry) { };
};
template <typename HEAD, typename... TAIL>
struct Instances<HEAD, TAIL...>
{
HEAD _head;
Instances<TAIL...> _tail;
Instances(Commands &registry) : _head(registry), _tail(registry) { }
};
Instances<
Cmd::qSupported,
Cmd::qXfer,
Cmd::vMustReplyEmpty,
Cmd::H,
Cmd::QNonStop,
Cmd::qSymbol,
Cmd::qTStatus,
Cmd::qC,
Cmd::qAttached,
Cmd::qOffsets,
Cmd::g,
Cmd::m,
Cmd::D,
Cmd::T,
Cmd::ask,
Cmd::X,
Cmd::M,
Cmd::bang,
Cmd::vStopped,
Cmd::vCont,
Cmd::p,
Cmd::P,
Cmd::vCtrlC,
Cmd::vFile,
Cmd::Z,
Cmd::G
> _instances { *this };
};
#endif /* _GDB_STUB_H_ */