diff --git a/.gitignore b/.gitignore index 669b289..37d6368 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,7 @@ # Directories build/ + +# IDEs and LSP specific .vscode/ __pycache__/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3434e5f..e4312a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ include(cmake/defaults.cmake) add_subdirectory(third_party) add_subdirectory(scripts) +add_subdirectory(plugins) set(hSIM_LIB src/memory.cc src/machine.cc diff --git a/include/machine.hh b/include/machine.hh index 3cc2ebe..3255d2b 100644 --- a/include/machine.hh +++ b/include/machine.hh @@ -2,11 +2,14 @@ #define HSIM_MACHINE_INCLUDED #include +#include +#include #include "config.hh" #include "cpu_state.hh" #include "elf_loader.hh" #include "executors.hh" +#include "machine_event.hh" #include "memory.hh" namespace hsim { @@ -32,9 +35,18 @@ class Machine final { } } + void addEventConsumer(IEventConsumerHandle consumer) { + m_eventManager.attach(std::move(consumer)); + } + + template void notify(Args &&...args) { + m_eventManager.notify(std::forward(args)...); + } + private: std::unique_ptr m_state; Memory m_mem{}; + EventManager m_eventManager; }; } // namespace hsim diff --git a/include/machine_event.hh b/include/machine_event.hh new file mode 100644 index 0000000..b8b00e6 --- /dev/null +++ b/include/machine_event.hh @@ -0,0 +1,45 @@ +#ifndef HSIM_MACHINE_EVENT_INCLUDED +#define HSIM_MACHINE_EVENT_INCLUDED + +#include +#include +#include + +#include "support.hh" + +namespace hsim { + +struct MemRead {}; +struct MemWrite {}; +struct PreInsn {}; +struct PostInsn {}; + +class IEventConsumer { + public: + virtual ~IEventConsumer() = default; + virtual void handle(MemRead event, Addr addr) = 0; + virtual void handle(MemWrite event, Addr addr, Word value) = 0; + virtual void handle(PreInsn event, Word insn) = 0; + virtual void handle(PostInsn event, Word insn) = 0; +}; + +using IEventConsumerHandle = std::unique_ptr; +class EventManager final { + public: + template void notify(Args &&...args) { + for (auto &consumer : m_consumers) { + consumer->handle(Event{}, std::forward(args)...); + } + } + void attach(IEventConsumerHandle consumer) { + m_consumers.push_back(std::move(consumer)); + } + void detach(IEventConsumerHandle consumer) { m_consumers.remove(consumer); } + + private: + std::list m_consumers; +}; + +} // namespace hsim + +#endif // HSIM_MACHINE_EVENT_INCLUDED diff --git a/include/plugin.hh b/include/plugin.hh new file mode 100644 index 0000000..f6a71e3 --- /dev/null +++ b/include/plugin.hh @@ -0,0 +1,68 @@ +#ifndef HSIM_PLUGIN_INCLUDED +#define HSIM_PLUGIN_INCLUDED + +#include +#include +#include + +#include + +#include "machine_event.hh" +#include "so_loader.hh" +#include "support.hh" + +namespace hsim { + +class IPlugin { + public: + virtual ~IPlugin() = default; + virtual void handle(MemRead event, Addr addr) = 0; + virtual void handle(MemWrite event, Addr addr, Word value) = 0; + virtual void handle(PreInsn event, Word insn) = 0; + virtual void handle(PostInsn event, Word insn) = 0; +}; + +using LoadPluginFunc = IPlugin *(*)(const std::string &options); +using UnloadPluginFunc = void (*)(IPlugin *plugin); + +constexpr std::string kLoadPluginFuncName = "loadPlugin"; +constexpr std::string kUnloadPluginFuncName = "unloadPlugin"; + +// NOTE plugins should use this defines when declaring a function +#define HSIM_LOAD_PLUGIN_FUNC extern "C" hsim::IPlugin *loadPlugin +#define HSIM_UNLOAD_PLUGIN_FUNC extern "C" void unloadPlugin + +class PluginConsumer : public IEventConsumer { + public: + PluginConsumer(const std::filesystem::path &path, + const std::string &options) + : m_sharedLib{path, kLazy}, m_plugin{nullptr, nullptr} { + auto loadFunc = m_sharedLib.get(kLoadPluginFuncName); + auto unloadFunc = + m_sharedLib.get(kUnloadPluginFuncName); + + m_plugin = std::unique_ptr(loadFunc(options), + unloadFunc); + } + + void handle(MemRead event, Addr addr) override { + m_plugin->handle(event, addr); + } + void handle(MemWrite event, Addr addr, Word value) override { + m_plugin->handle(event, addr, value); + } + void handle(PreInsn event, Word insn) override { + m_plugin->handle(event, insn); + } + void handle(PostInsn event, Word insn) override { + m_plugin->handle(event, insn); + } + + private: + SharedLib m_sharedLib; + std::unique_ptr m_plugin; +}; + +} // namespace hsim + +#endif // HSIM_PLUGIN_INCLUDED diff --git a/include/so_loader.hh b/include/so_loader.hh new file mode 100644 index 0000000..1bdd360 --- /dev/null +++ b/include/so_loader.hh @@ -0,0 +1,54 @@ +#ifndef HSIM_SO_LOADER_INCLUDED +#define HSIM_SO_LOADER_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include + +namespace hsim { + +enum SharedLibMode : std::uint16_t { + kLazy = RTLD_LAZY, + kNow = RTLD_NOW, + kGlobal = RTLD_GLOBAL, + kLocal = RTLD_LOCAL, +}; + +template +concept PointerT = std::is_pointer_v; + +class SharedLib { + private: + struct DlCloser { + void operator()(void *handle) { dlclose(handle); } + }; + + public: + SharedLib(const std::filesystem::path &libPath, SharedLibMode mode) + : m_handle{dlopen(libPath.c_str(), mode)} { + if (m_handle == nullptr) { + throw std::runtime_error{dlerror()}; + } + } + template T get(const std::string &symbol) { + void *loadedSymbol = dlsym(m_handle.get(), symbol.c_str()); + if (loadedSymbol == nullptr) { + throw std::runtime_error{dlerror()}; + } + + return reinterpret_cast(loadedSymbol); + } + + private: + std::unique_ptr m_handle; + static_assert(sizeof(m_handle) == sizeof(void *)); +}; + +} // namespace hsim + +#endif // HSIM_SO_LOADER_INCLUDED diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt new file mode 100644 index 0000000..1230f7f --- /dev/null +++ b/plugins/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(simple_plugin SHARED + simple_plugin.cc +) + +target_include_directories(simple_plugin + PRIVATE + ${CMAKE_SOURCE_DIR}/include +) + +set(PLUGIN_OUTPUT_DIR "${CMAKE_BINARY_DIR}/plugins") +file(MAKE_DIRECTORY ${PLUGIN_OUTPUT_DIR}) + +set_target_properties(simple_plugin PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${PLUGIN_OUTPUT_DIR} +) diff --git a/plugins/simple_plugin.cc b/plugins/simple_plugin.cc new file mode 100644 index 0000000..74200b5 --- /dev/null +++ b/plugins/simple_plugin.cc @@ -0,0 +1,49 @@ +#include +#include +#include + +#include "machine_event.hh" +#include "plugin.hh" +#include "support.hh" + +namespace hsim { + +class SimplePlugin : public hsim::IPlugin { + public: + SimplePlugin(std::string name) : m_name{std::move(name)} { + std::cout << "SimplePlugin: " << m_name << std::endl; + } + + ~SimplePlugin() override { + std::cout << "~SimplePlugin: " << m_name << std::endl; + } + + void handle([[maybe_unused]] MemRead event, Addr addr) override { + std::cout << m_name << " MemRead: " << std::hex << addr << std::endl; + } + + void handle([[maybe_unused]] MemWrite event, Addr addr, + Word value) override { + std::cout << m_name << " MemWrite: " << std::hex << addr << " " << value + << std::endl; + } + + void handle([[maybe_unused]] PreInsn event, Word insn) override { + std::cout << m_name << " PreInsn: " << std::hex << insn << std::endl; + } + + void handle([[maybe_unused]] PostInsn event, Word insn) override { + std::cout << m_name << " PostInsn: " << std::hex << insn << std::endl; + } + + private: + std::string m_name; +}; + +HSIM_LOAD_PLUGIN_FUNC(const std::string &options) { + return new SimplePlugin{options}; +} + +HSIM_UNLOAD_PLUGIN_FUNC(SimplePlugin *plugin) { delete plugin; } + +} // namespace hsim diff --git a/src/main.cc b/src/main.cc index a246ab1..7769eb5 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,10 +1,14 @@ +#include +#include #include -#include +#include // #include #include "config.hh" #include "machine.hh" +#include "machine_event.hh" +#include "plugin.hh" int main(int argc, char **argv) try { CLI::App app("hSim: high Performance CPU Simulator"); @@ -14,9 +18,7 @@ int main(int argc, char **argv) try { ->required() ->check(CLI::ExistingFile); - app.add_option("--log", config.log_path, "Path to log file") - ->required() - ->check(CLI::ExistingFile); + app.add_option("--log", config.log_path, "Path to log file")->required(); app.add_flag("--dump-exec", config.dump_exec, "Option to enable dump of state on execution") @@ -32,6 +34,11 @@ int main(int argc, char **argv) try { machine.run(); + // std::filesystem::path pluginPath = "./build/plugins/libsimple_plugin.so"; + // machine.addEventConsumer( + // std::make_unique(pluginPath, "vova")); + // machine.notify(0, 0); + return 0; } catch (const std::exception &e) { std::cout << e.what() << '\n';