Skip to content

mr0x13f/odin-plugin-experiment

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Odin Plugin Experiment

This is a proof of concept for a plugin system for applications written in Odin. The idea is to use metaprogramming to allow plugins to make alterations to the application's source code, specifically by replacing procedures. The altered source code can then be recompiled on the user's machine.

This approach comes with some inherent limitations:

  • Plugins cannot change types
    • At least, without having every plugin break every other plugin
  • Replaced procedures become proc pointers
    • Means parapoly procedures (probably?) can't be replaced
    • Causes indirection
    • Prevents inlining
  • The application needs to be recompiled on the users machine
    • This requires Odin to be installed
    • On Windows, Odin also requires MSVC to be installed

Why replacing procedures?

This PoC is based on the idea of a plugin system and its API being built around function replacements.

I believe that the best way to do a plugin/addon/mod system is to allow for arbitrary replacing of functions. This frees application developers from having to design and define explicit "hook" points for plugins, and allows plugins to do things that the application developers hadn't thought of. Replaced functions should be able to call out to the original function. This allows plugins to, for example, "hook" into a function, do some arbitrary logic, and then call the original, effectively prepending logic to the start of a function. It can also be done the other way around to append logic to the end of a function. The original could be called conditionally, or the result of the original could be modified before being returned. These different ways of using function replacements means that just this one tool can be used to change almost anything about an application.

Implementation

This implementation splits the application into two parts: host and app. The host is the executable, which will call out to app as a dynamic library app.dll, which contains most of the actual application logic. Plugins are packages stored in the plugins/ directory. The host can use these plugins to create an altered version of the app source code, which will be written to app_altered/. This altered source code references the packages found in plugins/. The host can then use the Odin compiler to compile this altered source code into a dynamic library altered_app.dll. This dynamic library can then be loaded on-the-fly, replacing the original un-altered app.dll.

Plugins are packages of Odin code. These can have procedures marked with the @replace attribute, denoting procedures that should replace procedures from app. These replacement procedures also get a proc pointer called original, which allows the original version of the proc to be called. If one proc from app has multiple replacements, they can be diasy-chained with the original parameter. In this example, there are two plugins plugin_a and plugin_b, which both replace app.start(). They both log something, and then call original. This means that, for the plugin that is loaded first, original will be the actual initial procedure from app. For the plugin that is loaded second, original will refer to the replacement procedure from the first plugin. The symbol of the initial procedure, in this case app.start will refer to the plugin that is loaded last. When the app is started and app.start() is called by host, the two replacements procs from the plugins will log their stuff before finally calling out to the initial version of app.start().

This results in the following console output:

aaaaaaa       <-- logged by plugin_a, loaded second
bbbbbbb       <-- logged by plugin_b, loaded first
hello world   <-- logged by the initial proc from app

Local setup

Assuming Odin is installed and the terminal is bash:

source .envrc
# Build and run without plugins:
build run no_plugins
# Build and run with plugins:
build run

To-do

The current implementation is a proof of concept and lacks a bunch of functionality that would be required for this to be useful in practice.

Curently,

  • only one replacement can be made per plugin
  • plugins can't have sub-packages
  • procs inside sub-packages of app can't be replaced
  • procs from plugins can't be replaced (requires extra step in metaprogram to also create altered veplugins)
  • procs with returns types can't be replaced
  • procs with any kind of attribute, directive or calling conventions can't be replaced

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published