Ptrace - 与 child 进程通信
Ptrace - communication with child process
我想按以下方式(伪代码)使用 ptrace
:
child:
foo();
now that foo is done parent should use ptrace to change things
parent did what he wanted to do
bar();
parent:
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
child应该如何与parent沟通它已经完成foo
并准备修改? raise(SIGSTOP)
也许吧?
parent应该如何等待child到运行foo
?
我想我们可以假设在应该使用 pthread
之前没有发出 SIGSTOP。
我可能会误解它,但是是否有任何特定原因让您想要将 `ptrace` 用于看起来像 IPC(进程间通信)的东西? Linux 上的 `ptrace` *通常不适合* IPC,您不应该真正使用它来修改 child 进程中的数据。
如果你想让你的 child 进程与 parent 进行通信,有许多不同的方法可以实现上述任务(即 Unix 域套接字、管道、信号量、共享内存),我建议你看一下在尝试使用 `ptrace` 进行 IPC 之前进入它们。
编辑:
您可以使用信号量让 parent 等待 child(请参阅 Linux 手册页中的 sem_overview
)并执行您需要执行的操作.您可以使用 sem_open
创建一个命名信号量并使用 child 并在 parent 中等待它,让 child 在上述任务完成时通知信号量。
或者,使跟踪的 child 进程使用断点指令,该指令将通过 SIGTRAP
停止它,允许您在其上 wait
然后执行您需要执行的操作。我相信 GDB 使用类似的方法进行调试(修补说明)。如果您使用的是 x86,则以下代码应该可以在您的代码中发出断点指令:
asm volatile ("int3;")
我还可以建议使用 process_vm_writev
而不是 ptrace
函数来写入进程内存 (PTRACE_POKETEXT
),因为它们可以批量 reads/writes 来处理内存。
为了进一步参考,我认为 debuggers_part2_code 是一个很好的例子,说明如何使用自己的调试工具。
你说你想在执行的某个时刻修改他跟踪进程的寄存器。您可能应该尝试澄清您的问题,因为您并不清楚您真正想要实现的目标:为什么您首先要修改寄存器。您希望在寄存器中找到什么?为什么要更改这些值?
您确定不想与套接字通信 and/or 共享内存吗?您可能应该提供更详细的示例来解释您要执行的操作。
现有代码中的断点
您在跟踪过程中有此代码:
foo();
// You want to modify something there.
bar();
在foo()
和bar()
之间,寄存器里到底写的是什么,实在搞不清楚。假设我们正在使用 x86_64.
如果你在 foo
returns:
时中断
EAX 包含 foo
的 return 值(如果有的话),它在您的调用者中无论如何都会被忽略(因此修改它没有多大意义);
被调用者保存的寄存器可能包含来自调用者的一些值,但您将不得不弄乱 DWARF 信息以尝试从中理解一些意义;
调用者保存的寄存器不会包含任何有用的信息(但您可以使用 DWARF 展开信息来查找对调用者有意义的其他数据)。
在 bar
的调用点(在调用者中或在 bar
的开头)中断可能对您来说更有趣,因为您可以访问 [=20] 的参数=].您可以在跟踪程序中修改它们,如果需要,您甚至可以强制使用一个值进行 return 调用。
提高信号
另一个解决方案是发出信号:
foo();
raise(SIGTRAP);
bar();
和以前一样,不清楚寄存器中有什么,您可能必须使用 DWARF 来尝试定位有趣的数据(这可能有效也可能无效)。
一个(可能)更简洁的解决方案是使用指令引发异常:
int
问题是如果你的程序不在tracer下运行,它就会死
为跟踪器添加一个挂钩
一个更简洁的解决方案是在 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;
}
您现在可以在此函数的开头添加一个断点,以便在跟踪器中处理它的功能:
可以访问参数;
可以修改;
您可以用给定的 return 值强制 return。
另一个类似的解决方案是使用静态跟踪点 (SDT, UST),但尝试从中修改数据可能没有多大意义。
伪造系统调用
您可以使用系统调用来与跟踪器通信:
或者使用 unused system call (NR_tuxcall
?)
要么使用未使用的系统调用号(但它可能会在某些时候被使用);
或者抢现有的
这个想法是,如果它不在您的跟踪器下运行,系统调用将失败并显示 SIGSYS
(或其他)。但是,在你的tracer下,你会拦截系统调用,自己处理。
打晚礼服:
movq 4, %rax # tuxcall
movq , %edi # param1
syscall
我想按以下方式(伪代码)使用 ptrace
:
child:
foo();
now that foo is done parent should use ptrace to change things
parent did what he wanted to do
bar();
parent:
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
child应该如何与parent沟通它已经完成
foo
并准备修改?raise(SIGSTOP)
也许吧?parent应该如何等待child到运行
foo
?
我想我们可以假设在应该使用 pthread
之前没有发出 SIGSTOP。
编辑:
您可以使用信号量让 parent 等待 child(请参阅 Linux 手册页中的 sem_overview
)并执行您需要执行的操作.您可以使用 sem_open
创建一个命名信号量并使用 child 并在 parent 中等待它,让 child 在上述任务完成时通知信号量。
或者,使跟踪的 child 进程使用断点指令,该指令将通过 SIGTRAP
停止它,允许您在其上 wait
然后执行您需要执行的操作。我相信 GDB 使用类似的方法进行调试(修补说明)。如果您使用的是 x86,则以下代码应该可以在您的代码中发出断点指令:
asm volatile ("int3;")
我还可以建议使用 process_vm_writev
而不是 ptrace
函数来写入进程内存 (PTRACE_POKETEXT
),因为它们可以批量 reads/writes 来处理内存。
为了进一步参考,我认为 debuggers_part2_code 是一个很好的例子,说明如何使用自己的调试工具。
你说你想在执行的某个时刻修改他跟踪进程的寄存器。您可能应该尝试澄清您的问题,因为您并不清楚您真正想要实现的目标:为什么您首先要修改寄存器。您希望在寄存器中找到什么?为什么要更改这些值?
您确定不想与套接字通信 and/or 共享内存吗?您可能应该提供更详细的示例来解释您要执行的操作。
现有代码中的断点
您在跟踪过程中有此代码:
foo();
// You want to modify something there.
bar();
在foo()
和bar()
之间,寄存器里到底写的是什么,实在搞不清楚。假设我们正在使用 x86_64.
如果你在 foo
returns:
EAX 包含
foo
的 return 值(如果有的话),它在您的调用者中无论如何都会被忽略(因此修改它没有多大意义);被调用者保存的寄存器可能包含来自调用者的一些值,但您将不得不弄乱 DWARF 信息以尝试从中理解一些意义;
调用者保存的寄存器不会包含任何有用的信息(但您可以使用 DWARF 展开信息来查找对调用者有意义的其他数据)。
在 bar
的调用点(在调用者中或在 bar
的开头)中断可能对您来说更有趣,因为您可以访问 [=20] 的参数=].您可以在跟踪程序中修改它们,如果需要,您甚至可以强制使用一个值进行 return 调用。
提高信号
另一个解决方案是发出信号:
foo();
raise(SIGTRAP);
bar();
和以前一样,不清楚寄存器中有什么,您可能必须使用 DWARF 来尝试定位有趣的数据(这可能有效也可能无效)。
一个(可能)更简洁的解决方案是使用指令引发异常:
int
问题是如果你的程序不在tracer下运行,它就会死
为跟踪器添加一个挂钩
一个更简洁的解决方案是在 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;
}
您现在可以在此函数的开头添加一个断点,以便在跟踪器中处理它的功能:
可以访问参数;
可以修改;
您可以用给定的 return 值强制 return。
另一个类似的解决方案是使用静态跟踪点 (SDT, UST),但尝试从中修改数据可能没有多大意义。
伪造系统调用
您可以使用系统调用来与跟踪器通信:
或者使用 unused system call (
NR_tuxcall
?)要么使用未使用的系统调用号(但它可能会在某些时候被使用);
或者抢现有的
这个想法是,如果它不在您的跟踪器下运行,系统调用将失败并显示 SIGSYS
(或其他)。但是,在你的tracer下,你会拦截系统调用,自己处理。
打晚礼服:
movq 4, %rax # tuxcall
movq , %edi # param1
syscall