PTRACE - Linux手册页
Linux程序员手册 第2部分
更新日期: 2020-06-09
名称
ptrace-进程跟踪
语法
#include <sys/ptrace.h> long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
说明
ptrace()系统调用提供了一种方法,一个进程("跟踪程序")可以观察并控制另一进程("跟踪")的执行,并检查和更改该跟踪程序的内存和寄存器。它主要用于实现断点调试和系统调用跟踪。
首先需要将示踪剂连接到示踪剂。附件和后续命令是针对每个线程的:在多线程进程中,每个线程都可以分别附加到(可能不同的)跟踪器,或者不附加就可以调试。因此," tracee"始终表示"(一个)线程",而不是"(可能是多线程)进程"。 Ptrace命令始终使用以下形式的调用发送到特定的tracee:
ptrace(PTRACE_foo,pid,...)
其中pid是相应Linux线程的线程ID。
(请注意,在此页面中,"多线程进程"表示由使用clone(2)CLONE_THREAD标志创建的线程组成的线程组。)
进程可以通过调用fork(2)并让生成的子进程执行PTRACE_TRACEME,然后(通常)执行execve(2)来启动跟踪。或者,一个过程可以开始使用PTRACE_ATTACH或PTRACE_SEIZE跟踪另一过程。
在跟踪过程中,即使忽略信号,每次发送信号时,跟踪也会停止。 (SIGKILL是一个例外,它具有通常的作用。)跟踪程序将在下一次调用waitpid(2)(或相关的" wait"系统调用之一)时得到通知。该调用将返回一个状态值,该状态值包含指示示踪停止的原因的信息。停止跟踪时,跟踪器可以使用各种ptrace请求来检查和修改跟踪。然后,示踪剂使示踪剂继续,可以选择忽略传递的信号(或什至替代传递另一个信号)。
如果PTRACE_O_TRACEEXEC选项无效,则跟踪过程对execve(2)的所有成功调用都会使它发送SIGTRAP信号,从而使父级有机会在新程序开始执行之前获得控制权。
跟踪程序完成跟踪后,可以使跟踪程序通过PTRACE_DETACH以正常的未跟踪模式继续执行。
request的值确定要执行的操作:
- PTRACE_TRACEME
- 指示此进程将由其父进程跟踪。如果进程的父级不希望跟踪该请求,则该进程可能不应发出此请求。 (pid,addr和数据将被忽略。)
- PTRACE_TRACEME请求仅由跟踪使用。其余请求仅由跟踪器使用。在以下请求中,pid指定要作用的跟踪的线程ID。对于除PTRACE_ATTACH,PTRACE_SEIZE,PTRACE_INTERRUPT和PTRACE_KILL以外的请求,必须停止跟踪。
- PTRACE_PEEKTEXT, PTRACE_PEEKDATA
- 在Tracee内存中的地址addr处读取一个单词,并将其作为ptrace()调用的结果返回。 Linux没有单独的文本和数据地址空间,因此这两个请求当前是等效的。 (数据将被忽略;但请参见"注释"。)
- PTRACE_PEEKUSER
- 在Tracee的USER区域的offset addr处读取一个单词,其中包含寄存器和有关该过程的其他信息(请参阅参考资料)。该字作为ptrace()调用的结果返回。通常,偏移量必须按字对齐,尽管这可能会因体系结构而异。请参阅注释。 (数据将被忽略;但请参见"注释"。)
- PTRACE_POKETEXT, PTRACE_POKEDATA
- 将字数据复制到示踪内存中的地址地址。至于PTRACE_PEEKTEXT和PTRACE_PEEKDATA,这两个请求当前是等效的。
- PTRACE_POKEUSER
- 将字数据复制到Tracee的USER区域中的offset addr。对于PTRACE_PEEKUSER,偏移量通常必须字对齐。为了保持内核的完整性,不允许对USER区域进行某些修改。
- PTRACE_GETREGS, PTRACE_GETFPREGS
- 分别将被跟踪的通用或浮点寄存器复制到跟踪器中的地址数据。请参阅以获取有关此数据格式的信息。 (忽略addr。)请注意,SPARC系统具有data的含义,并且addr相反。也就是说,将忽略数据,并将寄存器复制到地址addr。 PTRACE_GETREGS和PTRACE_GETFPREGS并非在所有体系结构上都存在。
- PTRACE_GETREGSET(since Linux 2.6.34)
- 读取示踪的寄存器。 addr以与体系结构相关的方式指定要读取的寄存器的类型。 NT_PRSTATUS(数值为1)通常会导致读取通用寄存器。例如,如果CPU具有浮点和/或向量寄存器,则可以通过将addr设置为相应的NT_foo常量来检索它们。数据指向结构iovec,该结构描述目标缓冲区的位置和长度。返回时,内核修改iov.len以指示返回的实际字节数。
- PTRACE_SETREGS, PTRACE_SETFPREGS
- 从跟踪器中的地址数据分别修改跟踪的通用或浮点寄存器。对于PTRACE_POKEUSER,可能不允许某些通用寄存器修改。 (忽略addr。)请注意,SPARC系统具有data的含义,并且addr相反。也就是说,将忽略数据,并从地址addr复制寄存器。 PTRACE_SETREGS和PTRACE_SETFPREGS并非在所有体系结构上都存在。
- PTRACE_SETREGSET(since Linux 2.6.34)
- 修改示踪的寄存器。 addr和data的含义类似于PTRACE_GETREGSET。
- PTRACE_GETSIGINFO(since Linux 2.3.99-pre6)
- 检索有关引起停止的信号的信息。将siginfo_t结构(请参见sigaction(2))从被跟踪对象复制到跟踪器中的地址数据。 (addr被忽略。)
- PTRACE_SETSIGINFO(since Linux 2.3.99-pre6)
- 设置信号信息:将siginfo_t结构从跟踪器中的地址数据复制到被跟踪器。这只会影响通常会传递给示踪物并被示踪剂捕获的信号。从ptrace()本身生成的合成信号中很难分辨出这些正常信号。 (addr被忽略。)
- PTRACE_PEEKSIGINFO(since Linux 3.10)
- 检索siginfo_t结构而不从队列中删除信号。 addr指向ptrace_peeksiginfo_args结构,该结构指定应从中开始复制信号的顺序位置以及要复制的信号数。 siginfo_t结构被复制到数据指向的缓冲区中。返回值包含复制信号的数量(零表示没有信号与指定的顺序位置相对应)。在返回的siginfo结构中,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(since Linux 3.11)
- 将阻塞信号的掩码的副本(请参见sigprocmask(2))放在数据指向的缓冲区中,该缓冲区应该是指向sigset_t类型的缓冲区的指针。 addr参数包含数据指向的缓冲区的大小(即sizeof(sigset_t))。
- PTRACE_SETSIGMASK(since Linux 3.11)
- 将阻塞信号的掩码(请参见sigprocmask(2))更改为在数据指向的缓冲区中指定的值,该值应该是指向sigset_t类型的缓冲区的指针。 addr参数包含数据指向的缓冲区的大小(即sizeof(sigset_t))。
- PTRACE_SETOPTIONS(since Linux 2.4.6; see BUGS for caveats)
- Set ptrace options from
data.
(addr
is ignored.)
datais interpreted as a bit mask of options,
which are specified by the following flags:- PTRACE_O_EXITKILL(since Linux 3.8)
- 如果跟踪器退出,则向跟踪器发送SIGKILL信号。对于希望确保跟踪永远不会逃脱跟踪程序控制的ptrace监牢者,此选项很有用。
- PTRACE_O_TRACECLONE(since Linux 2.5.46)
- 在下一个clone(2)处停止跟踪,并自动开始跟踪新克隆的进程,该进程将从SIGSTOP或PTRACE_EVENT_STOP(如果使用PTRACE_SEIZE)开始。跟踪程序的waitpid(2)将返回一个状态值,使得
status>>8 == (SIGTRAP | (PTRACE_EVENT_CLONE<<8))
- 可以使用PTRACE_GETEVENTMSG检索新过程的PID。
- 此选项可能无法在所有情况下都捕获clone(2)调用。如果被跟踪者使用CLONE_VFORK标志调用clone(2),则如果设置了PTRACE_O_TRACEVFORK,则将传递PTRACE_EVENT_VFORK;否则,如果tracee在退出信号设置为SIGCHLD的情况下调用clone(2),则在设置PTRACE_O_TRACEFORK的情况下将传递PTRACE_EVENT_FORK。
- PTRACE_O_TRACEEXEC(since Linux 2.5.46)
- 在下一个execve(2)处停止跟踪。跟踪程序的waitpid(2)将返回一个状态值,使得
status>>8 == (SIGTRAP | (PTRACE_EVENT_EXEC<<8))
- 如果执行线程不是线程组领导者,则在此停止之前,线程ID将重置为线程组领导者的ID。从Linux 3.0开始,可以使用PTRACE_GETEVENTMSG检索以前的线程ID。
- PTRACE_O_TRACEEXIT(since Linux 2.5.60)
- 在出口处停止示踪。跟踪程序的waitpid(2)将返回一个状态值,使得
status>>8 == (SIGTRAP | (PTRACE_EVENT_EXIT<<8))
- 可以使用PTRACE_GETEVENTMSG检索跟踪的退出状态。
- 在进程退出过程中,当寄存器仍然可用时,tracee会在进程退出的早期停止,从而使tracer可以看到退出发生的地方,而正常的退出通知是在进程完成退出之后完成的。即使上下文可用,跟踪器也无法阻止此时退出。
- PTRACE_O_TRACEFORK(since Linux 2.5.46)
- 在下一个fork(2)处停止跟踪,并自动开始跟踪新的分叉过程,该过程将从SIGSTOP或PTRACE_EVENT_STOP(如果使用PTRACE_SEIZE)开始。跟踪程序的waitpid(2)将返回一个状态值,使得
status>>8 == (SIGTRAP | (PTRACE_EVENT_FORK<<8))
- 可以使用PTRACE_GETEVENTMSG检索新过程的PID。
- PTRACE_O_TRACESYSGOOD(since Linux 2.4.6)
- 传递系统调用陷阱时,请在信号编号中设置第7位(即传递SIGTRAP | 0x80)。这使跟踪程序可以轻松区分正常陷阱和系统调用造成的陷阱。
- PTRACE_O_TRACEVFORK(since Linux 2.5.46)
- 在下一个vfork(2)处停止跟踪,并自动开始跟踪新的vforked进程,该进程将从SIGSTOP或PTRACE_EVENT_STOP(如果使用PTRACE_SEIZE)开始。跟踪程序的waitpid(2)将返回一个状态值,使得
status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK<<8))
- 可以使用PTRACE_GETEVENTMSG检索新过程的PID。
- PTRACE_O_TRACEVFORKDONE(since Linux 2.5.60)
- 在下一个vfork(2)完成时停止跟踪。跟踪程序的waitpid(2)将返回一个状态值,使得
status>>8 == (SIGTRAP | (PTRACE_EVENT_VFORK_DONE<<8))
- 自Linux 2.6.18起,可以使用PTRACE_GETEVENTMSG检索新进程的PID。
- PTRACE_O_TRACESECCOMP(since Linux 3.5)
- 触发seccomp(2)SECCOMP_RET_TRACE规则时,停止跟踪。跟踪程序的waitpid(2)将返回一个状态值,使得
status>>8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP<<8))
- 虽然这触发了PTRACE_EVENT停止,但它类似于syscall-enter-stop。有关详细信息,请参阅下面有关PTRACE_EVENT_SECCOMP的注释。可以使用PTRACE_GETEVENTMSG检索seccomp事件消息数据(来自seccomp过滤器规则的SECCOMP_RET_DATA部分)。
- PTRACE_O_SUSPEND_SECCOMP(since Linux 4.3)
- 暂停tracee的seccomp保护。这适用于任何模式,并且可以在示踪尚未安装seccomp筛选器时使用。也就是说,一个有效的用例是在被跟踪安装之前,暂停被跟踪的seccomp保护,让被跟踪安装过滤器,然后在恢复过滤器时清除此标志。设置此选项要求跟踪器具有CAP_SYS_ADMIN功能,未安装任何seccomp保护,并且自身未设置PTRACE_O_SUSPEND_SECCOMP。
- PTRACE_GETEVENTMSG(since Linux 2.5.46)
- 检索有关刚刚发生的ptrace事件的消息(无符号长),并将其放在跟踪器中的地址数据处。对于PTRACE_EVENT_EXIT,这是跟踪的退出状态。对于PTRACE_EVENT_FORK,PTRACE_EVENT_VFORK,PTRACE_EVENT_VFORK_DONE和PTRACE_EVENT_CLONE,这是新进程的PID。对于PTRACE_EVENT_SECCOMP,这是与触发规则关联的seccomp(2)筛选器的SECCOMP_RET_DATA。 (addr被忽略。)
- PTRACE_CONT
- 重新启动已停止的跟踪过程。如果数据不为零,则将其解释为要传递给示踪的信号的编号;否则为0。否则,不会传递任何信号。因此,例如,示踪剂可以控制是否传送了发送到示踪剂的信号。 (addr被忽略。)
- PTRACE_SYSCALL, PTRACE_SINGLESTEP
- 与PTRACE_CONT一样,重新启动已停止的跟踪,但是安排该跟踪分别在系统调用的下一个入口或从系统调用退出或在执行一条指令后停止。 (按照惯例,在收到信号后,示踪也将停止。)从示踪剂的角度看,示踪似乎已由于收到SIGTRAP而停止。因此,例如对于PTRACE_SYSCALL,其想法是在第一个停止位置检查系统调用的参数,然后执行另一个PTRACE_SYSCALL并在第二个停止位置检查系统调用的返回值。 data参数与PTRACE_CONT相同。 (addr被忽略。)
- PTRACE_SET_SYSCALL(since Linux 2.6.16)
- 在syscall-enter-stop中时,将要执行的系统调用的编号更改为data参数中指定的编号。 addr参数将被忽略。当前仅在arm(和arm64,虽然仅出于向后兼容)上才支持此请求,但是大多数其他体系结构具有完成此请求的其他方法(通常通过更改userland代码将系统调用号传入的寄存器)。
- PTRACE_SYSEMU, PTRACE_SYSEMU_SINGLESTEP(since Linux 2.6.14)
- 对于PTRACE_SYSEMU,继续并在进入下一个将不会执行的下一个系统调用时停止。请参阅下面有关syscall-stops的文档。对于PTRACE_SYSEMU_SINGLESTEP,请执行相同的操作,如果不是系统调用,也请执行单步操作。希望模拟所有示踪剂的系统调用的用户模式Linux之类的程序使用此调用。 data参数与PTRACE_CONT相同。 addr参数将被忽略。当前仅在x86上支持这些请求。
- PTRACE_LISTEN(since Linux 3.4)
- 重新启动已停止的跟踪,但阻止其执行。跟踪的结果状态类似于已由SIGSTOP(或其他停止信号)停止的过程。有关更多信息,请参见"组停止"小节。 PTRACE_LISTEN仅适用于PTRACE_SEIZE附加的跟踪。
- PTRACE_KILL
- 向跟踪发送SIGKILL终止它。 (地址和数据将被忽略。)
- 不建议使用此操作;不要使用它!而是直接使用kill(2)或tgkill(2)发送SIGKILL。 PTRACE_KILL的问题在于,它要求被检体处于信号传递停止状态,否则可能无法正常工作(即可能成功完成但不会杀死该被检体)。相比之下,直接发送SIGKILL没有这种限制。
- PTRACE_INTERRUPT(since Linux 3.4)
- 停止示踪。如果Tracee在内核空间中运行或休眠,并且PTRACE_SYSCALL有效,则系统调用将中断,并报告syscall-exit-stop。 (重新启动跟踪时,中断的系统调用将重新启动。)如果跟踪已被信号停止并且已向其发送PTRACE_LISTEN,则跟踪将以PTRACE_EVENT_STOP停止,WSTOPSIG(status)返回停止信号。如果同时生成任何其他ptrace-stop(例如,如果将信号发送到tracee),则会发生此ptrace-stop。如果以上都不适用(例如,如果跟踪在用户空间中运行),则以PTRACE_EVENT_STOP和WSTOPSIG(status)== SIGTRAP停止。 PTRACE_INTERRUPT仅适用于PTRACE_SEIZE附加的跟踪。
- PTRACE_ATTACH
- 附加到pid中指定的进程,使其成为调用进程的追迹。向该跟踪对象发送了SIGSTOP,但不一定已通过此调用完成而停止;使用waitpid(2)等待示踪停止。有关更多信息,请参见"连接和分离"小节。 (地址和数据将被忽略。)
- 执行PTRACE_ATTACH的权限由ptrace访问模式PTRACE_MODE_ATTACH_REALCREDS检查控制;见下文。
- PTRACE_SEIZE(since Linux 3.4)
- 附加到pid中指定的进程,使其成为调用进程的追迹。与PTRACE_ATTACH不同,PTRACE_SEIZE不会停止该过程。组停止报告为PTRACE_EVENT_STOP,WSTOPSIG(status)返回停止信号。自动连接的子级通过PTRACE_EVENT_STOP停止,并且WSTOPSIG(status)返回SIGTRAP而不是将SIGSTOP信号传递给他们。 execve(2)不提供额外的SIGTRAP。只有PTRACE_SEIZE进程可以接受PTRACE_INTERRUPT和PTRACE_LISTEN命令。刚刚描述的"扣押"行为是由使用PTRACE_O_TRACEFORK,PTRACE_O_TRACEVFORK和PTRACE_O_TRACECLONE自动附加的子级继承的。 addr必须为零。数据包含ptrace选项的位掩码,可立即激活。
- 执行PTRACE_SEIZE的权限由ptrace访问模式PTRACE_MODE_ATTACH_REALCREDS检查控制。见下文。
- PTRACE_SECCOMP_GET_FILTER(since Linux 4.4)
- 此操作允许跟踪程序转储被跟踪程序的经典BPF过滤器。
- addr是一个整数,指定要转储的过滤器的索引。最近安装的过滤器的索引为0。如果addr大于已安装的过滤器的数目,则操作将失败,并显示错误ENOENT。
- data是指向足以存储BPF程序的struct sock_filter数组的指针,如果不存储该程序,则为NULL。
- 成功后,返回值是BPF程序中的指令数。如果data为NULL,则此返回值可用于正确调整在后续调用中传递的struct sock_filter数组的大小。
- 如果呼叫者没有CAP_SYS_ADMIN功能,或者呼叫者处于严格或筛选seccomp模式,则此操作将失败,并显示错误EACCES。如果addr引用的过滤器不是经典的BPF过滤器,则该操作将失败,并显示错误EMEDIUMTYPE。
- 如果内核同时配置了CONFIG_SECCOMP_FILTER和CONFIG_CHECKPOINT_RESTORE选项,则此操作可用。
- PTRACE_DETACH
- 与PTRACE_CONT一样,重新启动已停止的跟踪,但首先从其分离。在Linux下,无论使用哪种方法来启动跟踪,都可以通过这种方式分离跟踪。 (addr被忽略。)
- PTRACE_GET_THREAD_AREA(since Linux 2.6.0)
- 此操作执行与get_thread_area(2)类似的任务。它读取GDT中的TLS条目,其索引在addr中给出,并将该条目的副本放入数据所指向的结构user_desc中。 (与get_thread_area(2)相比,结构user_desc的entry_number被忽略。)
- PTRACE_SET_THREAD_AREA(since Linux 2.6.0)
- 此操作执行与set_thread_area(2)类似的任务。它在GDT中设置TLS条目,其索引在addr中给出,并为其分配在data指向的结构user_desc中提供的数据。 (与set_thread_area(2)相比,结构user_desc的entry_number被忽略;换句话说,此ptrace操作不能用于分配空闲的TLS条目。)
- PTRACE_GET_SYSCALL_INFO(since Linux 5.3)
- 检索有关导致停止的系统调用的信息。信息放置在data参数指向的缓冲区中,该参数应该是指向struct ptrace_syscall_info类型的缓冲区的指针。 addr参数包含由数据参数指向的缓冲区的大小(即sizeof(struct ptrace_syscall_info))。返回值包含内核可以写入的字节数。如果内核要写入的数据大小超过addr参数指定的大小,则输出数据将被截断。
- ptrace_syscall_info结构包含以下字段:
struct ptrace_syscall_info { __u8 op; /* Type of system call stop */ __u32 arch; /* AUDIT_ARCH_* value; see seccomp(2) */ __u64 instruction_pointer; /* CPU instruction pointer */ __u64 stack_pointer; /* CPU stack pointer */ union { struct { /* op == PTRACE_SYSCALL_INFO_ENTRY */ __u64 nr; /* System call number */ __u64 args[6]; /* System call arguments */ } entry; struct { /* op == PTRACE_SYSCALL_INFO_EXIT */ __s64 rval; /* System call return value */ __u8 is_error; /* System call error flag; Boolean: does rval contain an error value (-ERRCODE) or a nonerror return value? */ } exit; struct { /* op == PTRACE_SYSCALL_INFO_SECCOMP */ __u64 nr; /* System call number */ __u64 args[6]; /* System call arguments */ __u32 ret_data; /* SECCOMP_RET_DATA portion of SECCOMP_RET_TRACE return value */ } seccomp; }; };
- 为各种ptrace系统调用停止定义了op,arch,instruction_pointer和stack_pointer字段。结构的其余部分是一个联合体。应该只读取那些对op字段指定的系统调用停止有意义的字段。
- The
op
field has one of the following values (defined in
<linux/ptrace.h>)indicating what type of stop occurred and
which part of the union is filled:- PTRACE_SYSCALL_INFO_ENTRY
- 联合的输入组件包含与系统调用输入停止有关的信息。
- PTRACE_SYSCALL_INFO_EXIT
- 联合的出口组件包含与系统调用出口停止有关的信息。
- PTRACE_SYSCALL_INFO_SECCOMP
- 联合的seccomp组件包含与PTRACE_EVENT_SECCOMP停止有关的信息。
- PTRACE_SYSCALL_INFO_NONE
- 工会的任何组成部分均不包含相关信息。
Death under ptrace
当(可能是多线程的)进程接收到终止信号(其处置设置为SIG_DFL且其默认操作是终止该进程)时,所有线程都会退出。跟踪将其死亡报告给跟踪器。通过waitpid(2)传递此事件的通知。
请注意,killing信号将首先导致信号传递停止(仅在一个跟踪上),并且只有在被跟踪程序注入后(或在将其分派到未跟踪的线程之后),该信号才会死亡发生在多线程进程中的所有跟踪上。 (下面将解释术语"信号传递停止"。)
SIGKILL不会生成信号传递停止,因此跟踪器无法抑制它。 SIGKILL甚至会在系统调用中终止(SIGKILL在死亡之前不会生成syscall-exit-stop)。最终结果是,即使ptrace了该进程的某些线程,SIGKILL也总是杀死该进程(所有线程)。
当跟踪调用_exit(2)时,它将其死亡报告给跟踪器。其他线程不受影响。
当任何线程执行exit_group(2)时,其线程组中的每个跟踪都将其死亡报告给其跟踪器。
如果PTRACE_O_TRACEEXIT选项打开,则PTRACE_EVENT_EXIT将在实际死亡之前发生。这适用于通过exit(2),exit_group(2)退出和信号死亡(SIGKILL除外,具体取决于内核版本;请参见下面的BUGS),以及在多线程进程中在execve(2)上拆除线程时。
跟踪器不能假定存在ptrace停止的跟踪。在许多情况下,示踪可能会在停止时死亡(例如SIGKILL)。因此,跟踪器必须准备好在任何ptrace操作上处理ESRCH错误。不幸的是,如果跟踪存在但未被ptrace停止(对于需要停止跟踪的命令),或者发出ptrace调用的进程未跟踪,则会返回相同的错误。跟踪程序需要跟踪跟踪程序的停止/运行状态,并且仅在知道已观察到跟踪程序进入ptrace-stop时,才将ESRCH解释为"跟踪程序意外死亡"。请注意,如果ptrace操作返回ESRCH,则不能保证waitpid(WNOHANG)会可靠地报告示踪剂的死亡状态。 waitpid(WNOHANG)可能返回0。换句话说,tracee可能"尚未完全死亡",但是已经拒绝了ptrace请求。
跟踪程序无法通过报告WIFEXITED(status)或WIFSIGNALED(status)来假定该跟踪对象始终终止其生命;在某些情况下不会发生这种情况。例如,如果线程组领导者以外的线程执行execve(2),则它消失;它的PID将永远不会再出现,并且任何后续的ptrace停止都将在线程组负责人的PID下报告。
Stopped states
示踪可以处于两种状态:运行或停止。出于ptrace的目的,即使该跟踪被长时间阻塞,在系统调用(例如read(2),pause(2)等)中被阻塞的跟踪仍被视为正在运行。 PTRACE_LISTEN之后的跟踪状态有些灰色区域:它不在任何ptrace-stop中(ptrace命令将不起作用,并且它将传递waitpid(2)通知),但也可以将其视为"已停止",因为它没有执行指令(未调度),并且如果它在PTRACE_LISTEN之前处于组停止状态,则在收到SIGCONT之前它不会响应信号。
停止跟踪时有很多状态,在ptrace讨论中通常将它们合并。因此,使用精确术语很重要。
在此手册页中,任何被跟踪准备好接受来自跟踪器的ptrace命令的停止状态都称为ptrace-stop。 Ptrace停止可进一步细分为信号传递停止,组停止,系统调用停止,PTRACE_EVENT停止等等。这些停止状态将在下面详细描述。
当正在运行的跟踪进入ptrace-stop时,它将使用waitpid(2)(或其他"等待"系统调用之一)通知其跟踪器。本手册页的大多数内容都假定跟踪程序等待:
pid = waitpid(pid_or_minus_1,&status,__ WALL);
Ptrace停止的跟踪报告为pid大于0并且WIFSTOPPED(status)为true的返回。
__WALL标志不包括WSTOPPED和WEXITED标志,但是暗含了它们的功能。
不建议在调用waitpid(2)时设置WCONTINUED标志:"继续"状态是按进程的,使用该状态可能会使Tracee的真实父级混淆。
使用WNOHANG标志可能会导致waitpid(2)返回0("没有可用的等待结果"),即使跟踪器知道应该有一个通知。例:
errno = 0; ptrace(PTRACE_CONT, pid, 0L, 0L); if (errno == ESRCH) { /* tracee is dead */ r = waitpid(tracee, &status, __WALL | WNOHANG); /* r can still be 0 here! */ }
存在以下几种ptrace停止:信号传递停止,组停止,PTRACE_EVENT停止,系统调用停止。它们全部由waitpid(2)报告,且WIFSTOPPED(status)为true。可以通过检查值状态>> 8来区分它们,如果该值存在歧义,可以通过查询PTRACE_GETSIGINFO来区分它们。 (注意:WSTOPSIG(status)宏不能用于执行此检查,因为它返回值(status >> 8)&0xff。)
Signal-delivery-stop
当(可能是多线程的)进程接收到除SIGKILL之外的任何信号时,内核会选择一个处理该信号的任意线程。 (如果信号是使用tgkill(2)生成的,则调用方可以显式选择目标线程。)如果跟踪了所选线程,则它将进入信号传递停止。此时,信号尚未传递到进程,并且可以被示踪剂抑制。如果跟踪器没有抑制信号,它将在下一个ptrace重新启动请求中将信号传递给跟踪器。信号传递的第二步在本手册页中称为信号注入。请注意,如果信号被阻塞,则直到信号被阻塞为止,信号传递停止才会发生,通常的例外是SIGSTOP不能被阻塞。
跟踪器将信号传递停止视为waitpid(2),其中WIFSTOPPED(status)为true,而信号由WSTOPSIG(status)返回。如果信号是SIGTRAP,则可能是另一种ptrace-stop;有关详细信息,请参见下面的"系统调用停止"和"执行"部分。如果WSTOPSIG(status)返回停止信号,则可能是组停止;见下文。
Signal injection and suppression
跟踪器观察到信号传递停止后,跟踪器应通过调用重新启动跟踪
ptrace(PTRACE_restart,pid,0,sig)
其中PTRACE_restart是重新启动的ptrace请求之一。如果sig为0,则不传送信号。否则,将发送信号信号。在本手册页中,此操作称为信号注入,以区别于信号传递停止。
sig值可能不同于WSTOPSIG(status)值:示踪剂可能导致注入不同的信号。
请注意,被抑制的信号仍然会导致系统调用过早返回。在这种情况下,系统调用将重新启动:如果跟踪器使用PTRACE_SYSCALL,则跟踪程序将观察被跟踪程序以重新执行被中断的系统调用(对于使用不同重启机制的一些系统调用,则为restart_syscall(2)系统调用)。甚至在信号被抑制后重新启动的系统调用(例如poll(2))也无法重新启动;但是,存在内核错误,即使没有可观察到的信号注入到跟踪中,也会导致某些系统调用因EINTR失败。
即使sig为非零,也不能保证重新启动在ptrace-stop中而不是信号传递-stop中发出的ptrace命令。没有错误报告;非零信号可以简单地被忽略。 Ptrace用户不应尝试以这种方式"创建新信号":而是使用tgkill(2)。
ptrace停止(不是信号传递停止)后重新启动tracee时,可能会忽略信号注入请求的事实,这是ptrace用户之间混淆的原因。一种典型的情况是,跟踪器观察到组停止,将其误认为是信号传递停止,然后使用以下命令重新启动跟踪
ptrace(PTRACE_restart,pid,0,stopsig)
旨在注入stopig,但stopig被忽略,并且tracee继续运行。
SIGCONT信号具有唤醒组停止进程(所有线程)的副作用。此副作用发生在信号传递停止之前。跟踪器不能抑制这种副作用(它只能抑制信号注入,如果安装了这样的处理程序,则它只会导致SIGCONT处理程序不在跟踪中执行)。实际上,如果在SIGCONT交付时未决,则从SIG停止唤醒后,可能会停止SIGCONT以外的信号的信号交付停止。换句话说,SIGCONT可能不是被示踪者发送后观察到的第一个信号。
停止信号导致进程(所有线程)进入组停止。这种副作用是在信号注入后发生的,因此可以被示踪剂抑制。
在Linux 2.4和更早版本中,不能注入SIGSTOP信号。
PTRACE_GETSIGINFO可用于检索与传递信号相对应的siginfo_t结构。 PTRACE_SETSIGINFO可以用来修改它。如果已使用PTRACE_SETSIGINFO更改siginfo_t,则重新启动命令中的si_signo字段和sig参数必须匹配,否则结果不确定。
Group-stop
当(可能是多线程的)进程接收到停止信号时,所有线程都会停止。如果跟踪到某些线程,它们将进入组停止。请注意,停止信号将首先导致信号传递停止(仅在一个跟踪上),并且只有在由跟踪器注入之后(或在将其分派到未跟踪的线程之后),才会将分组停止在多线程进程中的所有跟踪上启动。像往常一样,每个示踪剂分别将其组停止报告给相应的示踪剂。
跟踪器将组停止视为waitpid(2),并返回WIFSTOPPED(status)为true,并通过WSTOPSIG(status)提供停止信号。其他一些ptrace-stops类也返回相同的结果,因此建议的做法是执行调用
ptrace(PTRACE_GETSIGINFO,pid,0,&siginfo)
如果信号不是SIGSTOP,SIGTSTP,SIGTTIN或SIGTTOU,则可以避免该调用;只有这四个信号是停止信号。如果跟踪程序看到其他内容,则不能成为组停止。否则,跟踪程序需要调用PTRACE_GETSIGINFO。如果PTRACE_GETSIGINFO失败并显示EINVAL,则肯定是一个组停止。 (如果SIGKILL杀死了示踪符,则其他失败代码也是可能的,例如ESRCH(" no such process")。
如果使用PTRACE_SEIZE附加了跟踪,则组停止由PTRACE_EVENT_STOP指示:status >> 16 == PTRACE_EVENT_STOP。这允许检测组停止,而无需额外的PTRACE_GETSIGINFO调用。
从Linux 2.6.38开始,在跟踪程序看到跟踪程序ptrace-stop并重新启动或杀死它之前,该跟踪程序将不会运行,并且即使跟踪程序进入,也不会向跟踪程序发送通知(SIGKILL死亡除外)另一个waitpid(2)调用。
上一段中描述的内核行为会导致透明处理停止信号的问题。如果跟踪器在组停止后重新启动跟踪,则停止信号将被有效忽略-跟踪不会保持停止状态,而是运行。如果跟踪程序在进入下一个waitpid(2)之前没有重新启动跟踪程序,则将来的SIGCONT信号将不会报告给跟踪程序;这将导致SIGCONT信号对Tracee没有影响。
从Linux 3.4开始,有一种方法可以解决此问题:代替PTRACE_CONT,可以使用PTRACE_LISTEN命令以不执行的方式重新启动跟踪,而是等待可以通过waitpid(2 )(例如由SIGCONT重新启动时)。
PTRACE_EVENT stops
如果跟踪器设置了PTRACE_O_TRACE_ *选项,则跟踪器将输入称为PTRACE_EVENT停止的ptrace停止。
跟踪器将PTRACE_EVENT停止视为waitpid(2)并返回WIFSTOPPED(status),而WSTOPSIG(status)返回SIGTRAP(或对于PTRACE_EVENT_STOP,如果tracee在组停止中,则返回停止信号)。在状态字的高字节中设置了一个附加位:值status >> 8将为
(((PTRACE_EVENT_foo
存在以下事件:
- PTRACE_EVENT_VFORK
- 在从vfork(2)或带有CLONE_VFORK标志的克隆(2)返回之前停止。在此停止之后继续跟踪时,它将在继续执行之前等待子进程退出/执行(换句话说,vfork(2)的通常行为)。
- PTRACE_EVENT_FORK
- 在退出信号设置为SIGCHLD的fork(2)或clone(2)返回之前停止。
- PTRACE_EVENT_CLONE
- 在从clone(2)返回之前停止。
- PTRACE_EVENT_VFORK_DONE
- 在使用CLONE_VFORK标志从vfork(2)或clone(2)返回之前停止,但是在子级通过退出或执行取消阻塞此跟踪之后。
对于上述所有四个停止,该停止都发生在父级(即Tracee)中,而不是在新创建的线程中。 PTRACE_GETEVENTMSG可用于检索新线程的ID。
- PTRACE_EVENT_EXEC
- 在从execve(2)返回之前停止。从Linux 3.0开始,PTRACE_GETEVENTMSG返回以前的线程ID。
- PTRACE_EVENT_EXIT
- 在退出(包括exit_group(2)的死亡),发出信号死亡或由execve(2)在多线程进程中导致退出之前停止。 PTRACE_GETEVENTMSG返回退出状态。可以检查寄存器(与"实际"退出发生时不同)。示踪还活着;必须退出PTRACE_CONT或PTRACE_DETACH。
- PTRACE_EVENT_STOP
- 当附加新的子项时(仅当使用PTRACE_SEIZE附加时),由PTRACE_INTERRUPT命令,组停止或初始ptrace-stop引起的停止。
- PTRACE_EVENT_SECCOMP
- 跟踪器设置了PTRACE_O_TRACESECCOMP时,由跟踪syscall条目上的seccomp(2)规则触发停止。可以使用PTRACE_GETEVENTMSG检索seccomp事件消息数据(来自seccomp过滤器规则的SECCOMP_RET_DATA部分)。在下面的单独部分中详细描述了此停止的语义。
PTRACE_EVENT上的PTRACE_GETSIGINFO停止在si_signo中返回SIGTRAP,且si_code设置为(event
Syscall-stops
如果通过PTRACE_SYSCALL或PTRACE_SYSEMU重新启动了跟踪,则跟踪将在进入任何系统调用之前进入syscall-enter-stop(如果重新启动使用的是PTRACE_SYSEMU,则将不执行该跟踪,无论此时对寄存器所做的任何更改或如何停止后将重新启动跟踪)。无论哪种方法导致syscall-entry-stop,如果跟踪器使用PTRACE_SYSCALL重新启动跟踪,那么在系统调用完成或被信号中断时,跟踪器都会进入syscall-exit-stop。 (也就是说,信号传递停止永远不会在syscall-enter-stop和syscall-exit-stop之间发生;它会在syscall-exit-stop之后发生。)如果使用任何其他方法(包括PTRACE_SYSEMU)继续跟踪,则不会发生syscall-exit-stop。请注意,所有提及的PTRACE_SYSEMU同样适用于PTRACE_SYSEMU_SINGLESTEP。
但是,即使使用PTRACE_SYSCALL继续跟踪,也不能保证下一站将是syscall-exit-stop。其他可能性是,示踪可能会停止在PTRACE_EVENT停止(包括seccomp停止)中,退出(如果进入_exit(2)或exit_group(2)),被SIGKILL杀死,或者默默地死掉(如果它是线程组领导者) ,则execve(2)发生在另一个线程中,并且该线程未被同一跟踪程序跟踪;这种情况将在后面讨论)。
跟踪程序将syscall-enter-stop和syscall-exit-stop视为waitpid(2),返回时WIFSTOPPED(status)为true,而WSTOPSIG(status)返回SIGTRAP。如果跟踪器设置了PTRACE_O_TRACESYSGOOD选项,则WSTOPSIG(status)将给出值(SIGTRAP || 0x80)。
通过查询PTRACE_GETSIGINFO对于以下情况,可以将Syscall停止与SIGTRAP的信号传递停止区分开:
- si_code<= 0
- SIGTRAP是由于用户空间操作(例如,系统调用(tgkill(2),kill(2),sigqueue(3)等),POSIX计时器到期,状态更改)而传递的。 POSIX消息队列,或异步I / O请求的完成。
- si_code== SI_KERNEL (0x80)
- SIGTRAP由内核发送。
- si_code== SIGTRAP or si_code== (SIGTRAP|0x80)
- 这是系统调用停止。
但是,系统调用停止非常频繁(每个系统调用两次),并且对于每个系统调用停止执行PTRACE_GETSIGINFO可能会有些昂贵。
一些体系结构允许通过检查寄存器来区分情况。例如,在x86上,syscall-enter-stop中的rax == -ENOSYS。由于SIGTRAP(像任何其他信号一样)总是在syscall-exit-stop之后发生,并且rax几乎从不包含-ENOSYS,因此SIGTRAP看起来像" syscall-stop,它不是syscall-enter-stop"。换句话说,它看起来像"杂散的系统调用-退出-停止",并且可以通过这种方式检测到。但是这种检测是脆弱的,最好避免。
建议使用PTRACE_O_TRACESYSGOOD选项将syscall-stops与其他类型的ptrace-stops区别开来,因为它是可靠的且不会造成性能损失。
跟踪程序无法区分syscall-enter-stop和syscall-exit-stop。跟踪程序需要跟踪ptrace-stops的顺序,以免将syscall-enter-stop误解为syscall-exit-stop,反之亦然。通常,总是在syscall-enter-stop之后跟随syscall-exit-stop,PTRACE_EVENT停止或tracee的死亡。在这之间没有其他类型的ptrace-stop。但是,请注意,seccomp停止(请参见下文)可能导致syscall-exit-stops,而没有前面的syscall-entry-stops。如果使用seccomp,则需要注意不要误解此类停止,例如syscall-entry-stops。
如果在syscall-enter-stop之后,跟踪器使用了除PTRACE_SYSCALL以外的重新启动命令,则不会生成syscall-exit-stop。
syscall-stops上的PTRACE_GETSIGINFO返回si_signo中的SIGTRAP,且si_code设置为SIGTRAP或(SIGTRAP | 0x80)。
PTRACE_EVENT_SECCOMP stops (Linux 3.5 to 4.7)
在内核版本之间,PTRACE_EVENT_SECCOMP停止的行为以及它们与其他类型的ptrace停止的交互已更改。此文档记录了从其介绍到Linux 4.7(含Linux)的行为。下一部分将介绍更高内核版本中的行为。
每当触发SECCOMP_RET_TRACE规则时,就会发生PTRACE_EVENT_SECCOMP停止。这与用于重新启动系统调用的方法无关。值得注意的是,即使使用PTRACE_SYSEMU重新启动了示踪,并且无条件地跳过了此系统调用,seccomp仍会运行。
从此停止重新启动的行为就像停止是在相关系统调用之前发生的那样。特别是,PTRACE_SYSCALL和PTRACE_SYSEMU通常都将导致随后的syscall-entry-stop。但是,如果在PTRACE_EVENT_SECCOMP之后系统调用号为负,则将跳过syscall-entry-stop和系统调用本身。这意味着,如果在PTRACE_EVENT_SECCOMP之后系统调用号为负,并且使用PTRACE_SYSCALL重新启动跟踪,则下一个观察到的停止将是syscall-exit-stop,而不是预期的syscall-entry-stop。
PTRACE_EVENT_SECCOMP stops (since Linux 4.8)
从Linux 4.8开始,PTRACE_EVENT_SECCOMP停止已重新排序为在syscall-entry-stop和syscall-exit-stop之间发生。请注意,如果由于PTRACE_SYSEMU而跳过了系统调用,则seccomp将不再运行(并且不会报告PTRACE_EVENT_SECCOMP)。
在功能上,PTRACE_EVENT_SECCOMP停止功能与syscall-entry-stop相当(即,继续使用PTRACE_SYSCALL将导致syscall-exit-stops,系统调用号可能会更改,并且其他任何已修改的寄存器对于待执行系统都是可见的致电)。请注意,可能有但不必有前面的syscall-entry-stop。
停止PTRACE_EVENT_SECCOMP之后,将重新运行seccomp,现在SECCOMP_RET_TRACE规则的功能与SECCOMP_RET_ALLOW相同。具体来说,这意味着如果在PTRACE_EVENT_SECCOMP停止期间未修改寄存器,则将允许系统调用。
PTRACE_SINGLESTEP stops
[这些停靠点的详细信息尚待记录。]
Informational and restarting ptrace commands
大多数ptrace命令(除PTRACE_ATTACH,PTRACE_SEIZE,PTRACE_TRACEME,PTRACE_INTERRUPT和PTRACE_KILL以外的所有命令)都要求将跟踪置于ptrace-stop中,否则它们将因ESRCH而失败。
当tracee处于ptrace-stop中时,tracer可以使用参考性命令读取数据并将数据写入tracee。这些命令使Tracee处于ptrace-stopped状态:
ptrace(PTRACE_PEEKTEXT/PEEKDATA/PEEKUSER, pid, addr, 0); ptrace(PTRACE_POKETEXT/POKEDATA/POKEUSER, pid, addr, long_val); ptrace(PTRACE_GETREGS/GETFPREGS, pid, 0, &struct); ptrace(PTRACE_SETREGS/SETFPREGS, pid, 0, &struct); ptrace(PTRACE_GETREGSET, pid, NT_foo, &iov); ptrace(PTRACE_SETREGSET, pid, NT_foo, &iov); ptrace(PTRACE_GETSIGINFO, pid, 0, &siginfo); ptrace(PTRACE_SETSIGINFO, pid, 0, &siginfo); ptrace(PTRACE_GETEVENTMSG, pid, 0, &long_var); ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_flags);
请注意,未报告某些错误。例如,设置信号信息(siginfo)在某些ptrace-stop中可能没有任何作用,但调用可能会成功(返回0而不设置errno);如果当前ptrace-stop没有记录为返回有意义的事件消息,则查询PTRACE_GETEVENTMSG可能会成功并返回一些随机值。
通话
ptrace(PTRACE_SETOPTIONS,pid,0,PTRACE_O_flags);
影响一个痕迹。跟踪的当前标志被替换。标志由活动的PTRACE_O_TRACEFORK,PTRACE_O_TRACEVFORK或PTRACE_O_TRACECLONE选项创建和"自动附加"的新跟踪继承。
另一组命令使ptrace-stopped跟踪运行。它们具有以下形式:
ptrace(cmd,pid,0,sig);
其中cmd是PTRACE_CONT,PTRACE_LISTEN,PTRACE_DETACH,PTRACE_SYSCALL,PTRACE_SINGLESTEP,PTRACE_SYSEMU或PTRACE_SYSEMU_SINGLESTEP。如果示踪处于信号传递停止状态,则sig是要注入的信号(如果非零)。否则,可能会忽略sig。 (从ptrace停止而不是信号传递停止重新启动跟踪时,建议的做法是始终在信号中传递0。)
Attaching and detaching
可以使用调用将线程附加到跟踪器
ptrace(PTRACE_ATTACH,pid,0,0);
要么
ptrace(PTRACE_SEIZE,pid,0,PTRACE_O_flags);
PTRACE_ATTACH将SIGSTOP发送到此线程。如果跟踪程序希望此SIGSTOP不起作用,则需要对其进行抑制。请注意,如果在连接期间将其他信号同时发送到此线程,则示踪剂可能会看到示踪物首先与其他信号一起进入信号传递停止!通常的做法是重新注入这些信号,直到看到SIGSTOP,然后抑制SIGSTOP注入。这里的设计错误是ptrace附加和并发传递的SIGSTOP可能会竞争,并且并发SIGSTOP可能会丢失。
由于连接会发送SIGSTOP,而跟踪程序通常会抑制它,因此,这可能会导致从Tracee中当前正在执行的系统调用返回杂散的EINTR,如"信号注入和抑制"部分所述。
从Linux 3.4开始,可以使用PTRACE_SEIZE代替PTRACE_ATTACH。 PTRACE_SEIZE不会停止附加的进程。如果需要在连接后(或在任何其他时间)停止它而不发送任何信号,请使用PTRACE_INTERRUPT命令。
要求
ptrace(PTRACE_TRACEME,0,0,0);
将调用线程转换为Tracee。线程继续运行(不输入ptrace-stop)。一种常见的做法是遵循PTRACE_TRACEME
提高(SIGSTOP);
并允许父级(现在是我们的追踪器)观察我们的信号传递停止。
如果PTRACE_O_TRACEFORK,PTRACE_O_TRACEVFORK或PTRACE_O_TRACECLONE选项有效,则分别由vfork(2)或clone(2)和CLONE_VFORK标志,fork(2)或clone(2)创建且将退出信号设置为SIGCHLD的子级和其他类型的clone(2)自动附加到跟踪其父级的同一跟踪器。 SIGSTOP被传递给孩子,导致他们退出创建它们的系统调用后进入信号传递停止。
跟踪的分离通过以下方式执行:
ptrace(PTRACE_DETACH,pid,0,sig);
PTRACE_DETACH是重新启动操作;因此,它要求tracee处于ptrace-stop中。如果示踪处于信号传递停止状态,则可以注入信号。否则,可以无提示地忽略sig参数。
如果在跟踪程序要分离时跟踪程序正在运行,通常的解决方案是发送SIGSTOP(使用tgkill(2),以确保它转到正确的线程),等待跟踪程序在信号传递停止时停止SIGSTOP,然后将其分离(抑制SIGSTOP注入)。一个设计错误是,它可以与并发SIGSTOP竞争。另一个麻烦是,示踪可能会进入其他ptrace-stops,因此需要重新启动并再次等待,直到看到SIGSTOP。另一个麻烦是要确保该跟踪尚未被ptrace停止,因为在此期间没有信号传递发生-甚至没有SIGSTOP。
如果跟踪程序消失,则所有跟踪都将自动分离并重新启动,除非它们处于组停止状态。当前,从组停止重新启动的处理存在问题,但是"按计划"行为是使tracee停止并等待SIGCONT。如果示踪从信号传递停止重新开始,则将注入未决信号。
execve(2) under ptrace
当多线程进程中的一个线程调用execve(2)时,内核将销毁该进程中的所有其他线程,并将执行线程的线程ID重置为线程组ID(进程ID)。 (或者换句话说,当多线程进程执行execve(2)时,在调用完成时,似乎execve(2)出现在线程组头中,而不管执行execve( 2)。)线程ID的这种重置对于跟踪器来说非常混乱:
- *
- 如果打开了PTRACE_O_TRACEEXIT选项,则所有其他线程将停止在PTRACE_EVENT_EXIT中。然后,除线程组负责人以外的所有其他线程都报告死亡,就像它们通过_exit(2)以退出代码0退出一样。
- *
- 执行跟踪在execve(2)中时更改其线程ID。 (请记住,在ptrace下,从waitpid(2)返回或馈入ptrace调用的" pid"是被跟踪对象的线程ID。)也就是说,被跟踪对象的线程ID被重置为其进程ID,即与线程组长的线程ID相同。
- *
- 如果打开了PTRACE_O_TRACEEXEC选项,则会发生PTRACE_EVENT_EXEC停止。
- *
- 如果线程组领导者此时已报告其PTRACE_EVENT_EXIT停止,则对于跟踪器来说,死线程领导者似乎"从无处重新出现"。 (注意:直到至少有另一个活动线程,线程组负责人才通过WIFEXITED(status)报告死亡。这消除了跟踪器将其死亡然后再次出现的可能性。)如果线程组负责人仍然存在,对于跟踪器,这看起来好像线程组领导者从不同于其输入的系统调用中返回,甚至看起来"即使不在任何系统调用中也从系统调用中返回"。如果未跟踪线程组负责人(或被其他跟踪程序跟踪),则在execve(2)期间,它将看起来好像已成为执行跟踪的跟踪程序的跟踪。
以上所有效果都是示踪中线程ID更改的伪影。
建议使用PTRACE_O_TRACEEXEC选项来处理这种情况。首先,它启用PTRACE_EVENT_EXEC停止,该停止发生在execve(2)返回之前。在此停止中,跟踪程序可以使用PTRACE_GETEVENTMSG检索跟踪程序的先前线程ID。 (此功能在Linux 3.0中引入。)其次,PTRACE_O_TRACEEXEC选项禁用execve(2)上的旧SIGTRAP生成。
当跟踪程序收到PTRACE_EVENT_EXEC停止通知时,可以确保除了该跟踪程序和线程组负责人之外,该进程中没有其他线程处于活动状态。
收到PTRACE_EVENT_EXEC停止通知后,跟踪器应清除描述此进程线程的所有内部数据结构,并仅保留一个数据结构-一个描述单个仍在运行的跟踪,
线程ID ==线程组ID ==进程ID。
示例:两个线程同时调用execve(2):
*** we get syscall-enter-stop in thread 1: ** PID1 execve("/bin/foo", "foo" <unfinished ...> *** we issue PTRACE_SYSCALL for thread 1 ** *** we get syscall-enter-stop in thread 2: ** PID2 execve("/bin/bar", "bar" <unfinished ...> *** we issue PTRACE_SYSCALL for thread 2 ** *** we get PTRACE_EVENT_EXEC for PID0, we issue PTRACE_SYSCALL ** *** we get syscall-exit-stop for PID0: ** PID0 <... execve resumed> ) = 0
如果执行跟踪的PTRACE_O_TRACEEXEC选项无效,并且该跟踪是PTRACE_ATTACH而不是PTRACE_SEIZE d,则内核在execve(2)返回之后向该跟踪传递一个额外的SIGTRAP。这是一个普通信号(类似于kill -TRAP可能生成的信号),而不是一种特殊的ptrace-stop。为该信号使用PTRACE_GETSIGINFO返回si_code设置为0(SI_USER)。该信号可能会被信号屏蔽遮挡,因此可能会(稍后)传递。
通常,跟踪器(例如strace(1))不希望向用户显示此额外的执行后SIGTRAP信号,并且会抑制将其传递给被跟踪者(如果SIGTRAP设置为SIG_DFL,这是一个致命信号)。 )。但是,确定要抑制的SIGTRAP并不容易。建议设置PTRACE_O_TRACEEXEC选项或使用PTRACE_SEIZE从而抑制此额外的SIGTRAP。
Real parent
ptrace API(ab)在waitpid(2)上使用标准的UNIX父/子信令。当子进程被其他某个进程跟踪时,这通常导致该进程的真正父进程停止接收几种waitpid(2)通知。
这些错误中有许多已得到修复,但是从Linux 2.6.38开始,仍然存在一些错误;请参阅下面的错误。
从Linux 2.6.38开始,以下各项可以正常工作:
- *
- 首先将按信号的退出/死亡信号报告给跟踪器,然后,当跟踪器使用waitpid(2)结果时,报告给真正的父(仅当整个多线程进程退出时才报告给真正的父)。如果跟踪程序和真实父进程是同一进程,则报告仅发送一次。
返回值
成功后,PTRACE_PEEK *请求返回所请求的数据(但请参见NOTES),PTRACE_SECCOMP_GET_FILTER请求返回BPF程序中的指令数,其他请求返回零。
错误时,所有请求均返回-1,并且errno设置正确。由于成功的PTRACE_PEEK *请求返回的值可能为-1,因此调用者必须在调用之前清除errno,然后在之后进行检查以确定是否发生错误。
错误说明
- EBUSY
- (仅i386)分配或释放调试寄存器时出错。
- EFAULT
- 试图读取或写入示踪剂或被示踪剂的内存中的无效区域,可能是因为该区域未映射或不可访问。不幸的是,在Linux下,此故障的不同变体或多或少会任意返回EIO或EFAULT。
- EINVAL
- 试图设置一个无效的选项。
- EIO
- 请求无效,或者试图从跟踪器或被跟踪器的内存中的无效区域读取或写入无效区域,或者存在字对齐冲突,或者在重新启动请求期间指定了无效信号。
- EPERM
- 无法跟踪指定的进程。这可能是因为跟踪程序没有足够的特权(所需的功能是CAP_SYS_PTRACE)。由于明显的原因,非特权进程无法跟踪无法向其发送信号的进程或正在运行set-user-ID / set-group-ID程序的进程。或者,该过程可能已经被跟踪,或者(在2.6.26之前的内核上)是init(1)(PID 1)。
- ESRCH
- 指定的进程不存在,或者当前未被调用者跟踪,或者没有停止(对于要求停止跟踪的请求)。
遵循规范
SVr4、4.3BSD。
备注
尽管根据给定的原型解释了ptrace()的参数,但glibc当前将ptrace()声明为可变参数,仅固定了请求参数。建议始终提供四个参数,即使请求的操作不使用它们,请将未使用/忽略的参数设置为0L或(void *)0。
在2.6.26之前的Linux内核init(1)中,可能不会跟踪PID为1的进程。
跟踪父级仍然是跟踪器,即使该跟踪器调用execve(2)。
内存内容的布局和USER区域是特定于操作系统和体系结构的。提供的偏移量和返回的数据可能与struct user的定义不完全匹配。
"字"的大小由操作系统变体确定(例如,对于32位Linux,它是32位)。
本页记录了ptrace()调用当前在Linux中的工作方式。在UNIX的其他版本上,其行为也有很大不同。在任何情况下,ptrace()的使用都是高度特定于操作系统和体系结构的。
Ptrace access mode checking
内核用户空间API的各个部分(不仅是ptrace()操作)都需要所谓的" ptrace访问模式"检查,其结果确定是否允许操作(或者在少数情况下会导致"读取"以返回已清理数据的操作)。在一个进程可以检查有关另一进程的敏感信息或在某些情况下修改其状态的情况下,将执行这些检查。该检查基于两个过程的凭证和功能,"目标"过程是否可转储以及任何启用的Linux安全模块(LSM)(例如SELinux)执行的检查结果等因素。 ,Yama或Smack-和commoncap LSM(始终调用)。
在Linux 2.6.27之前,所有访问检查均为单一类型。从Linux 2.6.27开始,区分了两种访问模式级别:
- PTRACE_MODE_READ
- 对于"读取"操作或其他不太危险的操作,例如:get_robust_list(2); kcmp(2);读取/ proc / [pid] / auxv,/ proc / [pid] / environ或/ proc / [pid] / stat;或/ proc / [pid] / ns / *文件的readlink(2)。
- PTRACE_MODE_ATTACH
- 对于"写入"操作或其他更危险的操作,例如:将ptrace附加(PTRACE_ATTACH)附加到另一个进程或调用process_vm_writev(2)。 (PTRACE_MODE_ATTACH实际上是Linux 2.6.27之前的默认设置。)
从Linux 4.5开始,上述访问模式检查与以下修饰符之一组合(或):
- PTRACE_MODE_FSCREDS
- 使用调用者的文件系统UID和GID(请参阅凭据(7))或有效功能进行LSM检查。
- PTRACE_MODE_REALCREDS
- 使用呼叫者的真实UID和GID或允许的功能进行LSM检查。实际上,这是Linux 4.5之前的默认设置。
因为将凭证修饰符之一与上述访问模式之一组合是典型的,所以在内核源中为组合定义了一些宏:
- PTRACE_MODE_READ_FSCREDS
- 定义为PTRACE_MODE_READ | PTRACE_MODE_FSCREDS。
- PTRACE_MODE_READ_REALCREDS
- 定义为PTRACE_MODE_READ | PTRACE_MODE_REALCREDS。
- PTRACE_MODE_ATTACH_FSCREDS
- 定义为PTRACE_MODE_ATTACH | PTRACE_MODE_FSCREDS。
- PTRACE_MODE_ATTACH_REALCREDS
- 定义为PTRACE_MODE_ATTACH | PTRACE_MODE_REALCREDS。
可以使用访问模式对另一个修饰符进行"或"运算:
- PTRACE_MODE_NOAUDIT(since Linux 3.3)
- 不要审核此访问模式检查。该修饰符用于ptrace访问模式检查(例如,读取/ proc / [pid] / stat时的检查),这些检查只会导致对输出进行过滤或清理,而不会导致错误返回给调用者。在这些情况下,访问文件并不违反安全性,也没有理由生成安全审核记录。此修饰符禁止为特定的访问检查生成此类审核记录。
注意,本小节中描述的所有PTRACE_MODE_ *常量都是内核内部的,并且对用户空间不可见。这里提到常量名称是为了标记对各种系统调用和对各种伪文件的访问(例如,在/ proc下)执行的各种ptrace访问模式检查。这些名称在其他手册页中使用,以提供用于标记不同内核检查的简单速记。
用于ptrace访问模式检查的算法确定是否允许调用进程对目标进程执行相应的操作。 (在打开/ proc / [pid]文件的情况下,"调用进程"是打开文件的进程,而具有相应PID的进程是"目标进程"。)算法如下:
- 1.
- 如果调用线程和目标线程在同一线程组中,则始终允许访问。
- 2.
- 如果访问模式指定PTRACE_MODE_FSCREDS,则在下一步中进行检查时,请使用调用者的文件系统UID和GID。 (如凭据(7)中所述,文件系统UID和GID几乎始终具有与相应有效ID相同的值。)
- 否则,访问模式将指定PTRACE_MODE_REALCREDS,因此在下一步中使用调用者的真实UID和GID进行检查。 (大多数检查调用方的UID和GID的API都使用有效ID。出于历史原因,PTRACE_MODE_REALCREDS检查改为使用真实ID。)
- 3.
- Deny access if
neither
of the following is true:
- *
- 目标的真实,有效和已保存设置的用户ID与呼叫者的用户ID匹配,目标的真实,有效和已保存设置的组ID与呼叫者的组ID匹配。
- *
- 调用方在目标的用户名称空间中具有CAP_SYS_PTRACE功能。
- 4.
- 如果目标进程的" dumpable"属性具有非1的值(SUID_DUMP_USER;请参见prctl(2)中对PR_SET_DUMPABLE的讨论),并且调用方在目标进程的用户名称空间中不具有CAP_SYS_PTRACE功能,则拒绝访问。
- 5.
- The kernel LSM
security_ptrace_access_check()
interface is invoked to see if ptrace access is permitted.
The results depend on the LSM(s).
The implementation of this interface in the commoncap LSM performs
the following steps:- a)
- 如果访问模式包括PTRACE_MODE_FSCREDS,则使用在以下检查中设置的呼叫者的有效功能;否则(访问模式指定PTRACE_MODE_REALCREDS,因此)使用呼叫者的允许功能集。
- b)
- Deny access if
neither
of the following is true:
- *
- 调用方和目标进程位于同一用户名称空间中,并且调用方的功能是目标进程允许的功能的超集。
- *
- 调用方在目标进程的用户名称空间中具有CAP_SYS_PTRACE功能。
- 注意,commoncap LSM不能区分PTRACE_MODE_READ和PTRACE_MODE_ATTACH。
- 6.
- 如果前面的任何步骤都未拒绝访问,则允许访问。
/proc/sys/kernel/yama/ptrace_scope
在安装了Yama Linux安全模块(LSM)(即,内核已配置CONFIG_SECURITY_YAMA)的系统上,/ proc / sys / kernel / yama / ptrace_scope文件(自Linux 3.4起可用)可用于限制跟踪功能具有ptrace()的进程(因此也具有使用诸如strace(1)和gdb(1)之类的工具的能力)。此类限制的目的是防止攻击升级,从而使受感染的进程可以ptrace-attach附加到用户拥有的其他敏感进程(例如,GPG代理或SSH会话),以获得可能存在于内存中的附加凭据,从而扩大攻击范围。
更准确地说,Yama LSM限制了两种类型的操作:
- *
- 执行ptrace访问模式PTRACE_MODE_ATTACH检查的任何操作,例如ptrace()PTRACE_ATTACH。 (请参阅上面的" Ptrace访问模式检查"讨论。)
- *
- ptrace()PTRACE_TRACEME。
具有CAP_SYS_PTRACE功能的进程可以使用以下值之一更新/ proc / sys / kernel / yama / ptrace_scope文件:
- 0 ("classic ptrace permissions")
- 对执行PTRACE_MODE_ATTACH检查的操作没有其他限制(除了commoncap和其他LSM施加的限制之外)。
- PTRACE_TRACEME的使用保持不变。
- 1 ("restricted ptrace") [default value]
- 当执行需要PTRACE_MODE_ATTACH检查的操作时,调用进程必须在目标进程的用户名称空间中具有CAP_SYS_PTRACE功能,或者必须与目标进程具有预定义的关系。默认情况下,预定义的关系是目标进程必须是调用者的后代。
- 目标进程可以使用prctl(2)PR_SET_PTRACER操作来声明允许在目标上执行PTRACE_MODE_ATTACH操作的附加PID。有关更多详细信息,请参见内核源文件Documentation / admin-guide / LSM / Yama.rst(或Linux 4.13之前的Documentation / security / Yama.txt)。
- PTRACE_TRACEME的使用保持不变。
- 2 ("admin-only attach")
- 只有目标进程的用户名称空间中具有CAP_SYS_PTRACE功能的进程才能执行PTRACE_MODE_ATTACH操作或跟踪采用PTRACE_TRACEME的子级。
- 3 ("no attach")
- 没有进程可以执行PTRACE_MODE_ATTACH操作或跟踪使用PTRACE_TRACEME的子级。
- 一旦将此值写入文件,就无法更改。
关于值1和2,请注意,创建新的用户名称空间有效地删除了Yama提供的保护。这是因为当在子用户名称空间(以及该名称空间的后代)中执行操作时,其有效UID与子名称空间的创建者的UID匹配的父用户名称空间中的进程具有所有功能(包括CAP_SYS_PTRACE)。因此,当进程尝试使用用户名称空间对其自身进行沙箱处理时,会无意间削弱了Yama LSM提供的保护。
C library/kernel differences
在系统调用级别,PTRACE_PEEKTEXT,PTRACE_PEEKDATA和PTRACE_PEEKUSER请求具有不同的API:它们将结果存储在data参数指定的地址中,并且返回值是错误标志。 glibc包装器函数提供了上面的DESCRIPTION中给出的API,结果通过函数返回值返回。
BUGS
在具有2.6内核标头的主机上,PTRACE_SETOPTIONS声明的值与2.4的值不同。这导致在2.4内核上运行时,使用2.6内核标头编译的应用程序失败。如果已定义,则可以通过将PTRACE_SETOPTIONS重新定义为PTRACE_OLDSETOPTIONS来解决。
组停止通知将发送到跟踪器,但不会发送到真正的父级。最后确认于2.6.38.6。
如果跟踪了线程组负责人,并通过调用_exit(2)退出了线程,则将为其停止PTRACE_EVENT_EXIT(如果请求),但是直到所有其他线程退出后,才会传递后续的WIFEXITED通知。如上所述,如果其他线程之一调用execve(2),则永远不会报告线程组负责人的死亡。如果此跟踪程序未跟踪执行的线程,则跟踪程序将永远不会知道execve(2)发生了。一种可能的解决方法是PTRACE_DETACH线程组负责人,而不是在这种情况下重新启动它。最后确认于2.6.38.6。
在实际信号死亡之前,SIGKILL信号仍可能导致PTRACE_EVENT_EXIT停止。将来可能会更改。 SIGKILL旨在始终立即杀死任务,即使在ptrace下也是如此。最后确认于Linux 3.13。
如果将信号发送到被跟踪对象,则某些系统调用会使用EINTR返回,但跟踪器会抑制传递。 (这是非常典型的操作:通常由调试程序在每次附加时完成,以免引入虚假的SIGSTOP)。从Linux 3.2.9开始,以下系统调用会受到影响(此列表可能不完整):epoll_wait(2)和inotify(7)文件描述符中的read(2)。该错误的通常症状是,当您使用命令附加到静态进程时
strace -p <process-ID>
然后,而不是通常和预期的单行输出,例如
restart_syscall(<... resuming interrupted call ...>_
要么
select(6, [5], NULL, [5], NULL_
(" _"表示光标位置),您观察到多行。例如:
clock_gettime(CLOCK_MONOTONIC, {15370, 690928118}) = 0 epoll_wait(4,_
这里看不到的是,在strace(1)附加到该进程之前,该进程已在epoll_wait(2)中被阻止。附加导致epoll_wait(2)返回到用户空间,错误为EINTR。在这种情况下,程序通过检查当前时间对EINTR做出反应,然后再次执行epoll_wait(2)。 (不希望此类"杂散" EINTR错误的程序在连接strace(1)时可能会以意外方式运行。)
与一般规则相反,ptrace()的glibc包装器可以将errno设置为零。
另外参见
gdb(1),ltrace(1),strace(1),clone(2),execve(2),fork(2),gettid(2),prctl(2),seccomp(2),sigaction(2), tgkill(2),vfork(2),waitpid(2),exec(3),功能(7),signal(7)
出版信息
这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/。