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、子 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
ENDING CHILD PROCESS.

4

2 に答える 2

3

このコードには、愚かな間違いが 1 つあります。

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,...)

于 2010-12-31T00:48:53.387 に答える
2

解決策は、 ptraceを使用して親と子を同期することです。私はすでに親と子の間で通信しています (そしてptraceの man ページには、2 つのプロセスが親と子であるかのように動作すると書かれています) にもかかわらず、子は読み取り呼び出しでブロックされていますが、子は親が子の/proc/ child_pid /memファイルを読み取れるように Linux が十分に「停止」していないようです。しかし、親が最初に PTRACE_ATTACH を使用して (パイプを介してメッセージを受信した後に) ptraceを呼び出すと、ファイルを開くことができ、正しい内容を取得できます! 次に、親はptraceを呼び出しますもう一度、PTRACE_DETACH を使用して、メッセージを子プロセスに送り返して終了させます。

于 2010-12-31T07:41:11.993 に答える