From e1a6a41234684ad5244605d417356d2c6c22ca61 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Thu, 10 Feb 2022 02:06:42 +1100 Subject: [PATCH] frontend: Exposed memory pass-through handlers (address space taps) to Lua. --- docs/source/techspecs/luareference.rst | 110 +++++++- src/emu/debug/points.cpp | 84 +++--- src/frontend/mame/luaengine.h | 2 + src/frontend/mame/luaengine_mem.cpp | 344 +++++++++++++++++++++---- 4 files changed, 446 insertions(+), 94 deletions(-) diff --git a/docs/source/techspecs/luareference.rst b/docs/source/techspecs/luareference.rst index 5c8297a2610..fd7fe0a51f8 100644 --- a/docs/source/techspecs/luareference.rst +++ b/docs/source/techspecs/luareference.rst @@ -1363,6 +1363,42 @@ space:read_range(start, end, width, [step]) Reads a range of addresses as a binary string. The end address must be greater than or equal to the start address. The width must be 8, 16, 30 or 64. If the step is provided, it must be a positive number of elements. +space:add_change_notifier(callback) + Adds a + :ref:`handler change subscription ` to + the address space. The callback function is passed a single string as an + argument, either ``r`` if read handlers have potentially changed, ``w`` if + write handlers have potentially changed, or ``rw`` if both read and write + handlers have potentially changed. + + Note that handler change subscriptions must be explicitly removed before the + emulation session ends. +space:install_read_tap(start, end, name, callback) + Installs a :ref:`pass-through handler ` that will + receive notifications on reads from the specified range of addresses in the + address space. The start and end addresses are inclusive. The name must be + a string, and the callback must be a function. + + The callback is passed three arguments for the access offset, the data read, + and the memory access mask. To modify the data being read, return the + modified value from the callback function as an integer. If the callback + does not return an integer, the data will not be modified. + + Note that pass-through handlers must be explicitly removed before the + emulation session ends. +space:install_write_tap(start, end, name, callback) + Installs a :ref:`pass-through handler ` that will + receive notifications on write to the specified range of addresses in the + address space. The start and end addresses are inclusive. The name must be + a string, and the callback must be a function. + + The callback is passed three arguments for the access offset, the data + written, and the memory access mask. To modify the data being written, + return the modified value from the callback function as an integer. If the + callback does not return an integer, the data will not be modified. + + Note that pass-through handlers must be explicitly removed before the + emulation session ends. Properties ^^^^^^^^^^ @@ -1387,6 +1423,76 @@ space.map (read-only) The configured :ref:`address map ` for the space or ``nil``. +.. _luareference-mem-spacechangenotif: + +Address space change notifier +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tracks a subscription to :ref:`address space ` handler +changes. Note that you must remove subscriptions before the emulation session +ends. + +Instantiation +^^^^^^^^^^^^^ + +manager.machine.devices[tag].spaces[name]:add_change_notifier(callback) + Adds a handler change subscriptions to an + :ref:`address space `. + +Methods +^^^^^^^ + +notifier:remove() + Removes the notification subscription. The associated callback will not be + called on future handler changes for the address space. + +.. _luareference-mem-tap: + +Pass-through handler +~~~~~~~~~~~~~~~~~~~~ + +Tracks a pass-through handler installed in an +:ref:`address space `. A memory pass-through handler +receives notifications on accesses to a specified range of addresses, and can +modify the data that is read or written if desired. Note that you must remove +pass-through handlers before the emulation session ends. + +Instantiation +^^^^^^^^^^^^^ + +manager.machine.devices[tag].spaces[name]:install_read_tap(start, end, name, callback) + Installs a pass-through handler that will receive notifications on reads + from the specified range of addresses in an + :ref:`address space `. +manager.machine.devices[tag].spaces[name]:install_write_tap(start, end, name, callback) + Installs a pass-through handler that will receive notifications on writes to + the specified range of addresses in an + :ref:`address space `. + +Methods +^^^^^^^ + +passthrough:reinstall() + Reinstalls the pass-through handler in the address space. May be necessary + if the handler is removed due to other changes to handlers in the address + space. +passthrough:remove() + Removes the pass-through handler from the address space. The associated + callback will not be called in response to future memory accesses. + +Properties +^^^^^^^^^^ + +passthrough.addrstart (read-only) + The inclusive start address of the address range monitored by the + pass-through handler (i.e. the lowest address that the handler will be + notified for). +passthrough.addrend (read-only) + The inclusive end address of the address range monitored by the pass-through + handler (i.e. the highest address that the handler will be notified for). +passthrough.name (read-only) + The display name for the pass-through handler. + .. _luareference-mem-map: Address map @@ -3140,8 +3246,8 @@ Wraps MAME’s ``parsed_expression`` class, which represents a tokenised debugge expression. Note that parsed expressions can be created and used even when the debugger is not enabled. -Intantiation -^^^^^^^^^^^^ +Instantiation +^^^^^^^^^^^^^ emu.parsed_expression(symbols) Creates an empty expression that will use the supplied diff --git a/src/emu/debug/points.cpp b/src/emu/debug/points.cpp index c44bc3f7563..d4c1ebb3045 100644 --- a/src/emu/debug/points.cpp +++ b/src/emu/debug/points.cpp @@ -22,18 +22,19 @@ // debug_breakpoint - constructor //------------------------------------------------- -debug_breakpoint::debug_breakpoint(device_debug* debugInterface, - symbol_table &symbols, - int index, - offs_t address, - const char *condition, - const char *action) - : m_debugInterface(debugInterface), - m_index(index), - m_enabled(true), - m_address(address), - m_condition(symbols, (condition != nullptr) ? condition : "1"), - m_action((action != nullptr) ? action : "") +debug_breakpoint::debug_breakpoint( + device_debug *debugInterface, + symbol_table &symbols, + int index, + offs_t address, + const char *condition, + const char *action) : + m_debugInterface(debugInterface), + m_index(index), + m_enabled(true), + m_address(address), + m_condition(symbols, condition ? condition : "1"), + m_action(action ? action : "") { } @@ -59,7 +60,7 @@ bool debug_breakpoint::hit(offs_t pc) { return (m_condition.execute() != 0); } - catch (expression_error &) + catch (expression_error const &) { return false; } @@ -78,27 +79,28 @@ bool debug_breakpoint::hit(offs_t pc) // debug_watchpoint - constructor //------------------------------------------------- -debug_watchpoint::debug_watchpoint(device_debug* debugInterface, - symbol_table &symbols, - int index, - address_space &space, - read_or_write type, - offs_t address, - offs_t length, - const char *condition, - const char *action) - : m_debugInterface(debugInterface), - m_phr(nullptr), - m_phw(nullptr), - m_space(space), - m_index(index), - m_enabled(true), - m_type(type), - m_address(address & space.addrmask()), - m_length(length), - m_condition(symbols, (condition != nullptr) ? condition : "1"), - m_action((action != nullptr) ? action : ""), - m_installing(false) +debug_watchpoint::debug_watchpoint( + device_debug* debugInterface, + symbol_table &symbols, + int index, + address_space &space, + read_or_write type, + offs_t address, + offs_t length, + const char *condition, + const char *action) : + m_debugInterface(debugInterface), + m_phr(nullptr), + m_phw(nullptr), + m_space(space), + m_index(index), + m_enabled(true), + m_type(type), + m_address(address & space.addrmask()), + m_length(length), + m_condition(symbols, condition ? condition : "1"), + m_action(action ? action : ""), + m_installing(false) { std::fill(std::begin(m_start_address), std::end(m_start_address), 0); std::fill(std::begin(m_end_address), std::end(m_end_address), 0); @@ -170,12 +172,14 @@ debug_watchpoint::debug_watchpoint(device_debug* debugInterface, } install(read_or_write::READWRITE); - m_notifier = m_space.add_change_notifier([this](read_or_write mode) { - if (m_enabled) - { - install(mode); - } - }); + m_notifier = m_space.add_change_notifier( + [this] (read_or_write mode) + { + if (m_enabled) + { + install(mode); + } + }); } debug_watchpoint::~debug_watchpoint() diff --git a/src/frontend/mame/luaengine.h b/src/frontend/mame/luaengine.h index bb16aea6a43..1aaa83bbf0b 100644 --- a/src/frontend/mame/luaengine.h +++ b/src/frontend/mame/luaengine.h @@ -121,6 +121,8 @@ private: template class enum_parser; struct addr_space; + class tap_helper; + class addr_space_change_notif; struct save_item { void *base; diff --git a/src/frontend/mame/luaengine_mem.cpp b/src/frontend/mame/luaengine_mem.cpp index f95ebdb37c2..26518931e9e 100644 --- a/src/frontend/mame/luaengine_mem.cpp +++ b/src/frontend/mame/luaengine_mem.cpp @@ -183,6 +183,220 @@ int sol_lua_push(sol::types, lua_State *L, endianness_t &&value) } +//------------------------------------------------- +// tap_helper - class for managing address space +// taps +//------------------------------------------------- + +class lua_engine::tap_helper +{ +public: + tap_helper(tap_helper const &) = delete; + tap_helper(tap_helper &&) = delete; + + tap_helper( + lua_engine &engine, + address_space &space, + read_or_write mode, + offs_t start, + offs_t end, + std::string &&name, + sol::protected_function &&callback) + : m_callback(std::move(callback)) + , m_engine(engine) + , m_space(space) + , m_handler(nullptr) + , m_name(std::move(name)) + , m_start(start) + , m_end(end) + , m_mode(mode) + , m_installing(false) + , m_installed(false) + { + reinstall(); + } + + ~tap_helper() + { + if (m_handler && m_installed) + m_handler->remove(); + } + + offs_t start() const noexcept { return m_start; } + offs_t end() const noexcept { return m_end; } + std::string const &name() const noexcept { return m_name; } + + void reinstall() + { + switch (m_space.data_width()) + { + case 8: do_install(); break; + case 16: do_install(); break; + case 32: do_install(); break; + case 64: do_install(); break; + } + } + + void remove() + { + if (m_handler) + { + m_handler->remove(); + m_installed = false; + } + } + +private: + template + void do_install() + { + if (m_installing) + return; + m_installing = true; + if (m_handler) + m_handler->remove(); + + switch (m_mode) + { + case read_or_write::READ: + m_handler = m_space.install_read_tap( + m_start, + m_end, + m_name, + [this] (offs_t offset, T &data, T mem_mask) + { + auto result = m_engine.invoke(m_callback, offset, data, mem_mask).template get >(); + if (result) + data = *result; + }, + m_handler); + break; + case read_or_write::WRITE: + m_handler = m_space.install_write_tap( + m_start, + m_end, + m_name, + [this] (offs_t offset, T &data, T mem_mask) + { + auto result = m_engine.invoke(m_callback, offset, data, mem_mask).template get >(); + if (result) + data = *result; + }, + m_handler); + break; + case read_or_write::READWRITE: + // won't ever get here, but compilers complain about unhandled enum value + break; + } + + m_installed = true; + m_installing = false; + }; + + sol::protected_function m_callback; + lua_engine &m_engine; + address_space &m_space; + memory_passthrough_handler *m_handler; + std::string m_name; + offs_t const m_start; + offs_t const m_end; + read_or_write const m_mode; + bool m_installing; + bool m_installed; +}; + + +//------------------------------------------------- +// addr_space_change_notif - helper for hanging +// on to an address space change notification +// subscription +//------------------------------------------------- + +class lua_engine::addr_space_change_notif +{ +public: + addr_space_change_notif(addr_space_change_notif const &) = delete; + + addr_space_change_notif(addr_space_change_notif &&that) + : m_callback(std::move(that.m_callback)) + , m_engine(that.m_engine) + , m_space(that.m_space) + , m_id(-1) + { + that.remove(); + install(); + } + + addr_space_change_notif &operator=(addr_space_change_notif &&that) + { + if (&that != this) + { + remove(); + m_callback = std::move(that.m_callback); + m_engine = that.m_engine; + m_space = that.m_space; + that.remove(); + install(); + } + return *this; + } + + addr_space_change_notif(lua_engine &engine, address_space &space, sol::protected_function &&callback) + : m_callback(std::move(callback)) + , m_engine(&engine) + , m_space(&space) + , m_id(-1) + { + install(); + } + + ~addr_space_change_notif() + { + if (m_space) + m_space->remove_change_notifier(m_id); + } + + void remove() + { + if (m_space) + { + m_space->remove_change_notifier(m_id); + m_space = nullptr; + m_id = -1; + } + } + +private: + void install() + { + if (m_space) + { + m_id = m_space->add_change_notifier( + [this] (read_or_write mode) + { + char const *modestr = ""; + switch (mode) + { + case read_or_write::READ: modestr = "r"; break; + case read_or_write::WRITE: modestr = "w"; break; + case read_or_write::READWRITE: modestr = "rw"; break; + } + m_engine->invoke(m_callback, modestr); + }); + } + else + { + m_id = -1; + } + } + + sol::protected_function m_callback; + lua_engine *m_engine; + address_space *m_space; + int m_id; +}; + + //------------------------------------------------- // mem_read - templated memory readers for , // -> manager:machine().devices[":maincpu"].spaces["program"]:read_i8(0xC000) @@ -449,64 +663,79 @@ void lua_engine::initialize_memory(sol::table &emu) addr_space_type["write_direct_i64"] = &addr_space::direct_mem_write; addr_space_type["write_direct_u64"] = &addr_space::direct_mem_write; addr_space_type["read_range"] = - [] (addr_space &sp, sol::this_state s, u64 first, u64 last, int width, sol::object opt_step) -> sol::object - { - lua_State *L = s; - luaL_Buffer buff; - offs_t space_size = sp.space.addrmask(); - u64 step = 1; - if (opt_step.is()) + [] (addr_space &sp, sol::this_state s, u64 first, u64 last, int width, sol::object opt_step) -> sol::object { - step = opt_step.as(); - if (step < 1 || step > last - first) + lua_State *L = s; + luaL_Buffer buff; + offs_t space_size = sp.space.addrmask(); + u64 step = 1; + if (opt_step.is()) { - luaL_error(L, "Invalid step"); + step = opt_step.as(); + if (step < 1 || step > last - first) + { + luaL_error(L, "Invalid step"); + return sol::lua_nil; + } + } + if (first > space_size || last > space_size || last < first) + { + luaL_error(L, "Invalid offset"); return sol::lua_nil; } - } - if (first > space_size || last > space_size || last < first) + int byte_count = width / 8 * (last - first + 1) / step; + switch (width) + { + case 8: + { + u8 *dest = (u8 *)luaL_buffinitsize(L, &buff, byte_count); + for ( ; first <= last; first += step) + *dest++ = sp.mem_read(first); + break; + } + case 16: + { + u16 *dest = (u16 *)luaL_buffinitsize(L, &buff, byte_count); + for ( ; first <= last; first += step) + *dest++ = sp.mem_read(first); + break; + } + case 32: + { + u32 *dest = (u32 *)luaL_buffinitsize(L, &buff, byte_count); + for( ; first <= last; first += step) + *dest++ = sp.mem_read(first); + break; + } + case 64: + { + u64 *dest = (u64 *)luaL_buffinitsize(L, &buff, byte_count); + for( ; first <= last; first += step) + *dest++ = sp.mem_read(first); + break; + } + default: + luaL_error(L, "Invalid width. Must be 8/16/32/64"); + return sol::lua_nil; + } + luaL_pushresultsize(&buff, byte_count); + return sol::make_reference(L, sol::stack_reference(L, -1)); + }; + addr_space_type["add_change_notifier"] = + [this] (addr_space &sp, sol::protected_function &&cb) { - luaL_error(L, "Invalid offset"); - return sol::lua_nil; - } - int byte_count = width / 8 * (last - first + 1) / step; - switch (width) + return addr_space_change_notif(*this, sp.space, std::move(cb)); + }; + addr_space_type["install_read_tap"] = + [this] (addr_space &sp, offs_t start, offs_t end, std::string &&name, sol::protected_function &&cb) { - case 8: - { - u8 *dest = (u8 *)luaL_buffinitsize(L, &buff, byte_count); - for ( ; first <= last; first += step) - *dest++ = sp.mem_read(first); - break; - } - case 16: - { - u16 *dest = (u16 *)luaL_buffinitsize(L, &buff, byte_count); - for ( ; first <= last; first += step) - *dest++ = sp.mem_read(first); - break; - } - case 32: - { - u32 *dest = (u32 *)luaL_buffinitsize(L, &buff, byte_count); - for(; first <= last; first += step) - *dest++ = sp.mem_read(first); - break; - } - case 64: - { - u64 *dest = (u64 *)luaL_buffinitsize(L, &buff, byte_count); - for(; first <= last; first += step) - *dest++ = sp.mem_read(first); - break; - } - default: - luaL_error(L, "Invalid width. Must be 8/16/32/64"); - return sol::lua_nil; - } - luaL_pushresultsize(&buff, byte_count); - return sol::make_reference(L, sol::stack_reference(L, -1)); - }; + return std::make_unique(*this, sp.space, read_or_write::READ, start, end, std::move(name), std::move(cb)); + }; + addr_space_type["install_write_tap"] = + [this] (addr_space &sp, offs_t start, offs_t end, std::string &&name, sol::protected_function &&cb) + { + return std::make_unique(*this, sp.space, read_or_write::WRITE, start, end, std::move(name), std::move(cb)); + }; addr_space_type["name"] = sol::property([] (addr_space &sp) { return sp.space.name(); }); addr_space_type["shift"] = sol::property([] (addr_space &sp) { return sp.space.addr_shift(); }); addr_space_type["index"] = sol::property([] (addr_space &sp) { return sp.space.spacenum(); }); @@ -516,6 +745,17 @@ void lua_engine::initialize_memory(sol::table &emu) addr_space_type["map"] = sol::property([] (addr_space &sp) { return sp.space.map(); }); + auto change_notif_type = sol().registry().new_usertype("addr_space_change", sol::no_constructor); + change_notif_type["remove"] = &addr_space_change_notif::remove; + + auto tap_type = sol().registry().new_usertype("mempassthrough", sol::no_constructor); + tap_type["reinstall"] = &tap_helper::reinstall; + tap_type["remove"] = &tap_helper::remove; + tap_type["addrstart"] = sol::property(&tap_helper::start); + tap_type["addrend"] = sol::property(&tap_helper::end); + tap_type["name"] = sol::property(&tap_helper::name); + + auto addrmap_type = sol().registry().new_usertype("addrmap", sol::no_constructor); addrmap_type["spacenum"] = sol::readonly(&address_map::m_spacenum); addrmap_type["device"] = sol::readonly(&address_map::m_device);