/* * \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 #include #include #include #include /* public CLI-monitor includes */ #include /* local includes */ #include #include #include #include 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(), 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()); if (elf_binary.is_dynamically_linked()) { snprintf(gdb_main_breakpoint_args_buf, sizeof(gdb_main_breakpoint_args_buf), "\n \ \n \ \n \ \n \ \n \ \n \ \n \ \n \ \n \ \n", binary_name); } else { snprintf(gdb_main_breakpoint_args_buf, sizeof(gdb_main_breakpoint_args_buf), "\n \ \n \ \n \ \n", binary_name); } } catch (...) { throw Binary_elf_check_failed(); } /* * Insert the '' node for 'noux' */ config_bytes_written += snprintf(&config_buf[config_bytes_written], sizeof(config_buf) - config_bytes_written, "\n \ \n \ \n \ \n \ \n \ \n \ %s \ \n \ ", 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 '' node for 'gdb_monitor' */ config_bytes_written += Genode::snprintf(&config_buf[config_bytes_written], (sizeof(config_buf) - config_bytes_written), "\n \ \n \ \n \ %s \ \n \ \n \ \ ", 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 '' 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 '' 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 '' 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_ */