Remove unnecessarily stored data
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Hiload is an Elf hotreloader that patches C and C++ code live while
|
||||||
|
* developing, significantly tightening the traditional feedback loop.
|
||||||
|
*
|
||||||
|
* Currently not supported:
|
||||||
|
* - modifying modules loaded at runtime
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef HILOAD_H_
|
#ifndef HILOAD_H_
|
||||||
#define HILOAD_H_
|
#define HILOAD_H_
|
||||||
|
|
||||||
@@ -24,7 +32,7 @@ extern "C" {
|
|||||||
int hi_init(size_t n, const char **enabled_modules);
|
int hi_init(size_t n, const char **enabled_modules);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees allocated memory. Call when hiload is no longer used.
|
* Call when hiload is no longer needed.
|
||||||
*/
|
*/
|
||||||
void hi_deinit(void);
|
void hi_deinit(void);
|
||||||
|
|
||||||
31
src/files.c
31
src/files.c
@@ -1,7 +1,7 @@
|
|||||||
#include "files.h"
|
#include "files.h"
|
||||||
|
|
||||||
#include "logger.h"
|
|
||||||
#include "histring.h"
|
#include "histring.h"
|
||||||
|
#include "logger.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
char *file_to_str_dyn(const char *filename) {
|
char *file_to_str_dyn(const char *filename) {
|
||||||
|
|
||||||
char *s = hi_str_from_file(filename, 0, 0);
|
char *s = string_from_file(filename, 0, 0);
|
||||||
if (!s) {
|
if (!s) {
|
||||||
sc_log_error("Failed to read file: %s\n", filename);
|
sc_log_error("Failed to read file: %s\n", filename);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -28,23 +28,6 @@ static FileType file_interpret_as_type(const char *path) {
|
|||||||
return as_dir ? HI_FILE_TYPE_DIR : HI_FILE_TYPE_FILE;
|
return as_dir ? HI_FILE_TYPE_DIR : HI_FILE_TYPE_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *file_name_from_path(const char *path) {
|
|
||||||
const char *filename = path;
|
|
||||||
|
|
||||||
// Find the last directory separator
|
|
||||||
const char *last_slash = strrchr(path, '/');
|
|
||||||
const char *last_backslash = strrchr(path, '\\');
|
|
||||||
|
|
||||||
// Determine which separator was found last (if any)
|
|
||||||
if (last_slash && (!last_backslash || last_slash > last_backslash)) {
|
|
||||||
filename = last_slash + 1;
|
|
||||||
} else if (last_backslash) {
|
|
||||||
filename = last_backslash + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
HiResult file_copy(const char *srcname, const char *destination) {
|
HiResult file_copy(const char *srcname, const char *destination) {
|
||||||
|
|
||||||
HiResult ret = HI_FAIL;
|
HiResult ret = HI_FAIL;
|
||||||
@@ -61,8 +44,7 @@ HiResult file_copy(const char *srcname, const char *destination) {
|
|||||||
|
|
||||||
FileType dst_type = file_interpret_as_type(destination);
|
FileType dst_type = file_interpret_as_type(destination);
|
||||||
if (dst_type == HI_FILE_TYPE_DIR) {
|
if (dst_type == HI_FILE_TYPE_DIR) {
|
||||||
hi_str_concat_buf(sizeof(buf), buf, destination,
|
string_concat_buf(sizeof(buf), buf, destination, strpath_filename(srcname));
|
||||||
file_name_from_path(srcname));
|
|
||||||
dstname = buf;
|
dstname = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +71,10 @@ HiResult file_copy(const char *srcname, const char *destination) {
|
|||||||
ret = HI_OK;
|
ret = HI_OK;
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
if (src) fclose(src);
|
if (src)
|
||||||
if (dst) fclose(dst);
|
fclose(src);
|
||||||
|
if (dst)
|
||||||
|
fclose(dst);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -136,4 +120,3 @@ FileType file_type(const char *path) {
|
|||||||
|
|
||||||
return HI_FILE_TYPE_NONE;
|
return HI_FILE_TYPE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,5 +35,3 @@ char *file_to_str_dyn(const char *filename);
|
|||||||
HiResult file_copy(const char *filename, const char *dest);
|
HiResult file_copy(const char *filename, const char *dest);
|
||||||
|
|
||||||
FileType file_type(const char *path);
|
FileType file_type(const char *path);
|
||||||
|
|
||||||
const char *file_name_from_path(const char *path);
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ typedef enum {
|
|||||||
HI_FILE_PARENT = (1 << 30),
|
HI_FILE_PARENT = (1 << 30),
|
||||||
} FileWatchType;
|
} FileWatchType;
|
||||||
|
|
||||||
static inline const char *hi_file_watch_type_to_str(FileWatchType type) {
|
static inline const char *filewatch_type_to_str(FileWatchType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case HI_FILE_NONE:
|
case HI_FILE_NONE:
|
||||||
return "HI_FILE_NONE";
|
return "HI_FILE_NONE";
|
||||||
@@ -26,11 +26,11 @@ static inline const char *hi_file_watch_type_to_str(FileWatchType type) {
|
|||||||
case HI_FILE_MOVE:
|
case HI_FILE_MOVE:
|
||||||
return "HI_FILE_MOVE";
|
return "HI_FILE_MOVE";
|
||||||
default:
|
default:
|
||||||
return "Unknown HI_FILE_TYPE";
|
|
||||||
|
|
||||||
return "Unknown HI_FILE_TYPE";
|
return "Unknown HI_FILE_TYPE";
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "Unknown HI_FILE_TYPE";
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FILEWATCH_TYPE_H_
|
#endif // FILEWATCH_TYPE_H_
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ static FileEvent file_event_create(const struct inotify_event *inevent,
|
|||||||
return NULL_EVENT;
|
return NULL_EVENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileEvent file_event_pop(FileWatcher *fw) {
|
FileEvent filewatcher_event_pop(FileWatcher *fw) {
|
||||||
mtx_lock(&fw->mutex);
|
mtx_lock(&fw->mutex);
|
||||||
|
|
||||||
FileEvent e = NULL_EVENT;
|
FileEvent e = NULL_EVENT;
|
||||||
@@ -179,7 +179,7 @@ static void file_watcher_ctx_destroy(FileWatcherContext *ctx) {
|
|||||||
// Declare the thread func
|
// Declare the thread func
|
||||||
int file_watcher_watch(void *arg);
|
int file_watcher_watch(void *arg);
|
||||||
|
|
||||||
FileWatcher *file_watcher_create() {
|
FileWatcher *filewatcher_create() {
|
||||||
|
|
||||||
// Allocate context
|
// Allocate context
|
||||||
FileWatcher *fw = malloc(sizeof(FileWatcher));
|
FileWatcher *fw = malloc(sizeof(FileWatcher));
|
||||||
@@ -205,7 +205,7 @@ FileWatcher *file_watcher_create() {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
HiResult file_watcher_add(FileWatcher *fw, const char *filename, u32 flags) {
|
HiResult filewatcher_add(FileWatcher *fw, const char *filename, u32 flags) {
|
||||||
if (!fw) {
|
if (!fw) {
|
||||||
sc_log_error("Attempted to add file watcher for '%s' with null context\n",
|
sc_log_error("Attempted to add file watcher for '%s' with null context\n",
|
||||||
filename);
|
filename);
|
||||||
@@ -264,7 +264,7 @@ void file_watcher_notify(FileWatcher *fw) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void file_watcher_destroy(FileWatcher *fw) {
|
void filewatcher_destroy(FileWatcher *fw) {
|
||||||
if (!fw)
|
if (!fw)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -275,6 +275,10 @@ void file_watcher_destroy(FileWatcher *fw) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
file_watcher_ctx_destroy(&fw->context);
|
file_watcher_ctx_destroy(&fw->context);
|
||||||
|
|
||||||
|
mtx_destroy(&fw->mutex);
|
||||||
|
cnd_destroy(&fw->cond);
|
||||||
|
|
||||||
free(fw);
|
free(fw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +313,7 @@ int file_watcher_watch(void *arg) {
|
|||||||
if (event->len) {
|
if (event->len) {
|
||||||
sc_log_debug("Event created: queue-size: %u, %s %s\n",
|
sc_log_debug("Event created: queue-size: %u, %s %s\n",
|
||||||
vector_size(&ctx->events), e.pathname,
|
vector_size(&ctx->events), e.pathname,
|
||||||
hi_file_watch_type_to_str(e.type));
|
filewatch_type_to_str(e.type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
#ifndef HI_FILEWATCHER_H_
|
|
||||||
#define HI_FILEWATCHER_H_
|
|
||||||
|
|
||||||
#include "filewatch_context.h"
|
|
||||||
#include "filewatch_type.h"
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File event watcher using inotify.
|
* File event watcher using inotify.
|
||||||
*
|
*
|
||||||
* Create a FileWatcher and give it files to monitor. FileWatcher creates a thread
|
* Create a FileWatcher and give it files to monitor. FileWatcher creates a
|
||||||
* for listening system notifications. It then creates events for each filechange
|
* thread for listening system notifications. It then creates events for each
|
||||||
* for those files under scrutiny.
|
* filechange for those files under scrutiny.
|
||||||
*
|
*
|
||||||
* Paths are stored as absolute, and this doesn't handle symlinks in watched
|
* Paths are stored as absolute, and this doesn't handle symlinks in watched
|
||||||
* files. So if you have e.g. a symlink 'my-proj' to your project folder in
|
* files. So if you have e.g. a symlink 'my-proj' to your project folder in
|
||||||
@@ -18,6 +11,12 @@
|
|||||||
* not interpreted as equal.
|
* not interpreted as equal.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "filewatch_context.h"
|
||||||
|
#include "filewatch_type.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File Event produced by FileWatcher.
|
* File Event produced by FileWatcher.
|
||||||
*
|
*
|
||||||
@@ -47,15 +46,15 @@ typedef struct FileEvents FileEvents;
|
|||||||
*
|
*
|
||||||
* Starts a new watcher thread immediately.
|
* Starts a new watcher thread immediately.
|
||||||
*/
|
*/
|
||||||
FileWatcher *file_watcher_create(void);
|
FileWatcher *filewatcher_create(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy a previously created file watcher.
|
* Destroy a previously created file watcher, cleaning resources.
|
||||||
*
|
*
|
||||||
* Will join the thread and destroy event stack. Any FileEvent still in
|
* Will join the thread and destroy event stack. Any FileEvent still in
|
||||||
* existence will contain invalid data after this call.
|
* existence will contain invalid data after this call.
|
||||||
*/
|
*/
|
||||||
void file_watcher_destroy(FileWatcher *fw);
|
void filewatcher_destroy(FileWatcher *fw);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start watching for events on a file.
|
* Start watching for events on a file.
|
||||||
@@ -64,11 +63,9 @@ void file_watcher_destroy(FileWatcher *fw);
|
|||||||
* @param pathname Absolute path to the file or directory
|
* @param pathname Absolute path to the file or directory
|
||||||
* @param flags The event mask for the events to watch for
|
* @param flags The event mask for the events to watch for
|
||||||
*/
|
*/
|
||||||
HiResult file_watcher_add(FileWatcher *fw, const char *pathname, u32 flags);
|
HiResult filewatcher_add(FileWatcher *fw, const char *pathname, u32 flags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pop an event from event stack.
|
* Pop an event from event stack.
|
||||||
*/
|
*/
|
||||||
FileEvent file_event_pop(FileWatcher *ctx);
|
FileEvent filewatcher_event_pop(FileWatcher *ctx);
|
||||||
|
|
||||||
#endif // HI_FILEWATCHER_H_
|
|
||||||
|
|||||||
165
src/hiload.c
165
src/hiload.c
@@ -1,4 +1,4 @@
|
|||||||
#include "hiload/hiload.h"
|
#include "hiload.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "filewatcher/filewatcher.h"
|
#include "filewatcher/filewatcher.h"
|
||||||
@@ -23,13 +23,11 @@
|
|||||||
* assumption is that libraries from system folders won't be actively developed
|
* assumption is that libraries from system folders won't be actively developed
|
||||||
* on, nor do they contain references to the modifiable code.
|
* on, nor do they contain references to the modifiable code.
|
||||||
*/
|
*/
|
||||||
static const char *module_exclude_filter[] = {
|
static const char *system_folders[] = {
|
||||||
"/usr/", "/lib/", "/lib64/", "/bin/", "/opt/",
|
"/usr/", "/lib/", "/lib64/", "/bin/", "/opt/",
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
VectorMemoryMap memory_regions;
|
|
||||||
VectorStr enabled_modules;
|
|
||||||
FileWatcher *filewatcher;
|
FileWatcher *filewatcher;
|
||||||
VectorModuleData modules;
|
VectorModuleData modules;
|
||||||
} HiloadContext;
|
} HiloadContext;
|
||||||
@@ -37,11 +35,11 @@ typedef struct {
|
|||||||
static HiloadContext context = {0};
|
static HiloadContext context = {0};
|
||||||
|
|
||||||
// Callback function for dl_iterate_phdr
|
// Callback function for dl_iterate_phdr
|
||||||
static int gather_module_data_callback(struct dl_phdr_info *info, size_t size,
|
static int initial_gather_callback(struct dl_phdr_info *info, size_t size,
|
||||||
void *data) {
|
void *data) {
|
||||||
(void)size;
|
UNUSED(size);
|
||||||
|
|
||||||
// '' for executable, fname for rest
|
// '' for executable, filepath for rest
|
||||||
const char *modname = info->dlpi_name;
|
const char *modname = info->dlpi_name;
|
||||||
|
|
||||||
log_info("Found: '%s'\n", info->dlpi_name);
|
log_info("Found: '%s'\n", info->dlpi_name);
|
||||||
@@ -56,7 +54,7 @@ static int gather_module_data_callback(struct dl_phdr_info *info, size_t size,
|
|||||||
|
|
||||||
// Mark executable
|
// Mark executable
|
||||||
if (strcmp(modname, "") == 0) {
|
if (strcmp(modname, "") == 0) {
|
||||||
module.info = hi_modinfo_add(module.info, HI_MODULE_STATE_EXEC);
|
module.info = modinfo_add(module.info, HI_MODULE_STATE_EXEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dl_info dl_info = {0};
|
Dl_info dl_info = {0};
|
||||||
@@ -67,35 +65,12 @@ static int gather_module_data_callback(struct dl_phdr_info *info, size_t size,
|
|||||||
const char *modpath = dl_info.dli_fname;
|
const char *modpath = dl_info.dli_fname;
|
||||||
module.name = strdup(modpath);
|
module.name = strdup(modpath);
|
||||||
|
|
||||||
// Mark everything but system and virtual modules as patchable
|
|
||||||
if (module.name[0] == '/') {
|
|
||||||
if (!hi_str_starts_with(module.name, ARRLEN(module_exclude_filter),
|
|
||||||
module_exclude_filter)) {
|
|
||||||
module.info = hi_modinfo_add(module.info, HI_MODULE_STATE_PATCHABLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log_debugv("dli_fname: %s\n", dl_info.dli_fname);
|
log_debugv("dli_fname: %s\n", dl_info.dli_fname);
|
||||||
log_debugv("dli_sname: %s\n", dl_info.dli_sname);
|
log_debugv("dli_sname: %s\n", dl_info.dli_sname);
|
||||||
|
|
||||||
for (size_t i = 0; i < vector_size(&context.enabled_modules); i++) {
|
|
||||||
|
|
||||||
// If module is in the provided modules list
|
|
||||||
if (hi_path_has_filename(modname,
|
|
||||||
vector_at(&context.enabled_modules, i))) {
|
|
||||||
|
|
||||||
// Replace shortform with str pointer from module info
|
|
||||||
vector_at(&context.enabled_modules, i) = module.name;
|
|
||||||
|
|
||||||
if (!HIOK(file_watcher_add(context.filewatcher, modpath,
|
|
||||||
HI_FILE_ALL_EVENTS))) {
|
|
||||||
log_error("Failed to set filewatcher for: '%s'\n", modpath);
|
|
||||||
} else {
|
|
||||||
log_info("Watching file: %s\n", modpath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
// I don't know when this could happen since we're passing the info straight
|
||||||
|
// from the info struct
|
||||||
module.name = strdup(modname);
|
module.name = strdup(modname);
|
||||||
log_warn("Couldn't load dlinfo for %s: %s\n", module.name, dlerror());
|
log_warn("Couldn't load dlinfo for %s: %s\n", module.name, dlerror());
|
||||||
}
|
}
|
||||||
@@ -106,7 +81,7 @@ static int gather_module_data_callback(struct dl_phdr_info *info, size_t size,
|
|||||||
return 0; // Continue iteration
|
return 0; // Continue iteration
|
||||||
}
|
}
|
||||||
|
|
||||||
static void module_infos_free(VectorModuleData *modules) {
|
static void module_data_free(VectorModuleData *modules) {
|
||||||
if (!modules)
|
if (!modules)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -115,22 +90,72 @@ static void module_infos_free(VectorModuleData *modules) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static HiResult gather_module_infos(VectorModuleData *modules) {
|
static HiResult
|
||||||
|
module_data_initial_gather(VectorModuleData *modules, size_t enabled_count,
|
||||||
|
const char *enabled_modules[enabled_count]) {
|
||||||
|
|
||||||
vector_init(modules);
|
vector_init(modules);
|
||||||
|
|
||||||
// Iterate over all loaded shared objects
|
// Iterate over all loaded shared objects
|
||||||
if (dl_iterate_phdr(gather_module_data_callback, modules) != 0) {
|
if (dl_iterate_phdr(initial_gather_callback, modules) != 0) {
|
||||||
// Error occurred in callback
|
// Error occurred in callback
|
||||||
module_infos_free(modules);
|
module_data_free(modules);
|
||||||
return HI_FAIL;
|
return HI_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < vector_size(&context.modules); ++i) {
|
||||||
|
ModuleData *module = &vector_at(&context.modules, i);
|
||||||
|
|
||||||
|
// Mark everything but system and virtual modules as patchable
|
||||||
|
if (module->name[0] == '/') {
|
||||||
|
// Virtual module(s) don't have the root at start
|
||||||
|
if (string_starts_with_any(module->name, ARRLEN(system_folders),
|
||||||
|
system_folders)) {
|
||||||
|
// Exclude anything from system folders with libraries
|
||||||
|
continue;
|
||||||
|
} else if (string_last_token_is(module->name, "libhiload.so", '/')) {
|
||||||
|
// Exclude ourselves, so we don't accidentally try to override
|
||||||
|
// ourselves while running ourselves.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
module->info = modinfo_add(module->info, HI_MODULE_STATE_PATCHABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enable = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < enabled_count; ++i) {
|
||||||
|
const char *enabled_module = enabled_modules[i];
|
||||||
|
if (string_is_empty(enabled_module)) {
|
||||||
|
if (modinfo_has(module->info, HI_MODULE_STATE_EXEC)) {
|
||||||
|
enable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (string_last_token_is(module->name, enabled_module, '/')) {
|
||||||
|
enable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
module->info = modinfo_add(module->info, HI_MODULE_STATE_ENABLED);
|
||||||
|
|
||||||
|
// Add filewatcher
|
||||||
|
if (!HIOK(filewatcher_add(context.filewatcher, module->name,
|
||||||
|
HI_FILE_ALL_EVENTS))) {
|
||||||
|
log_error("Failed to set filewatcher for: '%s'\n", module->name);
|
||||||
|
} else {
|
||||||
|
log_info("Watching file: %s\n", module->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return HI_OK;
|
return HI_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ModuleData *get_module_by_path(const char *path,
|
static ModuleData *module_get(const char *path,
|
||||||
VectorModuleData *modules) {
|
const VectorModuleData *modules) {
|
||||||
|
|
||||||
for (size_t i = 0; i < vector_size(modules); i++) {
|
for (size_t i = 0; i < vector_size(modules); i++) {
|
||||||
ModuleData *module = &vector_at(modules, i);
|
ModuleData *module = &vector_at(modules, i);
|
||||||
@@ -139,6 +164,7 @@ static ModuleData *get_module_by_path(const char *path,
|
|||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,35 +173,23 @@ static ModuleData *get_module_by_path(const char *path,
|
|||||||
*
|
*
|
||||||
* Marks a module dirty if there has been any event.
|
* Marks a module dirty if there has been any event.
|
||||||
*/
|
*/
|
||||||
static void handle_events(FileWatcher *fw, VectorModuleData *modules) {
|
static void handle_file_events(FileWatcher *fw, VectorModuleData *modules) {
|
||||||
|
|
||||||
FileEvent event = file_event_pop(fw);
|
FileEvent event = filewatcher_event_pop(fw);
|
||||||
while (event.type != HI_FILE_NONE) {
|
while (event.type != HI_FILE_NONE) {
|
||||||
log_debug("Event pop: %s – %s\n", event.pathname,
|
log_debug("Event pop: %s – %s\n", event.pathname,
|
||||||
hi_file_watch_type_to_str(event.type));
|
filewatch_type_to_str(event.type));
|
||||||
|
|
||||||
ModuleData *module = get_module_by_path(event.pathname, modules);
|
ModuleData *module = module_get(event.pathname, modules);
|
||||||
if (!module) {
|
if (!module) {
|
||||||
log_warn("Watched module: %s not found.\n", event.pathname);
|
log_warn("Watched module: %s not found.\n", event.pathname);
|
||||||
} else {
|
} else {
|
||||||
module->info = hi_modinfo_add(module->info, HI_MODULE_STATE_DIRTY);
|
module->info = modinfo_add(module->info, HI_MODULE_STATE_DIRTY);
|
||||||
}
|
}
|
||||||
event = file_event_pop(context.filewatcher);
|
event = filewatcher_event_pop(context.filewatcher);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *find_string_from_array(const char *needle,
|
|
||||||
const VectorStr *haystack) {
|
|
||||||
|
|
||||||
for (size_t i = 0; i < vector_size(haystack); i++) {
|
|
||||||
if (strcmp(needle, vector_at(haystack, i)) == 0) {
|
|
||||||
return vector_at(haystack, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static HiResult reload_dirty_modules(HiloadContext *context) {
|
static HiResult reload_dirty_modules(HiloadContext *context) {
|
||||||
|
|
||||||
HiResult ret = HI_OK;
|
HiResult ret = HI_OK;
|
||||||
@@ -183,16 +197,11 @@ static HiResult reload_dirty_modules(HiloadContext *context) {
|
|||||||
ModuleData *module = &vector_at(&context->modules, i);
|
ModuleData *module = &vector_at(&context->modules, i);
|
||||||
|
|
||||||
// Operate on dirty modules only
|
// Operate on dirty modules only
|
||||||
if (hi_modinfo_has(module->info, HI_MODULE_STATE_DIRTY)) {
|
if (modinfo_has(module->info, HI_MODULE_STATE_DIRTY)) {
|
||||||
|
|
||||||
// Operate only if the module is marked as operatable
|
if (modinfo_has(module->info, HI_MODULE_STATE_ENABLED)) {
|
||||||
const char *module_name =
|
log_info("Reloading %s...\n", module->name);
|
||||||
find_string_from_array(module->name, &context->enabled_modules);
|
if (!HIOK(moduler_reload(&context->modules, module))) {
|
||||||
if (module_name) {
|
|
||||||
log_info("Reloading %s...\n", module_name);
|
|
||||||
|
|
||||||
if (!HIOK(moduler_reload(&context->modules, module,
|
|
||||||
&context->memory_regions))) {
|
|
||||||
ret = HI_FAIL;
|
ret = HI_FAIL;
|
||||||
log_error("Failed loading: %s\n", module->name);
|
log_error("Failed loading: %s\n", module->name);
|
||||||
}
|
}
|
||||||
@@ -205,6 +214,7 @@ static HiResult reload_dirty_modules(HiloadContext *context) {
|
|||||||
int hi_init(size_t n, const char **enabled_modules) {
|
int hi_init(size_t n, const char **enabled_modules) {
|
||||||
|
|
||||||
if (log_init() != 0) {
|
if (log_init() != 0) {
|
||||||
|
// When there's no logging, only fprintf
|
||||||
fprintf(stderr, "Failed to init logger.\n");
|
fprintf(stderr, "Failed to init logger.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -212,21 +222,10 @@ int hi_init(size_t n, const char **enabled_modules) {
|
|||||||
log_set_thread_name("Main");
|
log_set_thread_name("Main");
|
||||||
|
|
||||||
// Start the filewatcher
|
// Start the filewatcher
|
||||||
context.filewatcher = file_watcher_create();
|
context.filewatcher = filewatcher_create();
|
||||||
|
|
||||||
for (unsigned i = 0; i < n; i++) {
|
HiResult re =
|
||||||
const char *module_name = enabled_modules[i];
|
module_data_initial_gather(&context.modules, n, enabled_modules);
|
||||||
log_info("Enabling module: '%s'\n", module_name);
|
|
||||||
vector_add(&context.enabled_modules, module_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
vector_init(&context.memory_regions);
|
|
||||||
if (memmaps_from_process(&context.memory_regions) != HI_OK) {
|
|
||||||
log_error("Could not populate program memory maps.\n");
|
|
||||||
return HI_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
HiResult re = gather_module_infos(&context.modules);
|
|
||||||
if (re != HI_OK) {
|
if (re != HI_OK) {
|
||||||
log_error("Failed to gather module infos.\n");
|
log_error("Failed to gather module infos.\n");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -236,15 +235,15 @@ int hi_init(size_t n, const char **enabled_modules) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void hi_deinit() {
|
void hi_deinit() {
|
||||||
module_infos_free(&context.modules);
|
module_data_free(&context.modules);
|
||||||
file_watcher_destroy(context.filewatcher);
|
filewatcher_destroy(context.filewatcher);
|
||||||
log_term();
|
log_term();
|
||||||
memset(&context, 0, sizeof(context));
|
memset(&context, 0, sizeof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
HiResult hi_reload() {
|
HiResult hi_reload() {
|
||||||
|
|
||||||
handle_events(context.filewatcher, &context.modules);
|
handle_file_events(context.filewatcher, &context.modules);
|
||||||
reload_dirty_modules(&context);
|
reload_dirty_modules(&context);
|
||||||
return HI_OK;
|
return HI_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
size_t hi_str_concat_buf(size_t buflen, char buf[buflen], const char *first,
|
size_t string_concat_buf(size_t buflen, char buf[buflen], const char *first,
|
||||||
const char *second) {
|
const char *second) {
|
||||||
if (!buf)
|
if (!buf)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -16,6 +16,7 @@ size_t hi_str_concat_buf(size_t buflen, char buf[buflen], const char *first,
|
|||||||
size_t second_len = strlen(second);
|
size_t second_len = strlen(second);
|
||||||
|
|
||||||
if (buf + first_len + second_len > buf + buflen - 1) {
|
if (buf + first_len + second_len > buf + buflen - 1) {
|
||||||
|
// If combined length of the strings wouldn't fit in the buffer, exit early
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,22 +31,7 @@ size_t hi_str_concat_buf(size_t buflen, char buf[buflen], const char *first,
|
|||||||
return second_end - buf;
|
return second_end - buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
int hi_path_has_filename(const char *path, const char *filename) {
|
char *string_from_file(const char *filename, size_t *nread, size_t nmax) {
|
||||||
|
|
||||||
const char *compared_filename = path;
|
|
||||||
const char *basename = strrchr(path, '/');
|
|
||||||
|
|
||||||
if (basename)
|
|
||||||
compared_filename = basename + 1;
|
|
||||||
|
|
||||||
if (strcmp(compared_filename, filename) == 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *hi_str_from_file(const char *filename, size_t *nread,
|
|
||||||
size_t nmax) {
|
|
||||||
|
|
||||||
FILE *f = fopen(filename, "r");
|
FILE *f = fopen(filename, "r");
|
||||||
if (!f) {
|
if (!f) {
|
||||||
@@ -107,17 +93,61 @@ char *hi_str_from_file(const char *filename, size_t *nread,
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *hi_str_starts_with(const char path[static 1], size_t listn,
|
bool string_is_empty(const char s[static 1]) { return s[0] == '\0'; }
|
||||||
const char *pathlist[listn]) {
|
|
||||||
|
|
||||||
if (!pathlist)
|
bool string_starts_with(const char s[static 1],
|
||||||
return path;
|
const char startswith[static 1]) {
|
||||||
size_t pathlen = strlen(path);
|
size_t startswith_len = strlen(startswith);
|
||||||
|
size_t s_len = strlen(s);
|
||||||
|
|
||||||
|
if (startswith_len > s_len)
|
||||||
|
return false;
|
||||||
|
size_t len = MIN(strlen(s), strlen(startswith));
|
||||||
|
bool res = strncmp(s, startswith, len) == 0;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *string_starts_with_any(const char s[static 1], size_t listn,
|
||||||
|
const char *slist[listn]) {
|
||||||
|
if (!slist)
|
||||||
|
return s;
|
||||||
|
size_t pathlen = strlen(s);
|
||||||
for (size_t i = 0; i < listn; ++i) {
|
for (size_t i = 0; i < listn; ++i) {
|
||||||
size_t len = MIN(strlen(pathlist[i]), pathlen);
|
size_t len = MIN(strlen(slist[i]), pathlen);
|
||||||
if (strncmp(path, pathlist[i], len) == 0) {
|
if (strncmp(s, slist[i], len) == 0) {
|
||||||
return pathlist[i];
|
return slist[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *string_ends_with_any(const char s[static 1], size_t listn,
|
||||||
|
const char *slist[listn]) {
|
||||||
|
if (!slist)
|
||||||
|
return s;
|
||||||
|
size_t slen = strlen(s);
|
||||||
|
for (size_t i = 0; i < listn; ++i) {
|
||||||
|
|
||||||
|
size_t len = MIN(strlen(slist[i]), slen);
|
||||||
|
if (strncmp(s, slist[i], len) == 0) {
|
||||||
|
return slist[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int string_last_token_is(const char *s, const char *ifthis, char delim) {
|
||||||
|
|
||||||
|
const char *last_token = s;
|
||||||
|
const char *basename = strrchr(s, delim);
|
||||||
|
|
||||||
|
if (basename)
|
||||||
|
last_token = basename + 1;
|
||||||
|
|
||||||
|
if (strcmp(last_token, ifthis) == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *strpath_filename(const char *s) { return strrchr(s, '/'); }
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenate two strings into a buffer.
|
* Concatenate two strings into a buffer.
|
||||||
*
|
*
|
||||||
* If resulting string would be longer than @a buflen - 1, @a buf remains unchanged.
|
* If resulting string would be longer than @a buflen - 1, @a buf remains
|
||||||
|
* unchanged.
|
||||||
*
|
*
|
||||||
* @param bufsize Size of the destination buffer @a buf
|
* @param bufsize Size of the destination buffer @a buf
|
||||||
* @param buf The destination buffer
|
* @param buf The destination buffer
|
||||||
* @param first Null terminated character string
|
* @param first Null terminated character string
|
||||||
* @param second Null terminated character string
|
* @param second Null terminated character string
|
||||||
*/
|
*/
|
||||||
size_t hi_str_concat_buf(size_t bufsize, char buf[bufsize],
|
size_t string_concat_buf(size_t bufsize, char buf[bufsize], const char *first,
|
||||||
const char *first, const char *second);
|
const char *second);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy file content to a null terminated string, allocating memory while
|
* Copy file content to a null terminated string, allocating memory while
|
||||||
@@ -31,20 +33,34 @@ size_t hi_str_concat_buf(size_t bufsize, char buf[bufsize],
|
|||||||
* @param nmax if not 0, this amount of memory in bytes is read and used as
|
* @param nmax if not 0, this amount of memory in bytes is read and used as
|
||||||
* initial allocation
|
* initial allocation
|
||||||
*/
|
*/
|
||||||
char *hi_str_from_file(const char *filename, size_t *nread,
|
char *string_from_file(const char *filename, size_t *nread, size_t nmax);
|
||||||
size_t nmax);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the first string from @a pathlist that is fully included in @a path.
|
* Test if null terminated string is empty
|
||||||
*/
|
*/
|
||||||
const char *hi_str_starts_with(const char path[static 1], size_t listn,
|
bool string_is_empty(const char s[static 1]);
|
||||||
const char *pathlist[listn]);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first string from @a slist that @a s starts with.
|
||||||
|
*/
|
||||||
|
const char *string_starts_with_any(const char s[static 1], size_t listn,
|
||||||
|
const char *slist[listn]);
|
||||||
|
/**
|
||||||
|
* Return the first string from @a slist that @a s ends with.
|
||||||
|
*/
|
||||||
|
const char *string_ends_with_any(const char s[static 1], size_t listn,
|
||||||
|
const char *slist[listn]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find if @a filename exists at the end of @a path.
|
* Find if @a filename exists at the end of @a path.
|
||||||
*
|
*
|
||||||
|
* ```c
|
||||||
|
*
|
||||||
|
* if (string_last_token_is(fullpath, filename, '/')) { ... }
|
||||||
|
* ````
|
||||||
|
*
|
||||||
* @return 0 if not found, positive if found
|
* @return 0 if not found, positive if found
|
||||||
*/
|
*/
|
||||||
int hi_path_has_filename(const char *path, const char *filename);
|
int string_last_token_is(const char *s, const char *ifthis, char delim);
|
||||||
|
|
||||||
|
const char *strpath_filename(const char *s);
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ static HiResult moduler_apply_module_patch(VectorSymbol *psymbols,
|
|||||||
return HI_OK;
|
return HI_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
PatchData moduler_create_patch(ModuleData *module) {
|
static PatchData moduler_create_patch(ModuleData *module) {
|
||||||
|
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
struct tm *t = localtime(&now);
|
struct tm *t = localtime(&now);
|
||||||
@@ -281,7 +281,7 @@ PatchData moduler_create_patch(ModuleData *module) {
|
|||||||
|
|
||||||
char filename[512];
|
char filename[512];
|
||||||
size_t written =
|
size_t written =
|
||||||
hi_str_concat_buf(sizeof(filename), filename, module->name, file_append);
|
string_concat_buf(sizeof(filename), filename, module->name, file_append);
|
||||||
|
|
||||||
if (written == 0) {
|
if (written == 0) {
|
||||||
log_error("Failed to concat %s and %s\n", module->name, ".patch");
|
log_error("Failed to concat %s and %s\n", module->name, ".patch");
|
||||||
@@ -294,14 +294,13 @@ PatchData moduler_create_patch(ModuleData *module) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
HiResult moduler_reload(VectorModuleData *modules, ModuleData *module,
|
HiResult moduler_reload(VectorModuleData *modules, ModuleData *module) {
|
||||||
VectorMemoryMap *memmaps) {
|
|
||||||
|
|
||||||
// Return OK because this isn't an error case. We just don't do it yet
|
// Return OK because this isn't an error case. We just don't do it yet
|
||||||
// because we only handle reloading a complete solib. Can't do that with
|
// because we only handle reloading a complete solib. Can't do that with
|
||||||
// executables.
|
// executables.
|
||||||
if (hi_modinfo_has(module->info, HI_MODULE_STATE_EXEC)) {
|
if (modinfo_has(module->info, HI_MODULE_STATE_EXEC)) {
|
||||||
module->info = hi_modinfo_clear(module->info, HI_MODULE_STATE_DIRTY);
|
module->info = modinfo_clear(module->info, HI_MODULE_STATE_DIRTY);
|
||||||
return HI_OK;
|
return HI_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,18 +316,19 @@ HiResult moduler_reload(VectorModuleData *modules, ModuleData *module,
|
|||||||
void *new_handle = dlopen(patch.filename, RTLD_LAZY);
|
void *new_handle = dlopen(patch.filename, RTLD_LAZY);
|
||||||
if (!new_handle) {
|
if (!new_handle) {
|
||||||
log_error("Couldn't load: %s\n", dlerror());
|
log_error("Couldn't load: %s\n", dlerror());
|
||||||
module->info = hi_modinfo_clear(module->info, HI_MODULE_STATE_DIRTY);
|
module->info = modinfo_clear(module->info, HI_MODULE_STATE_DIRTY);
|
||||||
return HI_FAIL;
|
return HI_FAIL;
|
||||||
}
|
}
|
||||||
patch.dlhandle = new_handle;
|
patch.dlhandle = new_handle;
|
||||||
|
|
||||||
// refresh cache
|
VectorMemoryMap memmaps;
|
||||||
if (!HIOK(memmaps_from_process(memmaps))) {
|
vector_init(&memmaps);
|
||||||
|
if (!HIOK(memmaps_from_process(&memmaps))) {
|
||||||
log_error("Failed to collect memory information for process\n");
|
log_error("Failed to collect memory information for process\n");
|
||||||
return HI_FAIL;
|
return HI_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
patch.memreg = memmaps_find_by_name(patch.filename, memmaps);
|
patch.memreg = memmaps_find_by_name(patch.filename, &memmaps);
|
||||||
void *patch_base = (void *)patch.memreg.start;
|
void *patch_base = (void *)patch.memreg.start;
|
||||||
|
|
||||||
VectorSymbol patch_symbols;
|
VectorSymbol patch_symbols;
|
||||||
@@ -344,11 +344,11 @@ HiResult moduler_reload(VectorModuleData *modules, ModuleData *module,
|
|||||||
for (size_t i = 0; i < vector_size(modules); ++i) {
|
for (size_t i = 0; i < vector_size(modules); ++i) {
|
||||||
ModuleData mod = vector_at(modules, i);
|
ModuleData mod = vector_at(modules, i);
|
||||||
|
|
||||||
if (!hi_modinfo_has(mod.info, HI_MODULE_STATE_PATCHABLE))
|
if (!modinfo_has(mod.info, HI_MODULE_STATE_PATCHABLE))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
log_debug("Patching: %s\n", mod.name);
|
log_debug("Patching: %s\n", mod.name);
|
||||||
MemorySpan module_memory = memmaps_find_by_name(mod.name, memmaps);
|
MemorySpan module_memory = memmaps_find_by_name(mod.name, &memmaps);
|
||||||
moduler_apply_module_patch(&patch_symbols, module_memory);
|
moduler_apply_module_patch(&patch_symbols, module_memory);
|
||||||
|
|
||||||
if (strncmp(mod.name, patch.filename, strlen(mod.name)) == 0) {
|
if (strncmp(mod.name, patch.filename, strlen(mod.name)) == 0) {
|
||||||
@@ -383,7 +383,7 @@ HiResult moduler_reload(VectorModuleData *modules, ModuleData *module,
|
|||||||
symbol_term(&module_symbols);
|
symbol_term(&module_symbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
module->info = hi_modinfo_clear(module->info, HI_MODULE_STATE_DIRTY);
|
module->info = modinfo_clear(module->info, HI_MODULE_STATE_DIRTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
free((char *)patch.filename);
|
free((char *)patch.filename);
|
||||||
|
|||||||
@@ -8,33 +8,37 @@
|
|||||||
struct HiloadContext;
|
struct HiloadContext;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
HI_MODULE_STATE_DIRTY = (1 << 0),
|
HI_MODULE_STATE_ENABLED = (1 << 0),
|
||||||
|
HI_MODULE_STATE_DIRTY = (1 << 1),
|
||||||
HI_MODULE_STATE_PATCHABLE = (1 << 6), // non system module we will modify
|
HI_MODULE_STATE_PATCHABLE = (1 << 6), // non system module we will modify
|
||||||
HI_MODULE_STATE_EXEC = (1 << 7), // denote the current executable
|
HI_MODULE_STATE_EXEC = (1 << 7), // denote the current executable
|
||||||
} ModuleFlags;
|
} ModuleFlags;
|
||||||
|
|
||||||
typedef u8 ModuleInfo;
|
typedef u32 ModuleInfo;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *name; // Filename
|
/// Owning. Filename for the module.
|
||||||
|
const char *name;
|
||||||
|
/// Handle given by dlopen
|
||||||
void *dlhandle;
|
void *dlhandle;
|
||||||
|
/// Start address for the module
|
||||||
uptr address;
|
uptr address;
|
||||||
|
/// Additional information, see @a ModuleFlags
|
||||||
ModuleInfo info;
|
ModuleInfo info;
|
||||||
} ModuleData;
|
} ModuleData;
|
||||||
|
|
||||||
vector_def(ModuleData, ModuleData);
|
vector_def(ModuleData, ModuleData);
|
||||||
|
|
||||||
static inline ModuleInfo hi_modinfo_add(ModuleInfo flags, ModuleFlags flag) {
|
static inline ModuleInfo modinfo_add(ModuleInfo flags, ModuleFlags flag) {
|
||||||
return flags | flag;
|
return flags | flag;
|
||||||
}
|
}
|
||||||
static inline ModuleInfo hi_modinfo_clear(ModuleInfo flags, ModuleFlags flag) {
|
static inline ModuleInfo modinfo_clear(ModuleInfo flags, ModuleFlags flag) {
|
||||||
return flags & ~flag;
|
return flags & ~flag;
|
||||||
}
|
}
|
||||||
static inline bool hi_modinfo_has(ModuleInfo flags, ModuleFlags flag) {
|
static inline bool modinfo_has(ModuleInfo flags, ModuleFlags flag) {
|
||||||
return (flags & flag) != 0;
|
return (flags & flag) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HI_MODINFO_SET(info, flag) ((info) |= flag)
|
#define HI_MODINFO_SET(info, flag) ((info) |= flag)
|
||||||
#define HI_MODINFO_CLEAR(info, flag) ((info) &= ~flag)
|
#define HI_MODINFO_CLEAR(info, flag) ((info) &= ~flag)
|
||||||
|
|
||||||
HiResult moduler_reload(VectorModuleData *modules, ModuleData *module,
|
HiResult moduler_reload(VectorModuleData *modules, ModuleData *module);
|
||||||
VectorMemoryMap *memregs);
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "minimal_lib.h"
|
#include "minimal_lib.h"
|
||||||
|
|
||||||
#include "../../include/hiload/hiload.h"
|
#include "../../include/hiload.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|||||||
Reference in New Issue
Block a user