Initial support for RParse r2js plugins ##lang

This commit is contained in:
pancake 2024-04-09 21:29:10 +02:00 committed by pancake
parent 70b5885545
commit 0614fc6fe1
3 changed files with 195 additions and 2 deletions

View File

@ -41,6 +41,7 @@ typedef struct r_parse_t {
} RParse; // TODO rename to RAsmParseState
typedef struct r_parse_plugin_t {
// TODO R2_600 Use RPluginMeta instead
char *name;
char *desc;
bool (*init)(RParse *p, void *user); // returns an RAsmParseState*

View File

@ -31,6 +31,14 @@ typedef struct qjs_arch_plugin_t {
// JSValue encode_func;
} QjsArchPlugin;
typedef struct qjs_parse_plugin_t {
char *name;
RParsePlugin *iop;
R_BORROW JSContext *ctx;
JSValue fn_parse_js;
// JSValue encode_func;
} QjsParsePlugin;
typedef struct qjs_io_plugin_t {
char *name;
RIOPlugin *iop;
@ -44,6 +52,10 @@ typedef struct qjs_io_plugin_t {
// JSValue encode_func;
} QjsIoPlugin;
static void parse_plugin_fini(QjsParsePlugin *cp) {
free (cp->name);
}
static void core_plugin_fini(QjsCorePlugin *cp) {
free (cp->name);
}
@ -53,6 +65,7 @@ static void arch_plugin_fini(QjsArchPlugin *ap) {
free (ap->arch);
}
R_VEC_TYPE_WITH_FINI (RVecParsePlugin, QjsParsePlugin, parse_plugin_fini);
R_VEC_TYPE_WITH_FINI (RVecCorePlugin, QjsCorePlugin, core_plugin_fini);
R_VEC_TYPE_WITH_FINI (RVecArchPlugin, QjsArchPlugin, arch_plugin_fini);
R_VEC_TYPE (RVecIoPlugin, QjsIoPlugin); // R2_590 add finalizer function
@ -65,6 +78,7 @@ typedef struct qjs_plugin_manager_t {
RVecCorePlugin core_plugins;
RVecArchPlugin arch_plugins;
RVecIoPlugin io_plugins;
RVecParsePlugin parse_plugins;
} QjsPluginManager;
static QjsPluginManager *Gpm = NULL;
@ -74,6 +88,7 @@ static bool plugin_manager_init(QjsPluginManager *pm, RCore *core, JSRuntime *rt
RVecCorePlugin_init (&pm->core_plugins);
RVecArchPlugin_init (&pm->arch_plugins);
RVecIoPlugin_init (&pm->io_plugins);
RVecParsePlugin_init (&pm->parse_plugins);
return true;
}
@ -103,16 +118,40 @@ static QjsIoPlugin *plugin_manager_add_io_plugin(QjsPluginManager *pm, const cha
return cp;
}
static QjsParsePlugin *plugin_manager_add_parse_plugin(QjsPluginManager *pm, const char *name, JSContext *ctx, RParsePlugin *iop, JSValue func) {
r_return_val_if_fail (pm, NULL);
QjsParsePlugin *cp = RVecParsePlugin_emplace_back (&pm->parse_plugins);
if (cp) {
cp->name = name? strdup (name): NULL;
cp->ctx = ctx;
cp->iop = iop;
cp->fn_parse_js = func;
}
return cp;
}
static inline int compare_core_plugin_name(const QjsCorePlugin *cp, const void *data) {
const char *name = data;
return strcmp (cp->name, name);
}
static inline int compare_parse_plugin_name(const QjsParsePlugin *cp, const void *data) {
const char *name = data;
return strcmp (cp->name, name);
}
static inline int compare_io_plugin_name(const QjsIoPlugin *cp, const void *data) {
const char *name = data;
return strcmp (cp->name, name);
}
static QjsParsePlugin *plugin_manager_find_parse_plugin(const QjsPluginManager *pm, const char *name) {
r_return_val_if_fail (pm, NULL);
return RVecParsePlugin_find (&pm->parse_plugins, (void*) name, compare_parse_plugin_name);
}
static QjsCorePlugin *plugin_manager_find_core_plugin(const QjsPluginManager *pm, const char *name) {
r_return_val_if_fail (pm, NULL);
@ -125,6 +164,19 @@ static QjsIoPlugin *plugin_manager_find_io_plugin(const QjsPluginManager *pm, co
return RVecIoPlugin_find (&pm->io_plugins, (void*) name, compare_io_plugin_name);
}
static bool plugin_manager_remove_parse_plugin(QjsPluginManager *pm, const char *name) {
r_return_val_if_fail (pm, false);
ut64 index = RVecParsePlugin_find_index (&pm->parse_plugins, (void*) name, compare_parse_plugin_name);
if (index != UT64_MAX) {
pm->core->lang->cmdf (pm->core, "Lp-%s", name);
RVecParsePlugin_remove (&pm->parse_plugins, index);
return true;
}
return false;
}
static bool plugin_manager_remove_core_plugin(QjsPluginManager *pm, const char *name) {
r_return_val_if_fail (pm, false);
@ -180,10 +232,12 @@ static bool plugin_manager_remove_plugin(QjsPluginManager *pm, const char *type,
r_return_val_if_fail (pm, false);
if (R_STR_ISNOTEMPTY (type)) {
if (!strcmp (type, "parse")) {
return plugin_manager_remove_parse_plugin (pm, plugin_id);
}
if (!strcmp (type, "core")) {
return plugin_manager_remove_core_plugin (pm, plugin_id);
}
if (!strcmp (type, "arch")) {
return plugin_manager_remove_arch_plugin (pm, plugin_id);
}
@ -199,6 +253,7 @@ static void plugin_manager_fini (QjsPluginManager *pm) {
RVecCorePlugin_fini (&pm->core_plugins);
RVecArchPlugin_fini (&pm->arch_plugins);
RVecIoPlugin_fini (&pm->io_plugins);
RVecParsePlugin_fini (&pm->parse_plugins);
// XXX leaks, but calling it causes crash because not all JS objects are freed
// JS_FreeRuntime (pm->rt);
pm->rt = NULL;
@ -207,6 +262,7 @@ static void plugin_manager_fini (QjsPluginManager *pm) {
#include "qjs/loader.c"
#include "qjs/arch.c"
#include "qjs/core.c"
#include "qjs/parse.c"
#include "qjs/io.c"
///////////////////////////////////////////////////////////
@ -303,6 +359,8 @@ static JSValue r2plugin(JSContext *ctx, JSValueConst this_val, int argc, JSValue
return r2plugin_core_load (ctx, this_val, argc, argv);
} else if (!strcmp (n, "arch")) {
return r2plugin_arch_load (ctx, this_val, argc, argv);
} else if (!strcmp (n, "parse")) {
return r2plugin_parse_load (ctx, this_val, argc, argv);
} else if (!strcmp (n, "io")) {
return r2plugin_io (ctx, this_val, argc, argv);
#if 0
@ -311,7 +369,7 @@ static JSValue r2plugin(JSContext *ctx, JSValueConst this_val, int argc, JSValue
#endif
} else {
// invalid throw exception here
return JS_ThrowRangeError(ctx, "invalid r2plugin type");
return JS_ThrowRangeError (ctx, "invalid r2plugin type");
}
}
return JS_NewBool (ctx, false);

134
libr/lang/p/qjs/parse.c Normal file
View File

@ -0,0 +1,134 @@
#if 0
# QuickJS RPares plugin example
```js
(function() {
let { log } = console;
function parseExample() {
function parseCall(input) {
return input.replace("sp, -0x60", "LOCALVAR");
}
return {
name: "qjs",
desc: "Example QJS RParse plugin (qjs://)",
call: parseCall,
};
}
r2.plugin("jsparse", parseExample);
r2.cmd("-e asm.parser=qjs");
r2.cmd("-e asm.pseudo=true");
r2.cmd("pd 10");
})();
```
#endif
static int qjs_parse(RParse *p, const char *input, char *output) {
RCore *core = p->user;
QjsPluginManager *pm = R_UNWRAP4 (core, lang, session, plugin_data);
// Iterate over plugins until one returns "true" (meaning the plugin handled the input)
QjsParsePlugin *plugin;
R_VEC_FOREACH (&pm->parse_plugins, plugin) {
JSContext *ctx = plugin->ctx;
JSValueConst args[1] = { JS_NewString (ctx, input) };
JSValue res = JS_Call (ctx, plugin->fn_parse_js, JS_UNDEFINED, countof (args), args);
if (JS_IsString (res)) {
size_t namelen;
const char *nameptr = JS_ToCStringLen2 (ctx, &namelen, res, false);
if (!nameptr) {
R_LOG_WARN ("r2.plugin requires the function to return an object with the `name` field");
return false;
}
strcpy (output, nameptr);
return true;
}
}
return false;
}
static JSValue r2plugin_parse_load(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
JSRuntime *rt = JS_GetRuntime (ctx);
QjsPluginManager *pm = JS_GetRuntimeOpaque (rt);
if (argc != 2) {
return JS_ThrowRangeError (ctx, "r2.plugin expects two arguments");
}
JSValueConst args[1] = { JS_NewString (ctx, ""), };
// check if res is an object
JSValue res = JS_Call (ctx, argv[1], JS_UNDEFINED, countof (args), args);
if (!JS_IsObject (res)) {
return JS_ThrowRangeError (ctx, "r2.plugin function must return an object");
}
JSValue name = JS_GetPropertyStr (ctx, res, "name");
size_t namelen;
const char *nameptr = JS_ToCStringLen2 (ctx, &namelen, name, false);
if (!nameptr) {
R_LOG_WARN ("r2.plugin requires the function to return an object with the `name` field");
return JS_NewBool (ctx, false);
}
JSValue fn_parse_js = JS_GetPropertyStr (ctx, res, "parse");
if (!JS_IsFunction (ctx, fn_parse_js)) {
R_LOG_WARN ("r2.plugin('parse', X) expects X to be a function that returns an object with at least: parse");
// 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);
}
QjsParsePlugin *cp = plugin_manager_find_parse_plugin (pm, nameptr);
if (cp) {
R_LOG_WARN ("r2.plugin with name %s is already registered", nameptr);
// return JS_ThrowRangeError (ctx, "r2.plugin core already registered (only one exists)");
return JS_NewBool (ctx, false);
}
RParsePlugin *ap = R_NEW0 (RParsePlugin);
if (!ap) {
return JS_ThrowRangeError (ctx, "could not allocate qjs core plugin");
}
JSValue desc = JS_GetPropertyStr (ctx, res, "desc");
const char *descptr = JS_ToCStringLen2 (ctx, &namelen, desc, false);
#if 0
JSValue license = JS_GetPropertyStr (ctx, res, "license");
const char *licenseptr = JS_ToCStringLen2 (ctx, &namelen, license, false);
RPluginMeta meta = {
.name = strdup (nameptr),
.desc = descptr ? strdup (descptr) : NULL,
.license = descptr ? strdup (licenseptr) : NULL,
};
memcpy ((void*)&ap->meta, &meta, sizeof (RPluginMeta));
#else
ap->name = strdup (nameptr);
ap->desc = descptr ? strdup (descptr) : NULL;
// ap->license = strdup (licenseptr);
#endif
ap->parse = qjs_parse; // Technically this could all be handled by a single generic plugin
QjsParsePlugin *pp = plugin_manager_add_parse_plugin (pm, nameptr, ctx, ap, fn_parse_js);
pp->fn_parse_js = fn_parse_js;
RLibStruct *lib = R_NEW0 (RLibStruct);
if (!lib) {
free (ap);
return JS_NewBool (ctx, false);
}
lib->type = R_LIB_TYPE_PARSE;
lib->data = ap;
lib->version = R2_VERSION;
int ret = r_lib_open_ptr (pm->core->lib, ap->name, NULL, lib);
return JS_NewBool (ctx, ret == 1);
}