genode/repos/os/src/app/cli_monitor/gdb_command.h
Norman Feske 20f961cbd8 cli_monitor, launcher: handle exiting subsystems
Until now, the CLI monitor and the laucher allowed the user to explitly
kill subsystems but both used to ignore gracefully exiting subsystems.
It was the user's job to remove the remains of those subsystems. The
patch takes the burden of manually killing exited subsystems from the
user.

Fixes #1685
2015-09-30 15:48:12 +02:00

554 lines
18 KiB
C++

/*
* \brief Gdb command
* \author Norman Feske
* \author Christian Prochaska
* \date 2013-03-18
*/
/*
* Copyright (C) 2013 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _GDB_COMMAND_H_
#define _GDB_COMMAND_H_
/* Genode includes */
#include <base/elf.h>
#include <os/attached_rom_dataspace.h>
#include <rom_session/connection.h>
#include <util/arg_string.h>
#include <util/xml_node.h>
/* public CLI-monitor includes */
#include <cli_monitor/ram.h>
/* local includes */
#include <child_registry.h>
#include <gdb_prefix.h>
#include <format_util.h>
#include <subsystem_config_registry.h>
class Gdb_command_child : public Child
{
private:
Genode::Signal_context_capability _kill_gdb_sig_cap;
Genode::Signal_context_capability _exit_sig_cap;
Terminal::Session &_terminal;
bool _kill_requested;
void _kill_gdb()
{
_kill_requested = true;
Genode::Signal_transmitter(_kill_gdb_sig_cap).submit();
}
public:
Gdb_command_child(Ram &ram,
char const *label,
char const *binary,
Genode::Cap_session &cap_session,
Genode::size_t ram_quota,
Genode::size_t ram_limit,
Genode::Signal_context_capability yield_response_sig_cap,
Genode::Signal_context_capability kill_gdb_sig_cap,
Genode::Signal_context_capability exit_sig_cap,
Terminal::Session &terminal)
: Child(ram, label, binary, cap_session, ram_quota, ram_limit,
yield_response_sig_cap, exit_sig_cap),
_kill_gdb_sig_cap(kill_gdb_sig_cap),
_exit_sig_cap(exit_sig_cap),
_terminal(terminal),
_kill_requested(false)
{ }
bool kill_requested() const { return _kill_requested; }
/*
* Check if GDB-related (Noux) session-requests will be successful.
* If not, terminate the subsystem and tell the user about it.
*/
Genode::Service *resolve_session_request(const char *service_name,
const char *args)
{
if (_kill_requested)
return 0;
Genode::Service *service =
Child::resolve_session_request(service_name, args);
if (!service) {
tprintf(_terminal, "Error: GDB subsystem session request for service '%s' failed\n",
service_name);
PDBG("session request failed: service_name = %s, args = %s", service_name, args);
_kill_gdb();
return 0;
}
/* Find out if the session request comes from Noux */
const char *noux_label_suffix = " -> noux";
/* Arg::string() needs two extra bytes */
size_t label_size = strlen(name()) + strlen(noux_label_suffix) + 2;
char noux_label_buf[label_size];
snprintf(noux_label_buf, sizeof(noux_label_buf),
"%s%s", name(), noux_label_suffix);
char label_buf[label_size];
Genode::Arg_string::find_arg(args, "label").string(label_buf,
sizeof(label_buf),
"");
if (strcmp(label_buf, noux_label_buf, label_size) == 0) {
/* Try to create and close the session */
try {
Genode::Session_capability s = service->session(args, Genode::Affinity());
service->close(s);
} catch (...) {
tprintf(_terminal, "Error: GDB subsystem session request for service '%s' failed\n",
service_name);
PDBG("session request failed: service_name = %s, args = %s", service_name, args);
_kill_gdb();
return 0;
}
}
return service;
}
};
class Gdb_command : public Command
{
private:
typedef Genode::Xml_node Xml_node;
typedef Genode::Signal_context_capability Signal_context_capability;
struct Child_configuration_failed {}; /* exception */
Ram &_ram;
Child_registry &_children;
Genode::Cap_session &_cap;
Subsystem_config_registry &_subsystem_configs;
Signal_context_capability _yield_response_sigh_cap;
Signal_context_capability _kill_gdb_sig_cap;
Signal_context_capability _exit_sig_cap;
/**
* Generate the config node for the GDB subsystem
*/
Xml_node _gdb_config_node(const char *binary_name,
const char *target_config_addr,
const size_t target_config_size,
Genode::Number_of_bytes gdb_ram_preserve,
Terminal::Session &terminal)
{
/* local exception types */
struct Config_buffer_overflow { };
struct Binary_elf_check_failed { };
try {
Genode::Attached_rom_dataspace gdb_command_config_ds("gdb_command_config");
enum { CONFIG_BUF_SIZE = 4*1024 };
static char config_buf[CONFIG_BUF_SIZE];
int config_bytes_written = 0;
/*
* Copy the first part of the config file template
*/
Xml_node init_config_node(gdb_command_config_ds.local_addr<const char>(),
gdb_command_config_ds.size());
Xml_node noux_node = init_config_node.sub_node("start");
for (;; noux_node = noux_node.next("start"))
if (noux_node.attribute("name").has_value("noux"))
break;
Xml_node noux_config_node = noux_node.sub_node("config");
/* Genode::strncpy() makes the last character '\0', so we need to add 1 byte */
size_t bytes_to_copy = (Genode::addr_t)noux_config_node.content_addr() -
(Genode::addr_t)init_config_node.addr() + 1;
if ((sizeof(config_buf) - config_bytes_written) < bytes_to_copy)
throw Config_buffer_overflow();
strncpy(&config_buf[config_bytes_written],
init_config_node.addr(),
bytes_to_copy);
/* subtract the byte for '\0' again */
config_bytes_written += bytes_to_copy - 1;
/*
* Create the GDB arguments for breaking in 'main()'
*/
enum { GDB_MAIN_BREAKPOINT_ARGS_BUF_SIZE = 768 };
static char gdb_main_breakpoint_args_buf[GDB_MAIN_BREAKPOINT_ARGS_BUF_SIZE];
try {
Genode::Attached_rom_dataspace binary_rom_ds(binary_name);
Genode::Elf_binary elf_binary((Genode::addr_t)binary_rom_ds.local_addr<void>());
if (elf_binary.is_dynamically_linked()) {
snprintf(gdb_main_breakpoint_args_buf,
sizeof(gdb_main_breakpoint_args_buf),
"<arg value=\"-ex\" /><arg value=\"symbol-file /gdb/ld.lib.so\" />\n \
<arg value=\"-ex\" /><arg value=\"b call_program_main\" />\n \
<arg value=\"-ex\" /><arg value=\"c\" />\n \
<arg value=\"-ex\" /><arg value=\"delete 1\" />\n \
<arg value=\"-ex\" /><arg value=\"symbol-file /gdb/%s\" />\n \
<arg value=\"-ex\" /><arg value=\"b main()\" />\n \
<arg value=\"-ex\" /><arg value=\"set solib-search-path /gdb\" />\n \
<arg value=\"-ex\" /><arg value=\"sharedlibrary\" />\n \
<arg value=\"-ex\" /><arg value=\"c\" />\n \
<arg value=\"-ex\" /><arg value=\"delete 2\" />\n",
binary_name);
} else {
snprintf(gdb_main_breakpoint_args_buf,
sizeof(gdb_main_breakpoint_args_buf),
"<arg value=\"-ex\" /><arg value=\"symbol-file /gdb/%s\" />\n \
<arg value=\"-ex\" /><arg value=\"b main\" />\n \
<arg value=\"-ex\" /><arg value=\"c\" />\n \
<arg value=\"-ex\" /><arg value=\"delete 1\" />\n",
binary_name);
}
} catch (...) {
throw Binary_elf_check_failed();
}
/*
* Insert the '<start>' node for 'noux'
*/
config_bytes_written += snprintf(&config_buf[config_bytes_written],
sizeof(config_buf) - config_bytes_written, "\n \
<start name=\"/bin/%sgdb\">\n \
<arg value=\"/gdb/%s\"/>\n \
<arg value=\"-ex\" /><arg value=\"set interactive-mode off\" />\n \
<arg value=\"-ex\" /><arg value=\"directory /gdb/src\" />\n \
<arg value=\"-ex\" /><arg value=\"target remote /dev/gdb\" />\n \
%s \
<arg value=\"-ex\" /><arg value=\"set interactive-mode auto\" />\n \
</start>",
gdb_prefix,
binary_name,
gdb_main_breakpoint_args_buf);
/*
* Copy the second part of the config file template
*/
Xml_node gdb_monitor_node = noux_node.next("start");
/* Genode::strncpy() makes the last character '\0', so we need to add 1 byte */
bytes_to_copy = (Genode::addr_t)gdb_monitor_node.content_addr() -
(Genode::addr_t)noux_config_node.content_addr() + 1;
if ((sizeof(config_buf) - config_bytes_written) < bytes_to_copy)
throw Config_buffer_overflow();
strncpy(&config_buf[config_bytes_written],
noux_config_node.content_addr(),
bytes_to_copy);
/* subtract the byte for '\0' again */
config_bytes_written += bytes_to_copy - 1;
/*
* Create a zero-terminated string for the GDB target config node
*/
char target_config[target_config_size + 1];
if (target_config_addr)
Genode::strncpy(target_config, target_config_addr, sizeof(target_config));
else
target_config[0] = '\0';
/*
* Insert the '<config>' node for 'gdb_monitor'
*/
config_bytes_written += Genode::snprintf(&config_buf[config_bytes_written],
(sizeof(config_buf) - config_bytes_written), "\n \
<config>\n \
<target name=\"%s\">\n \
%s \
</target>\n \
<preserve name=\"RAM\" quantum=\"%zu\"/>\n \
</config> \
",
binary_name,
target_config,
(size_t)gdb_ram_preserve);
/*
* Copy the third (and final) part of the config file template
*/
/* Genode::strncpy() makes the last character '\0', so we need to add 1 byte */
bytes_to_copy = ((Genode::addr_t)init_config_node.addr() +
init_config_node.size()) -
(Genode::addr_t)gdb_monitor_node.content_addr() + 1;
if ((sizeof(config_buf) - config_bytes_written) < bytes_to_copy)
throw Config_buffer_overflow();
strncpy(&config_buf[config_bytes_written],
gdb_monitor_node.content_addr(),
bytes_to_copy);
/* subtract the byte for '\0' again */
config_bytes_written += bytes_to_copy - 1;
return Xml_node(config_buf, config_bytes_written);
} catch (Config_buffer_overflow) {
tprintf(terminal, "Error: the buffer for the generated GDB "
"subsystem configuration is too small.\n");
throw Child_configuration_failed();
} catch (Binary_elf_check_failed) {
tprintf(terminal, "Error: could not determine link type of the "
"GDB target binary.\n");
throw Child_configuration_failed();
}
}
void _execute_subsystem(char const *name, Command_line &cmd,
Terminal::Session &terminal,
Xml_node subsystem_node)
{
Genode::Number_of_bytes ram = 0;
Genode::Number_of_bytes ram_limit = 0;
Genode::Number_of_bytes gdb_ram_preserve = 10*1024*1024;
/* read default RAM quota from config */
try {
Xml_node rsc = subsystem_node.sub_node("resource");
for (;; rsc = rsc.next("resource")) {
if (rsc.attribute("name").has_value("RAM")) {
rsc.attribute("quantum").value(&ram);
if (rsc.has_attribute("limit"))
rsc.attribute("limit").value(&ram_limit);
break;
}
}
} catch (...) { }
cmd.parameter("--ram", ram);
cmd.parameter("--ram-limit", ram_limit);
cmd.parameter("--gdb-ram-preserve", gdb_ram_preserve);
/* account for cli_monitor local metadata */
size_t preserve_ram = 100*1024;
if ((ram + preserve_ram) > Genode::env()->ram_session()->avail()) {
tprintf(terminal, "Error: RAM quota exceeds available quota\n");
return;
}
bool const verbose = cmd.parameter_exists("--verbose");
/*
* Determine binary name
*
* Use subsystem name by default, override with '<binary>' declaration.
*/
char binary_name[128];
strncpy(binary_name, name, sizeof(binary_name));
try {
Xml_node bin = subsystem_node.sub_node("binary");
bin.attribute("name").value(binary_name, sizeof(binary_name));
} catch (...) { }
/* generate unique child name */
char label[Child_registry::CHILD_NAME_MAX_LEN];
_children.unique_child_name(name, label, sizeof(label));
tprintf(terminal, "starting new subsystem '%s'\n", label);
if (verbose) {
tprintf(terminal, " RAM quota: ");
tprint_bytes(terminal, ram);
tprintf(terminal,"\n");
if (ram_limit) {
tprintf(terminal, " RAM limit: ");
tprint_bytes(terminal, ram_limit);
tprintf(terminal,"\n");
}
tprintf(terminal, " binary: %s\n", binary_name);
}
Child *child = 0;
try {
/* create child configuration */
const char *target_config_addr = 0;
size_t target_config_size = 0;
try {
Xml_node target_config_node = subsystem_node.sub_node("config");
target_config_addr = target_config_node.addr();
target_config_size = target_config_node.size();
} catch (...) { }
Xml_node config_node = _gdb_config_node(binary_name,
target_config_addr,
target_config_size,
gdb_ram_preserve,
terminal);
/* create child */
child = new (Genode::env()->heap())
Gdb_command_child(_ram, label, "init", _cap, ram, ram_limit,
_yield_response_sigh_cap, _kill_gdb_sig_cap,
_exit_sig_cap, terminal);
/* configure child */
try {
child->configure(config_node.addr(), config_node.size());
if (verbose)
tprintf(terminal, " config: inline\n");
} catch (...) {
if (verbose)
tprintf(terminal, " config: none\n");
}
}
catch (Child_configuration_failed) {
return;
}
catch (Genode::Rom_connection::Rom_connection_failed) {
tprintf(terminal, "Error: could not obtain ROM module \"%s\"\n",
binary_name);
return;
}
catch (Child::Quota_exceeded) {
tprintf(terminal, "Error: insufficient memory, need ");
tprint_bytes(terminal, ram + Child::DONATED_RAM_QUOTA);
tprintf(terminal, ", have ");
tprint_bytes(terminal, Genode::env()->ram_session()->avail());
tprintf(terminal, "\n");
return;
}
catch (Genode::Allocator::Out_of_memory) {
tprintf(terminal, "Error: could not allocate meta data, out of memory\n");
return;
}
_children.insert(child);
child->start();
}
public:
Gdb_command(Ram &ram, Genode::Cap_session &cap, Child_registry &children,
Subsystem_config_registry &subsustem_configs,
Signal_context_capability yield_response_sigh_cap,
Signal_context_capability kill_gdb_sig_cap,
Signal_context_capability exit_sig_cap)
:
Command("gdb", "create new subsystem with GDB"),
_ram(ram), _children(children), _cap(cap),
_subsystem_configs(subsustem_configs),
_yield_response_sigh_cap(yield_response_sigh_cap),
_kill_gdb_sig_cap(kill_gdb_sig_cap),
_exit_sig_cap(exit_sig_cap)
{
add_parameter(new Parameter("--ram", Parameter::NUMBER, "initial RAM quota"));
add_parameter(new Parameter("--ram-limit", Parameter::NUMBER, "limit for expanding RAM quota"));
add_parameter(new Parameter("--gdb-ram-preserve", Parameter::NUMBER,
"RAM quota which GDB monitor should preserve for itself (default: 5M)"));
add_parameter(new Parameter("--verbose", Parameter::VOID, "show diagnostics"));
}
void _for_each_argument(Argument_fn const &fn) const override
{
/* functor for processing a subsystem configuration */
auto process_subsystem_config_fn = [&] (Genode::Xml_node node) {
char name[Parameter::Name::size()];
try { node.attribute("name").value(name, sizeof(name)); }
catch (Xml_node::Nonexistent_attribute) {
PWRN("Missing name in '<subsystem>' configuration");
return;
}
char const *prefix = "config: ";
size_t const prefix_len = strlen(prefix);
char help[Parameter::Short_help::size() + prefix_len];
strncpy(help, prefix, ~0);
try { node.attribute("help").value(help + prefix_len,
sizeof(help) - prefix_len); }
catch (Xml_node::Nonexistent_attribute) {
PWRN("Missing help in '<subsystem>' configuration");
return;
}
Argument arg(name, help);
fn(arg);
};
/* scan subsystem config registry for possible subsystem arguments */
_subsystem_configs.for_each_config(process_subsystem_config_fn);
}
void execute(Command_line &cmd, Terminal::Session &terminal)
{
/* check if the GDB-related ROM modules are available */
try {
Genode::Rom_connection gdb_command_config("gdb_command_config");
Genode::Rom_connection terminal_crosslink("terminal_crosslink");
Genode::Rom_connection noux("noux");
Genode::Rom_connection gdb_monitor("gdb_monitor");
} catch (Genode::Rom_connection::Rom_connection_failed) {
tprintf(terminal, "Error: The 'gdb' command needs the following ROM "
"modules (of which some are currently missing): "
"gdb_command_config, terminal_crosslink, noux, ",
"gdb_monitor\n");
return;
}
char name[128];
name[0] = 0;
if (cmd.argument(0, name, sizeof(name)) == false) {
tprintf(terminal, "Error: no configuration name specified\n");
return;
}
char buf[128];
if (cmd.argument(1, buf, sizeof(buf))) {
tprintf(terminal, "Error: unexpected argument \"%s\"\n", buf);
return;
}
try {
_subsystem_configs.for_config(name, [&] (Genode::Xml_node node)
{
_execute_subsystem(name, cmd, terminal, node);
});
} catch (Subsystem_config_registry::Nonexistent_subsystem_config) {
tprintf(terminal, "Error: no configuration for \"%s\"\n", name);
}
}
};
#endif /* _GDB_COMMAND_H_ */