frontend: Exposed memory pass-through handlers (address space taps) to Lua.

This commit is contained in:
Vas Crabb 2022-02-10 02:06:42 +11:00
parent 53bfaabe4c
commit e1a6a41234
4 changed files with 446 additions and 94 deletions

View File

@ -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 <luareference-mem-spacechangenotif>` 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 <luareference-mem-tap>` 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 <luareference-mem-tap>` 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 <luareference-mem-map>` for the space or
``nil``.
.. _luareference-mem-spacechangenotif:
Address space change notifier
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tracks a subscription to :ref:`address space <luareference-mem-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 <luareference-mem-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 <luareference-mem-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 <luareference-mem-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 <luareference-mem-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 MAMEs ``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

View File

@ -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()

View File

@ -121,6 +121,8 @@ private:
template<typename T, size_t SIZE> class enum_parser;
struct addr_space;
class tap_helper;
class addr_space_change_notif;
struct save_item {
void *base;

View File

@ -183,6 +183,220 @@ int sol_lua_push(sol::types<endianness_t>, 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<u8>(); break;
case 16: do_install<u16>(); break;
case 32: do_install<u32>(); break;
case 64: do_install<u64>(); break;
}
}
void remove()
{
if (m_handler)
{
m_handler->remove();
m_installed = false;
}
}
private:
template <typename T>
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<sol::optional<T> >();
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<sol::optional<T> >();
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 <sign>,<size>
// -> 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<s64>;
addr_space_type["write_direct_u64"] = &addr_space::direct_mem_write<u64>;
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<u64>())
[] (addr_space &sp, sol::this_state s, u64 first, u64 last, int width, sol::object opt_step) -> sol::object
{
step = opt_step.as<u64>();
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<u64>())
{
luaL_error(L, "Invalid step");
step = opt_step.as<u64>();
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<u8>(first);
break;
}
case 16:
{
u16 *dest = (u16 *)luaL_buffinitsize(L, &buff, byte_count);
for ( ; first <= last; first += step)
*dest++ = sp.mem_read<u16>(first);
break;
}
case 32:
{
u32 *dest = (u32 *)luaL_buffinitsize(L, &buff, byte_count);
for( ; first <= last; first += step)
*dest++ = sp.mem_read<u32>(first);
break;
}
case 64:
{
u64 *dest = (u64 *)luaL_buffinitsize(L, &buff, byte_count);
for( ; first <= last; first += step)
*dest++ = sp.mem_read<u64>(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<u8>(first);
break;
}
case 16:
{
u16 *dest = (u16 *)luaL_buffinitsize(L, &buff, byte_count);
for ( ; first <= last; first += step)
*dest++ = sp.mem_read<u16>(first);
break;
}
case 32:
{
u32 *dest = (u32 *)luaL_buffinitsize(L, &buff, byte_count);
for(; first <= last; first += step)
*dest++ = sp.mem_read<u32>(first);
break;
}
case 64:
{
u64 *dest = (u64 *)luaL_buffinitsize(L, &buff, byte_count);
for(; first <= last; first += step)
*dest++ = sp.mem_read<u64>(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<tap_helper>(*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<tap_helper>(*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_notif>("addr_space_change", sol::no_constructor);
change_notif_type["remove"] = &addr_space_change_notif::remove;
auto tap_type = sol().registry().new_usertype<tap_helper>("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<address_map>("addrmap", sol::no_constructor);
addrmap_type["spacenum"] = sol::readonly(&address_map::m_spacenum);
addrmap_type["device"] = sol::readonly(&address_map::m_device);