process_vm_writev系统调用及示例

process_vm_readv/process_vm_writev 函数详解

1. 函数介绍

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

2. 函数原型

#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);

3. 功能

4. 参数

共同参数说明:

  • 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结构体定义:

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

5. 返回值

成功时:

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

失败时:

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

常见错误码:

  • EACCES: 没有权限访问目标进程内存
  • EFAULT: 指定的内存地址范围无效
  • EINVAL: 参数无效(如flags非0,iovcnt过大等)
  • ENOMEM: 内存不足
  • EPERM: 没有权限操作目标进程
  • ESRCH: 目标进程不存在或已终止

6. 相似函数或关联函数

相似函数:

  • readv/writev: 在单个进程内进行分散/聚集I/O操作
  • preadv/pwritev: 带偏移量的分散/聚集I/O操作
  • ptrace: 更通用的进程调试和控制接口

关联函数:

  • kill: 向进程发送信号
  • wait/waitpid: 等待子进程状态变化
  • getpid/getppid: 获取进程ID信息

7. 示例代码

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

#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[1];
    struct iovec remote_iov[1];
    ssize_t bytes_transferred;
    
    // 分配本地缓冲区
    local_buffer = malloc(size);
    if (!local_buffer) {
        perror("malloc failed");
        return -1;
    }
    
    // 设置本地iovec(当前进程的缓冲区)
    local_iov[0].iov_base = local_buffer;
    local_iov[0].iov_len = size;
    
    // 设置远程iovec(目标进程的内存地址)
    remote_iov[0].iov_base = remote_addr;
    remote_iov[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[i] >= 32 && local_buffer[i] <= 126) {
            putchar(local_buffer[i]);
        } else {
            printf("\\x%02x", (unsigned char)local_buffer[i]);
        }
    }
    printf("\n");
    
    free(local_buffer);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("使用方法: %s <目标进程PID> <内存地址> <读取大小>\n", argv[0]);
        printf("示例: %s 1234 0x601040 100\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    unsigned long addr = strtoul(argv[2], NULL, 0);
    size_t size = strtoul(argv[3], NULL, 0);
    
    printf("尝试读取进程 %d 的内存地址 0x%lx,大小 %zu 字节\n", 
           target_pid, addr, size);
    
    return read_target_process_memory(target_pid, (void*)addr, size);
}

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

#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[MAX_REGIONS];
    struct iovec remote_iov[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[i].data = malloc(regions[i].size);
        if (!regions[i].data) {
            printf("为区域 %d 分配内存失败\n", i);
            // 释放已分配的内存
            for (int j = 0; j < i; j++) {
                free(regions[j].data);
            }
            return -1;
        }
        
        // 设置本地iovec
        local_iov[i].iov_base = regions[i].data;
        local_iov[i].iov_len = regions[i].size;
        
        // 设置远程iovec
        remote_iov[i].iov_base = regions[i].remote_addr;
        remote_iov[i].iov_len = regions[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[i].data);
        }
        return -1;
    }
    
    printf("批量读取成功,总共传输 %zd 字节\n", total_bytes);
    
    // 显示每个区域的数据
    for (i = 0; i < count; i++) {
        printf("区域 %d (地址 0x%lx, 大小 %zu): ", 
               i, (unsigned long)regions[i].remote_addr, regions[i].size);
        for (size_t j = 0; j < regions[i].size && j < 20; j++) {
            if (regions[i].data[j] >= 32 && regions[i].data[j] <= 126) {
                putchar(regions[i].data[j]);
            } else {
                printf(".");
            }
        }
        printf("\n");
    }
    
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("使用方法: %s <目标进程PID>\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    memory_region_t regions[3];
    
    // 设置要读取的内存区域(实际使用时需要根据目标进程调整地址)
    regions[0].remote_addr = (void*)0x601000;  // 示例地址1
    regions[0].size = 32;
    regions[1].remote_addr = (void*)0x601020;  // 示例地址2
    regions[1].size = 16;
    regions[2].remote_addr = (void*)0x601030;  // 示例地址3
    regions[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[i].data);
        }
    } else {
        printf("批量读取操作失败\n");
    }
    
    return 0;
}

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

#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[1];
    struct iovec remote_iov[1];
    ssize_t bytes_written;
    
    // 设置本地iovec(要写入的数据)
    local_iov[0].iov_base = (void*)data;  // 强制转换,实际不会修改
    local_iov[0].iov_len = size;
    
    // 设置远程iovec(目标地址)
    remote_iov[0].iov_base = remote_addr;
    remote_iov[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[]) {
    if (argc != 2) {
        printf("使用方法: %s <目标进程PID>\n", argv[0]);
        return 1;
    }
    
    pid_t target_pid = atoi(argv[1]);
    char new_message[] = "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:完整的测试目标进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>

// 这些变量的地址将被其他进程读取和修改
int shared_counter = 42;
char shared_message[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. 调用进程和目标进程必须具有相同的用户ID
2. 或者调用进程必须具有 CAP_SYS_PTRACE 权限
3. 目标进程必须正在运行(不能是僵尸进程)

地址获取:

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

错误处理:

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

安全考虑:

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

性能特点:

1. 比传统的IPC机制更高效
2. 避免了内核缓冲区复制
3. 适合大量数据传输场景

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注