// license:BSD-3-Clause // copyright-holders:Aaron Giles, Vas Crabb /********************************************************************* debugcon.cpp Debugger console engine. *********************************************************************/ #include "emu.h" #include "debugcon.h" #include "debugcpu.h" #include "debugvw.h" #include "textbuf.h" #include "debugger.h" #include "fileio.h" #include "corestr.h" #include #include #include /*************************************************************************** CONSTANTS ***************************************************************************/ #define CONSOLE_BUF_SIZE (1024 * 1024) #define CONSOLE_MAX_LINES (CONSOLE_BUF_SIZE / 20) #define ERRORLOG_BUF_SIZE (1024 * 1024) #define ERRORLOG_MAX_LINES (ERRORLOG_BUF_SIZE / 20) /*************************************************************************** Initialization and tear down ***************************************************************************/ debugger_console::debugger_console(running_machine &machine) : m_machine(machine) , m_visiblecpu(nullptr) , m_console_textbuf(nullptr) , m_errorlog_textbuf(nullptr) , m_logfile(nullptr) { /* allocate text buffers */ m_console_textbuf = text_buffer_alloc(CONSOLE_BUF_SIZE, CONSOLE_MAX_LINES); if (!m_console_textbuf) return; m_errorlog_textbuf = text_buffer_alloc(ERRORLOG_BUF_SIZE, ERRORLOG_MAX_LINES); if (!m_errorlog_textbuf) return; /* due to initialization order, @machine is holding our debug.log handle */ m_logfile = machine.steal_debuglogfile(); /* print the opening lines */ printf("%s debugger version %s\n", emulator_info::get_appname(), emulator_info::get_build_version()); printf("Currently targeting %s (%s)\n", m_machine.system().name, m_machine.system().type.fullname()); /* request callback upon exiting */ m_machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&debugger_console::exit, this)); /* listen in on the errorlog */ using namespace std::placeholders; m_machine.add_logerror_callback(std::bind(&debugger_console::errorlog_write_line, this, _1)); /* register our own custom-command help */ register_command("helpcustom", CMDFLAG_NONE, 0, 0, std::bind(&debugger_console::execute_help_custom, this, _1)); register_command("condump", CMDFLAG_NONE, 1, 1, std::bind(&debugger_console::execute_condump, this, _1)); /* first CPU is visible by default */ for (device_t &device : device_enumerator(m_machine.root_device())) { auto *cpu = dynamic_cast(&device); if (cpu) { m_visiblecpu = cpu; break; } } } debugger_console::~debugger_console() { } //------------------------------------------------- // exit - frees the console system //------------------------------------------------- void debugger_console::exit() { // free allocated memory m_console_textbuf.reset(); m_errorlog_textbuf.reset(); // free the command list m_commandlist.clear(); // close the logfile, if any m_logfile.reset(); } /*************************************************************************** Command Handling ***************************************************************************/ inline bool debugger_console::debug_command::compare::operator()(const debug_command &a, const debug_command &b) const { return a.command < b.command; } inline bool debugger_console::debug_command::compare::operator()(const char *a, const debug_command &b) const { return strcmp(a, b.command.c_str()) < 0; } inline bool debugger_console::debug_command::compare::operator()(const debug_command &a, const char *b) const { return strcmp(a.command.c_str(), b) < 0; } debugger_console::debug_command::debug_command(std::string_view _command, u32 _flags, int _minparams, int _maxparams, std::function &)> &&_handler) : command(_command), params(nullptr), help(nullptr), handler(std::move(_handler)), flags(_flags), minparams(_minparams), maxparams(_maxparams) { } //------------------------------------------------------------ // execute_help_custom - execute the helpcustom command //------------------------------------------------------------ void debugger_console::execute_help_custom(const std::vector ¶ms) { for (const debug_command &cmd : m_commandlist) { if (cmd.flags & CMDFLAG_CUSTOM_HELP) { std::string buf = cmd.command + " help"; std::vector temp_params = { buf }; internal_execute_command(true, temp_params); } } } /*------------------------------------------------------------ execute_condump - execute the condump command ------------------------------------------------------------*/ void debugger_console::execute_condump(const std::vector& params) { std::string filename(params[0]); const char* mode; /* replace macros */ strreplace(filename, "{game}", m_machine.basename()); mode = "w"; /* opening for append? */ if (filename.length() >= 2 && filename[0] == '>' && filename[1] == '>') { mode = "a"; filename = filename.substr(2); } FILE* f = fopen(filename.c_str(), mode); if (!f) { printf("Error opening file '%s'\n", filename); return; } for (std::string_view line_info : text_buffer_lines(*m_console_textbuf)) { fwrite(line_info.data(), sizeof(char), line_info.length(), f); fputc('\n', f); } fclose(f); printf("Wrote console contents to '%s'\n", filename); } //------------------------------------------------- // visible_symtable - return the locally-visible // symbol table //------------------------------------------------- symbol_table &debugger_console::visible_symtable() const { return m_visiblecpu->debug()->symtable(); } //------------------------------------------------- // trim_parameter - trim spaces and quotes around // a command parameter //------------------------------------------------- std::string_view debugger_console::trim_parameter(std::string_view param, bool keep_quotes) { std::string_view::size_type len = param.length(); bool repeat; // loop until all adornments are gone do { repeat = false; // check for begin/end quotes if (len >= 2 && param[0] == '"' && param[len - 1] == '"') { if (!keep_quotes) { param = param.substr(1, len - 2); len -= 2; } } // check for start/end braces else if (len >= 2 && param[0] == '{' && param[len - 1] == '}') { param = param.substr(1, len - 2); len -= 2; repeat = true; } // check for leading spaces else if (len >= 1 && param[0] == ' ') { param.remove_prefix(1); len--; repeat = true; } // check for trailing spaces else if (len >= 1 && param[len - 1] == ' ') { param.remove_suffix(1); len--; repeat = true; } } while (repeat); return param; } //------------------------------------------------- // internal_execute_command - executes a // command //------------------------------------------------- CMDERR debugger_console::internal_execute_command(bool execute, std::vector ¶ms) { // no params is an error if (params.empty()) return CMDERR::none(); // the first parameter has the command and the real first parameter; separate them std::string_view command_param = params[0]; std::string_view::size_type pos = 0; while (pos < command_param.length() && !isspace(u8(command_param[pos]))) pos++; const std::string command(strmakelower(command_param.substr(0, pos))); while (pos < command_param.length() && isspace(u8(command_param[pos]))) pos++; if (pos == command_param.length() && params.size() == 1) params.clear(); else params[0].remove_prefix(pos); // search the command list auto const found = m_commandlist.lower_bound(command.c_str()); // error if not found if (m_commandlist.end() == found || std::string_view(command) != std::string_view(found->command).substr(0, command.length())) return CMDERR::unknown_command(0); if (found->command.length() > command.length()) { auto const next = std::next(found); if (m_commandlist.end() != next && std::string_view(command) == std::string_view(next->command).substr(0, command.length())) return CMDERR::ambiguous_command(0); } // now go back and trim quotes and braces and any spaces they reveal for (std::string_view ¶m : params) param = trim_parameter(param, found->flags & CMDFLAG_KEEP_QUOTES); // see if we have the right number of parameters if (params.size() < found->minparams) return CMDERR::not_enough_params(0); if (params.size() > found->maxparams) return CMDERR::too_many_params(0); // execute the handler if (execute) found->handler(params); return CMDERR::none(); } //------------------------------------------------- // internal_parse_command - parses a command // and either executes or just validates it //------------------------------------------------- CMDERR debugger_console::internal_parse_command(std::string_view command, bool execute) { std::string_view::size_type pos = 0; std::string_view::size_type len = command.length(); while (pos < len) { std::string parens; std::vector params; bool foundend = false, instring = false, isexpr = false; // skip leading spaces while (pos < len && isspace(u8(command[pos]))) pos++; std::string_view::size_type startpos = pos; // find a semicolon or the end for (params.push_back(command.substr(pos)); !foundend && pos < len; pos++) { char c = command[pos]; if (instring) { if (c == '"' && command[pos - 1] != '\\') instring = false; } else { switch (c) { case '"': instring = true; break; case '(': case '[': case '{': parens.push_back(c); break; case ')': if (parens.empty() || parens.back() != '(') return CMDERR::unbalanced_parens(pos); parens.pop_back(); break; case ']': if (parens.empty() || parens.back() != '[') return CMDERR::unbalanced_parens(pos); parens.pop_back(); break; case '}': if (parens.empty() || parens.back() != '{') return CMDERR::unbalanced_parens(pos); parens.pop_back(); break; case ',': if (parens.empty()) { params.back().remove_suffix(len - pos); params.push_back(command.substr(pos + 1)); } break; case ';': if (parens.empty()) { params.back().remove_suffix(len - pos); foundend = true; } break; case '-': if (parens.empty() && params.size() == 1 && pos > 0 && command[pos - 1] == '-') isexpr = true; break; case '+': if (parens.empty() && params.size() == 1 && pos > 0 && command[pos - 1] == '+') isexpr = true; break; case '=': if (parens.empty() && params.size() == 1) isexpr = true; break; default: break; } } } // check for unbalanced parentheses or quotes if (instring) return CMDERR::unbalanced_quotes(pos); if (!parens.empty()) return CMDERR::unbalanced_parens(pos); // process the command std::string_view command_or_expr = params[0]; // allow for "do" commands if (command_or_expr.length() > 3 && tolower(u8(command_or_expr[0])) == 'd' && tolower(u8(command_or_expr[1])) == 'o' && isspace(u8(command_or_expr[2]))) { isexpr = true; command_or_expr.remove_prefix(3); } // if it smells like an assignment expression, treat it as such if (isexpr && params.size() == 1) { try { parsed_expression expr(visible_symtable(), command_or_expr); if (execute) expr.execute(); } catch (expression_error &err) { return CMDERR::expression_error(err); } } else { const CMDERR result = internal_execute_command(execute, params); if (result.error_class() != CMDERR::NONE) return CMDERR(result.error_class(), startpos); } } return CMDERR::none(); } //------------------------------------------------- // execute_command - execute a command string //------------------------------------------------- CMDERR debugger_console::execute_command(std::string_view command, bool echo) { // echo if requested if (echo) printf(">%s\n", command); // parse and execute const CMDERR result = internal_parse_command(command, true); // display errors if (result.error_class() != CMDERR::NONE) { if (!echo) printf(">%s\n", command); printf(" %*s^\n", result.error_offset(), ""); printf("%s\n", cmderr_to_string(result)); } // update all views if (echo) { m_machine.debug_view().update_all(); m_machine.debugger().refresh_display(); } return result; } //------------------------------------------------- // validate_command - validate a command string //------------------------------------------------- CMDERR debugger_console::validate_command(std::string_view command) { return internal_parse_command(command, false); } /*------------------------------------------------- register_command - register a command handler -------------------------------------------------*/ void debugger_console::register_command(std::string_view command, u32 flags, int minparams, int maxparams, std::function &)> &&handler) { if (m_machine.phase() != machine_phase::INIT) throw emu_fatalerror("Can only call debugger_console::register_command() at init time!"); if (!(m_machine.debug_flags & DEBUG_FLAG_ENABLED)) throw emu_fatalerror("Cannot call debugger_console::register_command() when debugger is not running"); auto const ins = m_commandlist.emplace(command, flags, minparams, maxparams, std::move(handler)); if (!ins.second) osd_printf_error("error: Duplicate debugger command %s registered\n", command); } //------------------------------------------------- // source_script - specifies a debug command // script to execute //------------------------------------------------- void debugger_console::source_script(const char *file) { // close any existing source file m_source_file.reset(); // open a new one if requested if (file != nullptr) { auto source_file = std::make_unique(file, std::ifstream::in); if (source_file->fail()) { if (m_machine.phase() == machine_phase::RUNNING) printf("Cannot open command file '%s'\n", file); else fatalerror("Cannot open command file '%s'\n", file); } else { m_source_file = std::move(source_file); } } } //------------------------------------------------- // process_source_file - executes commands from // a source file //------------------------------------------------- void debugger_console::process_source_file() { std::string buf; // loop until the file is exhausted or until we are executing again while (m_machine.debugger().cpu().is_stopped() && m_source_file && std::getline(*m_source_file, buf)) { // strip out comments (text after '//') size_t pos = buf.find("//"); if (pos != std::string::npos) buf.resize(pos); // strip whitespace buf = strtrimrightspace(buf); // execute the command if (!buf.empty()) execute_command(buf, true); } if (m_source_file && !m_source_file->good()) { if (!m_source_file->eof()) printf("I/O error, script processing terminated\n"); m_source_file.reset(); } } /*************************************************************************** Error Handling ***************************************************************************/ /*------------------------------------------------- cmderr_to_string - return a friendly string for a given command error -------------------------------------------------*/ std::string debugger_console::cmderr_to_string(CMDERR error) { const int offset = error.error_offset(); switch (error.error_class()) { case CMDERR::UNKNOWN_COMMAND: return "unknown command"; case CMDERR::AMBIGUOUS_COMMAND: return "ambiguous command"; case CMDERR::UNBALANCED_PARENS: return "unbalanced parentheses"; case CMDERR::UNBALANCED_QUOTES: return "unbalanced quotes"; case CMDERR::NOT_ENOUGH_PARAMS: return "not enough parameters for command"; case CMDERR::TOO_MANY_PARAMS: return "too many parameters for command"; case CMDERR::EXPRESSION_ERROR: return string_format("error in assignment expression: %s", expression_error(static_cast(offset)).code_string()); default: return "unknown error"; } } //************************************************************************** // PARAMETER VALIDATION HELPERS //************************************************************************** namespace { template inline std::string_view::size_type find_delimiter(std::string_view str, T &&is_delim) { unsigned parens = 0; for (std::string_view::size_type i = 0; str.length() > i; ++i) { if (str[i] == '(') { ++parens; } else if (parens) { if (str[i] == ')') --parens; } else if (is_delim(str[i])) { return i; } } return std::string_view::npos; } } // anonymous namespace /// \brief Validate parameter as a Boolean value /// /// Validates a parameter as a Boolean value. Fixed strings and /// expressions evaluating to numeric values are recognised. The result /// is unchanged for an empty string. /// \param [in] param The parameter string. /// \param [in,out] result The default value on entry, and the value of /// the parameter interpreted as a Boolean on success. Unchanged if /// the parameter is an empty string. /// \return true if the parameter is a valid Boolean value or an empty /// string, or false otherwise. bool debugger_console::validate_boolean_parameter(std::string_view param, bool &result) { // nullptr parameter does nothing and returns no error if (param.empty()) return true; // evaluate the expression; success if no error using namespace std::literals; bool const is_true = util::streqlower(param, "true"sv); bool const is_false = util::streqlower(param, "false"sv); if (is_true || is_false) { result = is_true; return true; } // try to evaluate as a number u64 val; if (!validate_number_parameter(param, val)) return false; result = val != 0; return true; } /// \brief Validate parameter as a numeric value /// /// Parses the parameter as an expression and evaluates it as a number. /// \param [in] param The parameter string. /// \param [out] result The numeric value of the expression on success. /// Unchanged on failure. /// \return true if the parameter is a valid expression that evaluates /// to a numeric value, or false otherwise. bool debugger_console::validate_number_parameter(std::string_view param, u64 &result) { // evaluate the expression; success if no error try { result = parsed_expression(visible_symtable(), param).execute(); return true; } catch (expression_error const &error) { // print an error pointing to the character that caused it printf("Error in expression: %s\n", param); printf(" %*s^", error.offset(), ""); printf("%s\n", error.code_string()); return false; } } /// \brief Validate parameter as a device /// /// Validates a parameter as a device identifier and retrieves the /// device on success. A string corresponding to the tag of a device /// refers to that device; an empty string refers to the current CPU /// with debugger focus; any other string is parsed as an expression /// and treated as an index of a device implementing /// #device_execute_interface and #device_state_interface, and exposing /// a generic PC base value. /// \param [in] param The parameter string. /// \param [out] result A pointer to the device on success, or unchanged /// on failure. /// \return true if the parameter refers to a device in the current /// system, or false otherwise. bool debugger_console::validate_device_parameter(std::string_view param, device_t *&result) { // if no parameter, use the visible CPU if (param.empty()) { device_t *const current = m_visiblecpu; if (current) { result = current; return true; } else { printf("No valid CPU is currently selected\n"); return false; } } // next look for a tag match std::string_view relative = param; device_t &base = get_device_search_base(relative); device_t *device = base.subdevice(strmakelower(relative)); if (device) { result = device; return true; } // then evaluate as an expression; on an error assume it was a tag u64 cpunum; try { cpunum = parsed_expression(visible_symtable(), param).execute(); } catch (expression_error &) { printf("Unable to find device '%s'\n", param); return false; } // attempt to find by numerical index device = get_cpu_by_index(cpunum); if (device) { result = device; return true; } else { // if out of range, complain printf("Invalid CPU index %u\n", cpunum); return false; } } /// \brief Validate a parameter as a CPU /// /// Validates a parameter as a CPU identifier. Uses the same rules as /// #validate_device_parameter to identify devices, but additionally /// checks that the device is a "CPU" for the debugger's purposes. /// \param [in] The parameter string. /// \param [out] result The device on success, or unchanged on failure. /// \return true if the parameter refers to a CPU-like device in the /// current system, or false otherwise. bool debugger_console::validate_cpu_parameter(std::string_view param, device_t *&result) { // first do the standard device thing device_t *device; if (!validate_device_parameter(param, device)) return false; // check that it's a "CPU" for the debugger's purposes device_execute_interface const *execute; if (device->interface(execute)) { result = device; return true; } printf("Device %s is not a CPU\n", device->name()); return false; } /// \brief Validate a parameter as an address space identifier /// /// Validates a parameter as an address space identifier. Uses the same /// rules as #validate_device_parameter to identify devices. If the /// default address space number is negative, the first address space /// exposed by the device will be used as the default. /// \param [in] The parameter string. /// \param [in] spacenum The default address space index. If negative, /// the first address space exposed by the device (i.e. the address /// space with the lowest index) will be used as the default. /// \param [out] result The addresfs space on success, or unchanged on /// failure. /// \return true if the parameter refers to an address space in the /// current system, or false otherwise. bool debugger_console::validate_device_space_parameter(std::string_view param, int spacenum, address_space *&result) { device_t *device; std::string spacename; if (param.empty()) { // if no parameter, use the visible CPU device = m_visiblecpu; if (!device) { printf("No valid CPU is currently selected\n"); return false; } } else { // look for a tag match on the whole parameter value std::string_view relative = param; device_t &base = get_device_search_base(relative); device = base.subdevice(strmakelower(relative)); // if that failed, treat the last component as an address space if (!device) { auto const delimiter = relative.find_last_of(":^"); bool const found = std::string_view::npos != delimiter; if (!found || (':' == relative[delimiter])) { spacename = strmakelower(relative.substr(found ? (delimiter + 1) : 0)); relative = relative.substr(0, !found ? 0 : !delimiter ? 1 : delimiter); if (!relative.empty()) device = base.subdevice(strmakelower(relative)); else if (m_visiblecpu) device = m_visiblecpu; else device = &m_machine.root_device(); } } } // if still no device found, evaluate as an expression if (!device) { u64 cpunum; try { cpunum = parsed_expression(visible_symtable(), param).execute(); } catch (expression_error const &) { // parsing failed - assume it was a tag printf("Unable to find device '%s'\n", param); return false; } // attempt to find by numerical index device = get_cpu_by_index(cpunum); if (!device) { // if out of range, complain printf("Invalid CPU index %u\n", cpunum); return false; } } // ensure the device implements the memory interface device_memory_interface *memory; if (!device->interface(memory)) { printf("No memory interface found for device %s\n", device->name()); return false; } // fall back to supplied default space if appropriate if (spacename.empty() && (0 <= spacenum)) { if (memory->has_space(spacenum)) { result = &memory->space(spacenum); return true; } else { printf("No matching memory space found for device '%s'\n", device->tag()); return false; } } // otherwise find the specified space or fall back to the first populated space for (int i = 0; memory->max_space_count() > i; ++i) { if (memory->has_space(i) && (spacename.empty() || (memory->space(i).name() == spacename))) { result = &memory->space(i); return true; } } // report appropriate error message if (spacename.empty()) printf("No memory spaces found for device '%s'\n", device->tag()); else printf("Memory space '%s' not found found for device '%s'\n", spacename, device->tag()); return false; } /// \brief Validate a parameter as a target address /// /// Validates a parameter as an numeric expression to use as an address /// optionally followed by a colon and a device identifier. If the /// device identifier is not presnt, the current CPU with debugger focus /// is assumed. See #validate_device_parameter for information on how /// device parametersare interpreted. /// \param [in] The parameter string. /// \param [in] spacenum The default address space index. If negative, /// the first address space exposed by the device (i.e. the address /// space with the lowest index) will be used as the default. /// \param [out] space The address space on success, or unchanged on /// failure. /// \param [out] addr The address on success, or unchanged on failure. /// \return true if the address is a valid expression evaluating to a /// number and the address space is found, or false otherwise. bool debugger_console::validate_target_address_parameter(std::string_view param, int spacenum, address_space *&space, u64 &addr) { // check for the device delimiter std::string_view::size_type const devdelim = find_delimiter(param, [] (char ch) { return ':' == ch; }); std::string_view device; if (devdelim != std::string::npos) device = param.substr(devdelim + 1); // parse the address first u64 addrval; if (!validate_number_parameter(param.substr(0, devdelim), addrval)) return false; // find the address space if (!validate_device_space_parameter(device, spacenum, space)) return false; // set the address now that we have the space addr = addrval; return true; } /// \brief Validate a parameter as a memory region /// /// Validates a parameter as a memory region tag and retrieves the /// specified memory region. /// \param [in] The parameter string. /// \param [out] result The memory region on success, or unchanged on /// failure. /// \return true if the parameter refers to a memory region in the /// current system, or false otherwise. bool debugger_console::validate_memory_region_parameter(std::string_view param, memory_region *&result) { auto const ®ions = m_machine.memory().regions(); std::string_view relative = param; device_t &base = get_device_search_base(relative); auto const iter = regions.find(base.subtag(strmakelower(relative))); if (regions.end() != iter) { result = iter->second.get(); return true; } else { printf("No matching memory region found for '%s'\n", param); return false; } } /// \brief Get search base for device or address space parameter /// /// Handles prefix prefixes used to indicate that a device tag should be /// interpreted relative to the selected CPU. Removes the recognised /// prefixes from the parameter value. /// \param [in,out] param The parameter string. Recognised prefixes /// affecting the search base are removed, leaving a tag relative to /// the base device. /// \return A reference to the base device that the tag should be /// interpreted relative to. device_t &debugger_console::get_device_search_base(std::string_view ¶m) const { if (!param.empty()) { // handle ".:" or ".^" prefix for tag relative to current CPU if any if (('.' == param[0]) && ((param.size() == 1) || (':' == param[1]) || ('^' == param[1]))) { param.remove_prefix(((param.size() > 1) && (':' == param[1])) ? 2 : 1); device_t *const current = m_visiblecpu; return current ? *current : m_machine.root_device(); } // a sibling path makes most sense relative to current CPU if ('^' == param[0]) { device_t *const current = m_visiblecpu; return current ? *current : m_machine.root_device(); } } // default to root device return m_machine.root_device(); } /// \brief Get CPU by index /// /// Looks up a CPU by the number the debugger assigns it based on its /// position in the device tree relative to other CPUs. /// \param [in] cpunum Zero-based index of the CPU to find. /// \return A pointer to the CPU if found, or \c nullptr if no CPU has /// the specified index. device_t *debugger_console::get_cpu_by_index(u64 cpunum) const { unsigned index = 0; for (device_execute_interface &exec : execute_interface_enumerator(m_machine.root_device())) { // real CPUs should have pcbase device_state_interface const *state; if (exec.device().interface(state) && state->state_find_entry(STATE_GENPCBASE)) { if (index++ == cpunum) { return &exec.device(); } } } return nullptr; } //------------------------------------------------- // validate_expression_parameter - validates // an expression parameter //-----------------------------------------------*/ bool debugger_console::validate_expression_parameter(std::string_view param, parsed_expression &result) { try { // parse the expression; success if no error result.parse(param); return true; } catch (expression_error const &err) { // output an error printf("Error in expression: %s\n", param); printf(" %*s^", err.offset(), ""); printf("%s\n", err.code_string()); return false; } } //------------------------------------------------- // validate_command_parameter - validates a // command parameter //------------------------------------------------- bool debugger_console::validate_command_parameter(std::string_view param) { // validate the comment; success if no error CMDERR err = validate_command(param); if (err.error_class() == CMDERR::NONE) return true; // output an error printf("Error in command: %s\n", param); printf(" %*s^", err.error_offset(), ""); printf("%s\n", cmderr_to_string(err)); return false; } /*************************************************************************** Console Management ***************************************************************************/ //------------------------------------------------- // print_core - write preformatted text // to the debug console //------------------------------------------------- void debugger_console::print_core(std::string_view text) { text_buffer_print(*m_console_textbuf, text); if (m_logfile) m_logfile->write(text.data(), text.length()); } //------------------------------------------------- // print_core_wrap - write preformatted text // to the debug console, with wrapping //------------------------------------------------- void debugger_console::print_core_wrap(std::string_view text, int wrapcol) { // FIXME: look into honoring wrapcol for the logfile text_buffer_print_wrap(*m_console_textbuf, text, wrapcol); if (m_logfile) m_logfile->write(text.data(), text.length()); } //------------------------------------------------- // vprintf - vprintfs the given arguments using // the format to the debug console //------------------------------------------------- void debugger_console::vprintf(util::format_argument_pack const &args) { print_core(util::string_format(args)); // force an update of any console views m_machine.debug_view().update_all(DVT_CONSOLE); } void debugger_console::vprintf(util::format_argument_pack &&args) { print_core(util::string_format(std::move(args))); // force an update of any console views m_machine.debug_view().update_all(DVT_CONSOLE); } //------------------------------------------------- // vprintf_wrap - vprintfs the given arguments // using the format to the debug console //------------------------------------------------- void debugger_console::vprintf_wrap(int wrapcol, util::format_argument_pack const &args) { print_core_wrap(util::string_format(args), wrapcol); // force an update of any console views m_machine.debug_view().update_all(DVT_CONSOLE); } void debugger_console::vprintf_wrap(int wrapcol, util::format_argument_pack &&args) { print_core_wrap(util::string_format(std::move(args)), wrapcol); // force an update of any console views m_machine.debug_view().update_all(DVT_CONSOLE); } //------------------------------------------------- // errorlog_write_line - writes a line to the // errorlog ring buffer //------------------------------------------------- void debugger_console::errorlog_write_line(const char *line) { if (m_errorlog_textbuf) text_buffer_print(*m_errorlog_textbuf, line); // force an update of any log views m_machine.debug_view().update_all(DVT_LOG); }