process_vm_readv系统调用及示例

process_vm_readv/process_vm_writev 函数详解

  1. 函数介绍

process_vm_readv 和 process_vm_writev 是Linux系统提供的两个系统调用,用于在不同进程之间直接传输数据。它们允许一个进程直接读取或写入另一个进程的内存空间,而无需通过传统的管道、套接字等IPC机制。这种直接内存访问方式提供了更高的性能,特别适用于调试工具、进程监控、内存分析等场景。

  1. 函数原型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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);

  1. 功能
  • process_vm_readv: 从指定进程(pid)的内存中读取数据到当前进程的内存中

  • process_vm_writev: 将当前进程的内存数据写入到指定进程(pid)的内存中

  • 这两个函数都支持分散/聚集I/O操作,可以同时处理多个不连续的内存区域

  • 数据传输直接在用户空间进行,避免了内核缓冲区的复制,提高了性能

  1. 参数

共同参数说明:

pid_t pid: 目标进程的进程ID

  • 必须是正在运行的进程

  • 调用进程必须有权限访问该进程(相同用户或具有CAP_SYS_PTRACE权限)

*const struct iovec local_iov: 本地内存区域描述符数组

  • 描述当前进程中用于读写操作的内存缓冲区

  • 每个iovec结构包含基地址和长度

unsigned long liovcnt: 本地iovec数组的元素个数

  • 指定local_iov数组中有效元素的数量

*const struct iovec remote_iov: 远程内存区域描述符数组

  • 描述目标进程中用于读写操作的内存地址

  • 每个iovec结构包含基地址和长度

unsigned long riovcnt: 远程iovec数组的元素个数

  • 指定remote_iov数组中有效元素的数量

unsigned long flags: 标志位(保留字段)

  • 当前必须设置为0

iovec结构体定义:

1
2
3
4
5
struct iovec {
void *iov_base; // 内存区域的起始地址
size_t iov_len; // 内存区域的长度(字节数)
};

  1. 返回值

成功时:

  • 返回实际传输的字节数

  • 可能小于请求的总字节数(部分传输)

失败时:

  • 返回-1,并设置errno错误码

常见错误码:

  • EACCES: 没有权限访问目标进程内存

  • EFAULT: 指定的内存地址范围无效

  • EINVAL: 参数无效(如flags非0,iovcnt过大等)

  • ENOMEM: 内存不足

  • EPERM: 没有权限操作目标进程

  • ESRCH: 目标进程不存在或已终止

  1. 相似函数或关联函数

相似函数:

  • readv/writev: 在单个进程内进行分散/聚集I/O操作

  • preadv/pwritev: 带偏移量的分散/聚集I/O操作

  • ptrace: 更通用的进程调试和控制接口

关联函数:

  • kill: 向进程发送信号

  • wait/waitpid: 等待子进程状态变化

  • getpid/getppid: 获取进程ID信息

  1. 示例代码

示例1:基础内存读取示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#define _GNU_SOURCE
#include <sys/uio.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

/**
* 读取目标进程内存的简单示例
* 注意:需要知道目标进程的确切内存地址
*/
int read_target_process_memory(pid_t target_pid, void *remote_addr, size_t size) {
char *local_buffer;
struct iovec local_iov&#91;1];
struct iovec remote_iov&#91;1];
ssize_t bytes_transferred;

// 分配本地缓冲区
local_buffer = malloc(size);
if (!local_buffer) {
perror("malloc failed");
return -1;
}

// 设置本地iovec(当前进程的缓冲区)
local_iov&#91;0].iov_base = local_buffer;
local_iov&#91;0].iov_len = size;

// 设置远程iovec(目标进程的内存地址)
remote_iov&#91;0].iov_base = remote_addr;
remote_iov&#91;0].iov_len = size;

// 执行读取操作
bytes_transferred = process_vm_readv(target_pid,
local_iov, 1, // 本地1个iovec
remote_iov, 1, // 远程1个iovec
0); // flags必须为0

if (bytes_transferred == -1) {
printf("process_vm_readv failed: %s\n", strerror(errno));
free(local_buffer);
return -1;
}

printf("成功读取 %zd 字节数据\n", bytes_transferred);
printf("读取的数据内容: ");
for (size_t i = 0; i < bytes_transferred && i < 50; i++) {
if (local_buffer&#91;i] >= 32 && local_buffer&#91;i] <= 126) {
putchar(local_buffer&#91;i]);
} else {
printf("\\x%02x", (unsigned char)local_buffer&#91;i]);
}
}
printf("\n");

free(local_buffer);
return 0;
}

int main(int argc, char *argv&#91;]) {
if (argc != 4) {
printf("使用方法: %s <目标进程PID> <内存地址> <读取大小>\n", argv&#91;0]);
printf("示例: %s 1234 0x601040 100\n", argv&#91;0]);
return 1;
}

pid_t target_pid = atoi(argv&#91;1]);
unsigned long addr = strtoul(argv&#91;2], NULL, 0);
size_t size = strtoul(argv&#91;3], NULL, 0);

printf("尝试读取进程 %d 的内存地址 0x%lx,大小 %zu 字节\n",
target_pid, addr, size);

return read_target_process_memory(target_pid, (void*)addr, size);
}

示例2:批量内存操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#define _GNU_SOURCE
#include <sys/uio.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define MAX_REGIONS 10

/**
* 批量读取多个内存区域
*/
typedef struct {
void *remote_addr; // 远程地址
size_t size; // 区域大小
char *data; // 读取到的数据
} memory_region_t;

int batch_read_memory_regions(pid_t target_pid, memory_region_t *regions, int count) {
struct iovec local_iov&#91;MAX_REGIONS];
struct iovec remote_iov&#91;MAX_REGIONS];
ssize_t total_bytes;
int i;

// 参数检查
if (count > MAX_REGIONS) {
printf("区域数量超过最大限制 %d\n", MAX_REGIONS);
return -1;
}

// 为每个区域分配本地缓冲区并设置iovec
for (i = 0; i < count; i++) {
regions&#91;i].data = malloc(regions&#91;i].size);
if (!regions&#91;i].data) {
printf("为区域 %d 分配内存失败\n", i);
// 释放已分配的内存
for (int j = 0; j < i; j++) {
free(regions&#91;j].data);
}
return -1;
}

// 设置本地iovec
local_iov&#91;i].iov_base = regions&#91;i].data;
local_iov&#91;i].iov_len = regions&#91;i].size;

// 设置远程iovec
remote_iov&#91;i].iov_base = regions&#91;i].remote_addr;
remote_iov&#91;i].iov_len = regions&#91;i].size;
}

// 执行批量读取
total_bytes = process_vm_readv(target_pid,
local_iov, count,
remote_iov, count,
0);

if (total_bytes == -1) {
printf("批量读取失败: %s\n", strerror(errno));
// 释放所有缓冲区
for (i = 0; i < count; i++) {
free(regions&#91;i].data);
}
return -1;
}

printf("批量读取成功,总共传输 %zd 字节\n", total_bytes);

// 显示每个区域的数据
for (i = 0; i < count; i++) {
printf("区域 %d (地址 0x%lx, 大小 %zu): ",
i, (unsigned long)regions&#91;i].remote_addr, regions&#91;i].size);
for (size_t j = 0; j < regions&#91;i].size && j < 20; j++) {
if (regions&#91;i].data&#91;j] >= 32 && regions&#91;i].data&#91;j] <= 126) {
putchar(regions&#91;i].data&#91;j]);
} else {
printf(".");
}
}
printf("\n");
}

return 0;
}

int main(int argc, char *argv&#91;]) {
if (argc != 2) {
printf("使用方法: %s <目标进程PID>\n", argv&#91;0]);
return 1;
}

pid_t target_pid = atoi(argv&#91;1]);
memory_region_t regions&#91;3];

// 设置要读取的内存区域(实际使用时需要根据目标进程调整地址)
regions&#91;0].remote_addr = (void*)0x601000; // 示例地址1
regions&#91;0].size = 32;
regions&#91;1].remote_addr = (void*)0x601020; // 示例地址2
regions&#91;1].size = 16;
regions&#91;2].remote_addr = (void*)0x601030; // 示例地址3
regions&#91;2].size = 8;

printf("批量读取进程 %d 的多个内存区域\n", target_pid);

if (batch_read_memory_regions(target_pid, regions, 3) == 0) {
printf("批量读取操作完成\n");
// 释放内存
for (int i = 0; i < 3; i++) {
free(regions&#91;i].data);
}
} else {
printf("批量读取操作失败\n");
}

return 0;
}

示例3:内存写入和修改示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#define _GNU_SOURCE
#include <sys/uio.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

/**
* 写入数据到目标进程内存
*/
int write_to_target_memory(pid_t target_pid, void *remote_addr,
const void *data, size_t size) {
struct iovec local_iov&#91;1];
struct iovec remote_iov&#91;1];
ssize_t bytes_written;

// 设置本地iovec(要写入的数据)
local_iov&#91;0].iov_base = (void*)data; // 强制转换,实际不会修改
local_iov&#91;0].iov_len = size;

// 设置远程iovec(目标地址)
remote_iov&#91;0].iov_base = remote_addr;
remote_iov&#91;0].iov_len = size;

// 执行写入操作
bytes_written = process_vm_writev(target_pid,
local_iov, 1,
remote_iov, 1,
0);

if (bytes_written == -1) {
printf("process_vm_writev 失败: %s\n", strerror(errno));
return -1;
}

printf("成功写入 %zd 字节到进程 %d 的地址 0x%lx\n",
bytes_written, target_pid, (unsigned long)remote_addr);

return 0;
}

/**
* 发送信号给目标进程
*/
int send_signal_to_process(pid_t target_pid, int signal) {
if (kill(target_pid, signal) == -1) {
printf("发送信号失败: %s\n", strerror(errno));
return -1;
}
printf("成功发送信号 %d 到进程 %d\n", signal, target_pid);
return 0;
}

int main(int argc, char *argv&#91;]) {
if (argc != 2) {
printf("使用方法: %s <目标进程PID>\n", argv&#91;0]);
return 1;
}

pid_t target_pid = atoi(argv&#91;1]);
char new_message&#91;] = "Hello from process_vm_writev!";
int new_value = 999999;

printf("准备修改进程 %d 的内存\n", target_pid);

// 写入字符串数据(需要知道目标进程的确切地址)
if (write_to_target_memory(target_pid, (void*)0x601060,
new_message, strlen(new_message) + 1) == 0) {
printf("字符串写入成功\n");
}

// 写入整数数据
if (write_to_target_memory(target_pid, (void*)0x601040,
&new_value, sizeof(new_value)) == 0) {
printf("整数写入成功\n");
}

// 发送信号让目标进程重新显示数据
send_signal_to_process(target_pid, SIGUSR1);

return 0;
}

示例4:完整的测试目标进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

// 这些变量的地址将被其他进程读取和修改
int shared_counter = 42;
char shared_message&#91;256] = "Original message from target process";
double shared_double = 3.14159;

volatile int running = 1;

void signal_handler(int sig) {
if (sig == SIGUSR1) {
printf("收到SIGUSR1信号,显示当前数据:\n");
printf(" Counter: %d\n", shared_counter);
printf(" Message: %s\n", shared_message);
printf(" Double: %.5f\n", shared_double);
} else if (sig == SIGINT) {
printf("收到终止信号\n");
running = 0;
}
}

int main() {
printf("=== 目标测试进程 ===\n");
printf("进程PID: %d\n", getpid());
printf("变量地址信息:\n");
printf(" shared_counter 地址: %p\n", &shared_counter);
printf(" shared_message 地址: %p\n", shared_message);
printf(" shared_double 地址: %p\n", &shared_double);
printf("\n");
printf("初始数据:\n");
printf(" Counter: %d\n", shared_counter);
printf(" Message: %s\n", shared_message);
printf(" Double: %.5f\n", shared_double);
printf("\n");
printf("按 Ctrl+C 退出,或等待其他进程发送 SIGUSR1 信号\n");

// 注册信号处理程序
signal(SIGUSR1, signal_handler);
signal(SIGINT, signal_handler);

// 主循环
while (running) {
sleep(1);
}

printf("目标进程退出\n");
return 0;
}

使用注意事项

权限要求:

  1. 调用进程和目标进程必须具有相同的用户ID2. 或者调用进程必须具有 CAP_SYS_PTRACE 权限3. 目标进程必须正在运行(不能是僵尸进程)

地址获取:

  1. 需要知道目标进程的确切内存地址2. 可以通过调试器、符号表或/proc/[pid]/maps文件获取3. 地址必须在目标进程的有效地址空间内

错误处理:

  1. 始终检查返回值2. 处理部分传输的情况3. 确保本地缓冲区足够大

安全考虑:

  1. 这些函数可能被恶意程序用于内存篡改2. 在生产环境中应谨慎使用3. 系统管理员可以通过安全策略限制使用

性能特点:

  1. 比传统的IPC机制更高效2. 避免了内核缓冲区复制3. 适合大量数据传输场景
data-ad-format="auto" data-full-width-responsive="true">