Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to run a function on a given thread #762

Merged
merged 2 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 147 additions & 1 deletion bindings/gumjs/gumquickprocess.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020-2022 Ole André Vadla Ravnås <[email protected]>
* Copyright (C) 2020-2024 Ole André Vadla Ravnås <[email protected]>
* Copyright (C) 2020-2023 Francesco Tamagni <[email protected]>
* Copyright (C) 2023 Grant Douglas <[email protected]>
*
Expand Down Expand Up @@ -40,6 +40,7 @@
#endif

typedef struct _GumQuickMatchContext GumQuickMatchContext;
typedef struct _GumQuickRunOnThreadContext GumQuickRunOnThreadContext;
typedef struct _GumQuickFindModuleByNameContext GumQuickFindModuleByNameContext;
typedef struct _GumQuickFindRangeByAddressContext
GumQuickFindRangeByAddressContext;
Expand All @@ -60,6 +61,12 @@ struct _GumQuickMatchContext
GumQuickProcess * parent;
};

struct _GumQuickRunOnThreadContext
{
JSValue user_func;
GumQuickCore * core;
};

struct _GumQuickFindModuleByNameContext
{
const gchar * name;
Expand Down Expand Up @@ -89,6 +96,15 @@ GUMJS_DECLARE_FUNCTION (gumjs_process_get_current_thread_id)
GUMJS_DECLARE_FUNCTION (gumjs_process_enumerate_threads)
static gboolean gum_emit_thread (const GumThreadDetails * details,
GumQuickMatchContext * mc);
GUMJS_DECLARE_FUNCTION (gumjs_process_run_on_thread)
static void gum_quick_run_on_thread_context_free (
GumQuickRunOnThreadContext * rc);
static void gum_do_call_on_thread (const GumCpuContext * cpu_context,
gpointer user_data);
static void gum_quick_process_maybe_start_stalker_gc_timer (
GumQuickProcess * self, GumQuickScope * scope);
static gboolean gum_quick_process_on_stalker_gc_timer_tick (
GumQuickProcess * self);
GUMJS_DECLARE_FUNCTION (gumjs_process_find_module_by_name)
static gboolean gum_store_module_if_name_matches (
const GumModuleDetails * details, GumQuickFindModuleByNameContext * fc);
Expand Down Expand Up @@ -124,6 +140,7 @@ static const JSCFunctionListEntry gumjs_process_entries[] =
JS_CFUNC_DEF ("isDebuggerAttached", 0, gumjs_process_is_debugger_attached),
JS_CFUNC_DEF ("getCurrentThreadId", 0, gumjs_process_get_current_thread_id),
JS_CFUNC_DEF ("_enumerateThreads", 0, gumjs_process_enumerate_threads),
JS_CFUNC_DEF ("_runOnThread", 0, gumjs_process_run_on_thread),
JS_CFUNC_DEF ("findModuleByName", 0, gumjs_process_find_module_by_name),
JS_CFUNC_DEF ("_enumerateModules", 0, gumjs_process_enumerate_modules),
JS_CFUNC_DEF ("findRangeByAddress", 0, gumjs_process_find_range_by_address),
Expand All @@ -146,8 +163,12 @@ _gum_quick_process_init (GumQuickProcess * self,

self->module = module;
self->core = core;

self->main_module_value = JS_UNINITIALIZED;

self->stalker = NULL;
self->stalker_gc_timer = NULL;

_gum_quick_core_store_module_data (core, "process", self);

obj = JS_NewObject (ctx);
Expand All @@ -174,6 +195,8 @@ _gum_quick_process_flush (GumQuickProcess * self)
void
_gum_quick_process_dispose (GumQuickProcess * self)
{
g_assert (self->stalker_gc_timer == NULL);

g_clear_pointer (&self->exception_handler, gum_quick_exception_handler_free);
gumjs_free_main_module_value (self);
}
Expand Down Expand Up @@ -316,6 +339,129 @@ gum_emit_thread (const GumThreadDetails * details,
return _gum_quick_process_match_result (ctx, &result, &mc->result);
}

GUMJS_DEFINE_FUNCTION (gumjs_process_run_on_thread)
{
GumQuickProcess * self;
GumThreadId thread_id;
JSValue user_func;
GumQuickScope scope = GUM_QUICK_SCOPE_INIT (core);
GumQuickRunOnThreadContext * rc;
gboolean success;

self = gumjs_get_parent_module (core);

if (!_gum_quick_args_parse (args, "ZF", &thread_id, &user_func))
return JS_EXCEPTION;

if (self->stalker == NULL)
self->stalker = gum_stalker_new ();

rc = g_slice_new (GumQuickRunOnThreadContext);
rc->user_func = JS_DupValue (core->ctx, user_func);
rc->core = core;

_gum_quick_scope_suspend (&scope);

success = gum_stalker_run_on_thread (self->stalker, thread_id,
gum_do_call_on_thread, rc,
(GDestroyNotify) gum_quick_run_on_thread_context_free);

_gum_quick_scope_resume (&scope);

gum_quick_process_maybe_start_stalker_gc_timer (self, &scope);

if (!success)
goto run_failed;

return JS_UNDEFINED;

run_failed:
{
_gum_quick_throw_literal (ctx, "failed to run on thread");

return JS_EXCEPTION;
}
}

static void
gum_quick_run_on_thread_context_free (GumQuickRunOnThreadContext * rc)
{
GumQuickCore * core = rc->core;
GumQuickScope scope;

_gum_quick_scope_enter (&scope, core);
JS_FreeValue (core->ctx, rc->user_func);
_gum_quick_scope_leave (&scope);

g_slice_free (GumQuickRunOnThreadContext, rc);
}

static void
gum_do_call_on_thread (const GumCpuContext * cpu_context,
gpointer user_data)
{
GumQuickRunOnThreadContext * rc = user_data;
GumQuickScope scope;

_gum_quick_scope_enter (&scope, rc->core);
_gum_quick_scope_call (&scope, rc->user_func, JS_UNDEFINED, 0, NULL);
_gum_quick_scope_leave (&scope);
}

static void
gum_quick_process_maybe_start_stalker_gc_timer (GumQuickProcess * self,
GumQuickScope * scope)
{
GumQuickCore * core = self->core;
GSource * source;

if (self->stalker_gc_timer != NULL)
return;

if (!gum_stalker_garbage_collect (self->stalker))
{
g_object_unref (self->stalker);
self->stalker = NULL;
return;
}

source = g_timeout_source_new (10);
g_source_set_callback (source,
(GSourceFunc) gum_quick_process_on_stalker_gc_timer_tick, self, NULL);
self->stalker_gc_timer = source;

_gum_quick_core_pin (core);
_gum_quick_scope_suspend (scope);

g_source_attach (source,
gum_script_scheduler_get_js_context (core->scheduler));
g_source_unref (source);

_gum_quick_scope_resume (scope);
}

static gboolean
gum_quick_process_on_stalker_gc_timer_tick (GumQuickProcess * self)
{
gboolean pending_garbage;

pending_garbage = gum_stalker_garbage_collect (self->stalker);
if (!pending_garbage)
{
GumQuickCore * core = self->core;
GumQuickScope scope;

_gum_quick_scope_enter (&scope, core);

_gum_quick_core_unpin (core);
self->stalker_gc_timer = NULL;

_gum_quick_scope_leave (&scope);
}

return pending_garbage ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
}

GUMJS_DEFINE_FUNCTION (gumjs_process_find_module_by_name)
{
GumQuickFindModuleByNameContext fc;
Expand Down
6 changes: 5 additions & 1 deletion bindings/gumjs/gumquickprocess.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 Ole André Vadla Ravnås <[email protected]>
* Copyright (C) 2020-2024 Ole André Vadla Ravnås <[email protected]>
* Copyright (C) 2023 Francesco Tamagni <[email protected]>
*
* Licence: wxWindows Library Licence, Version 3.1
Expand All @@ -22,6 +22,10 @@ struct _GumQuickProcess
GumQuickCore * core;

JSValue main_module_value;

GumStalker * stalker;
GSource * stalker_gc_timer;

GumQuickExceptionHandler * exception_handler;
};

Expand Down
Loading
Loading