process_vm_readv和process_vm_writev系统调用的用法及示例

process_vm_readvprocess_vm_writev 是 Linux 提供的系统调用,允许一个进程直接读写另一个进程的内存空间,常用于高效进程间通信(IPC)。以下是它们的用法和示例:


1. 函数原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <sys/uio.h>

ssize_t process_vm_readv(pid_t pid,
const struct iovec *local_iov,
unsigned long local_iovcnt,
const struct iovec *remote_iov,
unsigned long remote_iovcnt,
unsigned long flags);

ssize_t process_vm_writev(pid_t pid,
const struct iovec *local_iov,
unsigned long local_iovcnt,
const struct iovec *remote_iov,
unsigned long remote_iovcnt,
unsigned long flags);

参数说明

  • pid: 目标进程的进程 ID。
  • local_iov: 指向本地 iovec 结构数组的指针,描述本地缓冲区的地址和长度。
  • local_iovcnt: 本地 iovec 数组的元素数量。
  • remote_iov: 指向目标进程 iovec 结构数组的指针,描述目标进程内存的地址和长度。
  • remote_iovcnt: 目标进程 iovec 数组的元素数量。
  • flags: 保留参数,通常设为 0

2. 示例代码

示例 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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

int main() {
pid_t target_pid = 1234; // 目标进程的 PID
struct iovec local[1], remote[1];
char buffer[100];

// 初始化本地 iovec(读取数据存放的缓冲区)
local[0].iov_base = buffer;
local[0].iov_len = sizeof(buffer);

// 初始化远程 iovec(目标进程的内存地址)
remote[0].iov_base = (void *)0x7ffd12345678; // 假设的目标进程地址
remote[0].iov_len = sizeof(buffer);

// 调用 process_vm_readv 读取数据
ssize_t bytes_read = process_vm_readv(target_pid, local, 1, remote, 1, 0);
if (bytes_read < 0) {
perror("process_vm_readv failed");
exit(EXIT_FAILURE);
}

printf("Read %zd bytes: %s\n", bytes_read, buffer);
return 0;
}

示例 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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

int main() {
pid_t target_pid = 1234; // 目标进程的 PID
struct iovec local[1], remote[1];
const char *data = "Hello from another process!";

// 初始化本地 iovec(待写入的数据)
local[0].iov_base = (void *)data;
local[0].iov_len = strlen(data) + 1; // 包含字符串结尾的 '\0'

// 初始化远程 iovec(目标进程的内存地址)
remote[0].iov_base = (void *)0x7ffd12345678; // 假设的目标进程地址
remote[0].iov_len = strlen(data) + 1;

// 调用 process_vm_writev 写入数据
ssize_t bytes_written = process_vm_writev(target_pid, local, 1, remote, 1, 0);
if (bytes_written < 0) {
perror("process_vm_writev failed");
exit(EXIT_FAILURE);
}

printf("Wrote %zd bytes\n", bytes_written);
return 0;
}

3. 关键注意事项

  1. 权限要求:

    • 调用进程需有权限操作目标进程(如 CAP_SYS_PTRACE 能力或 root 权限)。
    • 目标进程的内存地址必须有效且可读写。
  2. 错误处理:

    • 检查返回值:成功时返回传输的字节数,失败返回 -1 并设置 errno
    • 常见错误码:EPERM(权限不足)、ESRCH(目标进程不存在)、EINVAL(参数无效)。
  3. 地址有效性:

    • 目标进程的内存地址需通过调试符号、/proc/[pid]/maps 或其他方式获取,硬编码地址可能不可靠。
  4. 性能优势:

    • 相比 ptrace,这两个系统调用避免了频繁的上下文切换,适合大量数据传输。

4. 对比 ptrace

  • ptrace: 逐字节读写,适合调试或小数据操作,但性能较差。
  • process_vm_readv/writev: 批量操作内存,效率更高,适合大规模数据传输。

通过合理使用这两个系统调用,可以实现高效的进程间通信,但需谨慎处理权限和内存安全问题。