Skip to content

Commit

Permalink
linux: Optimize Memory.read() for Linux/Android (#988)
Browse files Browse the repository at this point in the history
By making use of process_vm_readv() if the kernel supports it.
  • Loading branch information
DoranekoSystems authored Jan 23, 2025
1 parent a94b047 commit f61b78b
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 6 deletions.
89 changes: 83 additions & 6 deletions gum/backend-linux/gummemory-linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,46 @@

#include "gumlinux-priv.h"
#include "gummemory-priv.h"
#include "gum/gumlinux.h"
#include "valgrind.h"

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <unistd.h>

#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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
33 changes: 33 additions & 0 deletions gum/backend-linux/gumprocess-linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#ifdef HAVE_SYS_USER_H
# include <sys/user.h>
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions gum/backend-linux/include/gum/gumlinux.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit f61b78b

Please sign in to comment.