From f61b78b5095e69c847020bcc539f90a54559a507 Mon Sep 17 00:00:00 2001 From: Kenjiro Ichise Date: Fri, 24 Jan 2025 04:29:30 +0900 Subject: [PATCH] linux: Optimize Memory.read() for Linux/Android (#988) By making use of process_vm_readv() if the kernel supports it. --- gum/backend-linux/gummemory-linux.c | 89 ++++++++++++++++++++++-- gum/backend-linux/gumprocess-linux.c | 33 +++++++++ gum/backend-linux/include/gum/gumlinux.h | 2 + 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/gum/backend-linux/gummemory-linux.c b/gum/backend-linux/gummemory-linux.c index 6581d7025..3a972d6f2 100644 --- a/gum/backend-linux/gummemory-linux.c +++ b/gum/backend-linux/gummemory-linux.c @@ -8,17 +8,46 @@ #include "gumlinux-priv.h" #include "gummemory-priv.h" +#include "gum/gumlinux.h" #include "valgrind.h" #include #include #include #include +#include #include +#if defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 4 +# define GUM_SYS_PROCESS_VM_READV 347 +#elif defined (HAVE_I386) && GLIB_SIZEOF_VOID_P == 8 +# define GUM_SYS_PROCESS_VM_READV 310 +#elif defined (HAVE_ARM) +# define GUM_SYS_PROCESS_VM_READV (__NR_SYSCALL_BASE + 376) +#elif defined (HAVE_ARM64) +# define GUM_SYS_PROCESS_VM_READV 270 +#elif defined (HAVE_MIPS) +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define GUM_SYS_PROCESS_VM_READV (__NR_Linux + 345) +# elif _MIPS_SIM == _MIPS_SIM_ABI64 +# define GUM_SYS_PROCESS_VM_READV (__NR_Linux + 304) +# elif _MIPS_SIM == _MIPS_SIM_NABI32 +# define GUM_SYS_PROCESS_VM_READV (__NR_Linux + 309) +# else +# error Unexpected MIPS ABI +# endif +#else +# error FIXME +#endif + static gboolean gum_memory_get_protection (gconstpointer address, gsize n, gsize * size, GumPageProtection * prot); +static gssize gum_libc_process_vm_readv (pid_t pid, + const struct iovec * local_iov, gulong liovcnt, + const struct iovec * remote_iov, gulong riovcnt, + gulong flags); + gboolean gum_memory_is_readable (gconstpointer address, gsize len) @@ -64,14 +93,51 @@ gum_memory_read (gconstpointer address, { guint8 * result = NULL; gsize result_len = 0; - gsize size; - GumPageProtection prot; + static gboolean kernel_feature_likely_enabled = TRUE; + gboolean still_pending = TRUE; + + if (kernel_feature_likely_enabled && gum_linux_check_kernel_version (3, 2, 0)) + { + gssize n; + struct iovec local_iov = { + .iov_base = g_malloc (len), + .iov_len = len + }; + struct iovec remote_iov = { + .iov_base = (void *) address, + .iov_len = len + }; + + n = gum_libc_process_vm_readv (getpid (), &local_iov, 1, &remote_iov, 1, 0); + if (n > 0) + { + result_len = n; + result = local_iov.iov_base; + if (result_len != len) + result = g_realloc (result, result_len); + } + else + { + g_free (local_iov.iov_base); + } + + if (n == -1 && errno == ENOSYS) + kernel_feature_likely_enabled = FALSE; + else + still_pending = FALSE; + } - if (gum_memory_get_protection (address, len, &size, &prot) - && (prot & GUM_PAGE_READ) != 0) + if (still_pending) { - result_len = MIN (len, size); - result = g_memdup (address, result_len); + gsize size; + GumPageProtection prot; + + if (gum_memory_get_protection (address, len, &size, &prot) && + (prot & GUM_PAGE_READ) != 0) + { + result_len = MIN (len, size); + result = g_memdup (address, result_len); + } } if (n_bytes_read != NULL) @@ -243,3 +309,14 @@ gum_memory_get_protection (gconstpointer address, return success; } +static gssize +gum_libc_process_vm_readv (pid_t pid, + const struct iovec * local_iov, + gulong liovcnt, + const struct iovec * remote_iov, + gulong riovcnt, + gulong flags) +{ + return syscall (GUM_SYS_PROCESS_VM_READV, pid, local_iov, liovcnt, remote_iov, + riovcnt, flags); +} diff --git a/gum/backend-linux/gumprocess-linux.c b/gum/backend-linux/gumprocess-linux.c index 639611833..18239c0bb 100644 --- a/gum/backend-linux/gumprocess-linux.c +++ b/gum/backend-linux/gumprocess-linux.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #ifdef HAVE_SYS_USER_H # include @@ -2086,6 +2087,38 @@ gum_do_unset_hardware_watchpoint (GumThreadId thread_id, #endif } +gboolean +gum_linux_check_kernel_version (guint major, + guint minor, + guint micro) +{ + static gboolean initialized = FALSE; + static guint kern_major = G_MAXUINT; + static guint kern_minor = G_MAXUINT; + static guint kern_micro = G_MAXUINT; + + if (!initialized) + { + struct utsname un; + G_GNUC_UNUSED int res; + + res = uname (&un); + g_assert (res == 0); + + sscanf (un.release, "%u.%u.%u", &kern_major, &kern_minor, &kern_micro); + + initialized = TRUE; + } + + if (kern_major > major) + return TRUE; + + if (kern_major == major && kern_minor > minor) + return TRUE; + + return kern_major == major && kern_minor == minor && kern_micro >= micro; +} + GumCpuType gum_linux_cpu_type_from_file (const gchar * path, GError ** error) diff --git a/gum/backend-linux/include/gum/gumlinux.h b/gum/backend-linux/include/gum/gumlinux.h index 0579468c2..28152bdd8 100644 --- a/gum/backend-linux/include/gum/gumlinux.h +++ b/gum/backend-linux/include/gum/gumlinux.h @@ -22,6 +22,8 @@ struct _GumLinuxNamedRange gsize size; }; +GUM_API gboolean gum_linux_check_kernel_version (guint major, guint minor, + guint micro); GUM_API GumCpuType gum_linux_cpu_type_from_file (const gchar * path, GError ** error); GUM_API GumCpuType gum_linux_cpu_type_from_pid (pid_t pid, GError ** error);