mirror of
https://github.com/genodelabs/genode.git
synced 2025-06-22 08:50:09 +00:00
- 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
1208 lines
29 KiB
C++
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 ®ion) {
|
|
|
|
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 ®istry) : _last(registry) { };
|
|
};
|
|
|
|
template <typename HEAD, typename... TAIL>
|
|
struct Instances<HEAD, TAIL...>
|
|
{
|
|
HEAD _head;
|
|
Instances<TAIL...> _tail;
|
|
Instances(Commands ®istry) : _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_ */
|