Support loading/unloading multiple QJS plugins ##js

* This is a hacky and experimental feature, without
* breaking the ABI for all the Core plugins this is
* not possible to be done properly, so i'll keep it
* until 5.9, also rlib api is also unable to unload
* plugins, which will require more breaking changes
This commit is contained in:
pancake 2022-12-29 19:25:34 +01:00 committed by pancake
parent e4e73cefef
commit f27b8d9bfb
6 changed files with 205 additions and 72 deletions

View File

@ -499,6 +499,7 @@ R_API int r_cmd_call(RCmd *cmd, const char *input) {
return true;
}
r_list_foreach (cmd->plist, iter, cp) {
/// XXX the plugin call have no plugin context!! we cant have multiple plugins here
if (cp->call && cp->call (cmd->data, input)) {
free (nstr);
return true;

View File

@ -516,14 +516,14 @@ static int cmd_plugins(void *data, const char *input) {
break;
case 'g': // "Lg"
if (input[1] == 'j') {
r_core_cmd0 (core, "gLj");
r_core_cmd_call (core, "gLj");
} else {
r_core_cmd0 (core, "gL");
r_core_cmd_call (core, "gL");
}
break;
case 'o': // "Lo"
case 'i': // "Li"
r_core_cmdf (core, "%cL%s", input[0], input + 1);
r_core_cmd_callf (core, "%cL%s", input[0], input + 1);
break;
case 'c': { // "Lc"
RListIter *iter;
@ -537,8 +537,8 @@ static int cmd_plugins(void *data, const char *input) {
pj_a (pj);
r_list_foreach (core->rcmd->plist, iter, cp) {
pj_o (pj);
pj_ks (pj, "Name", cp->name);
pj_ks (pj, "Description", cp->desc);
pj_ks (pj, "name", cp->name);
pj_ks (pj, "desc", cp->desc);
pj_end (pj);
}
pj_end (pj);

View File

@ -84,8 +84,7 @@ static void r_core_debug_breakpoint_hit(RCore *core, RBreakpointItem *bpi) {
static void r_core_debug_syscall_hit(RCore *core) {
const char *cmdhit = r_config_get (core->config, "cmd.onsyscall");
if (cmdhit && cmdhit[0] != 0) {
if (R_STR_ISNOTEMPTY (cmdhit)) {
r_core_cmd0 (core, cmdhit);
r_cons_flush ();
}

View File

@ -9,15 +9,76 @@
#include "../js_require.c"
#include "../js_r2papi.c"
static R_TH_LOCAL JSContext *Gctx = NULL;
static R_TH_LOCAL JSValue Gfunc;
typedef struct {
JSContext *ctx;
JSRuntime *r;
RCore *core;
char *name; // if name != NULL its a plugin reference
JSRuntime *r;
JSContext *ctx;
JSValue func;
} QjsContext;
// XXX remove globals
static R_TH_LOCAL RList *Glist = NULL;
static R_TH_LOCAL int Gplug = 0;
static void qjsctx_free_item(QjsContext *c) {
if (c) {
free (c->name);
free (c);
}
}
static QjsContext *qjsctx_find(RCore *core, const char *name) {
r_return_val_if_fail (core, NULL);
QjsContext *qc;
RListIter *iter;
r_list_foreach (Glist, iter, qc) {
if (name && core) {
if (qc->core == core && qc->name && !strcmp (qc->name, name)) {
return qc;
}
} else if (qc->core == core) {
return qc;
}
}
return NULL;
}
static QjsContext *qjsctx_add(RCore *core, const char *name, JSContext *ctx, JSValue func) {
QjsContext *qc = R_NEW0 (QjsContext);
if (qc) {
qc->name = name? strdup (name): NULL;
qc->core = core;
qc->ctx = ctx;
qc->func = func;
if (!Glist) {
Glist = r_list_newf ((RListFree)qjsctx_free_item);
}
r_list_append (Glist, qc);
}
return qc;
}
static bool qjsctx_del(RCore *core, const char *name) {
r_return_val_if_fail (core && name, false);
QjsContext *qc;
RListIter *iter;
r_list_foreach (Glist, iter, qc) {
if (qc->core == core && name && qc->name && !strcmp (qc->name, name)) {
r_list_delete (Glist, iter);
return true;
}
}
return false;
}
static void qjsctx_free(void) {
r_list_free (Glist);
Glist = NULL;
}
///////////////////////////////////////////////////////////
static bool eval(JSContext *ctx, const char *code);
static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) {
@ -86,49 +147,82 @@ static JSValue b64(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst
}
}
}
// JS_FreeValue (ctx, argv[0]);
JSValue v = JS_NewString (ctx, r_str_get (ret));
free (ret);
return v;
}
static int r2plugin_core_call(void *_core, const char *input) {
static int r2plugin_core_call2(QjsContext *qc, RCore *core, const char *input) {
JSValueConst args[1] = {
JS_NewString (Gctx, input)
JS_NewString (qc->ctx, input)
};
JSValue res = JS_Call (Gctx, Gfunc, JS_UNDEFINED, countof (args), args);
return JS_ToBool (Gctx, res) == 1;
JSValue res = JS_Call (qc->ctx, qc->func, JS_UNDEFINED, countof (args), args);
return JS_ToBool (qc->ctx, res) == 1;
}
static JSValue r2plugin_core(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (Gctx) {
return JS_ThrowRangeError (ctx, "r2plugin core already registered (only one exists)");
// R2_590 - XXX this is a hack to not break the ABI
#define MAXPLUGS 5
static R_TH_LOCAL QjsContext *GcallsData[MAXPLUGS] = { NULL };
typedef int (*CallX)(void *, const char *);
static int call0(void *c, const char *i) { return r2plugin_core_call2 (GcallsData[0], c, i); }
static int call1(void *c, const char *i) { return r2plugin_core_call2 (GcallsData[1], c, i); }
static int call2(void *c, const char *i) { return r2plugin_core_call2 (GcallsData[2], c, i); }
static int call3(void *c, const char *i) { return r2plugin_core_call2 (GcallsData[3], c, i); }
static int call4(void *c, const char *i) { return r2plugin_core_call2 (GcallsData[4], c, i); }
static const CallX Gcalls[MAXPLUGS] = { &call0, &call1, &call2, &call3, &call4 };
#if 0
static int r2plugin_core_call(void *_core, const char *input) {
return r2_plugin_core_call2 (NULL, _core, input);
#if 0
QjsContext *qc = qjsctx_find (_core, "qjs-example");
if (!qc) {
R_LOG_WARN ("Internal error, cannot find the qjs context");
return 0;
}
if (!qc->name) {
return 0;
}
JSValueConst args[1] = {
JS_NewString (qc->ctx, input)
};
JSValue res = JS_Call (qc->ctx, qc->func, JS_UNDEFINED, countof (args), args);
return JS_ToBool (qc->ctx, res) == 1;
#endif
}
#endif
static JSValue r2plugin_core(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
JSRuntime *rt = JS_GetRuntime (ctx);
QjsContext *k = JS_GetRuntimeOpaque (rt);
RCore *core = k->core;
if (argc != 2) {
return JS_ThrowRangeError(ctx, "r2plugin expects two arguments");
return JS_ThrowRangeError (ctx, "r2.plugin expects two arguments");
}
JSValueConst args[1] = {
JS_NewString (ctx, "POP"),
JS_NewString (ctx, ""),
};
JSValue res = JS_Call (ctx, argv[1], JS_UNDEFINED, countof (args), args);
// check if res is an object
if (!JS_IsObject (res)) {
return JS_ThrowRangeError(ctx, "r2plugin function must return an object");
return JS_ThrowRangeError (ctx, "r2.plugin function must return an object");
}
RCorePlugin *ap = R_NEW0 (RCorePlugin);
if (!ap) {
return JS_ThrowRangeError (ctx, "heap stuff");
}
JSValue name = JS_GetPropertyStr (ctx, res, "name");
size_t namelen;
const char *nameptr = JS_ToCStringLen2 (ctx, &namelen, name, false);
if (nameptr) {
ap->name = strdup (nameptr);
} else {
return JS_ThrowRangeError(ctx, "r2plugin requires the function to return an object with the `name` field");
R_LOG_WARN ("r2.plugin requires the function to return an object with the `name` field");
return JS_NewBool (ctx, false);
}
JSValue desc = JS_GetPropertyStr (ctx, res, "desc");
const char *descptr = JS_ToCStringLen2 (ctx, &namelen, desc, false);
@ -142,22 +236,37 @@ static JSValue r2plugin_core(JSContext *ctx, JSValueConst this_val, int argc, JS
}
JSValue func = JS_GetPropertyStr (ctx, res, "call");
if (!JS_IsFunction (ctx, func)) {
return JS_ThrowRangeError(ctx, "r2plugin requires the function to return an object with the `call` field to be a function");
R_LOG_WARN ("r2.plugin requires the function to return an object with the `call` field to be a function");
// return JS_ThrowRangeError (ctx, "r2.plugin requires the function to return an object with the `call` field to be a function");
return JS_NewBool (ctx, false);
}
// XXX dont do globals
Gctx = ctx;
Gfunc = func;
QjsContext *qc = qjsctx_find (core, ap->name);
if (qc) {
R_LOG_WARN ("r2.plugin with name %s is already registered", ap->name);
free ((char*)ap->name);
free (ap);
// return JS_ThrowRangeError (ctx, "r2.plugin core already registered (only one exists)");
return JS_NewBool (ctx, false);
}
eprintf ("ADDING LE PLUGIN BECAUSE WE DDIND FIND IT THE FUNC\n");
if (Gplug >= MAXPLUGS) {
R_LOG_WARN ("Maximum number of plugins loaded! this is a limitation induced by the");
return JS_NewBool (ctx, false);
}
qc = qjsctx_add (core, nameptr, ctx, func);
ap->call = Gcalls[Gplug];
eprintf ("%s = %p\n", ap->name, qc);
GcallsData[Gplug] = qc;
Gplug++;
ap->call = r2plugin_core_call;
int ret = -1;
RLibStruct *lib = R_NEW0 (RLibStruct);
if (lib) {
lib->type = R_LIB_TYPE_CORE;
lib->data = ap;
lib->version = R2_VERSION;
ret = r_lib_open_ptr (core->lib, "qjs", NULL, lib);
ret = r_lib_open_ptr (core->lib, nameptr, NULL, lib);
}
return JS_NewBool (ctx, ret == 0);
}
@ -179,10 +288,7 @@ static JSValue r2plugin(JSContext *ctx, JSValueConst this_val, int argc, JSValue
}
static JSValue r2plugin_unload(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc != 1) {
return JS_ThrowRangeError(ctx, "r2.unload takes only one string as argument");
}
if (!JS_IsString (argv[0])) {
if (argc != 1 || !JS_IsString (argv[0])) {
return JS_ThrowRangeError(ctx, "r2.unload takes only one string as argument");
}
JSRuntime *rt = JS_GetRuntime (ctx);
@ -190,10 +296,10 @@ static JSValue r2plugin_unload(JSContext *ctx, JSValueConst this_val, int argc,
size_t plen;
const char *name = JS_ToCStringLen2 (ctx, &plen, argv[0], false);
k->core->lang->cmdf (k->core, "L-%s", name);
Gctx = NULL;
bool res = qjsctx_del (k->core, name);
// invalid throw exception here
// return JS_ThrowRangeError(ctx, "invalid r2plugin type");
return JS_NewBool (ctx, true);
return JS_NewBool (ctx, res);
}
static JSValue r2cmd(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
@ -303,7 +409,6 @@ static const JSCFunctionListEntry js_r2_funcs[] = {
JS_CFUNC_DEF ("cmd", 1, r2cmd),
JS_CFUNC_DEF ("plugin", 2, r2plugin),
JS_CFUNC_DEF ("unload", 1, r2plugin_unload),
// JS_SetPropertyStr (ctx, global_obj, "r2plugin", JS_NewCFunction (ctx, r2plugin, "r2plugin", 1));
// JS_CFUNC_DEF ("cmdj", 1, r2cmdj), // can be implemented in js
JS_CFUNC_DEF ("log", 1, r2log),
JS_CFUNC_DEF ("error", 1, r2error),
@ -391,11 +496,11 @@ static JSContext *JS_NewCustomContext(JSRuntime *rt) {
static void eval_jobs(JSContext *ctx) {
JSRuntime *rt = JS_GetRuntime (ctx);
JSContext * pctx = NULL;
JSContext *pctx = NULL;
do {
int res = JS_ExecutePendingJob (rt, &pctx);
if (res == -1) {
eprintf ("exception in job%c", 10);
eprintf ("exception in job\n");
}
} while (pctx);
}
@ -441,18 +546,20 @@ static bool lang_quickjs_file(RLangSession *s, const char *file) {
return rc;
}
static void *init(RLangSession *s) {
QjsContext *k = R_NEW0 (QjsContext);
if (k) {
JSRuntime *rt = JS_NewRuntime ();
JS_SetRuntimeOpaque (rt, k);
k->r = rt;
k->ctx = JS_NewCustomContext (rt);
// already initialized by customcontext register_helpers (k->ctx);
k->core = s->lang->user;
static void *init(RLangSession *ls) {
RCore *core = (RCore *)ls->lang->user;
JSRuntime *rt = JS_NewRuntime ();
JSContext *ctx = JS_NewCustomContext (rt);
JSValue jv = JS_NewBool (ctx, false); // fake function
QjsContext *qc = qjsctx_add (core, NULL, ctx, jv);
if (qc) {
qc->r = rt;
qc->core = ls->lang->user;
JS_SetRuntimeOpaque (rt, qc);
// XXX we still have a global list of plugins.. we can probably use this pointer to hold everything
ls->plugin_data = qc; // implicit
}
s->plugin_data = k; // implicit
return k;
return qc;
}
static bool fini(RLangSession *s) {
@ -461,7 +568,8 @@ static bool fini(RLangSession *s) {
JS_FreeContext (k->ctx);
k->ctx = NULL;
k->r = NULL;
free (k);
qjsctx_free ();
// free (k);
return NULL;
}

View File

@ -1,24 +1,49 @@
(function() {
let { log } = console;
const { log } = console;
function examplePlugin() {
function coreCall(input) {
if (input.startsWith("test")) {
log("This is a QJS test");
return true;
function examplePlugin() {
function coreCall(input) {
if (input.startsWith("t1")) {
log("This is a QJS test");
return true;
}
return false;
}
return false;
return {
name: "qjs-example",
desc: "Example QJS plugin (type 't1') in the r2 shell",
call: coreCall,
};
}
return {
name: "qjs-example",
desc: "Example QJS plugin (type 'test') in the r2 shell",
call: coreCall,
};
}
log("Installing the `qjs-example` core plugin");
log("Type 'test' to confirm it works");
r2plugin("core", examplePlugin);
log(r2cmd("Lc"));
function examplePlugin2() {
function coreCall(input) {
if (input.startsWith("t2")) {
log("This is another QJS test");
return true;
}
return false;
}
return {
name: "qjs-example2",
desc: "Example QJS plugin (type 't2') in the r2 shell",
call: coreCall,
};
}
log("Installing the `qjs-example` core plugin");
log("Type 'test' to confirm it works");
console.log("load true", r2.plugin("core", examplePlugin));
console.log("load true", r2.plugin("core", examplePlugin2));
if (false) {
console.log("load true", r2.plugin("core", examplePlugin));
console.log("load true", r2.plugin("core", examplePlugin2));
console.log("load false", r2.plugin("core", examplePlugin));
console.log("unload false", r2.unload("false"));
console.log("unload true", r2.unload("qjs-example"));
console.log("unload false", r2.unload("qjs-example"));
log("Plugins:");
log(r2cmd("Lc"));
}
})();

View File

@ -672,9 +672,9 @@ static js_force_inline JSValue JS_NewFloat64(JSContext *ctx, double d)
} u, t;
u.d = d;
if (d < INT32_MIN) {
val = d = INT32_MIN;
val = INT32_MIN;
} else if (d > INT32_MAX) {
val = d = INT32_MAX;
val = INT32_MAX;
} else {
val = (int32_t)d;
}