【问题标题】:Ptrace - communication with child processPtrace - 与子进程的通信
【发布时间】:2016-04-17 14:04:36
【问题描述】:

我想通过以下方式使用ptrace(伪代码):

孩子:

    foo();
    now that foo is done parent should use ptrace to change things
    parent did what he wanted to do
    bar();  

父母:

pid = fork();
if (pid == 0)
    //child
    exec(child_program)
else
    //parent
    attach ptrace
    let child run
    use ptrace to modify it's data
    let child continue
  1. 孩子应该如何与父母沟通它已经完成foo并准备好进行修改? raise(SIGSTOP) 可能吗?

  2. 父母应该如何等待孩子运行foo?

我认为我们可以假设在应该使用 pthread 之前没有引发 SIGSTOP。

【问题讨论】:

  • 您应该在 strace 环境中启动调试器会话。在那里您可以看到调试器如何操作被跟踪的进程以及它如何与 SIGCHLD 和其他信号进行交互。
  • 如果foo 是一个共享库函数,您可以使用LD_PRELOAD 通过您的替代实现加载共享对象。否则,您需要希望 foo 不会在任何地方内联,并且您的可执行文件具有调试信息。
  • @ColonelThirtyTwo foo 只是子程序中定义的 C 中的一些函数,而不是共享库。

标签: c linux ptrace


【解决方案1】:
我可能会误解它,但是您是否有任何具体原因要使用“ptrace”来处理看起来像 IPC(进程间通信)的东西? Linux 上的`ptrace` *通常不适合* IPC,您不应该真正使用它来修改子进程中的数据。 如果您希望您的子进程与父进程通信,有多种不同的方法可以实现上述任务(即 Unix 域套接字、管道、信号量、共享内存),我建议您在尝试使用 IPC 之前先研究它们`ptrace`。


编辑:

您可以使用信号量让父母等待孩子(请参阅 Linux 手册页中的 sem_overview)并做您需要做的事情。您可以使用sem_open 创建一个命名信号量,并让孩子在父母中等待它,让孩子在完成上述任务时通知信号量。

或者,使跟踪的子进程使用断点指令,该指令将通过SIGTRAP 停止它,允许您对其进行wait,然后执行您需要执行的操作。我相信 GDB 使用类似的方法进行调试(修补指令)。如果您使用的是 x86,以下代码应该可以在您的代码中发出断点指令:

asm volatile ("int3;")

我还建议使用process_vm_writev 而不是ptrace 函数来写入进程内存 (PTRACE_POKETEXT),因为它们可以批量读取/写入进程内存。

作为进一步的参考,我认为debuggers_part2_code 是一个很好的例子,展示了如何推出自己的调试工具。

【讨论】:

  • 不,我想使用ptrace来修改孩子的记忆(使用PTRACE_POKETEXT和PTRACE_SETREGS)。只需要通信(某种),因为内存/寄存器应该在执行的特定时刻被更改(在foo之后和bar之前的伪代码中)使用gdb术语我想在子代码中放置一个断点,让它运行直到它到达它,然后(使用ptrace)修改这个和那个,然后让孩子继续。
  • 然后按照我的建议使用信号量,或者让 ptraced 进程使用断点指令,并在其上发出wait,一旦指令被命中并且处理的已收到@987654337 就会触发@.
  • 我编辑了我的问题以提供更多信息,希望对您有所帮助。我认为信号量仍然是一个更好的解决方案,但是如果你想要一个类似 GDB 的方法,只需在你想要断点的地方使用你的架构的陷阱指令,使用内联 asm。
  • 处理完子进程后,您需要使用PTRACE_CONT 继续执行子进程。
【解决方案2】:

你说你想在执行的某个时刻修改他跟踪的进程的寄存器。您可能应该尝试澄清您的问题,因为您并不清楚您真正想要实现的目标:为什么要首先修改寄存器。您希望在寄存器中找到什么?为什么要更改这些值?

您确定不想与套接字和/或共享内存通信吗?您可能应该提供一个更详细的示例来解释您正在尝试做什么。

现有代码中的断点

你在被追踪的进程中有这段代码:

foo();
// You want to modify something there.
bar();

在foo() 和bar() 之间真的不清楚寄存器中有什么。假设我们使用的是 x86_64。

如果你在foo 返回时中断:

  • EAX 包含foo 的返回值(如果有),无论如何都会在您的调用者中被忽略(因此修改它没有多大意义);

  • 被调用者保存的寄存器可能包含来自调用者的一些值,但您必须弄乱 DWARF 信息才能尝试从中理解;

  • 调用方保存的寄存器不会包含任何有用的信息(但您可以使用 DWARF 展开信息来查找对调用方有意义的其他数据)。

在bar 的调用点(无论是在调用者中还是在bar 的开头)中断对您来说可能会更有趣,因为您可以访问bar 的参数。你可以在你的追踪进程中修改它们,如果你愿意,你甚至可以强制返回一个带有值的调用。

发出信号

另一种解决方案是发出信号:

foo();
raise(SIGTRAP);
bar();

和以前一样,不清楚寄存器中有什么,您可能必须使用 DWARF 来尝试定位感兴趣的数据(这可能会或可能不会起作用)。

一个(可能)更清洁的解决方案是使用指令引发异常:

int     $3

问题是,如果你的程序不在跟踪器下运行,它就会死掉。

为追踪器添加一个钩子

更简洁的解决方案是在foo 和bar 之间添加另一个函数:

foo();
int res = delegate_to_tracer(x, y, z);
bar();

delegate_to_tracer 可以被存根为:

int delegate_to_tracer(int x, int y, int z)
{
  // No-op implementation used when there is no tracer:
  return 0;
}

您现在可以在此函数的开头添加一个断点,以便在跟踪器中处理它的功能:

  • 可以访问参数;

  • 你可以修改它们;

  • 您可以使用给定的返回值强制返回。

另一个类似的解决方案是使用静态跟踪点(SDT、UST),但尝试从中修改数据可能没有多大意义。

伪造系统调用

您可以使用系统调用来与跟踪器通信:

  • 使用unused system call (NR_tuxcall?)

  • 要么使用未使用的系统调用号(但它可能会在某些时候被使用);

  • 或者蹲一个现有的。

这个想法是,如果它不在您的跟踪器下运行,系统调用将失败并显示SIGSYS(或其他)。但是,在你的跟踪器下,你会拦截系统调用并自己处理。

打个晚礼服:

movq    $184, %rax # tuxcall
movq    $42,  %edi # param1
syscall

【讨论】:

    猜你喜欢
    • 2012-07-07
    • 1970-01-01
    • 1970-01-01
    • 2014-02-14
    • 2020-06-26
    • 2015-02-19
    • 1970-01-01
    • 2012-04-15
    • 2013-05-21
    相关资源
    最近更新 更多