569 lines
25 KiB
C++
569 lines
25 KiB
C++
/**
|
|
* @file hot_reload.h
|
|
* @brief Public C API for the hot-reload library.
|
|
*
|
|
* 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.
|
|
*
|
|
* @section hotreload_what_it_does 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_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) 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
|
|
*
|
|
* 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
|
|
* 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 "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)
|
|
#elif defined(HOT_RELOAD_USE_SHARED)
|
|
#define HOT_RELOAD_API __declspec(dllimport)
|
|
#else
|
|
#define HOT_RELOAD_API
|
|
#endif
|
|
#else
|
|
#if defined(HOT_RELOAD_BUILD_SHARED) || defined(HOT_RELOAD_USE_SHARED)
|
|
#define HOT_RELOAD_API __attribute__((visibility("default")))
|
|
#else
|
|
#define HOT_RELOAD_API
|
|
#endif
|
|
#endif
|
|
|
|
/**
|
|
* @def HOT_RELOAD_EXPORT
|
|
* @brief Marks a single declaration in a hot-reloadable *module* for export
|
|
* under `hot_reload_restrict_exports()` (`cmake/HotReload.cmake`).
|
|
*
|
|
* 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
|
|
#define HOT_RELOAD_EXPORT __attribute__((visibility("default")))
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C"
|
|
{
|
|
#endif
|
|
|
|
/**
|
|
* @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 lives in
|
|
* `c-api/HotReloadApi.cpp`: a small wrapper struct that owns a
|
|
* `hot_reload::Context` (`Context.hpp`) by composition -- never a cast between unrelated
|
|
* types, and never visible from this header. New C++ code should generally prefer
|
|
* `hot_reload::Context` directly; see that header's own doc comment for why.
|
|
*/
|
|
typedef struct HotReloadContext HotReloadContext;
|
|
|
|
/**
|
|
* @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,
|
|
|
|
/**
|
|
* 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. 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 @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, ARM64, and ARM32).
|
|
* Nothing was patched; hot_reload_load() still succeeded, you just
|
|
* cannot hot_reload_reload() on this target.
|
|
*/
|
|
HOT_RELOAD_ERROR_UNSUPPORTED_ARCHITECTURE
|
|
} HotReloadStatus;
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
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().
|
|
*/
|
|
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
|
|
* 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.
|
|
*/
|
|
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 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. 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.
|
|
*/
|
|
HOT_RELOAD_SYMBOL_SKIPPED_UNWRITABLE
|
|
} HotReloadSymbolEvent;
|
|
|
|
/**
|
|
* @brief One symbol's outcome, passed to HotReloadOptions::on_symbol_event.
|
|
*/
|
|
typedef struct HotReloadSymbolEventInfo
|
|
{
|
|
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);
|
|
|
|
/**
|
|
* @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);
|
|
|
|
/**
|
|
* @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. See ::HotReloadSymbolEventFn. */
|
|
HotReloadDeletedSymbolFn on_deleted_symbol; /**< Nullable. See ::HotReloadDeletedSymbolFn. */
|
|
void* user_data; /**< Passed back to both callbacks, untouched. */
|
|
} HotReloadOptions;
|
|
|
|
/** @} */ /* 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);
|
|
|
|
/** @} */ /* 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);
|
|
|
|
/**
|
|
* @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);
|
|
|
|
/**
|
|
* @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);
|
|
|
|
/** @} */ /* 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);
|
|
|
|
/**
|
|
* @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);
|
|
|
|
/**
|
|
* @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
|
|
|
|
#endif /* HOT_RELOAD_H */
|