Чтение файла / proc / pid / mem дочернего процесса из родительского процесса - PullRequest
2 голосов
/ 30 декабря 2010

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

  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 .

В основном: адрес arg.a: 0x7ffffe1964f0
Stack_start: 7ffffe196000
Адрес arg_ptr-> a: 0x7ffffe1964f0
РОДИТЕЛЬ (пид 20376, пид 20377)
РОДИТЕЛЬ: child_offset: 4f0
РЕБЕНОК (pid 20377, родительский pid 20376).
func1: старое значение: 42
func1: адрес: 0x7ffffe1964f0
func1: новое значение: 53
РОДИТЕЛЬ: сообщение от ребенка: 1
РЕБЕНОК: child_mem_fd: 4
PARENT: child_path: / proc / 20377 / mem
РЕБЕНОК: новое значение 53
РОДИТЕЛЬ: child_mem_fd: 7
РОДИТЕЛЬ: новое значение 42
ЗАВЕРШЕНИЕ РЕБЕНКА.

Ответы [ 2 ]

3 голосов
/ 31 декабря 2010

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

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

Может быть, этого достаточновам проверить, что GDB делает в этом случае и реплицирует его? (возможно, это просто идет через ptrace(PTRACE_PEEKDATA,...))

2 голосов
/ 31 декабря 2010

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

...