Fix documentation
This commit is contained in:
1 parent
a317c07124
commit
ab21b3f30d
27 files changed
+2137
-1043
No files matched your search
@@ -1,79 +1,126 @@
|
||||
/* hot_reload.h
|
||||
/**
|
||||
* @file hot_reload.h
|
||||
* @brief Public C API for the hot-reload library.
|
||||
*
|
||||
* Public C API for the hot-reload library -- see cpp-style-guide.md
|
||||
* Part II (C API Style) for the naming/formatting rules this header
|
||||
* follows, and section 11 ("Runtime Loading") for why this boundary is
|
||||
* plain C in the first place: anything crossing a dlopen()/LoadLibrary()
|
||||
* boundary must have C linkage, so this is exactly the kind of surface
|
||||
* that section calls out.
|
||||
* See @c cpp-style-guide.md Part II (C API Style) for the naming/formatting
|
||||
* rules this header follows, and section 11 ("Runtime Loading") for why
|
||||
* this boundary is plain C in the first place: anything crossing a
|
||||
* `dlopen()`/`LoadLibrary()` boundary must have C linkage, so this is
|
||||
* exactly the kind of surface that section calls out.
|
||||
*
|
||||
* WHAT THIS LIBRARY DOES
|
||||
* -----------------------
|
||||
* hot_reload_load() loads a shared library (.so/.dll) under a logical
|
||||
* name. hot_reload_reload() loads a NEW build of that same shared
|
||||
* library and, for every function the two builds have in common,
|
||||
* rewrites the FIRST FEW BYTES of the old function in memory into a
|
||||
* jump to the new function -- so every existing call site (anyone who
|
||||
* already resolved the old function's address, including via a plain
|
||||
* C function pointer) transparently starts running the new code,
|
||||
* without restarting the process.
|
||||
* @section hotreload_what_it_does What this library does
|
||||
*
|
||||
* WHAT THIS LIBRARY DELIBERATELY DOES NOT DO
|
||||
* --------------------------------------------
|
||||
* It never patches anything that isn't executable code. A symbol is
|
||||
* only ever a patch candidate if the loader's own symbol table marks
|
||||
* it as a function (ELF STT_FUNC / a PE export whose address falls
|
||||
* inside an executable section) -- see the design note in
|
||||
* SymbolTable.hpp for exactly how that's determined. Global variables,
|
||||
* class/struct instances, vtables, and anything else classified as
|
||||
* data are always left alone, on both the old and the new module --
|
||||
* there is no code path in this library that ever writes to a data
|
||||
* symbol. This is a mechanical guarantee from how symbols are
|
||||
* classified, not a naming convention you opt into.
|
||||
* hot_reload_load() loads a shared library (`.so`/`.dll`) under a logical
|
||||
* name. hot_reload_reload() loads a *new* build of that same shared
|
||||
* library and, for every function the two builds have in common, rewrites
|
||||
* the first few bytes of the old function in memory into a jump to the
|
||||
* new function -- so every existing call site (anyone who already
|
||||
* resolved the old function's address, including via a plain C function
|
||||
* pointer) transparently starts running the new code, without restarting
|
||||
* the process.
|
||||
*
|
||||
* It also cannot verify that a hot-reloadable function's SIGNATURE
|
||||
* @section hotreload_what_it_wont What this library deliberately does not do
|
||||
*
|
||||
* It never patches anything that isn't executable code. A symbol is only
|
||||
* ever a patch candidate if the loader's own symbol table marks it as a
|
||||
* function (ELF `STT_FUNC` / a PE export whose address falls inside an
|
||||
* executable section) -- see the design note at the top of
|
||||
* `SymbolTable.cpp` (and detail::ExportedSymbol::isFunction) for exactly
|
||||
* how that's determined. Global variables, class/struct
|
||||
* instances, vtables, and anything else classified as data are always
|
||||
* left alone, on both the old and the new module -- there is no code path
|
||||
* in this library that ever writes to a data symbol. This is a mechanical
|
||||
* guarantee from how symbols are classified, not a naming convention you
|
||||
* opt into.
|
||||
*
|
||||
* It also cannot verify that a hot-reloadable function's *signature*
|
||||
* hasn't changed. Patching only ever redirects control flow at a fixed
|
||||
* entry point; it has no way to know, from a symbol table alone, that
|
||||
* `void tick(int)` became `void tick(int, int)` between builds. If a
|
||||
* function's signature changes, give it a new exported name (so the
|
||||
* old symbol is treated as removed and the new one as added -- see
|
||||
* HOT_RELOAD_SYMBOL_REMOVED below) rather than reusing the old name.
|
||||
* See the top-level README for the full list of hazards this library
|
||||
* cannot detect for you.
|
||||
* function's signature changes, give it a new exported name (so the old
|
||||
* symbol is treated as removed and the new one as added -- see
|
||||
* ::HOT_RELOAD_SYMBOL_REMOVED) rather than reusing the old name. See the
|
||||
* "Limitations" section of the top-level README (and "What this cannot
|
||||
* detect" in `source/HotReload/README.md`) for the full list of hazards
|
||||
* this library cannot detect for you.
|
||||
*
|
||||
* @section hotreload_requirements Requirements on a hot-reloadable module
|
||||
*
|
||||
* REQUIREMENTS ON A HOT-RELOADABLE MODULE
|
||||
* ------------------------------------------
|
||||
* Matching an old build's function up with the new build's requires a
|
||||
* name that is stable across recompiles. extern "C" linkage -- see
|
||||
* cpp-style-guide.md section 11 -- is the simplest way to get one: no
|
||||
* name that is stable across recompiles. `extern "C"` linkage -- see
|
||||
* `cpp-style-guide.md` section 11 -- is the simplest way to get one: no
|
||||
* mangling at all, so the name never depends on the compiler, its
|
||||
* version, or the function's signature.
|
||||
*
|
||||
* A real, unmodified C++ mangled name works too, and needs no special
|
||||
* handling by this library -- classification only ever looks at a
|
||||
* symbol's type and section, never at what its name looks like, so an
|
||||
* ordinary out-of-line method, a virtual method (redirected wherever
|
||||
* it's reached from -- directly or through a vtable slot, since both
|
||||
* hold the same address), and an operator overload all patch exactly
|
||||
* like any extern "C" function. What extern "C" actually buys you is
|
||||
* a name you can type by hand into hot_reload_get_symbol() and a
|
||||
* guarantee that it won't change if you rebuild with a different
|
||||
* compiler; a mangled name gives up both of those, only stays stable
|
||||
* within one ABI family (GCC and Clang share the Itanium C++ ABI;
|
||||
* MSVC does not), and -- the one hazard that has nothing to do with
|
||||
* this library and everything to do with what "hot-reloadable" can
|
||||
* even mean -- only exists to patch at all if the compiler actually
|
||||
* emitted an out-of-line body for it, which an implicitly-inline
|
||||
* method defined in a class body, or a template instantiated at the
|
||||
* call site, generally will not. See the top-level README's "Hot
|
||||
* reloading C++ symbols" section for the full picture, including how
|
||||
* to get a readable name for logging with hot_reload_demangle().
|
||||
* ordinary out-of-line method, a virtual method (redirected wherever it's
|
||||
* reached from -- directly or through a vtable slot, since both hold the
|
||||
* same address), and an operator overload all patch exactly like any
|
||||
* `extern "C"` function. What `extern "C"` actually buys you is a name
|
||||
* you can type by hand into hot_reload_get_symbol() and a guarantee that
|
||||
* it won't change if you rebuild with a different compiler; a mangled
|
||||
* name gives up both of those, only stays stable within one ABI family
|
||||
* (GCC and Clang share the Itanium C++ ABI; MSVC does not), and -- the
|
||||
* one hazard that has nothing to do with this library and everything to
|
||||
* do with what "hot-reloadable" can even mean -- only exists to patch at
|
||||
* all if the compiler actually emitted an out-of-line body for it, which
|
||||
* an implicitly-inline method defined in a class body, or a template
|
||||
* instantiated at the call site, generally will not. See the "Hot
|
||||
* reloading C++ symbols" section of `source/HotReload/README.md` for the
|
||||
* full picture, including how to get a readable name for logging with
|
||||
* hot_reload_demangle().
|
||||
*
|
||||
* Every function the module exports needs room at its entry point for the
|
||||
* jump this library writes over it. Compile the module with
|
||||
* `-fpatchable-function-entry=32,0` (GCC/Clang) or `/hotpatch` (MSVC) to
|
||||
* guarantee that; without it, a function too small to hold the jump is
|
||||
* skipped and reported as ::HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL rather than
|
||||
* patched. Also avoid identical-code-folding linker options (MSVC
|
||||
* `/OPT:ICF`, `--icf` in gold/lld) for the module: two different exported
|
||||
* functions folded into one address can only be patched to one target.
|
||||
*
|
||||
* @section hotreload_lifetime What carries over a reload -- and what does not
|
||||
*
|
||||
* Only *code* is redirected. Every build of the module is a separate
|
||||
* mapping with its own copy of everything else, so:
|
||||
* - Global and `static` variables are not carried over. Code that runs
|
||||
* after a reload uses the *new* build's copies, freshly initialised;
|
||||
* keep state you need to survive in memory the host owns and pass it in.
|
||||
* - Static constructors of the new build run when it is loaded (on every
|
||||
* reload); static destructors of every version run when the context is
|
||||
* destroyed.
|
||||
* - Every version stays loaded until hot_reload_context_destroy(). Old
|
||||
* entry points keep working (they are now jumps), one extra hop per
|
||||
* reload; destroying the context unloads them all, so no pointer into
|
||||
* any of the modules may be used afterwards.
|
||||
*
|
||||
* @section hotreload_threads Threading
|
||||
*
|
||||
* A context is not thread-safe: serialize all calls that use one context.
|
||||
* Call hot_reload_reload() from a quiescent point -- one where no thread is
|
||||
* executing inside the module's functions -- since a function that is being
|
||||
* executed while its entry bytes are rewritten cannot be protected.
|
||||
*/
|
||||
#ifndef HOT_RELOAD_H
|
||||
#define HOT_RELOAD_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* @def HOT_RELOAD_API
|
||||
* @brief Marks every function of this library's own C API.
|
||||
*
|
||||
* Expands to nothing in the default configuration, where the library is linked statically.
|
||||
* Define `HOT_RELOAD_BUILD_SHARED` while building the library as a shared library, and
|
||||
* `HOT_RELOAD_USE_SHARED` while compiling code that uses such a build, to get
|
||||
* `__declspec(dllexport)`/`__declspec(dllimport)` on Windows or default symbol visibility
|
||||
* elsewhere.
|
||||
*
|
||||
* Not to be confused with ::HOT_RELOAD_EXPORT, which marks symbols in a hot-reloadable
|
||||
* *module* rather than in this library.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#ifdef HOT_RELOAD_BUILD_SHARED
|
||||
#define HOT_RELOAD_API __declspec(dllexport)
|
||||
@@ -90,23 +137,29 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Marks a single declaration for export under
|
||||
* hot_reload_restrict_exports() (cmake/HotReload.cmake) without
|
||||
* otherwise changing its linkage: pair with extern "C" yourself for a
|
||||
* free function you want a stable, human-typable name for (see this
|
||||
* file's own comment on when you'd want that) -- extern "C" first,
|
||||
* then this macro, then the return type, e.g.
|
||||
* `extern "C" HOT_RELOAD_EXPORT int my_function(...)`, which is the
|
||||
* one ordering GCC/Clang accept without a warning here. Leave the
|
||||
* extern "C" off to keep a C++ class or method's real mangled name,
|
||||
* e.g. a virtual method a host only ever reaches through a vtable
|
||||
* slot (see the top-level README's "Hot reloading C++ symbols"
|
||||
* section).
|
||||
/**
|
||||
* @def HOT_RELOAD_EXPORT
|
||||
* @brief Marks a single declaration in a hot-reloadable *module* for export
|
||||
* under `hot_reload_restrict_exports()` (`cmake/HotReload.cmake`).
|
||||
*
|
||||
* hot_reload_restrict_exports() is a narrower export surface, not a
|
||||
* quieter one: see the README's "Why do std::string/std::vector
|
||||
* symbols show up as PATCHED?" section for why it does not, and
|
||||
* structurally cannot, suppress those specifically. */
|
||||
* This is for the plugins you hot-reload, not for this library's own API
|
||||
* above (::HOT_RELOAD_API). It does not otherwise change a declaration's
|
||||
* linkage: pair it with `extern "C"` yourself for a free function you want
|
||||
* a stable, human-typable name for (see @ref hotreload_requirements for
|
||||
* when you'd want that) -- `extern "C"` first, then this macro, then the
|
||||
* return type, e.g. `extern "C" HOT_RELOAD_EXPORT int my_function(...)`,
|
||||
* which is the one ordering GCC/Clang accept without a warning here. Leave
|
||||
* the `extern "C"` off to keep a C++ class or method's real mangled name,
|
||||
* e.g. a virtual method a host only ever reaches through a vtable slot
|
||||
* (see the "Hot reloading C++ symbols" section of
|
||||
* `source/HotReload/README.md`).
|
||||
*
|
||||
* @note `hot_reload_restrict_exports()` is a *narrower* export surface,
|
||||
* not a *quieter* one: see the "Why do std::string/std::vector
|
||||
* symbols show up as PATCHED?" section of `source/HotReload/README.md`
|
||||
* for why it does not, and structurally cannot, suppress those
|
||||
* specifically.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#define HOT_RELOAD_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
@@ -118,196 +171,394 @@ extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* Opaque -- see cpp-style-guide.md section 16 ("Do: use opaque
|
||||
* pointers ... to hide implementation details"). The real definition
|
||||
* is the C++ class hot_reload::HotReloadContext (HotReloadContext.hpp),
|
||||
* never visible from this header. */
|
||||
/**
|
||||
* @defgroup hotreload_types Types and callbacks
|
||||
* @brief The status codes, event/option structs, and callback signatures
|
||||
* used throughout the rest of this API.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Opaque handle to a hot-reload context.
|
||||
*
|
||||
* See `cpp-style-guide.md` section 16 ("Do: use opaque pointers ... to
|
||||
* hide implementation details"). The real definition is the C++ class
|
||||
* `hot_reload::HotReloadContext` (`HotReloadContext.hpp`), never visible
|
||||
* from this header.
|
||||
*/
|
||||
typedef struct HotReloadContext HotReloadContext;
|
||||
|
||||
/* Every fallible function returns one of these -- see section 16
|
||||
* ("Do: return an integer error code / status enum from every
|
||||
* fallible function"). */
|
||||
/**
|
||||
* @brief Result of any fallible call in this API.
|
||||
*
|
||||
* See `cpp-style-guide.md` section 16 ("Do: return an integer error
|
||||
* code / status enum from every fallible function"). Render one of these
|
||||
* as text with hot_reload_status_string(); for a fuller, platform-specific
|
||||
* explanation of a ::HOT_RELOAD_ERROR_LOAD_FAILED, see
|
||||
* hot_reload_last_error().
|
||||
*/
|
||||
typedef enum HotReloadStatus
|
||||
{
|
||||
/** Success. */
|
||||
HOT_RELOAD_OK = 0,
|
||||
|
||||
/* ctx, module_name, or path/new_path was NULL, or module_name
|
||||
* was not found for hot_reload_reload / hot_reload_get_symbol. */
|
||||
/**
|
||||
* A required argument (@c ctx, @c module_name, or @c path / @c new_path)
|
||||
* was `NULL`, or hot_reload_load() was called for a @c module_name that is
|
||||
* already loaded -- call hot_reload_reload() for that name instead.
|
||||
*/
|
||||
HOT_RELOAD_ERROR_INVALID_ARGUMENT,
|
||||
|
||||
/* dlopen()/LoadLibrary() (or the equivalent parse of the file on
|
||||
* disk) failed for the given path. On hot_reload_reload(),
|
||||
* this is non-destructive: the previously loaded version is left
|
||||
* running untouched, exactly as if reload had not been called --
|
||||
* see the file comment above HotReloadContext::reload(). Call
|
||||
* hot_reload_last_error() for the platform's own description of
|
||||
* why -- this one status code covers everything from a missing
|
||||
* file to a genuinely corrupt image to (a real case, found while
|
||||
* building this feature) a shared library with an unresolved
|
||||
* symbol reference, and those need very different fixes. */
|
||||
/**
|
||||
* `dlopen()`/`LoadLibrary()` (or the equivalent parse of the file on
|
||||
* disk) failed for the given path.
|
||||
*
|
||||
* On hot_reload_reload(), this is non-destructive: the previously
|
||||
* loaded version is left running untouched, exactly as if reload had
|
||||
* not been called. Call hot_reload_last_error() for the
|
||||
* platform's own description of why -- this one status code covers
|
||||
* everything from a missing file to a genuinely corrupt image to (a
|
||||
* real case, found while building this feature) a shared library
|
||||
* with an unresolved symbol reference, and those need very different
|
||||
* fixes.
|
||||
*
|
||||
* It is also reported when the file could not be copied to its staging
|
||||
* name (see hot_reload_load()): the source is unreadable, or the directory
|
||||
* it lives in is not writable.
|
||||
*/
|
||||
HOT_RELOAD_ERROR_LOAD_FAILED,
|
||||
|
||||
/* hot_reload_reload() was called for a module_name that was never
|
||||
* hot_reload_load()'ed first, so there is no "old" version to
|
||||
* diff against. Call hot_reload_load() once, up front. */
|
||||
/**
|
||||
* hot_reload_reload() was called for a @c module_name that was never
|
||||
* hot_reload_load()'ed first, so there is no "old" version to diff
|
||||
* against. Call hot_reload_load() once, up front.
|
||||
*/
|
||||
HOT_RELOAD_ERROR_NO_PREVIOUS_VERSION,
|
||||
|
||||
/* This process's CPU architecture has no patch backend compiled
|
||||
* in (see Architecture.hpp -- currently x86, x86-64, and ARM64).
|
||||
* Nothing was patched; hot_reload_load() still succeeded, you
|
||||
* just cannot hot_reload_reload() on this target. */
|
||||
/**
|
||||
* This process's CPU architecture has no patch backend compiled in
|
||||
* (see `Architecture.hpp` -- currently x86, x86-64, and ARM64).
|
||||
* Nothing was patched; hot_reload_load() still succeeded, you just
|
||||
* cannot hot_reload_reload() on this target.
|
||||
*/
|
||||
HOT_RELOAD_ERROR_UNSUPPORTED_ARCHITECTURE
|
||||
} HotReloadStatus;
|
||||
|
||||
/* Reported once per matched/unmatched symbol during hot_reload_reload()
|
||||
* via HotReloadOptions::on_symbol_event, so the host application can
|
||||
* log (or assert on, in a test) exactly what happened -- nothing here
|
||||
* is silent. */
|
||||
/**
|
||||
* @brief What happened to one symbol during a hot_reload_reload() call.
|
||||
*
|
||||
* Reported once per matched/unmatched symbol via
|
||||
* HotReloadOptions::on_symbol_event, so the host application can log (or
|
||||
* assert on, in a test) exactly what happened -- nothing here is silent.
|
||||
*/
|
||||
typedef enum HotReloadSymbolEvent
|
||||
{
|
||||
/* Existed in both builds and was classified as code in both --
|
||||
* the old function's entry now jumps to the new one. */
|
||||
/**
|
||||
* Existed in both builds and was classified as code in both -- the
|
||||
* old function's entry now jumps to the new one.
|
||||
*/
|
||||
HOT_RELOAD_SYMBOL_PATCHED,
|
||||
|
||||
/* Exists in the new build only. Nothing to patch -- it becomes
|
||||
* reachable the normal way, e.g. via hot_reload_get_symbol(). */
|
||||
/**
|
||||
* Exists in the new build only. Nothing to patch -- it becomes
|
||||
* reachable the normal way, e.g. via hot_reload_get_symbol().
|
||||
*/
|
||||
HOT_RELOAD_SYMBOL_ADDED,
|
||||
|
||||
/* Existed in the old build, missing from the new one. Per
|
||||
* cpp-style-guide.md's spirit of never leaving a caller with an
|
||||
/**
|
||||
* Existed in the old build, missing from the new one.
|
||||
*
|
||||
* Per `cpp-style-guide.md`'s spirit of never leaving a caller with an
|
||||
* ambiguous, half-updated state: the old implementation is left
|
||||
* running as-is unless HotReloadOptions::on_deleted_symbol
|
||||
* supplies a stub to redirect to instead -- see that field. */
|
||||
* running as-is unless HotReloadOptions::on_deleted_symbol supplies a
|
||||
* stub to redirect to instead -- see that field.
|
||||
*/
|
||||
HOT_RELOAD_SYMBOL_REMOVED,
|
||||
|
||||
/* Present in both builds by name, but the symbol table classifies
|
||||
* it as data (or something else non-function) in either build --
|
||||
* e.g. an accidentally-exported global. Never touched, in either
|
||||
* direction. This is the mechanical half of the "only executable
|
||||
* code changes" guarantee described at the top of this file. */
|
||||
/**
|
||||
* Present in both builds by name, but the symbol table classifies it
|
||||
* as data (or something else non-function) in either build -- e.g.
|
||||
* an accidentally-exported global.
|
||||
*
|
||||
* Never touched, in either direction. This is the mechanical half of
|
||||
* the "only executable code changes" guarantee described in
|
||||
* @ref hotreload_what_it_wont.
|
||||
*/
|
||||
HOT_RELOAD_SYMBOL_SKIPPED_NOT_CODE,
|
||||
|
||||
/* Classified as a function in both builds, but the OLD function's
|
||||
* compiled size is smaller than the number of bytes this
|
||||
* platform's jump sequence needs to write -- patching it would
|
||||
* overwrite whatever comes after it in memory. Skipped rather
|
||||
* than guessed; see hot_reload_enable_patchable_functions() in
|
||||
* cmake/HotReload.cmake for how to stop this from happening. */
|
||||
/**
|
||||
* Classified as a function in both builds, but the old function's
|
||||
* compiled size is smaller than the number of bytes this platform's
|
||||
* jump sequence needs to write -- patching it would overwrite
|
||||
* whatever comes after it in memory.
|
||||
*
|
||||
* Skipped rather than guessed. Compile the module with
|
||||
* `-fpatchable-function-entry=32,0` (GCC/Clang) or `/hotpatch` (MSVC) to
|
||||
* stop this from happening; `hot_reload_enable_patchable_functions()` in
|
||||
* `cmake/HotReload.cmake` applies whichever one your compiler needs.
|
||||
*/
|
||||
HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL,
|
||||
|
||||
/* Classified as a function in both builds, correctly sized, but
|
||||
* the OS refused to make its page temporarily writable (mprotect /
|
||||
* VirtualProtect failed -- e.g. a hardened environment that denies
|
||||
* W^X transitions). Rare in a normal developer setup; reported
|
||||
* rather than silently ignored either way. */
|
||||
/**
|
||||
* Classified as a function in both builds, correctly sized, but the
|
||||
* OS refused to make its page temporarily writable (`mprotect` /
|
||||
* `VirtualProtect` failed -- e.g. a hardened environment that denies
|
||||
* W^X transitions).
|
||||
*
|
||||
* Rare in a normal developer setup; reported rather than silently
|
||||
* ignored either way.
|
||||
*/
|
||||
HOT_RELOAD_SYMBOL_SKIPPED_UNWRITABLE
|
||||
} HotReloadSymbolEvent;
|
||||
|
||||
/* One event report, passed to HotReloadOptions::on_symbol_event.
|
||||
* old_address/new_address are NULL when not applicable (e.g.
|
||||
* old_address is NULL for HOT_RELOAD_SYMBOL_ADDED). */
|
||||
/**
|
||||
* @brief One symbol's outcome, passed to HotReloadOptions::on_symbol_event.
|
||||
*/
|
||||
typedef struct HotReloadSymbolEventInfo
|
||||
{
|
||||
const char* name;
|
||||
HotReloadSymbolEvent kind;
|
||||
void* old_address;
|
||||
void* new_address;
|
||||
const char* name; /**< The symbol's real name (mangled, if it's a C++ symbol -- see hot_reload_demangle()). */
|
||||
HotReloadSymbolEvent kind; /**< What happened to it -- see ::HotReloadSymbolEvent. */
|
||||
void* old_address; /**< The symbol's address in the previous build, or `NULL` if not applicable (e.g. ::HOT_RELOAD_SYMBOL_ADDED). */
|
||||
void* new_address; /**< The symbol's address in the new build, or `NULL` if not applicable (e.g. ::HOT_RELOAD_SYMBOL_REMOVED). */
|
||||
} HotReloadSymbolEventInfo;
|
||||
|
||||
/**
|
||||
* @brief Callback invoked once per symbol on every hot_reload_reload() call.
|
||||
*
|
||||
* Called synchronously, on the thread that called hot_reload_reload(), while the reload is in
|
||||
* progress.
|
||||
*
|
||||
* @param event Describes one symbol's outcome; valid only for the duration of the call.
|
||||
* @param user_data Whatever was passed as HotReloadOptions::user_data, untouched.
|
||||
*
|
||||
* @warning Do not call hot_reload_load() or hot_reload_reload() on the same context from inside
|
||||
* this callback.
|
||||
*/
|
||||
typedef void (*HotReloadSymbolEventFn)(const HotReloadSymbolEventInfo* event, void* user_data);
|
||||
|
||||
/* Called instead of patching, once per HOT_RELOAD_SYMBOL_REMOVED
|
||||
* event, so the host can catch stale references during development
|
||||
* instead of them silently continuing to run old code forever. Return
|
||||
* the address of a replacement to redirect the old function to (its
|
||||
* only safe use is a fixed-signature stub -- e.g. `void stub(void)`--
|
||||
* that logs/asserts; this library has no way to know the deleted
|
||||
* function's real signature, so redirecting to anything that reads
|
||||
* arguments the caller didn't pass is undefined behavior, exactly
|
||||
* like calling any function through the wrong function-pointer type).
|
||||
* Return NULL (or pass a NULL callback in HotReloadOptions to begin
|
||||
* with) to leave the old implementation running untouched -- the
|
||||
* default, and the safe choice unless you specifically want deleted
|
||||
* functions to be loud. */
|
||||
/**
|
||||
* @brief Callback invoked instead of patching, once per ::HOT_RELOAD_SYMBOL_REMOVED event.
|
||||
*
|
||||
* Lets the host catch stale references during development instead of them
|
||||
* silently continuing to run old code forever.
|
||||
*
|
||||
* Called synchronously, on the thread that called hot_reload_reload(), while the reload is in
|
||||
* progress; like HotReloadSymbolEventFn, it must not call hot_reload_load() or
|
||||
* hot_reload_reload() on the same context.
|
||||
*
|
||||
* @param symbol_name The removed function's real (possibly mangled) name.
|
||||
* @param user_data Whatever was passed as HotReloadOptions::user_data, untouched.
|
||||
* @return The address of a replacement to redirect the old function to, or
|
||||
* `NULL` to leave the old implementation running untouched (the
|
||||
* default, and the safe choice unless you specifically want
|
||||
* deleted functions to be loud).
|
||||
*
|
||||
* @warning A non-`NULL` return's only safe use is a fixed-signature stub
|
||||
* (e.g. `void stub(void)`) that logs/asserts; this library has
|
||||
* no way to know the deleted function's real signature, so
|
||||
* redirecting to anything that reads arguments the caller didn't
|
||||
* pass is undefined behavior, exactly like calling any function
|
||||
* through the wrong function-pointer type.
|
||||
*/
|
||||
typedef void* (*HotReloadDeletedSymbolFn)(const char* symbol_name, void* user_data);
|
||||
|
||||
/* Passed to hot_reload_context_create(); NULL is a valid options
|
||||
* pointer meaning "no callbacks, default policy for everything". */
|
||||
/**
|
||||
* @brief Options passed to hot_reload_context_create().
|
||||
*
|
||||
* A `NULL` options pointer is valid and means "no callbacks, default
|
||||
* policy for everything."
|
||||
*/
|
||||
typedef struct HotReloadOptions
|
||||
{
|
||||
HotReloadSymbolEventFn on_symbol_event; /* nullable */
|
||||
HotReloadDeletedSymbolFn on_deleted_symbol; /* nullable */
|
||||
void* user_data; /* passed back to both callbacks, untouched */
|
||||
HotReloadSymbolEventFn on_symbol_event; /**< Nullable. See ::HotReloadSymbolEventFn. */
|
||||
HotReloadDeletedSymbolFn on_deleted_symbol; /**< Nullable. See ::HotReloadDeletedSymbolFn. */
|
||||
void* user_data; /**< Passed back to both callbacks, untouched. */
|
||||
} HotReloadOptions;
|
||||
|
||||
/* Caller owns the returned pointer and must call
|
||||
* hot_reload_context_destroy() -- see cpp-style-guide.md section 16
|
||||
* ("Document ownership transfer explicitly"). options may be NULL. */
|
||||
/** @} */ /* end of hotreload_types */
|
||||
|
||||
/**
|
||||
* @defgroup hotreload_lifecycle Context lifecycle
|
||||
* @brief Creating and destroying a hot-reload context.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Creates a new hot-reload context.
|
||||
* @param options Nullable; see ::HotReloadOptions. Its contents are copied, so it need not
|
||||
* outlive this call.
|
||||
* @return A new context, or `NULL` if allocation failed. The caller owns it and must call
|
||||
* hot_reload_context_destroy() (see `cpp-style-guide.md` section 16,
|
||||
* "Document ownership transfer explicitly").
|
||||
*/
|
||||
HOT_RELOAD_API HotReloadContext* hot_reload_context_create(const HotReloadOptions* options);
|
||||
|
||||
/**
|
||||
* @brief Destroys a context created by hot_reload_context_create().
|
||||
*
|
||||
* Unloads every version of every module the context loaded (running their static destructors).
|
||||
* From then on, every pointer obtained through this context -- functions, data, and the old
|
||||
* entry points that had been patched to jump into newer versions -- is dangling and must not be
|
||||
* used. Destroy a context only when nothing can still call into its modules.
|
||||
*
|
||||
* @param ctx The context to destroy. Safe to pass `NULL`.
|
||||
*/
|
||||
HOT_RELOAD_API void hot_reload_context_destroy(HotReloadContext* ctx);
|
||||
|
||||
/* Loads path for the first time under module_name -- an arbitrary
|
||||
* caller-chosen key used to look the module back up in later
|
||||
* hot_reload_reload()/hot_reload_get_symbol() calls; it does not need
|
||||
* to match anything on disk. Returns HOT_RELOAD_ERROR_INVALID_ARGUMENT
|
||||
* if module_name was already loaded -- call hot_reload_reload()
|
||||
* instead of hot_reload_load() again for the same name. */
|
||||
/** @} */ /* end of hotreload_lifecycle */
|
||||
|
||||
/**
|
||||
* @defgroup hotreload_loading Loading and reloading
|
||||
* @brief The core operations: register a module, hot-reload it, and
|
||||
* resolve its symbols.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Loads a module for the first time under a logical name.
|
||||
*
|
||||
* The file is not loaded in place: it is first copied to a uniquely named sibling
|
||||
* (`<path>.hotreload.<module_name>.<n>`) and that copy is loaded, which keeps a rebuild that
|
||||
* overwrites @c path in place from being mistaken for the already-loaded version. The copy is
|
||||
* deleted again right after loading on POSIX; on Windows it stays until the module is unloaded
|
||||
* (see the top-level README). This means the directory containing @c path must be writable.
|
||||
*
|
||||
* The module's static constructors run as part of loading it.
|
||||
*
|
||||
* @note If the module's symbol table cannot be read (for instance a `.so` whose section headers
|
||||
* were stripped), loading still succeeds but the module exports nothing as far as this
|
||||
* library is concerned: hot_reload_get_symbol() finds nothing and a later
|
||||
* hot_reload_reload() has nothing to patch.
|
||||
*
|
||||
* @param ctx The context to load into.
|
||||
* @param module_name An arbitrary caller-chosen key used to look the
|
||||
* module back up in later hot_reload_reload()/
|
||||
* hot_reload_get_symbol() calls; it does not need to match
|
||||
* anything on disk.
|
||||
* @param path Path to the shared library (`.so`/`.dll`) to load. Always treated as a file path:
|
||||
* a bare file name such as `plugin.so` means the file of that name in the current
|
||||
* working directory (it is never looked up through the library search path).
|
||||
* @return ::HOT_RELOAD_OK, or ::HOT_RELOAD_ERROR_INVALID_ARGUMENT if
|
||||
* an argument was `NULL` or @c module_name was already loaded (call
|
||||
* hot_reload_reload() instead of hot_reload_load() again for the same
|
||||
* name), or ::HOT_RELOAD_ERROR_LOAD_FAILED (see hot_reload_last_error()
|
||||
* for why).
|
||||
*/
|
||||
HOT_RELOAD_API HotReloadStatus hot_reload_load(HotReloadContext* ctx, const char* module_name, const char* path);
|
||||
|
||||
/* Loads a NEW build of the module previously registered under
|
||||
* module_name, diffs its exported functions against the previous
|
||||
* version, and patches every one that is safe to patch in place --
|
||||
* see HotReloadSymbolEvent above for exactly what happens to each
|
||||
* kind of change. On any HOT_RELOAD_ERROR_* return, nothing was
|
||||
* changed: the previous version keeps running exactly as it was. */
|
||||
/**
|
||||
* @brief Hot-reloads a module previously registered with hot_reload_load().
|
||||
*
|
||||
* Loads a new build of the module, diffs its exported functions against
|
||||
* the previous version, and patches every one that is safe to patch in
|
||||
* place -- see ::HotReloadSymbolEvent for exactly what happens to each
|
||||
* kind of change. The previous version stays loaded (its entry points now
|
||||
* jump into the new one), and the new build's static constructors run.
|
||||
* If the new build exports no functions at all -- including when its symbol
|
||||
* table could not be read -- every previously exported function is reported
|
||||
* as ::HOT_RELOAD_SYMBOL_REMOVED.
|
||||
*
|
||||
* Call this from a quiescent point (see @ref hotreload_threads), and see
|
||||
* @ref hotreload_lifetime for what does not carry over. @c new_path follows
|
||||
* the same rules as @c path in hot_reload_load().
|
||||
*
|
||||
* @param ctx The context @c module_name was hot_reload_load()'ed into.
|
||||
* @param module_name The same logical name passed to hot_reload_load().
|
||||
* @param new_path Path to the new build of the shared library.
|
||||
* @return ::HOT_RELOAD_OK, or an `HOT_RELOAD_ERROR_*` code. On any error,
|
||||
* nothing was changed: the previous version keeps running exactly
|
||||
* as it was.
|
||||
*/
|
||||
HOT_RELOAD_API HotReloadStatus hot_reload_reload(HotReloadContext* ctx, const char* module_name, const char* new_path);
|
||||
|
||||
/* Resolves symbol_name in module_name's CURRENT build (i.e. always
|
||||
* the freshest one loaded, patched or not) -- for calling a function
|
||||
* that did not exist yet when the host first started, such as one
|
||||
* reported via HOT_RELOAD_SYMBOL_ADDED. Returns NULL if module_name
|
||||
* is unknown or symbol_name is not an exported function in it. */
|
||||
/**
|
||||
* @brief Resolves a symbol in a module's current build.
|
||||
*
|
||||
* Always resolves against the freshest build loaded for @c module_name,
|
||||
* patched or not -- useful for calling a function that did not exist yet
|
||||
* when the host first started, such as one reported via
|
||||
* ::HOT_RELOAD_SYMBOL_ADDED. Any exported symbol resolves, data included
|
||||
* (a global variable, say); only functions are ever patched.
|
||||
*
|
||||
* @param ctx The context @c module_name was hot_reload_load()'ed into.
|
||||
* @param module_name The module to resolve @c symbol_name in.
|
||||
* @param symbol_name The symbol's real (possibly mangled) name.
|
||||
* @return The symbol's current address, or `NULL` if an argument is `NULL`,
|
||||
* @c module_name is unknown, or @c symbol_name is not exported by
|
||||
* the module's newest build.
|
||||
*/
|
||||
HOT_RELOAD_API void* hot_reload_get_symbol(HotReloadContext* ctx, const char* module_name, const char* symbol_name);
|
||||
|
||||
/* Only meaningful immediately after hot_reload_load() or
|
||||
* hot_reload_reload() on this SAME ctx returned something other than
|
||||
* HOT_RELOAD_OK: the underlying platform's own description of why --
|
||||
* dlerror() on POSIX, the formatted message for GetLastError() on
|
||||
* Windows -- e.g. "undefined symbol: _ZN6Widget4tickEi" for a shared
|
||||
* library with an unresolved reference (a real, confusing-until-you-
|
||||
* see-this-message case: a non-inline static class member that is
|
||||
* declared but never given an out-of-class definition compiles fine
|
||||
* and links fine into a shared library -- the linker allows a shared
|
||||
* library to keep undefined symbols by default -- and only fails,
|
||||
* right here, once something ODR-uses it and the loader tries to
|
||||
* resolve it for real). Overwritten by the next
|
||||
* hot_reload_load()/hot_reload_reload() call on this ctx; copy it out
|
||||
* immediately if you need to keep it past that point (e.g. for a log
|
||||
* line). Empty if the platform gave no further detail, or if the
|
||||
* failure never reached the platform loader at all (e.g. a NULL
|
||||
* argument, which is HOT_RELOAD_ERROR_INVALID_ARGUMENT before
|
||||
* anything platform-specific happens). A static string internally --
|
||||
* never free() this. */
|
||||
/** @} */ /* end of hotreload_loading */
|
||||
|
||||
/**
|
||||
* @defgroup hotreload_diagnostics Diagnostics
|
||||
* @brief Turning a status code or a mangled name into something readable.
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief The platform's own explanation for the most recent failure on this context.
|
||||
*
|
||||
* Only meaningful immediately after hot_reload_load() or
|
||||
* hot_reload_reload() on this *same* @c ctx returned something other than
|
||||
* ::HOT_RELOAD_OK: the underlying platform's own description of why --
|
||||
* `dlerror()` on POSIX, the formatted message for `GetLastError()` on
|
||||
* Windows -- e.g. `"undefined symbol: _ZN6Widget4tickEi"` for a shared
|
||||
* library with an unresolved reference (a real, confusing-until-you-see-
|
||||
* this-message case: a non-inline static class member that is declared
|
||||
* but never given an out-of-class definition compiles fine and links fine
|
||||
* into a shared library -- the linker allows a shared library to keep
|
||||
* undefined symbols by default -- and only fails, right here, once
|
||||
* something ODR-uses it and the loader tries to resolve it for real).
|
||||
*
|
||||
* @param ctx The context to query.
|
||||
* @return The platform's error message. Empty if it gave no further
|
||||
* detail, or if the failure never reached the platform loader at
|
||||
* all (e.g. a `NULL` argument, which is
|
||||
* ::HOT_RELOAD_ERROR_INVALID_ARGUMENT before anything
|
||||
* platform-specific happens). Owned by @c ctx -- never `free()`
|
||||
* this. It stays valid only until the next hot_reload_load()/
|
||||
* hot_reload_reload() call on this @c ctx, or until the context is
|
||||
* destroyed; copy it out immediately if you need to keep it past
|
||||
* that point (e.g. for a log line).
|
||||
*/
|
||||
HOT_RELOAD_API const char* hot_reload_last_error(HotReloadContext* ctx);
|
||||
|
||||
/* A static string literal -- never free() this. */
|
||||
/**
|
||||
* @brief Renders a ::HotReloadStatus as text.
|
||||
* @param status The status to render.
|
||||
* @return A static string literal -- never `free()` this.
|
||||
*/
|
||||
HOT_RELOAD_API const char* hot_reload_status_string(HotReloadStatus status);
|
||||
|
||||
/* Renders name for a human to read: a real C++ mangled name (Itanium
|
||||
* on GCC/Clang/MinGW, MSVC's own scheme under MSVC -- whichever this
|
||||
* library itself was built with) becomes something like
|
||||
* "Widget::tick(int)"; anything else, including an ordinary extern "C"
|
||||
* name, comes back unchanged. Safe to call unconditionally on any
|
||||
* HotReloadSymbolEventInfo::name or hot_reload_get_symbol() key --
|
||||
* this never changes what name means for lookup purposes, it is only
|
||||
* ever applied when you ask for it, purely for display. Returns NULL
|
||||
* only if name itself is NULL or allocation fails; caller must free
|
||||
* a non-NULL result with hot_reload_free_string(). */
|
||||
/**
|
||||
* @brief Renders a symbol name for a human to read.
|
||||
*
|
||||
* A real C++ mangled name (Itanium on GCC/Clang/MinGW, MSVC's own scheme
|
||||
* under MSVC -- whichever this library itself was built with) becomes
|
||||
* something like `"Widget::tick(int)"`; anything else, including an
|
||||
* ordinary `extern "C"` name, comes back unchanged. Safe to call
|
||||
* unconditionally on any HotReloadSymbolEventInfo::name or
|
||||
* hot_reload_get_symbol() key -- this never changes what a name means for
|
||||
* lookup purposes, it is only ever applied when you ask for it, purely
|
||||
* for display.
|
||||
*
|
||||
* @param name The name to render.
|
||||
* @return The rendered name, or `NULL` if @c name itself is `NULL` or
|
||||
* allocation fails. Caller must free a non-`NULL` result with
|
||||
* hot_reload_free_string().
|
||||
*/
|
||||
HOT_RELOAD_API char* hot_reload_demangle(const char* name);
|
||||
|
||||
/**
|
||||
* @brief Frees a string returned by hot_reload_demangle().
|
||||
* @param str The string to free. Safe to pass `NULL`.
|
||||
*/
|
||||
HOT_RELOAD_API void hot_reload_free_string(char* str);
|
||||
|
||||
/** @} */ /* end of hotreload_diagnostics */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in new issue
Block a user