Skip to content

Commit

Permalink
Add support for query thread user-time
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Jan 31, 2024
1 parent a485997 commit f814e50
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 0 deletions.
24 changes: 24 additions & 0 deletions bindings/gumjs/gumquickthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ enum _GumBacktracerType

GUMJS_DECLARE_FUNCTION (gumjs_thread_backtrace)
GUMJS_DECLARE_FUNCTION (gumjs_thread_sleep)
GUMJS_DECLARE_FUNCTION (gumjs_thread_get_user_time)
GUMJS_DECLARE_FUNCTION (gumjs_thread_get_user_time_by_id)

static const JSCFunctionListEntry gumjs_thread_entries[] =
{
JS_CFUNC_DEF ("backtrace", 0, gumjs_thread_backtrace),
JS_CFUNC_DEF ("sleep", 0, gumjs_thread_sleep),
JS_CFUNC_DEF ("getUserTime", 0, gumjs_thread_get_user_time),
JS_CFUNC_DEF ("getUserTimeById", 0, gumjs_thread_get_user_time_by_id),
};

static const JSCFunctionListEntry gumjs_backtracer_entries[] =
Expand Down Expand Up @@ -149,3 +153,23 @@ GUMJS_DEFINE_FUNCTION (gumjs_thread_sleep)

return JS_UNDEFINED;
}

GUMJS_DEFINE_FUNCTION (gumjs_thread_get_user_time)
{
guint64 user_time = gum_thead_get_user_time ();

return JS_NewFloat64 (ctx, ((double)user_time) / G_USEC_PER_SEC);
}

GUMJS_DEFINE_FUNCTION (gumjs_thread_get_user_time_by_id)
{
GumThreadId thread_id;
guint64 user_time = 0;

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

user_time = gum_thead_get_user_time_by_id (thread_id);

return JS_NewFloat64 (ctx, ((double)user_time) / G_USEC_PER_SEC);
}
24 changes: 24 additions & 0 deletions bindings/gumjs/gumv8thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ using namespace v8;

GUMJS_DECLARE_FUNCTION (gumjs_thread_backtrace)
GUMJS_DECLARE_FUNCTION (gumjs_thread_sleep)
GUMJS_DECLARE_FUNCTION (gumjs_thread_get_user_time)
GUMJS_DECLARE_FUNCTION (gumjs_thread_get_user_time_by_id)

static const GumV8Function gumjs_thread_functions[] =
{
{ "backtrace", gumjs_thread_backtrace },
{ "sleep", gumjs_thread_sleep },
{ "getUserTime", gumjs_thread_get_user_time},
{ "getUserTimeById", gumjs_thread_get_user_time_by_id },

{ NULL, NULL }
};
Expand Down Expand Up @@ -156,3 +160,23 @@ GUMJS_DEFINE_FUNCTION (gumjs_thread_sleep)
g_usleep (delay * G_USEC_PER_SEC);
}
}

GUMJS_DEFINE_FUNCTION (gumjs_thread_get_user_time)
{
guint64 user_time = gum_thead_get_user_time ();

info.GetReturnValue ().Set ((double)user_time / G_USEC_PER_SEC);
}

GUMJS_DEFINE_FUNCTION (gumjs_thread_get_user_time_by_id)
{
GumThreadId thread_id;
guint64 user_time = 0;

if (!_gum_v8_args_parse (args, "Z", &thread_id))
return;

user_time = gum_thead_get_user_time_by_id (thread_id);

info.GetReturnValue ().Set ((double)user_time / G_USEC_PER_SEC);
}
6 changes: 6 additions & 0 deletions gum/backend-darwin/gumprocess-darwin.c
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,12 @@ gum_thread_resume (GumThreadId thread_id,
#endif
}

guint64
gum_thead_get_user_time (void)
{
return 0;
}

gboolean
gum_module_load (const gchar * module_name,
GError ** error)
Expand Down
6 changes: 6 additions & 0 deletions gum/backend-freebsd/gumprocess-freebsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,12 @@ gum_thread_resume (GumThreadId thread_id,
}
}

guint64
gum_thead_get_user_time (void)
{
return 0;
}

gboolean
gum_module_load (const gchar * module_name,
GError ** error)
Expand Down
23 changes: 23 additions & 0 deletions gum/backend-linux/gumprocess-linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
#ifdef HAVE_SYS_USER_H
# include <sys/user.h>
#endif
#ifdef HAVE_LINUX
#include <sys/resource.h>
#endif

#define GUM_PAGE_START(value, page_size) \
(GUM_ADDRESS (value) & ~GUM_ADDRESS (page_size - 1))
Expand Down Expand Up @@ -1548,6 +1551,26 @@ gum_thread_resume (GumThreadId thread_id,
}
}

/**
* gum_thead_get_user_time:
*
* Returns: the time the thread has spend executing in user-mode in micro-seconds
*/
guint64
gum_thead_get_user_time (void)
{
guint64 user_time = 0;
struct rusage usage;

if (getrusage(RUSAGE_THREAD, &usage) == 0)
{
user_time = (usage.ru_utime.tv_sec * G_USEC_PER_SEC)
+ usage.ru_utime.tv_usec;
}

return user_time;
}

gboolean
gum_module_load (const gchar * module_name,
GError ** error)
Expand Down
6 changes: 6 additions & 0 deletions gum/backend-qnx/gumprocess-qnx.c
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,12 @@ gum_thread_resume (GumThreadId thread_id,
}
}

guint64
gum_thead_get_user_time (void)
{
return 0;
}

gboolean
gum_module_load (const gchar * module_name,
GError ** error)
Expand Down
6 changes: 6 additions & 0 deletions gum/backend-windows/gumprocess-windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,12 @@ gum_thread_resume (GumThreadId thread_id,
}
}

guint64
gum_thead_get_user_time (void)
{
return 0;
}

gboolean
gum_module_load (const gchar * module_name,
GError ** error)
Expand Down
39 changes: 39 additions & 0 deletions gum/gumprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "gum-init.h"
#include "gumcloak.h"
#include "gumstalker.h"

typedef struct _GumEmitThreadsContext GumEmitThreadsContext;
typedef struct _GumResolveModulePointerContext GumResolveModulePointerContext;
Expand Down Expand Up @@ -57,6 +58,8 @@ static gboolean gum_emit_module_if_not_cloaked (
const GumModuleDetails * details, gpointer user_data);
static gboolean gum_emit_range_if_not_cloaked (const GumRangeDetails * details,
gpointer user_data);
static void gum_get_user_time (const GumCpuContext * cpu_context,
gpointer user_data);
static gboolean gum_store_address_if_name_matches (
const GumSymbolDetails * details, gpointer user_data);

Expand Down Expand Up @@ -327,6 +330,42 @@ gum_emit_range_if_not_cloaked (const GumRangeDetails * details,
return ctx->func (details, ctx->user_data);
}

/**
* gum_thead_get_user_time_by_id:
* @thread_id: thread to query
*
* Returns: the time the thread has spend executing in user-mode in micro-seconds
*/
guint64
gum_thead_get_user_time_by_id (GumThreadId thread_id)
{
guint64 user_time = 0;
GumStalker * stalker = NULL;

if (thread_id == gum_process_get_current_thread_id ())
{
return gum_thead_get_user_time ();
}

stalker = gum_stalker_new ();

gum_stalker_run_on_thread_sync (stalker, thread_id, gum_get_user_time,
&user_time);

while (gum_stalker_garbage_collect (stalker))
g_usleep (10000);

g_object_unref (stalker);
return user_time;
}

static void
gum_get_user_time (const GumCpuContext * cpu_context, gpointer user_data)
{
guint64 * user_time = (guint64 *) user_data;
*user_time = gum_thead_get_user_time ();
}

/**
* gum_process_enumerate_malloc_ranges:
* @func: (scope call): function called with #GumMallocRangeDetails
Expand Down
2 changes: 2 additions & 0 deletions gum/gumprocess.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ GUM_API gint gum_thread_get_system_error (void);
GUM_API void gum_thread_set_system_error (gint value);
GUM_API gboolean gum_thread_suspend (GumThreadId thread_id, GError ** error);
GUM_API gboolean gum_thread_resume (GumThreadId thread_id, GError ** error);
GUM_API guint64 gum_thead_get_user_time_by_id (GumThreadId thread_id);
GUM_API guint64 gum_thead_get_user_time (void);
GUM_API gboolean gum_module_load (const gchar * module_name, GError ** error);
GUM_API gboolean gum_module_ensure_initialized (const gchar * module_name);
GUM_API void gum_module_enumerate_imports (const gchar * module_name,
Expand Down
99 changes: 99 additions & 0 deletions tests/core/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ TESTLIST_BEGIN (process)
TESTENTRY (process_threads)
TESTENTRY (process_threads_exclude_cloaked)
TESTENTRY (process_threads_should_include_name)
TESTENTRY (process_threads_get_user_time)
TESTENTRY (process_threads_get_user_time_by_id_self)
TESTENTRY (process_threads_get_user_time_by_id_other)
TESTENTRY (process_modules)
TESTENTRY (process_ranges)
TESTENTRY (process_ranges_exclude_cloaked)
Expand Down Expand Up @@ -150,6 +153,7 @@ static gboolean process_potential_export_search_result (
static GThread * create_sleeping_dummy_thread_sync (const gchar * name,
volatile gboolean * done, GumThreadId * thread_id);
static gpointer sleeping_dummy (gpointer data);
static void do_work (void);
static gboolean thread_found_cb (const GumThreadDetails * details,
gpointer user_data);
static gboolean thread_check_cb (const GumThreadDetails * details,
Expand Down Expand Up @@ -286,6 +290,82 @@ check_thread_enumeration_testable (void)
return TRUE;
}

TESTCASE (process_threads_get_user_time)
{
guint64 user_time_a, user_time_b;

if (!check_thread_enumeration_testable ())
return;

do_work ();
user_time_a = gum_thead_get_user_time ();

do_work ();
user_time_b = gum_thead_get_user_time ();
#if defined (HAVE_LINUX)
g_assert_cmpuint (user_time_a, !=, 0);
g_assert_cmpuint (user_time_b, >, user_time_a);
#else
g_assert_cmpuint (user_time_a, ==, 0);
g_assert_cmpuint (user_time_b, ==, 0);
#endif

}

TESTCASE (process_threads_get_user_time_by_id_self)
{
GumThreadId tid = gum_process_get_current_thread_id ();
guint64 user_time_a, user_time_b;

if (!check_thread_enumeration_testable ())
return;

do_work ();
user_time_a = gum_thead_get_user_time_by_id (tid);

do_work ();
user_time_b = gum_thead_get_user_time_by_id (tid);
#if defined (HAVE_LINUX)
g_assert_cmpuint (user_time_a, !=, 0);
g_assert_cmpuint (user_time_b, >, user_time_a);
#else
g_assert_cmpuint (user_time_a, ==, 0);
g_assert_cmpuint (user_time_b, ==, 0);
#endif
}

TESTCASE (process_threads_get_user_time_by_id_other)
{
volatile gboolean done = FALSE;
GThread * thread;
GumThreadDetails d = { 0, };
guint64 user_time_a, user_time_b;

if (!check_thread_enumeration_testable ())
return;

thread = create_sleeping_dummy_thread_sync ("user_time", &done, &d.id);

/* Sleep for a short while to let the other thread wake and run */
g_usleep (250000);
user_time_a = gum_thead_get_user_time_by_id (d.id);

/* Sleep for a short while to let the other thread wake and run */
g_usleep (250000);
user_time_b = gum_thead_get_user_time_by_id (d.id);

#if defined (HAVE_LINUX)
g_assert_cmpuint (user_time_a, !=, 0);
g_assert_cmpuint (user_time_b, >, user_time_a);
#else
g_assert_cmpuint (user_time_b, ==, 0);
g_assert_cmpuint (user_time_a, ==, 0);
#endif

done = TRUE;
g_thread_join (thread);
}

TESTCASE (process_modules)
{
TestForEachContext ctx;
Expand Down Expand Up @@ -1150,6 +1230,8 @@ sleeping_dummy (gpointer data)
pthread_setname_np (pthread_self (), sync_data->name);
#endif

do_work ();

g_mutex_lock (&sync_data->mutex);
sync_data->started = TRUE;
sync_data->thread_id = gum_process_get_current_thread_id ();
Expand All @@ -1162,6 +1244,23 @@ sleeping_dummy (gpointer data)
return NULL;
}

static void
do_work (void)
{

GTimer * timer = g_timer_new ();

g_timer_start (timer);
/* Do some work and use some CPU cycles */
static guint no_opt = 0;
while (g_timer_elapsed (timer, NULL) < 0.1)
{
no_opt = no_opt + 1;
}

g_timer_destroy (timer);
}

static gboolean
thread_found_cb (const GumThreadDetails * details,
gpointer user_data)
Expand Down
Loading

0 comments on commit f814e50

Please sign in to comment.