/* * \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 #include #include #include #include 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) { 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 { }; Current(Inferior_pd &pd) : pd(pd) { } }; Constructible _current { }; /** * Only one stop notification is sent directly, then * additional stop replies are sent as response to 'vStopped'. */ bool notification_in_progress { false }; bool gdb_connected { false }; 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) { if ((_current.constructed() && _current->thread.constructed()) || ((pid.value > 0) && (inferior.id() != pid.value))) return; _current.construct(inferior); inferior._threads.for_each([&] (Monitored_thread &thread) { if (_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(); 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(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) { 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) { inferior.for_each_thread([&] (Monitored_thread &thread) { if (thread.stop_state == Stop_state::STOPPED_REPLY_SENT) thread.stop_state = Stop_state::STOPPED_REPLY_ACKED; }); }); bool handled = false; state.inferiors.for_each([&] (Inferior_pd const &inferior) { inferior.for_each_thread([&] (Monitored_thread &thread) { if (handled) return; if (thread.stop_state != Stop_state::STOPPED_REPLY_PENDING) return; thread.stop_state = Stop_state::STOPPED_REPLY_SENT; long unsigned int pid = inferior.id(); long unsigned int tid = thread.id(); gdb_response(out, [&] (Output &out) { print(out, "T", Gdb_hex((uint8_t)thread.stop_reply_signal), "thread:p", Gdb_hex(pid), ".", Gdb_hex(tid), ";"); if (thread.stop_reply_signal == Stop_reply_signal::TRAP) print(out, "swbreak:;"); }); handled = true; }); }); if (!handled) { state.notification_in_progress = false; gdb_ok(out); } } }; /** * Resume the inferior */ struct vCont : Command_without_separator { vCont(Commands &c) : Command_without_separator(c, "vCont") { } void execute(State &state, Const_byte_range_ptr const &args, Output &out) const override { if (_verbose) log("vCont command args: ", Cstring(args.start, args.num_bytes)); bool handled = false; with_skipped_prefix(args, "?", [&] (Const_byte_range_ptr const &) { gdb_response(out, [&] (Output &out) { print(out, "vCont;c;s;t"); }); handled = true; }); 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) { 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 struct Instances; template struct Instances { LAST _last; Instances(Commands ®istry) : _last(registry) { }; }; template struct Instances { HEAD _head; Instances _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_ */