基础接口
process_vm_readv(2)
NAME
process_vm_readv, process_vm_writev - 在进程间传输数据
SYNOPSIS
#define _GNU_SOURCE
#include <sys/uio.h>
ssize_t process_vm_readv(pid_t pid,
const struct iovec *local_iov,
unsigned long liovcnt,
const struct iovec *remote_iov,
unsigned long riovcnt,
unsigned long flags);
ssize_t process_vm_writev(pid_t pid,
const struct iovec *local_iov,
unsigned long liovcnt,
const struct iovec *remote_iov,
unsigned long riovcnt,
unsigned long flags);
DESCRIPTION
这些系统调用允许直接在调用进程和指定进程(pid)的内存之间传输数据,
无需通过内核缓冲区复制。
process_vm_readv() 从远程进程读取数据到本地进程
process_vm_writev() 从本地进程写入数据到远程进程
参数说明:
- pid: 目标进程ID
- local_iov: 本地内存区域描述符数组
- liovcnt: 本地iovec数组元素个数
- remote_iov: 远程内存区域描述符数组
- riovcnt: 远程iovec数组元素个数
- flags: 保留字段,必须为0
RETURN VALUE
成功时返回传输的字节数,失败时返回-1并设置errno
ERRORS
EACCES 没有权限访问目标进程内存
EFAULT 指定的地址范围无效
EINVAL 参数无效
ENOMEM 内存不足
EPERM 没有权限操作目标进程
ESRCH 目标进程不存在
VERSIONS
Linux 3.2+ 支持这些系统调用
CONFORMING TO
这些是Linux特有的系统调用
NOTES
需要相同用户ID或CAP_SYS_PTRACE权限
目标进程必须正在运行
不会触发目标进程的信号处理程序
数据结构
iovec 结构体
struct iovec {
void *iov_base; /* 起始地址 */
size_t iov_len; /* 缓冲区长度 */
};
使用示例
示例1:基本内存读取
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// 目标进程中需要读取的变量
int target_global_var = 42;
char target_string[] = "Hello from target process!";
int read_remote_memory(pid_t target_pid) {
struct iovec local_iov[2];
struct iovec remote_iov[2];
ssize_t result;
int local_int;
char local_buffer[100];
// 设置本地缓冲区
local_iov[0].iov_base = &local_int;
local_iov[0].iov_len = sizeof(local_int);
local_iov[1].iov_base = local_buffer;
local_iov[1].iov_len = sizeof(local_buffer);
// 设置远程内存地址(需要知道目标进程中的确切地址)
remote_iov[0].iov_base = &target_global_var; // 实际使用中需要通过其他方式获取
remote_iov[0].iov_len = sizeof(target_global_var);
remote_iov[1].iov_base = target_string;
remote_iov[1].iov_len = strlen(target_string) + 1;
// 读取远程进程内存
result = process_vm_readv(target_pid,
local_iov, 2,
remote_iov, 2,
0);
if (result == -1) {
perror("process_vm_readv");
return -1;
}
printf("Read %zd bytes\n", result);
printf("Remote int value: %d\n", local_int);
printf("Remote string: %s\n", local_buffer);
return 0;
}
示例2:内存写入操作
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// 目标进程中的可修改变量
int target_writable_var = 100;
char target_writable_buffer[256] = "Original content";
int write_remote_memory(pid_t target_pid) {
struct iovec local_iov[2];
struct iovec remote_iov[2];
ssize_t result;
int new_value = 999;
char new_string[] = "Modified by process_vm_writev!";
// 设置本地数据源
local_iov[0].iov_base = &new_value;
local_iov[0].iov_len = sizeof(new_value);
local_iov[1].iov_base = new_string;
local_iov[1].iov_len = strlen(new_string) + 1;
// 设置远程内存地址
remote_iov[0].iov_base = &target_writable_var;
remote_iov[0].iov_len = sizeof(target_writable_var);
remote_iov[1].iov_base = target_writable_buffer;
remote_iov[1].iov_len = strlen(new_string) + 1;
// 写入远程进程内存
result = process_vm_writev(target_pid,
local_iov, 2,
remote_iov, 2,
0);
if (result == -1) {
perror("process_vm_writev");
return -1;
}
printf("Wrote %zd bytes to remote process\n", result);
return 0;
}
示例3:完整的工作示例
目标进程代码 (target.c)
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
volatile int shared_int = 12345;
char shared_string[256] = "This is shared data from target process";
int running = 1;
void signal_handler(int sig) {
printf("Received signal %d\n", sig);
running = 0;
}
int main() {
printf("Target process PID: %d\n", getpid());
printf("Shared int address: %p\n", &shared_int);
printf("Shared string address: %p\n", shared_string);
printf("Shared int value: %d\n", shared_int);
printf("Shared string value: %s\n", shared_string);
// 安装信号处理程序
signal(SIGUSR1, signal_handler);
printf("Target process running... Send SIGUSR1 to stop\n");
while (running) {
printf("shared_int = %d, shared_string = %s\n",
shared_int, shared_string);
sleep(2);
}
printf("Target process exiting...\n");
printf("Final values - shared_int = %d, shared_string = %s\n",
shared_int, shared_string);
return 0;
}
访问进程代码 (accessor.c)
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <target_pid>\n", argv[0]);
exit(1);
}
pid_t target_pid = atoi(argv[1]);
struct iovec local_iov[2];
struct iovec remote_iov[2];
ssize_t result;
int local_int;
char local_string[256];
int new_int = 99999;
char new_string[] = "Modified by accessor process!";
printf("Accessing process PID: %d\n", getpid());
printf("Target PID: %d\n", target_pid);
// 读取远程进程内存
printf("\n--- Reading remote memory ---\n");
local_iov[0].iov_base = &local_int;
local_iov[0].iov_len = sizeof(local_int);
local_iov[1].iov_base = local_string;
local_iov[1].iov_len = sizeof(local_string);
// 注意:这里需要知道目标进程的确切内存地址
// 在实际应用中,这些地址需要通过调试信息或其他方式获取
remote_iov[0].iov_base = (void*)0x601040; // 需要根据实际情况调整
remote_iov[0].iov_len = sizeof(int);
remote_iov[1].iov_base = (void*)0x601060; // 需要根据实际情况调整
remote_iov[1].iov_len = sizeof(local_string);
result = process_vm_readv(target_pid,
local_iov, 2,
remote_iov, 2,
0);
if (result == -1) {
perror("process_vm_readv");
printf("Note: You need to adjust memory addresses based on target process\n");
return 1;
}
printf("Read %zd bytes\n", result);
printf("Remote int value: %d\n", local_int);
printf("Remote string: %s\n", local_string);
// 修改远程进程内存
printf("\n--- Writing to remote memory ---\n");
local_iov[0].iov_base = &new_int;
local_iov[0].iov_len = sizeof(new_int);
local_iov[1].iov_base = new_string;
local_iov[1].iov_len = strlen(new_string) + 1;
result = process_vm_writev(target_pid,
local_iov, 2,
remote_iov, 2,
0);
if (result == -1) {
perror("process_vm_writev");
return 1;
}
printf("Wrote %zd bytes to remote process\n", result);
// 再次读取验证修改
printf("\n--- Verifying changes ---\n");
result = process_vm_readv(target_pid,
local_iov, 2,
remote_iov, 2,
0);
if (result != -1) {
printf("Remote int value after write: %d\n", local_int);
printf("Remote string after write: %s\n", local_string);
}
// 发送信号给目标进程
printf("\n--- Sending signal to target process ---\n");
if (kill(target_pid, SIGUSR1) == -1) {
perror("kill");
} else {
printf("Signal sent successfully\n");
}
return 0;
}
示例4:实用工具 – 进程内存检查器
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
// 简单的内存转储工具
int dump_remote_memory(pid_t pid, unsigned long addr, size_t size) {
char *buffer;
struct iovec local_iov[1];
struct iovec remote_iov[1];
ssize_t result;
size_t i;
buffer = malloc(size);
if (!buffer) {
perror("malloc");
return -1;
}
local_iov[0].iov_base = buffer;
local_iov[0].iov_len = size;
remote_iov[0].iov_base = (void*)addr;
remote_iov[0].iov_len = size;
result = process_vm_readv(pid,
local_iov, 1,
remote_iov, 1,
0);
if (result == -1) {
perror("process_vm_readv");
free(buffer);
return -1;
}
printf("Memory dump at 0x%lx (%zd bytes):\n", addr, result);
for (i = 0; i < (size_t)result; i++) {
if (i % 16 == 0) {
printf("\n%08lx: ", addr + i);
}
printf("%02x ", (unsigned char)buffer[i]);
}
printf("\n");
// 显示可打印字符
printf("\nASCII: ");
for (i = 0; i < (size_t)result; i++) {
if (i % 16 == 0 && i > 0) {
printf("\n ");
}
printf("%c", (buffer[i] >= 32 && buffer[i] <= 126) ? buffer[i] : '.');
}
printf("\n");
free(buffer);
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 4) {
fprintf(stderr, "Usage: %s <pid> <address> <size>\n", argv[0]);
fprintf(stderr, "Example: %s 1234 0x601040 64\n", argv[0]);
exit(1);
}
pid_t target_pid = atoi(argv[1]);
unsigned long address = strtoul(argv[2], NULL, 0);
size_t size = strtoul(argv[3], NULL, 0);
printf("Dumping %zu bytes from process %d at address 0x%lx\n",
size, target_pid, address);
return dump_remote_memory(target_pid, address, size);
}
示例5:批量内存操作
#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MAX_IOV 1024
typedef struct {
void *remote_addr;
size_t size;
void *local_buffer;
} memory_region_t;
// 批量读取多个内存区域
int batch_read_memory(pid_t pid, memory_region_t *regions, int count) {
struct iovec local_iov[MAX_IOV];
struct iovec remote_iov[MAX_IOV];
ssize_t result;
int i;
if (count > MAX_IOV) {
fprintf(stderr, "Too many regions\n");
return -1;
}
// 设置iovec数组
for (i = 0; i < count; i++) {
local_iov[i].iov_base = regions[i].local_buffer;
local_iov[i].iov_len = regions[i].size;
remote_iov[i].iov_base = regions[i].remote_addr;
remote_iov[i].iov_len = regions[i].size;
}
result = process_vm_readv(pid,
local_iov, count,
remote_iov, count,
0);
if (result == -1) {
perror("process_vm_readv");
return -1;
}
printf("Batch read completed: %zd bytes total\n", result);
return 0;
}
// 批量写入多个内存区域
int batch_write_memory(pid_t pid, memory_region_t *regions, int count) {
struct iovec local_iov[MAX_IOV];
struct iovec remote_iov[MAX_IOV];
ssize_t result;
int i;
if (count > MAX_IOV) {
fprintf(stderr, "Too many regions\n");
return -1;
}
// 设置iovec数组
for (i = 0; i < count; i++) {
local_iov[i].iov_base = regions[i].local_buffer;
local_iov[i].iov_len = regions[i].size;
remote_iov[i].iov_base = regions[i].remote_addr;
remote_iov[i].iov_len = regions[i].size;
}
result = process_vm_writev(pid,
local_iov, count,
remote_iov, count,
0);
if (result == -1) {
perror("process_vm_writev");
return -1;
}
printf("Batch write completed: %zd bytes total\n", result);
return 0;
}
int main() {
printf("Batch memory operations example\n");
printf("This example shows how to perform batch operations\n");
printf("You need to provide actual PID and memory addresses\n");
return 0;
}
使用限制和注意事项
权限要求
- 相同用户: 调用进程和目标进程必须有相同的有效用户ID
- ptrace权限: 或者调用进程需要
CAP_SYS_PTRACE
权能 - 进程状态: 目标进程必须正在运行(不是僵尸进程)
安全限制
- 内存保护: 不能访问目标进程的受保护内存区域
- 地址有效性: 必须确保远程地址在目标进程的有效地址空间内
- 对齐要求: 某些体系结构可能有内存对齐要求
技术限制
- 最大IOV数量: 系统可能限制iovec数组的最大长度
- 性能考虑: 大量小的传输可能不如批量传输高效
- 原子性: 单次调用内的多个传输不是原子的
错误处理
- 部分传输: 可能只传输部分数据,需要检查返回值
- 地址错误: 无效地址会导致整个操作失败
- 进程终止: 目标进程在操作过程中终止会导致错误
最佳实践
- 地址获取: 使用调试信息或符号表获取准确的内存地址
- 缓冲区管理: 确保本地缓冲区足够大且生命周期正确
- 错误检查: 始终检查返回值并处理错误情况
- 权限验证: 在执行操作前验证权限和进程状态
这些系统调用提供了强大的进程间内存访问能力,但使用时需要谨慎处理安全和权限问题。