Чтение файла /proc/pid/mem дочернего процесса из родительского

В приведенной ниже программе я пытаюсь добиться следующего:

  1. Процесс A присваивает значение переменной стека a.
  2. Процесс A (родительский) создает процесс B (дочерний) с PID child_pid.
  3. Процесс B вызывает функцию func1, передавая указатель на a.
  4. Процесс B изменяет значение переменной a с помощью указателя.
  5. Процесс B открывает свой файл /proc/self/mem, ищет страницу, содержащую a, и печатает новое значение a .
  6. Процесс A (одновременно) открывает /proc/child_pid/mem, ищет нужную страницу, и печатает новое значение a.

Проблема в том, что на шаге 6 родитель видит только старое значение a в /proc/child_pid/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->a: 0x7ffffe1964f0
PARENT (pid 20376, дочерний pid 20377)
PARENT: child_offset: 4f0
CHILD (pid 20377, родительский 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
ЗАВЕРШЕНИЕ ДЕТСКОГО ПРОЦЕССА.


person Amittai Aviram    schedule 30.12.2010    source источник


Ответы (2)


В этом коде есть одна глупая ошибка:

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. Вы можете найти там Алана Кокса, отправляющего кого-то в Solar Designer... для меня это означает "здесь будут драконы" и что это не решаемо, если вы не взламываете ядра во сне :(

Может, вам достаточно проверить, что в таком случае делает gdb и копирует? (Вероятно, он просто идет через ptrace(PTRACE_PEEKDATA,...))

person viraptor    schedule 31.12.2010
comment
Спасибо! Да, родительский элемент должен был открыть child_path, а не path. С правильным кодом я получаю те же результаты, что и вы. Однако я считаю, что нашел решение. Увидеть ниже. :-) - person Amittai Aviram; 31.12.2010

Решение состоит в том, чтобы использовать ptrace для синхронизации родителя с дочерним. Несмотря на то, что я уже общаюсь между родителем и дочерним элементом (и на странице руководства для ptrace говорится, что это заставляет два процесса вести себя так, как если бы они были родительским и дочерним), и даже несмотря на то, что дочерний процесс блокируется на вызов read, дочерний процесс, по-видимому, недостаточно "остановлен" для Linux, чтобы позволить родителю прочитать дочерний /proc/child_pid/mem файл. Но если родитель сначала вызывает ptrace (после получения сообщения по каналу) с PTRACE_ATTACH, то он может открыть файл и получить правильное содержимое! Затем родитель снова вызывает ptrace с PTRACE_DETACH, прежде чем отправить сообщение обратно дочернему элементу для завершения.

person Amittai Aviram    schedule 31.12.2010