【问题标题】:Low-overhead way to access the memory space of a traced process?访问被跟踪进程的内存空间的低开销方式?
【发布时间】:2010-10-31 19:08:10
【问题描述】:

我正在寻找一种有效的方式来访问(用于读取和写入操作)我的 ptraced 子进程的内存空间。正在访问的块的大小可能从几个字节到几兆字节不等,因此使用带有PTRACE_PEEKDATAPTRACE_POKEDATA 的 ptrace 调用,每次只读取一个单词并在每次调用它们时切换上下文似乎毫无意义的资源浪费。不过,我能找到的唯一一种替代解决方案是 /proc/<pid>/mem 文件,但它早已被设为只读。

还有其他(相对简单的)方法可以完成这项工作吗?理想的解决方案是以某种方式与其父进程共享我的子进程的地址空间,然后使用简单的 memcpy 调用在两个方向复制我需要的数据,但我不知道如何做以及从哪里开始。

有什么想法吗?

【问题讨论】:

  • 您想查看任何内存信息或您想要共享的特定数据吗?我的意思是,你想访问整个内存空间?
  • 我想访问每次启动程序时位置、长度和数量都不同的特定数据。但为此我认为访问被跟踪进程的整个内存空间是读取和修改它们的最自然和最简单的方法。
  • 啊,大约 3 年前我也有同样的问题! =)

标签: linux ptrace


【解决方案1】:

如果这是 Linux(标签表明它是),您可以使用带有 CLONE_VM 标志的 clone() 与父级共享整个子级地址空间。由于两个进程共享相同的 VM 空间,所有修改将在两者之间立即可见,基本上为零开销。

这确实意味着您不能在孩子中使用exec();因为它将替换两个进程的虚拟机空间。

【讨论】:

  • 这不需要访问孩子的源代码来调用所述克隆(),或者你是从父级克隆(),然后从新的“父线程”调用 ptrace() ?
【解决方案2】:

您是否可以控制子进程及其源代码?如果是这样,您可以考虑使用Shared memory

【讨论】:

    【解决方案3】:

    考虑将一些调试函数注入 ptraced 进程并通过 ptrace_setregs 调用它。类似于 gdb 如何运行 ptraced 进程的任何功能。

    您还可以尝试通过 LD_PRELOAD 将一些代码注入进程。您甚至可以尝试在没有 ptrace 的情况下使用信号进行工作。

    更新1: Gdb 注入或“劣质函数调用”相当复杂。请参阅文件 gdb-6.6.50.20070809›gdb›infcall.c 中的函数 call_function_by_hand:http://sources.debian.net/src/gdb/7.6.2-1/gdb/infcall.c?hl=462#L462

    /* All this stuff with a dummy frame may seem unnecessarily complicated
       (why not just save registers in GDB?).  The purpose of pushing a dummy
       frame which looks just like a real frame is so that if you call a
       function and then hit a breakpoint (get a signal, etc), "backtrace"
       will look right.  Whether the backtrace needs to actually show the
       stack at the time the inferior function was called is debatable, but
       it certainly needs to not display garbage.  So if you are contemplating
       making dummy frames be different from normal frames, consider that.  */
    
    /* Perform a function call in the inferior.
       ARGS is a vector of values of arguments (NARGS of them).
       FUNCTION is a value, the function to be called.
       Returns a value representing what the function returned.
       May fail to return, if a breakpoint or signal is hit
       during the execution of the function.
    
       ARGS is modified to contain coerced values.  */
    
    struct value *
    call_function_by_hand (struct value *function, int nargs, struct value **args)
    {
    ...
      frame = get_current_frame ();
      gdbarch = get_frame_arch (frame);
    
      if (!gdbarch_push_dummy_call_p (gdbarch))
        error (_("This target does not support function calls."));
    
      /* A cleanup for the inferior status.
         This is only needed while we're preparing the inferior function call.  */
      inf_status = save_infcall_control_state ();
      inf_status_cleanup
        = make_cleanup_restore_infcall_control_state (inf_status);
    
      /* Save the caller's registers and other state associated with the
         inferior itself so that they can be restored once the
         callee returns.  To allow nested calls the registers are (further
         down) pushed onto a dummy frame stack.  Include a cleanup (which
         is tossed once the regcache has been pushed).  */
      caller_state = save_infcall_suspend_state ();
      make_cleanup_restore_infcall_suspend_state (caller_state);
    ...
        sp = push_dummy_code (gdbarch, sp, funaddr, args, nargs,
                      target_values_type, &real_pc, &bp_addr,
                      get_current_regcache ());
    ... pass args ...
      /* Create the dummy stack frame.  Pass in the call dummy address as,
         presumably, the ABI code knows where, in the call dummy, the
         return address should be pointed.  */
      sp = gdbarch_push_dummy_call (gdbarch, function, get_current_regcache (),
                    bp_addr, nargs, args,
                    sp, struct_return, struct_addr);
    ...
      /* Everything's ready, push all the info needed to restore the
         caller (and identify the dummy-frame) onto the dummy-frame
         stack.  */
      dummy_frame_push (caller_state, &dummy_id);
    ... 
        /* Run the inferior until it stops.  */
    
        e = run_inferior_call (tp, real_pc);
      }
    

    【讨论】:

      【解决方案4】:

      如果您控制子进程,也许您可​​以添加一个调试接口,允许您写入相关内存?

      【讨论】:

        【解决方案5】:

        对于阅读,最好的办法是解析/proc/<pid>/maps 文件以获取感兴趣的内存区域的虚拟地址。

        然后您可以通过打开/proc/<pid>/mem 来阅读这些内容,并在感兴趣的区域上使用大缓冲区执行read() 调用。

        对于编写,我还没有找到一种简单的方法来编写整个块,我相信这与子进程的锁定和稳定性有关,通过ptrace() 调用可以保证这一点,但直接访问另一个进程' 记忆不能。我通常在 ptrace(PTRACE_POKEDATA, ...) 周围写一个包装器来镜像 Windows 的 WriteProcessMemory()

        【讨论】:

          【解决方案6】:

          clone 或 mmap 是您正在寻找的。 在两个进程之间映射一个临时文件,并使用该内存空间来回传递数据。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-08-19
            • 2016-02-10
            • 1970-01-01
            • 1970-01-01
            • 2019-09-03
            • 2014-02-01
            • 1970-01-01
            相关资源
            最近更新 更多