ptrace 系统调用

ptrace 是 Linux 环境下,许许多多分析调试工具,如 straceltrace 等,所使用的最核心的系统调用。ptrace,即 process trace,指进程追踪。它能让一个进程控制及影响另一个进程的行为,比如让另外一个进程停止运行,获取另外一个进程内存地址空间中的值,设置另外一个进程内存中的值,获取另外一个进程的寄存器的值,设置另外一个进程的寄存器的值,等等等等。

ptrace 系统调用的 C 库接口原型如下:

       #include <sys/ptrace.h>

       long ptrace(enum __ptrace_request request, pid_t pid,
                   void *addr, void *data);

后面更详细地描述 ptrace 系统调用。

描述

ptrace() 提供了一种方法,让一个进程(称为 “tracer”)可以观测及控制另外一个进程(称为 “tracee”)的执行,让 tracer 检测和修改 tracee 的内存和寄存器。它主要用于实现断点调试和系统调用追踪,前者即 GDB 这种调试器,后者即 strace 等系统调用追踪工具。

Tracee 首先需要被 attached 到 tracer 上。Attachment 和后续的命令是针对特定线程的:在一个多线程进程中,每个线程可以被单独 attached 到一个(有可能不同的) tracer,或者不被 attached,即不被调试。因此,“tracee” 总是意味着 “(一个) 线程”,而不是 “一个 (可能是多线程的) 进程”。Ptrace 命令总是用下面形式的调用发送给一个特定的 tracee:

    ptrace(PTRACE_foo, pid, ...)

其中 pid 是对应 Linux 线程的线程 ID。

(注意,在本页中,一个 “多线程进程” 意味着使用 clone(2)CLONE_THREAD 标记创建的线程构成的线程组。 )

进程可以通过调用 fork(2),并让生成的子进程执行 PTRACE_TRACEME,以初始化一个追踪,然后(典型地)执行一个 execve(2)。另外,一个进程可以使用 PTRACE_ATTACHPTRACE_SEIZE 开启追踪另一个进程。

在被追踪时,tracee 将会在每次传递信号时停下来,即使信号被忽略。(信号的具体含义是什么?)(一个例外是 SIGKILL,它的行为就像通常的那样。)信号既包括通过用 kill 工具或系统同名系统调用向进程发送的信号,也包括系统调用开始或结束这样的事件。 Tracer 将在它下次调用 waitpid(2) 时得到通知(或一个与 “wait” 相关的系统调用);那个调用将返回一个 status 值,其中包含了指示 tracee 断下来的原因的信息。当 tracee 停止时,tracer 可以使用各种 ptrace 请求调查和修改 tracee。随后 tracer 可以让 tracee 继续执行,或者也可以忽略传递过来的信号(或者甚至可以传递一个不同的信号给 tracee)。

如果 PTRACE_O_TRACEEXEC 选项没有设置,被追踪的进程所有对于 execve(2) 的成功调用将使得它发送一个 **SIGTRAP ** 信号,这给了父进程一个在新程序开始执行前获得控制权的机会。

当 tracer 结束了追踪,它可以通过 PTRACE_DETACH 使 tracee 继续以正常的、未被追踪的模式执行。

从 API 设计的层面来看,ptrace 就像是用一个 API 向用户暴露了一个库的能力一样。除了需要用 waitpid(2) 获得被追踪的 tracee 进程的信号通知之外,其它的追踪功能基本上都可以通过 ptrace 系统调用完成。

request 的值决定了要执行的行为:

PTRACE_TRACEME

表示这个进程将被它的父进程追踪。如果父进程不希望追踪它的话,则进程可能不应该发起这个请求。(pidaddr,和 data 被忽略。)
PTRACE_TRACEME 请求只被用于 tracee;其余的请求只被用于 tracer。在下面的请求中,pid 指定了将在其上执行的 tracee 的 线程 ID。除 PTRACE_ATTACHPTRACE_SEIZEPTRACE_INTERRUPT,和 PTRACE_KILL 请求外,tracee 必须被停下来


被追踪的进程不调用 PTRACE_TRACEME 的话,其它进程是否可以对它成功调用 PTRACE_ATTACHPTRACE_SEIZE 等?还是说这个请求主要被用于协调被追踪的进程和其父进程的行动?
Tracee 进程在执行这个调用时不会停下来等待父进程 attach,即 tracee 进程在执行这个调用之后,会直接执行后面的语句。

PTRACE_PEEKTEXTPTRACE_PEEKDATA

在 tracee 的内存中的地址 addr 处读取一个字,返回该字的值作为 ptrace() 调用的返回值。Linux 没有单独的 text 和 data 地址空间,因此这两个请求目前是等价的。(data 被忽略;请参考 NOTES。)

PTRACE_PEEKUSER

在 tracee 的 USER 区域的 addr 偏移处读取一个字,USER 区域包含关于进程的寄存器和其它信息(请参考 <sys/user.h>)。字的值被作为 ptrace() 调用的结果返回。典型地,偏移量必须是字对齐的,尽管这可能因架构不同而异。请参考 NOTES。(data 被忽略;请参考 NOTES。)


USER 区域是内核维护的一段特殊内存区域,其中保存了进程的寄存器值等信息。这个调用和 PTRACE_GETREGS 有什么区别?两者的功能是否有一定的重合?这个 USER 区域的格式在什么地方定义?或者说,我怎么知道可以在哪个便宜处获得什么值呢?


USER 区域的格式定义,如 <sys/user.h> 文件中定义的 struct user

PTRACE_POKETEXTPTRACE_POKEDATA

把字 data 拷贝到 tracee 的内存中的地址 addr 处。就像 PTRACE_PEEKTEXTPTRACE_PEEKDATA 那样,这两个请求目前也是等价的。

PTRACE_POKEUSER

把字 data 拷贝到 tracee 的 USER 区域的偏移量 addr 处。就像 PTRACE_PEEKUSER 那样,典型情况下,偏移量必须是字对齐的。为了维护内核的完整性,对于 USER 区域的一些修改是不被允许的。

PTRACE_GETREGSPTRACE_GETFPREGS

分别拷贝 tracee 的通用或浮点寄存器到 tracer 中的地址 data 处。请参考 <sys/user.h> 来了解关于这个数据的格式的信息。(addr 被忽略。)注意 SPARC 系统中,dataaddr 的含义不同;即 data 被忽略,寄存器被拷贝到地址 addr 处。PTRACE_GETREGSPTRACE_GETFPREGS 并不是存在于所有的体系结构中的。

PTRACE_GETREGSET (自 Linux 2.6.34 始)

读取 tracee 的寄存器。addr 以一种机器相关的方式指定要读取的寄存器的类型。NT_PRSTATUS (具有数字值 1)通常指定读取通用寄存器。如果 CPU 有,比如,浮点和/或向量寄存器,它们可以通过把 addr 设置为对应的 NT_foo 常量来提取。data 指向一个 struct iovec,其描述了目标缓冲区的位置和长度。返回时,内核修改 iov.len 来表示实际返回的字节数。

PTRACE_SETREGSPTRACE_SETFPREGS

分别用 tracer 中地址 data 处的数据修改 tracee 的通用或浮点寄存器。至于 PTRACE_POKEUSER ,一些通用寄存器修改可能是不被允许的。(addr 被忽略。)注意 SPARC 系统中 dataaddr 参数的含义是相反的;即 data 被忽略,而寄存器的值是从地址 addr 处拷贝而来的。PTRACE_SETREGSPTRACE_SETFPREGS 并不是存在于所有的体系结构中的。

PTRACE_SETREGSET (自 Linux 2.6.34 始)

修改 tracee 的寄存器。dataaddr 参数的含义与 PTRACE_GETREGSET 的相同。

PTRACE_GETSIGINFO (自 Linux 2.3.99-pre6 始)

提取导致进程停止的信号的信息。把一个 siginfo_t 结构(请参考 sigaction(2))从 tracee 拷贝到 tracer 的地址 data 处。(addr 被忽略。)

PTRACE_SETSIGINFO (自 Linux 2.3.99-pre6 始)

设置信号信息:把一个 siginfo_t 结构从 tracer 的地址 data 处拷贝到 tracee 中。这只会影响到那些通常将被传送给 tracee,但被 tracer 捕获的信号。从 ptrace() 本身生成的合成信号中区分出这些普通的信号可能比较困难。(addr 被忽略。)

PTRACE_PEEKSIGINFO (自 Linux 3.10 始)

提取 siginfo_t 结构而不将信号从队列中移除。addr 指向一个 ptrace_peeksiginfo_args 结构,后者描述了拷贝的信号应该开始的顺序位置,要拷贝的信号的个数。siginfo_t 结构被拷贝到 data 指向的缓冲区。返回值包含拷贝的信号的个数(零表示没有信号对应于指定的顺序位置)。在返回的 siginfo_t 结构中,si_code 字段包含没有公开给用户空间的信息 (__SI_CHLD, __SI_FAULT, 等等)。

           struct ptrace_peeksiginfo_args {
               u64 off;    /* Ordinal position in queue at which
                              to start copying signals */
               u32 flags;  /* PTRACE_PEEKSIGINFO_SHARED or 0 */
               s32 nr;     /* Number of signals to copy */
           };

目前,只有一个标记, PTRACE_PEEKSIGINFO_SHARED,用于从进程范围的信号队列转储信号。如果没有设置这个标记,将从指定线程的每个线程队列读取信号。

PTRACE_GETSIGMASK (自 Linux 3.11 始)

把阻塞的信号的掩码(参考 sigprocmask(2))的一份拷贝放进 data 指向的缓冲区中,后者应该是一个指向 sigset_t 类型缓冲区的指针。addr 参数包含了 data 指向的缓冲区的大小(比如,sizeof(sigset_t))。

PTRACE_SETSIGMASK (自 Linux 3.11 始)

修改阻塞的信号的掩码(参考 sigprocmask(2))为 data 指向的缓冲区中指定的值,后者应该是一个指向 sigset_t 类型缓冲区的指针。addr 参数包含了 data 指向的缓冲区的大小(比如,sizeof(sigset_t))。

PTRACE_SETOPTIONS (自 Linux 2.4.6 始;有关注意事项,请参阅BUGS)

data 设置 ptrace 选项。(addr 被忽略。)data 被解释为选项的位掩码,选项由下面的标记指定:

  • PTRACE_O_EXITKILL (自 Linux 3.8 始)
    如果 tracer 退出就给 tracee 发送一个 SIGKILL 信号。这个选项对于想要确保 tracee 永远无法逃脱 tracer 控制的 ptrace jailers 是有用的。

  • PTRACE_O_TRACECLONE (自 Linux 2.5.46 始)
    在下一次调用 clone(2) 时停止 tracee,并自动地启动追踪新创建的进程,它将以一个 SIGSTOP 信号启动,或者如果使用了 PTRACE_SEIZE 则以 PTRACE_EVENT_STOP。Tracer 调用 waitpid(2) 将返回一个这样的 status 值:status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8)) 。新进程的 PID 可以通过 PTRACE_GETEVENTMSG 提取。
    这个选项可能无法在所有情况下捕获 clone(2) 调用。如果 tracee 以 CLONE_VFORK 标记调用 clone(2) ,则如果 PTRACE_O_TRACEVFORK 被设置将传递 PTRACE_EVENT_VFORK 来替代;否则如果 tracee 以退出信号设置为 SIGCHLD 调用 clone(2),则如果 PTRACE_O_TRACEFORK 被设置将传递 PTRACE_EVENT_FORK

  • PTRACE_O_TRACEEXEC (自 Linux 2.5.46 始)
    在下一次调用 execve(2) 时停止 tracee。Tracer 调用 waitpid(2) 将返回一个这样的 status 值:status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8)) 。如果执行线程不是线程组的 leader,则在停止前线程 ID 将被重置为线程组的 leader 的 ID。自 Linux 3.0 开始,前者的线程 ID 可以通过 PTRACE_GETEVENTMSG 提取。

  • PTRACE_O_TRACEEXIT (自 Linux 2.5.60 始)
    在 tracee 退出时停止它。Tracer 调用 waitpid(2) 将返回一个这样的 status 值:status>>8 == (SIGTRAP | (PTRACE_EVENT_EXIT<<8)) 。Tracee 的退出状态可以通过 PTRACE_GETEVENTMSG 提取。
    Tracee 在进程退出时提前停止,当寄存器依然可用时,允许 tracer 查看退出发生在哪里,然而正常的退出通知将在进程完成退出之后完成。即使上下文依然可用,tracer 依然不能阻止退出发生在这个点。

  • PTRACE_O_TRACEFORK (自 Linux 2.5.46 始)
    在下一次调用 fork(2) 时停止 tracee,并自动地启动追踪新 forked 的进程,它将以一个 SIGSTOP 信号启动,或者如果使用了 PTRACE_SEIZE 则以 PTRACE_EVENT_STOP。Tracer 调用 waitpid(2) 将返回一个这样的 status 值:status>>8 == (SIGTRAP | (PTRACE_EVENT_FORK<<8)) 。新进程的 PID 可以通过 PTRACE_GETEVENTMSG 提取。

  • PTRACE_O_TRACESYSGOOD (自 Linux 2.4.6 始)
    在传递系统调用陷入时,设置信号值的位 7(比如传递 SIGTRAP|0x80)。这可以让 tracer 非常容易地从系统调用导致的陷入中区分出普通的陷入。

  • PTRACE_O_TRACEVFORK (自 Linux 2.5.46 始)
    在下一次调用 vfork(2) 时停止 tracee,并自动地启动追踪新 vforked 的进程,它将以一个 SIGSTOP 信号启动,或者如果使用了 PTRACE_SEIZE 则以 PTRACE_EVENT_STOP。Tracer 调用 waitpid(2) 将返回一个这样的 status 值:status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK<<8)) 。新进程的 PID 可以通过 PTRACE_GETEVENTMSG 提取。

  • PTRACE_O_TRACEVFORKDONE (自 Linux 2.5.60 始)
    在 tracee 完成下一次 vfork(2) 时停止它。Tracer 调用 waitpid(2) 将返回一个这样的 status 值:status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK_DONE<<8)) 。新进程的 PID 可以(自 Linux 2.6.18 始)通过 PTRACE_GETEVENTMSG 提取。

  • PTRACE_O_TRACESECCOMP (自 Linux 3.5 始)
    在一个 seccomp(2) SECCOMP_RET_TRACE 规则被触发时停止 tracee。Tracer 调用 waitpid(2) 将返回一个这样的 status 值:status>>8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP<<8)) 。尽管这触发一个 PTRACE_EVENT 停止,但它与 syscall-enter-stop 类似。更多详情,请参考下面关于 PTRACE_EVENT_SECCOMP 的说明。Seccomp 事件消息数据(来自于 seccomp 过滤器规则的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值