从父进程读取子进程的/ proc / pid / mem文件

时间:2010-12-30 19:25:06

标签: linux ipc fork procfs systems-programming

在下面的程序中,我试图导致以下情况发生:

  1. 处理 A 为堆栈变量 a 分配值。
  2. 进程 A (父)使用PID child_pid 创建进程 B (子)。
  3. 进程 B 调用函数 func1 ,将指针传递给 a
  4. 进程 B 通过指针更改变量 a 的值。
  5. 进程 B 打开其 / proc / self / mem 文件,搜索包含 a 的页面,并打印新值< EM>一
  6. 处理 A (同时)打开 / proc / child_pid / mem ,寻求右页,并打印 a 的新值。
  7. 问题在于,在第6步中,父级只会在 / proc / child_pid中看到 a / mem ,而孩子确实可以在 / proc / self / mem 中看到新值。为什么会这样?有什么方法可以让父母通过 / proc 文件系统查看孩子对地址空间的更改?

    #include <fcntl.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    #define PAGE_SIZE 0x1000
    #define LOG_PAGE_SIZE 0xc
    #define PAGE_ROUND_DOWN(v) ((v) & (~(PAGE_SIZE - 1)))
    #define PAGE_ROUND_UP(v) (((v) + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1)))
    #define OFFSET_IN_PAGE(v) ((v) & (PAGE_SIZE - 1))
    # if defined ARCH && ARCH == 32
    #define BP "ebp"
    #define SP "esp"
    #else
    #define BP "rbp"
    #define SP "rsp"
    #endif
    
    typedef struct arg_t {
     int a;
    } arg_t;
    
    
    void func1(void * data) {
     arg_t * arg_ptr = (arg_t *)data;
     printf("func1: old value: %d\n", arg_ptr->a);
     arg_ptr->a = 53;
     printf("func1: address: %p\n", &arg_ptr->a);
     printf("func1: new value: %d\n", arg_ptr->a);
    }
    
    
    void expore_proc_mem(void (*fn)(void *), void * data) {
    
     off_t frame_pointer, stack_start;
     char buffer[PAGE_SIZE];
     const char * path = "/proc/self/mem";
     int child_pid, status;
     int parent_to_child[2];
     int child_to_parent[2];
     arg_t * arg_ptr;
     off_t child_offset;
    
     asm volatile ("mov %%"BP", %0" : "=m" (frame_pointer));
     stack_start = PAGE_ROUND_DOWN(frame_pointer);
    
     printf("Stack_start: %lx\n",
            (unsigned long)stack_start);
    
     arg_ptr = (arg_t *)data;
     child_offset = 
      OFFSET_IN_PAGE((off_t)&arg_ptr->a);
     printf("Address of arg_ptr->a: %p\n",
            &arg_ptr->a);
    
     pipe(parent_to_child);
     pipe(child_to_parent);
     bool msg;
     int child_mem_fd;
     char child_path[0x20];
    
     child_pid = fork();
     if (child_pid == -1) {
      perror("fork");
      exit(EXIT_FAILURE);
     }
     if (!child_pid) {
      close(child_to_parent[0]);
      close(parent_to_child[1]);
      printf("CHILD (pid %d, parent pid %d).\n",
             getpid(), getppid());
      fn(data);
      msg = true;
      write(child_to_parent[1], &msg, 1);
      child_mem_fd = open("/proc/self/mem", O_RDONLY);
      if (child_mem_fd == -1) {
       perror("open (child)");
       exit(EXIT_FAILURE);
      }
      printf("CHILD: child_mem_fd: %d\n", child_mem_fd);
      if (lseek(child_mem_fd, stack_start, SEEK_SET) == (off_t)-1) {
       perror("lseek");
       exit(EXIT_FAILURE);
      }
    
      if (read(child_mem_fd, buffer, sizeof(buffer)) 
          != sizeof(buffer)) {
       perror("read");
       exit(EXIT_FAILURE);
      }
    
      printf("CHILD: new value %d\n",
             *(int *)(buffer + child_offset));
    
      read(parent_to_child[0], &msg, 1);
      exit(EXIT_SUCCESS);
     }
     else {
      printf("PARENT (pid %d, child pid %d)\n",
             getpid(), child_pid);
      printf("PARENT: child_offset: %lx\n",
             child_offset);
      read(child_to_parent[0], &msg, 1);
      printf("PARENT: message from child: %d\n", msg);
      snprintf(child_path, 0x20, "/proc/%d/mem", child_pid);
      printf("PARENT: child_path: %s\n", child_path);
      child_mem_fd = open(path, O_RDONLY);
      if (child_mem_fd == -1) {
       perror("open (child)");
       exit(EXIT_FAILURE);
      }
      printf("PARENT: child_mem_fd: %d\n", child_mem_fd);
      if (lseek(child_mem_fd, stack_start, SEEK_SET) == (off_t)-1) {
       perror("lseek");
       exit(EXIT_FAILURE);
      }
    
      if (read(child_mem_fd, buffer, sizeof(buffer)) 
          != sizeof(buffer)) {
       perror("read");
       exit(EXIT_FAILURE);
      }
    
      printf("PARENT: new value %d\n",
             *(int *)(buffer + child_offset));
    
      close(child_mem_fd);
    
      printf("ENDING CHILD PROCESS.\n");
    
      write(parent_to_child[1], &msg, 1);
      if (waitpid(child_pid, &status, 0) == -1) {
       perror("waitpid");
       exit(EXIT_FAILURE);
      }
     }
    
    }
    
    int main(void) {
    
     arg_t arg;
     arg.a = 42;
     printf("In main: address of arg.a: %p\n", &arg.a);
     explore_proc_mem(&func1, &arg.a);
    
     return EXIT_SUCCESS;
    }
    

    该程序产生以下输出。请注意, a (粗体)的值在父母和孩子读取 / proc / child_pid / mem 文件。

    在main中:arg.a的地址:0x7ffffe1964f0
    Stack_start:7ffffe196000
    地址arg_ptr-&gt; a:0x7ffffe1964f0
    父母(pid 20376,儿童pid 20377)
    父母:child_offset:4f0
    儿童(pid 20377,父母pid 20376) func1:旧值:42
    func1:地址:0x7ffffe1964f0
    func1:新值:53
    父母:来自孩子的信息:1
    孩子:child_mem_fd:4
    父母:child_path:/ proc / 20377 / mem
    儿童:新值 53
    父母:child_mem_fd:7
    父母:新值 42
    结束儿童过程。

2 个答案:

答案 0 :(得分:3)

这段代码中有一个愚蠢的错误:

const char * path = "/proc/self/mem";
...
snprintf(child_path, 0x20, "/proc/%d/mem", child_pid);
printf("PARENT: child_path: %s\n", child_path);
child_mem_fd = open(path, O_RDONLY);

所以你总是在这里阅读父母的记忆。但是在改变之后,我得到了:

CHILD: child_mem_fd: 4
CHILD: new value 53
read (parent): No such process

我不知道为什么会发生这种情况 - 也许/proc刷新条目太慢了? (它来自父级中的perror("read") - 必须添加注释以查看哪个失败了)但这似乎很奇怪,因为seek工作 - 以及open本身。

这个问题似乎也不新鲜:http://lkml.indiana.edu/hypermail/linux/kernel/0007.1/0939.html(ESRCH是“没有这样的过程”)

实际上更好的链接是:http://www.webservertalk.com/archive242-2004-7-295131.html - 标记进程pthread-attach-safe存在问题。你可以在那里找到艾伦考克斯派人到太阳设计师...对我来说,这里拼写“这里是龙”,如果你不在睡眠中破解内核,它就无法解决:(

也许这足以让你检查gdb在这种情况下做了什么并复制它? (可能只是通过ptrace(PTRACE_PEEKDATA,...)

答案 1 :(得分:2)

解决方案是使用 ptrace 将父级与子级同步。即使我已经在父母和孩子之间进行交流(并且 ptrace 的手册页说它导致两个进程的行为就像它们是父母和孩子一样),即使孩子阻止读取调用,孩子显然没有“停止”足够Linux让父母读取孩子的 / proc / child_pid / mem 文件。但是,如果父级首先使用PTRACE_ATTACH调用 ptrace (在通过管道接收消息之后),则它可以打开文件 - 并获取正确的内容!然后,父母再次使用PTRACE_DETACH调用 ptrace ,然后再将消息发送给孩子终止。