3

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

  1. 进程A为堆栈变量a赋值。
  2. 进程A(父)使用 PID child_pid创建进程B (子) 。
  3. 进程B调用函数func1,传递一个指向 a 的指针
  4. 进程B通过指针改变变量a的值。
  5. 进程B打开它的/proc/self/mem文件,寻找包含a的页面,并打印a的新值。
  6. 进程A(同时)打开/proc/ child_pid /mem,寻找正确的页面,并打印a的新值。

问题是,在第 6 步中,父级只能在/proc/ child_pid /mem中看到a的值,而子级确实可以在其/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->a 的地址:0x7ffffe1964f0
PARENT(pid 20376,child pid 20377)
PARENT:child_offset:4f0
CHILD(pid 20377,parent pid 20376)。
func1:旧值:42
func1:地址:0x7ffffe1964f0
func1:新值:53
PARENT:来自孩子的消息:1
CHILD:child_mem_fd:4
PARENT:child_path:/proc/20377/mem
CHILD:新值53
PARENT:child_mem_fd:7
PARENT :新值42
结束子进程。

4

2 回答 2

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 存在问题。你可以在那里找到 Alan Cox 派人去 Solar Designer... 对我来说,它拼写“这里是龙”,如果你不在睡眠中破解内核,这是无法解决的 :(

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

于 2010-12-31T00:48:53.387 回答
2

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

于 2010-12-31T07:41:11.993 回答