// license:BSD-3-Clause // copyright-holders:Miodrag Milanovic,Luca Bruno /*************************************************************************** luaengine.cpp Controls execution of the core MAME system. ***************************************************************************/ #include "emu.h" #include "luaengine.ipp" #include "debug/debugcon.h" #include "debug/debugcpu.h" #include "debug/debugvw.h" #include "debug/express.h" #include "debug/points.h" #include "debug/textbuf.h" #include "debugger.h" namespace { struct wrap_textbuf { wrap_textbuf(text_buffer const &buf) : textbuf(buf) { } std::reference_wrapper textbuf; }; template sol::object do_breakpoint_enable(device_debug &dev, sol::this_state s, sol::object index) { if (index == sol::lua_nil) { dev.breakpoint_enable_all(Enable); dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); return sol::lua_nil; } else if (index.is()) { bool result(dev.breakpoint_enable(index.as(), Enable)); if (result) { dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); } return sol::make_object(s, result); } else { osd_printf_error("[LUA ERROR] must call bpenable with integer or nil\n"); return sol::lua_nil; } } template sol::object do_watchpoint_enable(device_debug &dev, sol::this_state s, sol::object index) { if (index == sol::lua_nil) { dev.watchpoint_enable_all(Enable); dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); return sol::lua_nil; } else if (index.is()) { bool result(dev.watchpoint_enable(index.as(), Enable)); if (result) dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); return sol::make_object(s, result); } else { osd_printf_error("[LUA ERROR] must call wpenable with integer or nil"); return sol::lua_nil; } } } // anonymous namespace class lua_engine::symbol_table_wrapper { public: symbol_table_wrapper(symbol_table_wrapper const &) = delete; symbol_table_wrapper(running_machine &machine, std::shared_ptr const &parent, device_t *device) : m_table(machine, parent ? &parent->table() : nullptr, device) , m_parent(parent) { } symbol_table &table() { return m_table; } symbol_table const &table() const { return m_table; } std::shared_ptr const &parent() { return m_parent; } symbol_entry &add(char const *name) { return m_table.add(name, symbol_table::READ_WRITE); } symbol_entry &add(char const *name, u64 value) { return m_table.add(name, value); } symbol_entry &add(char const *name, sol::protected_function getter, std::optional setter, std::optional format) { symbol_table::setter_func setfun; if (setter) setfun = [cbfunc = std::move(*setter)] (u64 value) { invoke(cbfunc, value); }; return m_table.add( name, [cbfunc = std::move(getter)] () -> u64 { auto result = invoke(cbfunc).get >(); if (result) { return *result; } else { osd_printf_error("[LUA EROR] invalid return from symbol value getter callback\n"); return 0; } }, std::move(setfun), (format && *format) ? *format : ""); } symbol_entry *find(char const *name) const { return m_table.find(name); } symbol_entry *find_deep(char const *name) { return m_table.find_deep(name); } u64 value(const char *symbol) { return m_table.value(symbol); } void set_value(const char *symbol, u64 value) { m_table.set_value(symbol, value); } u64 read_memory(addr_space &space, offs_t address, int size, bool translate) { return m_table.read_memory(space.space, address, size, translate); } void write_memory(addr_space &space, offs_t address, u64 data, int size, bool translate) { m_table.write_memory(space.space, address, data, size, translate); } private: symbol_table m_table; std::shared_ptr const m_parent; }; class lua_engine::expression_wrapper { public: expression_wrapper(expression_wrapper const &) = delete; expression_wrapper &operator=(expression_wrapper const &) = delete; expression_wrapper(std::shared_ptr const &symtable) : m_expression(symtable->table()) , m_symbols(symtable) { } expression_wrapper(std::shared_ptr const &symtable, char const *expression, int default_base) : m_expression(symtable->table(), expression, default_base) , m_symbols(symtable) { } expression_wrapper(std::shared_ptr const &symtable, char const *expression) : m_expression(symtable->table(), expression) , m_symbols(symtable) { } void set_default_base(int base) { m_expression.set_default_base(base); } void parse(char const *string) { m_expression.parse(string); } u64 execute() { return m_expression.execute(); } bool is_empty() const { return m_expression.is_empty(); } char const *original_string() const { return m_expression.original_string(); } std::shared_ptr const &symbols() { return m_symbols; } void set_symbols(std::shared_ptr const &symtable) { m_expression.set_symbols(symtable->table()); m_symbols = symtable; } private: parsed_expression m_expression; std::shared_ptr m_symbols; }; void lua_engine::initialize_debug(sol::table &emu) { static const enum_parser s_read_or_write_parser = { { "r", read_or_write::READ }, { "w", read_or_write::WRITE }, { "rw", read_or_write::READWRITE }, { "wr", read_or_write::READWRITE } }; static const enum_parser s_expression_space_parser = { { "p", EXPSPACE_PROGRAM_LOGICAL }, { "lp", EXPSPACE_PROGRAM_LOGICAL }, { "pp", EXPSPACE_PROGRAM_PHYSICAL }, { "d", EXPSPACE_DATA_LOGICAL }, { "ld", EXPSPACE_DATA_LOGICAL }, { "pd", EXPSPACE_DATA_PHYSICAL }, { "i", EXPSPACE_IO_LOGICAL }, { "li", EXPSPACE_IO_LOGICAL }, { "pi", EXPSPACE_IO_PHYSICAL }, { "3", EXPSPACE_OPCODE_LOGICAL }, { "l3", EXPSPACE_OPCODE_LOGICAL }, { "p3", EXPSPACE_OPCODE_PHYSICAL }, { "r", EXPSPACE_PRGDIRECT }, { "o", EXPSPACE_OPDIRECT }, { "m", EXPSPACE_REGION } }; auto symbol_table_type = emu.new_usertype( "symbol_table", sol::call_constructor, sol::factories( [] (running_machine &machine) { return std::make_shared(machine, nullptr, nullptr); }, [] (std::shared_ptr const &parent, device_t *device) { return std::make_shared(parent->table().machine(), parent, device); }, [] (std::shared_ptr const &parent) { return std::make_shared(parent->table().machine(), parent, nullptr); }, [] (device_t &device) { return std::make_shared(device.machine(), nullptr, &device); })); symbol_table_type.set_function("set_memory_modified_func", [] (symbol_table_wrapper &st, sol::object cb) { if (cb == sol::lua_nil) st.table().set_memory_modified_func(nullptr); else if (cb.is()) st.table().set_memory_modified_func([cbfunc = cb.as()] () { invoke(cbfunc); }); else osd_printf_error("[LUA ERROR] must call set_memory_modified_func with function or nil\n"); }); symbol_table_type["add"] = sol::overload( static_cast(&symbol_table_wrapper::add), static_cast(&symbol_table_wrapper::add), static_cast, std::optional)>(&symbol_table_wrapper::add), [] (symbol_table_wrapper &st, char const *name, sol::protected_function getter, sol::lua_nil_t, char const *format) -> symbol_entry & { return st.add(name, getter, std::nullopt, format); }, [] (symbol_table_wrapper &st, char const *name, sol::protected_function getter, std::optional setter) -> symbol_entry & { return st.add(name, getter, setter, nullptr); }, [] (symbol_table_wrapper &st, char const *name, sol::protected_function getter, char const *format) -> symbol_entry & { return st.add(name, getter, std::nullopt, format); }, [] (symbol_table_wrapper &st, char const *name, sol::protected_function getter) -> symbol_entry & { return st.add(name, getter, std::nullopt, nullptr); }, [] (symbol_table_wrapper &st, sol::this_state s, char const *name, int minparams, int maxparams, sol::protected_function execute) -> symbol_entry & { return st.table().add( name, minparams, maxparams, [L = s.L, cbref = sol::reference(execute)] (int numparams, u64 const *paramlist) -> u64 { sol::stack_reference traceback(L, -sol::stack::push(L, sol::default_traceback_error_handler)); cbref.push(); sol::stack_aligned_stack_handler_function func(L, -1, traceback); for (int i = 0; numparams > i; ++i) lua_pushinteger(L, paramlist[i]); auto result = func(sol::stack_count(numparams)).get >(); traceback.pop(); return result ? *result : 0; }); }); symbol_table_type.set_function("find", &symbol_table_wrapper::find); symbol_table_type.set_function("find_deep", &symbol_table_wrapper::find_deep); symbol_table_type.set_function("value", &symbol_table_wrapper::value); symbol_table_type.set_function("set_value", &symbol_table_wrapper::set_value); symbol_table_type.set_function("memory_value", [] (symbol_table_wrapper &st, char const *name, char const *space, u32 offset, int size, bool disable_se) { expression_space const es = s_expression_space_parser(space); return st.table().memory_value(name, es, offset, size, disable_se); }); symbol_table_type.set_function("set_memory_value", [] (symbol_table_wrapper &st, char const *name, char const *space, u32 offset, int size, u64 value, bool disable_se) { expression_space const es = s_expression_space_parser(space); st.table().set_memory_value(name, es, offset, size, value, disable_se); }); symbol_table_type.set_function("read_memory", &symbol_table_wrapper::read_memory); symbol_table_type.set_function("write_memory", &symbol_table_wrapper::write_memory); symbol_table_type["entries"] = sol::property([] (symbol_table_wrapper const &st) { return standard_tag_object_ptr_map(st.table().entries()); }); symbol_table_type["parent"] = sol::property(&symbol_table_wrapper::parent); auto parsed_expression_type = emu.new_usertype( "parsed_expression", sol::call_constructor, sol::constructors< expression_wrapper(std::shared_ptr const &), expression_wrapper(std::shared_ptr const &, char const *, int), expression_wrapper(std::shared_ptr const &, char const *)>()); parsed_expression_type.set_function("set_default_base", &expression_wrapper::set_default_base); parsed_expression_type.set_function("parse", &expression_wrapper::parse); parsed_expression_type.set_function("execute", &expression_wrapper::execute); parsed_expression_type["is_empty"] = sol::property(&expression_wrapper::is_empty); parsed_expression_type["original_string"] = sol::property(&expression_wrapper::original_string); parsed_expression_type["symbols"] = sol::property(&expression_wrapper::symbols, &expression_wrapper::set_symbols); auto symbol_entry_type = sol().registry().new_usertype("symbol_entry", sol::no_constructor); symbol_entry_type["name"] = sol::property(&symbol_entry::name); symbol_entry_type["format"] = sol::property(&symbol_entry::format); symbol_entry_type["is_function"] = sol::property(&symbol_entry::is_function); symbol_entry_type["is_lval"] = sol::property(&symbol_entry::is_lval); symbol_entry_type["value"] = sol::property(&symbol_entry::value, &symbol_entry::set_value); auto debugger_type = sol().registry().new_usertype("debugger", sol::no_constructor); debugger_type.set_function("command", [] (debugger_manager &debug, std::string const &cmd) { debug.console().execute_command(cmd, false); }); debugger_type["consolelog"] = sol::property([] (debugger_manager &debug) { return wrap_textbuf(debug.console().get_console_textbuf()); }); debugger_type["errorlog"] = sol::property([](debugger_manager &debug) { return wrap_textbuf(debug.console().get_errorlog_textbuf()); }); debugger_type["visible_cpu"] = sol::property( [](debugger_manager &debug) { return debug.console().get_visible_cpu(); }, [](debugger_manager &debug, device_t &dev) { debug.console().set_visible_cpu(&dev); }); debugger_type["execution_state"] = sol::property( [] (debugger_manager &debug) { return debug.cpu().is_stopped() ? "stop" : "run"; }, [] (debugger_manager &debug, std::string const &state) { if (state == "stop") debug.cpu().set_execution_stopped(); else debug.cpu().set_execution_running(); }); /* wrap_textbuf library (requires debugger to be active) * * manager:machine():debugger().consolelog * manager:machine():debugger().errorlog * * log[index] - get log entry * #log - entry count */ sol().registry().new_usertype("text_buffer", "new", sol::no_constructor, "__metatable", [](){}, "__newindex", [](){}, "__index", [](wrap_textbuf &buf, int index) { return text_buffer_get_seqnum_line(buf.textbuf, index - 1); }, "__len", [](wrap_textbuf &buf) { return text_buffer_num_lines(buf.textbuf) + text_buffer_line_index_to_seqnum(buf.textbuf, 0) - 1; }); auto device_debug_type = sol().registry().new_usertype("device_debug", sol::no_constructor); device_debug_type.set_function("step", [] (device_debug &dev, sol::object num) { int steps = 1; if (num.is()) steps = num.as(); dev.single_step(steps); }); device_debug_type.set_function("go", &device_debug::go); device_debug_type.set_function("bpset", [] (device_debug &dev, offs_t address, char const *cond, char const *act) { int result(dev.breakpoint_set(address, cond, act)); dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); return result; }); device_debug_type["bpclear"] = sol::overload( [] (device_debug &dev, int index) { bool result(dev.breakpoint_clear(index)); if (result) { dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); } return result; }, [] (device_debug &dev) { dev.breakpoint_clear_all(); dev.device().machine().debug_view().update_all(DVT_DISASSEMBLY); dev.device().machine().debug_view().update_all(DVT_BREAK_POINTS); }); device_debug_type.set_function("bpenable", &do_breakpoint_enable); device_debug_type.set_function("bpdisable", &do_breakpoint_enable); device_debug_type.set_function("bplist", [this] (device_debug &dev) { sol::table table = sol().create_table(); for (auto const &bpp : dev.breakpoint_list()) table[bpp.second->index()] = sol::make_reference(sol(), bpp.second.get()); return table; }); device_debug_type.set_function("wpset", [] (device_debug &dev, addr_space &sp, std::string const &type, offs_t addr, offs_t len, char const *cond, char const *act) { read_or_write const wptype = s_read_or_write_parser(type); int result(dev.watchpoint_set(sp.space, wptype, addr, len, cond, act)); dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); return result; }); device_debug_type["wpclear"] = sol::overload( [] (device_debug &dev, int index) { bool result(dev.watchpoint_clear(index)); if (result) dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); return result; }, [] (device_debug &dev) { dev.watchpoint_clear_all(); dev.device().machine().debug_view().update_all(DVT_WATCH_POINTS); }); device_debug_type.set_function("wpenable", &do_watchpoint_enable); device_debug_type.set_function("wpdisable", &do_watchpoint_enable); device_debug_type.set_function("wplist", [this] (device_debug &dev, addr_space &sp) { sol::table table = sol().create_table(); for (auto &wpp : dev.watchpoint_vector(sp.space.spacenum())) table[wpp->index()] = sol::make_reference(sol(), wpp.get()); return table; }); auto breakpoint_type = sol().registry().new_usertype("breakpoint", sol::no_constructor); breakpoint_type["index"] = sol::property(&debug_breakpoint::index); breakpoint_type["enabled"] = sol::property( &debug_breakpoint::enabled, [] (debug_breakpoint &bp, bool val) { if (bp.enabled() != val) { bp.setEnabled(val); bp.debugInterface()->device().machine().debug_view().update_all(DVT_DISASSEMBLY); bp.debugInterface()->device().machine().debug_view().update_all(DVT_BREAK_POINTS); } }); breakpoint_type["address"] = sol::property(&debug_breakpoint::address); breakpoint_type["condition"] = sol::property(&debug_breakpoint::condition); breakpoint_type["action"] = sol::property(&debug_breakpoint::action); auto watchpoint_type = sol().registry().new_usertype("watchpoint", sol::no_constructor); watchpoint_type["index"] = sol::property(&debug_watchpoint::index); watchpoint_type["enabled"] = sol::property( &debug_watchpoint::enabled, [] (debug_watchpoint &wp, bool val) { if (wp.enabled() != val) { wp.setEnabled(val); wp.debugInterface()->device().machine().debug_view().update_all(DVT_WATCH_POINTS); } }); watchpoint_type["type"] = sol::property( [] (debug_watchpoint &wp) -> char const * { switch (wp.type()) { case read_or_write::READ: return "r"; case read_or_write::WRITE: return "w"; case read_or_write::READWRITE: return "rw"; default: // huh? return ""; } }); watchpoint_type["address"] = sol::property(&debug_watchpoint::address); watchpoint_type["length"] = sol::property(&debug_watchpoint::length); watchpoint_type["condition"] = sol::property(&debug_watchpoint::condition); watchpoint_type["action"] = sol::property(&debug_watchpoint::action); }