diff --git a/bindings/gumjs/gumquickcore.c b/bindings/gumjs/gumquickcore.c index 236c2d53aa..700e114e92 100644 --- a/bindings/gumjs/gumquickcore.c +++ b/bindings/gumjs/gumquickcore.c @@ -5679,6 +5679,7 @@ gum_quick_core_setup_atoms (GumQuickCore * self) GUM_SETUP_ATOM (toolchain); GUM_SETUP_ATOM (traps); GUM_SETUP_ATOM (type); + GUM_SETUP_ATOM (userTime); GUM_SETUP_ATOM (value); GUM_SETUP_ATOM (written); @@ -5755,6 +5756,7 @@ gum_quick_core_teardown_atoms (GumQuickCore * self) GUM_TEARDOWN_ATOM (toolchain); GUM_TEARDOWN_ATOM (traps); GUM_TEARDOWN_ATOM (type); + GUM_TEARDOWN_ATOM (userTime); GUM_TEARDOWN_ATOM (value); GUM_TEARDOWN_ATOM (written); diff --git a/bindings/gumjs/gumquickcore.h b/bindings/gumjs/gumquickcore.h index ff5338ba55..f8c4df18eb 100644 --- a/bindings/gumjs/gumquickcore.h +++ b/bindings/gumjs/gumquickcore.h @@ -165,6 +165,7 @@ struct _GumQuickCore GUM_DECLARE_ATOM (toolchain); GUM_DECLARE_ATOM (traps); GUM_DECLARE_ATOM (type); + GUM_DECLARE_ATOM (userTime); GUM_DECLARE_ATOM (value); GUM_DECLARE_ATOM (written); diff --git a/bindings/gumjs/gumquickprocess.c b/bindings/gumjs/gumquickprocess.c index 595fba92f8..11047e1f78 100644 --- a/bindings/gumjs/gumquickprocess.c +++ b/bindings/gumjs/gumquickprocess.c @@ -320,6 +320,10 @@ gum_emit_thread (const GumThreadDetails * details, GUM_QUICK_CORE_ATOM (core, state), _gum_quick_thread_state_new (ctx, details->state), JS_PROP_C_W_E); + JS_DefinePropertyValue (ctx, thread, + GUM_QUICK_CORE_ATOM (core, userTime), + _gum_quick_uint64_new (ctx, details->user_time, core), + JS_PROP_C_W_E); JS_DefinePropertyValue (ctx, thread, GUM_QUICK_CORE_ATOM (core, context), _gum_quick_cpu_context_new (ctx, (GumCpuContext *) &details->cpu_context, diff --git a/bindings/gumjs/gumv8process.cpp b/bindings/gumjs/gumv8process.cpp index 489e591eb8..302c1f2885 100644 --- a/bindings/gumjs/gumv8process.cpp +++ b/bindings/gumjs/gumv8process.cpp @@ -275,6 +275,7 @@ gum_emit_thread (const GumThreadDetails * details, auto cpu_context = _gum_v8_cpu_context_new_immutable (&details->cpu_context, core); _gum_v8_object_set (thread, "context", cpu_context, core); + _gum_v8_object_set (thread, "userTime", Number::New (isolate, details->user_time), core); auto proceed = mc->OnMatch (thread); diff --git a/gum/backend-darwin/gumprocess-darwin.c b/gum/backend-darwin/gumprocess-darwin.c index 0e7d39caaa..ec464d4698 100644 --- a/gum/backend-darwin/gumprocess-darwin.c +++ b/gum/backend-darwin/gumprocess-darwin.c @@ -1537,6 +1537,7 @@ gum_darwin_enumerate_threads (mach_port_t task, details.state = gum_thread_state_from_darwin (info.run_state); gum_darwin_parse_unified_thread_state (&state, &details.cpu_context); + details.user_time = 0; if (!func (&details, user_data)) break; diff --git a/gum/backend-freebsd/gumprocess-freebsd.c b/gum/backend-freebsd/gumprocess-freebsd.c index 4740d7b4f8..966573d9ac 100644 --- a/gum/backend-freebsd/gumprocess-freebsd.c +++ b/gum/backend-freebsd/gumprocess-freebsd.c @@ -415,6 +415,7 @@ _gum_process_enumerate_threads (GumFoundThreadFunc func, { bzero (&details.cpu_context, sizeof (details.cpu_context)); } + details.user_time = 0; if (!func (&details, user_data)) break; diff --git a/gum/backend-linux/gumprocess-linux.c b/gum/backend-linux/gumprocess-linux.c index f9e2d6a1ff..b485068484 100644 --- a/gum/backend-linux/gumprocess-linux.c +++ b/gum/backend-linux/gumprocess-linux.c @@ -40,6 +40,9 @@ #ifdef HAVE_ASM_PTRACE_H # include #endif +#if defined (HAVE_LINUX) +#include +#endif #include #include #include @@ -231,8 +234,11 @@ static gint gum_do_modify_thread (gpointer data); static gboolean gum_await_ack (gint fd, GumModifyThreadAck expected_ack); static void gum_put_ack (gint fd, GumModifyThreadAck ack); -static void gum_store_cpu_context (GumThreadId thread_id, - GumCpuContext * cpu_context, gpointer user_data); +static void gum_store_context (const GumCpuContext * cpu_context, + gpointer user_data); +static void gum_store_cpu_context (const GumCpuContext * cpu_context, + gpointer user_data); +static void gum_store_user_time (guint64 * user_time); static void gum_do_enumerate_modules (const gchar * libc_name, GumFoundModuleFunc func, gpointer user_data); @@ -1014,11 +1020,14 @@ _gum_process_enumerate_threads (GumFoundThreadFunc func, { GDir * dir; const gchar * name; + GumStalker * stalker; gboolean carry_on = TRUE; dir = g_dir_open ("/proc/self/task", 0, NULL); g_assert (dir != NULL); + stalker = gum_stalker_new (); + while (carry_on && (name = g_dir_read_name (dir)) != NULL) { GumThreadDetails details; @@ -1031,27 +1040,58 @@ _gum_process_enumerate_threads (GumFoundThreadFunc func, if (gum_thread_read_state (details.id, &details.state)) { - if (gum_process_modify_thread (details.id, gum_store_cpu_context, - &details.cpu_context, GUM_MODIFY_THREAD_FLAGS_ABORT_SAFELY)) - { - carry_on = func (&details, user_data); - } + details.user_time = 0; + if (gum_stalker_run_on_thread_sync(stalker, details.id, + gum_store_context, &details)) + { + carry_on = func(&details, user_data); + } } g_free (thread_name); } + while (gum_stalker_garbage_collect (stalker)) + g_usleep (10000); + + g_object_unref (stalker); + g_dir_close (dir); } static void -gum_store_cpu_context (GumThreadId thread_id, - GumCpuContext * cpu_context, +gum_store_context (const GumCpuContext * cpu_context, + gpointer user_data) +{ + GumThreadDetails * details = (GumThreadDetails *) user_data; + gum_store_user_time (&details->user_time); + gum_store_cpu_context (cpu_context, &details->cpu_context); +} + +static void +gum_store_cpu_context (const GumCpuContext * cpu_context, gpointer user_data) { memcpy (user_data, cpu_context, sizeof (GumCpuContext)); } +static void +gum_store_user_time (guint64 * user_time) +{ + guint64 result = 0; +#if defined (HAVE_LINUX) + struct rusage usage; + + if (getrusage(RUSAGE_THREAD, &usage) == 0) + { + result = usage.ru_utime.tv_sec; + result *= 1000000; + result += usage.ru_utime.tv_usec; + } +#endif + *user_time = result; +} + gboolean _gum_process_collect_main_module (const GumModuleDetails * details, gpointer user_data) diff --git a/gum/backend-qnx/gumprocess-qnx.c b/gum/backend-qnx/gumprocess-qnx.c index 742cf5955d..5148bc2a30 100644 --- a/gum/backend-qnx/gumprocess-qnx.c +++ b/gum/backend-qnx/gumprocess-qnx.c @@ -258,6 +258,7 @@ _gum_process_enumerate_threads (GumFoundThreadFunc func, gum_process_modify_thread (details.id, gum_store_cpu_context, &details.cpu_context, GUM_MODIFY_THREAD_FLAGS_ABORT_SAFELY)) { + details.user_time = 0; carry_on = func (&details, user_data); } diff --git a/gum/backend-windows/gumprocess-windows.c b/gum/backend-windows/gumprocess-windows.c index 5a99772ef4..5cfb646fff 100644 --- a/gum/backend-windows/gumprocess-windows.c +++ b/gum/backend-windows/gumprocess-windows.c @@ -189,6 +189,7 @@ _gum_process_enumerate_threads (GumFoundThreadFunc func, if (gum_windows_get_thread_details (entry.th32ThreadID, &details)) { + details.user_time = 0; if (!func (&details, user_data)) break; } diff --git a/gum/gumprocess.h b/gum/gumprocess.h index 0b6bb9c277..bd8a56593d 100644 --- a/gum/gumprocess.h +++ b/gum/gumprocess.h @@ -62,6 +62,7 @@ struct _GumThreadDetails GumThreadId id; const gchar * name; GumThreadState state; + guint64 user_time; GumCpuContext cpu_context; }; diff --git a/tests/core/process.c b/tests/core/process.c index 8fa67d813e..46d56056f9 100644 --- a/tests/core/process.c +++ b/tests/core/process.c @@ -36,6 +36,7 @@ TESTLIST_BEGIN (process) TESTENTRY (process_threads) TESTENTRY (process_threads_exclude_cloaked) TESTENTRY (process_threads_should_include_name) + TESTENTRY (process_threads_should_include_time) TESTENTRY (process_modules) TESTENTRY (process_ranges) TESTENTRY (process_ranges_exclude_cloaked) @@ -286,6 +287,46 @@ check_thread_enumeration_testable (void) return TRUE; } +TESTCASE (process_threads_should_include_time) +{ + volatile gboolean done = FALSE; + GThread * thread; + GumThreadDetails ctx = {0}; + guint64 user_time; + + if (!check_thread_enumeration_testable ()) + return; + + thread = create_sleeping_dummy_thread_sync ("user_time", &done, &ctx.id); + + /* Sleep for a short while to let the other thread wake and run */ + g_usleep (250000); + + gum_process_enumerate_threads (thread_collect_if_matching_id, &ctx); + user_time = ctx.user_time; + +#if defined (HAVE_LINUX) + g_assert_cmpuint (user_time, !=, 0); +#else + g_assert_cmpuint (user_time, ==, 0); +#endif + + /* Sleep for a short while to let the other thread wake and run */ + g_usleep (250000); + + gum_process_enumerate_threads (thread_collect_if_matching_id, &ctx); +#if defined (HAVE_LINUX) + g_assert_cmpuint (ctx.user_time, >, user_time); +#else + g_assert_cmpuint (ctx.user_time, ==, 0); +#endif + + done = TRUE; + g_thread_join (thread); + + g_free ((gpointer) ctx.name); +} + TESTCASE (process_modules) { TestForEachContext ctx; @@ -1150,6 +1191,13 @@ sleeping_dummy (gpointer data) pthread_setname_np (pthread_self (), sync_data->name); #endif + /* Do some work and use some CPU cycles */ + static guint no_opt = 0; + for (guint i = 0; i < 1000000; i++) + { + no_opt = no_opt + i; + } + g_mutex_lock (&sync_data->mutex); sync_data->started = TRUE; sync_data->thread_id = gum_process_get_current_thread_id (); @@ -1197,6 +1245,7 @@ thread_collect_if_matching_id (const GumThreadDetails * details, ctx->name = g_strdup (details->name); ctx->state = details->state; ctx->cpu_context = details->cpu_context; + ctx->user_time = details->user_time; return FALSE; } diff --git a/tests/gumjs/script.c b/tests/gumjs/script.c index 20ace71377..18f54590d5 100644 --- a/tests/gumjs/script.c +++ b/tests/gumjs/script.c @@ -197,6 +197,7 @@ TESTLIST_BEGIN (script) TESTENTRY (process_threads_can_be_enumerated) TESTENTRY (process_threads_can_be_enumerated_legacy_style) TESTENTRY (process_threads_have_names) + TESTENTRY (process_threads_have_time) TESTENTRY (process_modules_can_be_enumerated) TESTENTRY (process_modules_can_be_enumerated_legacy_style) TESTENTRY (process_module_can_be_looked_up_from_address) @@ -5105,6 +5106,64 @@ TESTCASE (process_threads_have_names) g_async_queue_unref (ctx.controller_messages); } +TESTCASE (process_threads_have_time) +{ + GThread * thread; + GumThreadId thread_id; + volatile gboolean done = FALSE; + guint64 user_time_a, user_time_b; + +#ifdef HAVE_LINUX + if (!check_exception_handling_testable ()) + return; +#endif + +#ifdef HAVE_MIPS + if (!g_test_slow ()) + { + g_print (" "); + return; + } +#endif + + thread = create_sleeping_dummy_thread_sync (&done, &thread_id); + + COMPILE_AND_LOAD_SCRIPT ( + "Thread.sleep(0.25);" + GUM_PTR_CONST".writeU64(" + " Process.enumerateThreads()" + " .find(t => t.id == " GUM_PTR_CONST ")" + " .userTime);", + &user_time_a, + thread_id + ); + + EXPECT_NO_MESSAGES (); + + COMPILE_AND_LOAD_SCRIPT ( + "Thread.sleep(0.25);" + GUM_PTR_CONST".writeU64(" + " Process.enumerateThreads()" + " .find(t => t.id == " GUM_PTR_CONST ")" + " .userTime);", + &user_time_b, + thread_id + ); + + EXPECT_NO_MESSAGES (); + +#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 + + done = TRUE; + g_thread_join (thread); +} + static gpointer named_sleeper (gpointer data) {