Ptrace源码分析报告.docx

上传人:b****8 文档编号:9101124 上传时间:2023-02-03 格式:DOCX 页数:22 大小:853.34KB
下载 相关 举报
Ptrace源码分析报告.docx_第1页
第1页 / 共22页
Ptrace源码分析报告.docx_第2页
第2页 / 共22页
Ptrace源码分析报告.docx_第3页
第3页 / 共22页
Ptrace源码分析报告.docx_第4页
第4页 / 共22页
Ptrace源码分析报告.docx_第5页
第5页 / 共22页
点击查看更多>>
下载资源
资源描述

Ptrace源码分析报告.docx

《Ptrace源码分析报告.docx》由会员分享,可在线阅读,更多相关《Ptrace源码分析报告.docx(22页珍藏版)》请在冰豆网上搜索。

Ptrace源码分析报告.docx

Ptrace源码分析报告

Ptrace的源码分析

•前言

我们组选择了系统调用拦截这个题目,本想参考strace来做一个类似的系统调用的拦截程序。

但是后来发现我们发言前已经有同学介绍过系统调用ptrace的使用和相应的DEMO程序了,为了和他们的工作区别开来,我们的重点放在了研究ptrace的内核实现上。

在这里我们主要描述ptrace的概述、标记跟踪、被跟踪进程与跟踪进程间切换和ptrace调试四大部分。

虽然我们自己写了DEMO程序,也简略分析了strace,但是为了能够突出重点更详细的说明ptrace的结构,我们都将这些省去了。

下面是我们的报告,请李老师批阅。

•Ptrace内核实现

•Ptrace概述

Ptrace在Unix的早起版本里面就已经存在了,主要是用来对特定进程进行跟踪和调试的一个系统调用。

在ptrace的使用中,我们将相关的进程分为被跟踪的进程和跟踪进程两种。

通过ptrace系统调用,跟踪进程可以跟踪被跟踪进程的系统调用行为,并且还可以更改被跟踪进程的寄存器、内存和特定的调式寄存器(i386架构中只能改第七号调试寄存器)。

此外,ptrace还提供的单步调试的功能,它的软中断比硬件提供的单步调试功能更加精确。

使用这个系统调用,程序员可以很方便的调试自己的程序。

据我们所知,现有的strace和gdb都在一定程度上依赖于ptrace系统调用。

Ptrace这个系统调用对于程序调试有及其重要的作用,因此ptrace具有较大的研究意义。

2.1.2ptrace系统调用的格式

Ptrace的原型如图1所示,有四个参数,第一个参数request决定了ptrace的行为。

后面三个参数要根据第一个参数的取值才有特定的含义。

其中,第二个参数指定了对象进程的进程号,addr和data分别是内存地址和传入的数据。

Figure1

2.1.3ptrace的request参数

Ptrace的request参数决定了ptrace系统调用的行为,它在内核源码的"include/linux/ptrace.h"和"include/asm-i386/ptrace.h"中定义。

图二、三和图四在"include/linux/ptrace.h"中定义,图五在"include/asm-i386/ptrace.h"。

图二罗列了ptrace中与硬件架构无关的几个参数:

PTRACE_ME:

由被跟踪进程调用,调用该系统调用的进程把自己标记为被父进程跟踪。

下面的参数都是由跟踪进程调用的。

PTRACE_PEEKTEXT和PTRACE_PEEKDATA:

从被跟踪进程的内存空间里读取位于addr地址的一个字长度的数据。

忽略data参数。

PTRACE_PEEKUSER:

读取被跟踪进程的用户空间中偏移量为addr的一个字长度的数据。

忽略data参数。

PTRACE_POKETEXT和PTRACE_POKEDATA:

把data指向的一个字长的数据拷贝到被跟踪进程的内存中位置为addr内存空间里。

PTRACE_POKEUSER:

将data指向的一个字长的数据拷贝到被跟踪进程的用户空间中偏移量为addr的内存空间里。

PTRACE_CONT:

重启被跟踪进程,并将data指向的作为信号传给被跟踪进程。

如果data是零,那么只是正常的重启进程。

忽略addr参数。

PTRACE_KILL:

发送SIGKILL到被跟踪进程。

忽略addr和data参数。

PTRACE_SINGLESTEP:

和和PTRACE_CONT类似,只是被跟踪进程执行下一条指令的时候会立刻暂停。

PTRACE_ATTACH:

跟踪者用这个参数来将指定的pid的进程作为被跟踪进程,并将被跟踪进程作为跟踪进程的子进程。

PTRACE_DETACH:

和PTRACE_CONT类似,重启被跟踪进程。

但是会先回复PTRACE_ATTACH和PTRACE_ME的操作。

data被作为参数传递给被跟踪进程,忽略addr参数。

PTRACE_SYSCALL:

和PTRACE_CONT类似,只是被跟踪进程执行下一个系统调用的时候仍会暂停。

Figure2

图三表示了ptrace调用实现与硬件有关的四个参数。

PTRACE_SETOPTIONS:

设置ptrace跟踪的参数选项,选项的值由data中的位图变量指定(可取图4中的值)。

PTRACE_GETEVENTMSG:

获取被跟踪进程的信息,并存放在data指向的跟踪进程的内存空间里。

对于PTRACE_EVENT_EXIT信号,data中的值为跟踪进程终结的时候的返回值。

对于PTRACE_EVENT_FORK,PTRACE_EVENT_VFORK和PTRACE_EVENT_CLONE信号,则data中的值为新进程的pid。

忽略addr参数。

PTRACE_GETSIGINFO:

获取引起被跟踪进程暂停的信号,并保存在data指向的内存空间。

参数addr忽略。

PTRACE_SETSIGINFO:

设置引起被跟踪进程暂停的信号为data指向的内存空间中的siginfo_t结构体。

参数addr忽略。

Figure3

Figure4

 

图5是ptrace的实现和硬件有关的参数:

PTRACE_GETREGS和PTRACE_GETFPREGS:

读取被跟踪进程的寄存器的值到跟踪进程中,保存在data指向的位置。

参数addr忽略。

PTRACE_SETREGS和PTRACE_SETFPREGS:

将被跟踪进程的寄存器的值写改为跟踪进程中data指向的值。

参数addr忽略。

PTRACE_SYSEMU:

让被跟踪进程继续执行,并且在下一个系统调用前停下了,但是不会执行该系统调用。

PTRACE_SYSEMU_SINGLESTEP:

和PTRACE_SYSEMU类似,但是实在下一条指令执行前停下来。

Figure5

2.1.4ptrace的使用过程

Ptrace的使用过程主要分为三部分,具体的将在2.2,2.3和2.4中描述:

•实现被跟踪进程和跟踪进程的关联。

这个可以通过带PTRACE_TRACEME或者PTRACE_ATTACH参数的ptrace调用实现。

前者有被跟踪进程调用,后者由发起跟踪的进程调用。

•在被跟踪进程调用系统调用时被暂停,并切换到跟踪进程。

•在跟踪进程中做调试工作。

可以通过带PTRACE_PEEKTEXT等参数的ptrace调用实现。

2.1.5ptrace在内核源码的位置

头文件:

include/linux/syscalls.h:

声明了sys_ptrace()

include/linux/ptrace.h:

声明了externlongarch_ptrace()

include/asm-i386/ptrace.h:

维护了堆里面的寄存器值

C文件:

kernel/ptrace.c:

sys_ptrace()

arch/i386/kernel/ptrace.c:

定义了longarch_ptrace()

定义了intdo_syscall_trace()

arch/i386/kernel/entry.S中包含了跟踪进程和被跟踪进程间切换的代码。

2.1.6ptrace的调用过程

•建立跟踪关联

实现被跟踪进程和跟踪进程的关联。

这个可以通过带PTRACE_TRACEME或者PTRACE_ATTACH参数的ptrace调用实现。

前者有被跟踪进程调用,后者由发起跟踪的进程调用。

这两个参数实现都在"kernel/ptrace.c"文件中。

下面先讲较为复杂的PTRACE_ATTACH的函数。

intptrace_attach(structtask_struct*task)

第153行至157行判断指定的pid是否合法,不能是自己或者init()进程。

2.6内核中新加了线程组的概念,在task_struct结构中新增了tgid字段,tgid记录了主进程的pid。

第169行至178行循环申请任务列表的锁。

第181行判断了是不是已经被跟踪了,同一个进程只能被跟踪一次。

第183行的may_attach函数则判断了当前用户是否有能够跟踪指定的任务。

126行至131行判断了两个进程是否属于同一个用户或者同一个组;如果不是,则用capabale()判断当前进程是否可以被提升为特权用户进程。

smp_rmb()用来同步对称多处理器,表示读操作不可跨越。

然后判断目标进程的内核是否可以转储,不行的话则提升当前进程是否可以被提升为特权用户进程。

最近137行检查ptrace的安全性。

经过上面的一系列的判断,终于可以判定当前进程有权限跟踪目标进程了。

第188行判断当前进程是否为目标进程的父进程。

如果是,则只标记目标进程被跟踪(PT_PTRACED位);如果不是,则在标记目标进程被跟踪以外还标记PT_ADTTACHED位。

第190行判断当前进程是否有跟踪所有系统调用的权限(CAP_SYS_PTRACE位),如果是,则标记PT_PTRACE_CAP位,此时如果被跟踪进程中的excv系统调用执行带suid的文件时仍然可以被跟踪进程跟踪到。

第193行将当前进程(即跟踪进程)设置为被跟踪进程的父进程。

Figure6

PTRACE_TRACEME的源码如图7所示,由于是将自己标记为被父进程跟踪,不需要判断权限和修改自己的父进程为跟踪进程,因此不需要锁定任务列表的锁,而只需锁定自己的任务结构体。

第447行判断了是否已经被跟踪,448行判断安全性,453行把当前进程标识为被父进程跟踪。

•被跟踪进程与跟踪进程间的切换

2.3.1entry.S中被跟踪进程与跟踪进程间的切换

被跟踪进程在调用系统调用的时候会暂停下来,当前进程会前切换跟踪进程。

这节我要讲的是从被跟踪进程进入系统调用开始,到切换到跟踪进程位置的实现过程。

arch/i386/kernel/entry.S

Figure7

Figure8

Figure9

系统调用的入口在"arch/i386/kernel/entry.S"中的第225行。

226行先将当前系统调用号压栈保存,然后227行调用图7中的SAVE_ALL将所有的寄存去压栈保存。

231行检查是否被标识为被跟踪,如果是则跳转到338行。

然后在243行调用到"arch/i386/kernel/ptrace.c"中的do_syscall_trace()。

在do_syscall_trace()中,被跟踪线程依次调用ptrace_notify(),ptrace_stop()和schedule()将当前进程切换到跟踪进程(具体的在这节末描述)。

从do_syscall_trace()回来后回到第343行,若返回值为1,则表示PTRACE_SYSEMU被标记(即不执行系统调用),则进入resume_userspace;若返回值为0,则从栈中回复当前系统调用号到寄存器eax,检查系统调用号是否在系统调用列表上面,然后进入系统调用。

include/asm-i386/thread_info.h

当系统调用退出的时候,会回到第238行的syscall_exit,第242和243行检查系统调用过程中是否被_TIF_ALLWORK_MASK所标记的事件,如果有需要处理的则跳转到353行的syscall_exit_work.。

254行判断如果不包含_TIF_SYSCALL_TRACE,_TIF_SYSCALL_AUDIT或者_TIF_SINGLESTEP,则跳转到work_pending。

第356行sti打开所有中断。

然后调用"arch/i386/kernel/ptrace.c"中的do_syscall_trace(),从而再次将跟踪进程切换为当前进程。

2.3.2do_syscall_trace()中的进程切换

do_syscall_trace()在"arch/i386/kernel/ptrace.c"中定义,负责将当前进程从被跟踪进程切换到跟踪进程。

第660行判断是不是标记了TIF_SYSCALL_EMU,665行判断是否单步调试。

第669与670行在进入系统调用的时候检查系统调用号。

672行判断是否已经存在进程审计的上下文。

674行在系统调用退出时调用audit_syscall_exit将其审计信息保存在审计上下文结构体中;但是如果是单步,则直接跳到out什么都不做。

690行则判断如果当前进程没有被跟踪,则直接跳到out。

700行和701行判断如果是单步调试,则发送SIGTRAP给当前进程。

然后703行判断是不是标记了TIF_SYSCALL_TRACE或者TIF_SYSCALL_EMU,如果没有,则直接跳到out而不需要做下面的工作。

如果是标记了TIF_SYSCALL_TRACE或者TIF_SYSCALL_EMU,则最终会执行709行代码,通过调用ptrace_notify()函数来通知跟踪进程。

其中PT_TRACESYSGOOD是ptrace命令中PTRACE_SETOPTIONS参数的一个位图位。

设置PT_TRACESYSGOOD后,709行使得被跟踪进程通知跟踪进程的信号的第七个bit设置为1,这样有利于跟踪进程判断是不是系统调用产生的trap。

ptrace_notify()调用了ptrace_stop()来暂停当前进程,ptrace_stop()中调用schedule()将当前进程切换为跟踪进程。

722行和723行,在进入系统调用的时候,将其审计信息保存在审计上下文结构体中。

625行的ret的值在720行被设置过,如果没有标记TIF_SYSCALL_EMU,则直接返回0;如果标记了,则将系统调用号设置为不可用的-1,从而掉过该次系统调用。

然后729行和730行保存审计信息。

•Ptrace调试主要参数详解

Ptace的源码量很大,我们读了很多,但是全部写下来实在有些困难。

因此我们详细描述了一下的几个参数的实现方式。

•PTRACE_CONT,PTRACE_SYSCALL,PTRACE_SYSEMU

PTRACE_CONT,PTRACE_SYSCALL,PTRACE_SYSEMU作为一组来处理,分别处理修改对应的标志位。

PTRACE_SYSEMU:

让被跟踪进程继续执行,并且在下一个系统调用前停下了,但是不会执行该系统调用。

第487行设置了TIF_SYSCALL_EMU标记位,第488行清除了TIF_SYSCALL_TRACE标记位。

PTRACE_SYSCALL:

和PTRACE_CONT类似,只是被跟踪进程执行下一个系统调用的时候仍会暂停。

第490行设置了TIF_SYSCALL_TRACE标记位,第491行清除了TIF_SYSCALL_EMU标记位。

PTRACE_CONT标记重启被跟踪进程,并将data指向的作为信号传给被跟踪进程。

第493行和494行清除了TIF_SYSCALL_EMU标记位和TIF_SYSCALL_TRACE标记位。

Figure10

在"arch/i386/kernel/entry.S"中系统调用进入和退出的时候对这两个位进行了判断。

如图11和12所示,在进入系统调用的时候,会判断TIF_SYSCALL_EMU和TIF_SYSCALL_TRACE位,如果设置了,则进入系统调用的跟踪。

Figure11

Figure12

如图13所示,从系统调用的返回的时候,会进行TIF_SYSCALL_TRACE的判断,如果设置了,则进入系统调用的跟踪。

Figure13

•PTRACE_DETACH

PTRACE_DETACH:

和PTRACE_CONT类似,重启被跟踪进程。

但是会先回复PTRACE_ATTACH或者PTRACE_ME的动作,也就是取消跟踪。

data作为信号传递给被跟踪进程。

detach操作通过调用关系如下ptrace_detach->__ptrace_detach->__ptrace_unlink实现。

如图15所示,ptrace_detach检查data是否有效,并且调用ptrace_disable来消除ptrace跟踪的标记位(见图16),然后调用__ptrace_detach。

在__ptrace_detach中调用__ptrace_unlink将被跟踪进程的父进程设置为原来的父进程。

然后用wake_up_process唤醒被跟踪进程。

Figure14

Figure15

 

Figure16

•PTRACE_KILL

如图17所示,第510行判断被跟踪进程是否活着,如果已经死了,那么就不需要kill了。

如果还活着,则向被跟踪进程发送SIGKILL信号(这里先设置exit_code)。

然后514行清除单步调试标志位,515行唤醒被跟踪进程。

Figure17

如图18所示,第259行消除了单步跟踪标志位。

第262行至第265行消除了调试(如果TRAP_FLAG为1,则每执行一步就会陷入陷阱;PT_DTRACE是ptrace定义的单步执行的软件标志位)。

Figure18

当被跟踪进程进入到do_syscall_trace的时候,会检测是否设置了exit_code(这里指SIGKILL信号),如果设置了则向自己发送SIGKILL信号。

Figure19

•PTRACE_SINGLESTEP

PTRACE_SINGLESTEP和PTRACE_SYSEMU_SINGLESTEP的处理类似,第521行判断了data代表的信号是否有效,第531行将这个信号设置成发送给被跟踪进程的信号第533行唤醒了被跟踪进程。

中间的第524号至530行设置了相关的标志位,带SYSEMU表示下次系统调用暂停,但是不执行系统调用。

Figure20

在set_singlestep中设置了TRAP_FALG位与PT_DTRACE位。

前面描述过了,PT_DTRACE是ptrace定义的单步调试位。

Figure21

•PTRACE_PEEKTEXT,PTRACE_PEEKDATA

PTRACE_PEEKTEXT,PTRACE_PEEKDATA分别从被跟踪进程的指令空间和数据空间里读取一个字长的数据。

这两个操作在linux中是一样的处理,因为用户进程的指令空间和数据空间是一致的。

access_process_vm在kernel/ptrace.c中定义,它是对特定进程读写存储空间的函数。

在access_process_vm函数中,通过get_task_mm获取指定进程的虚存空间,然后用get_user_pages将用户空间的内存映射到内存空间来,接着用copy_to_user_page将一个字长的数据写到指定的内存空间(在PTRACE_POKETEXT,PTRACE_POKEDATA中用copy_from_user_page从指定的内存空间读取数据)。

Figure22

Figure23

•PTRACE_PEEKUSR

PTRACE_PEEKUSER:

读取被跟踪进程的用户空间中偏移量为addr的一个字长度的数据。

第386行到388行检查addr是否为可用的在堆栈中的位置。

第391行判断addr参数指向的是不是前17个(FRAME_SIZE)寄存器,如果是,则调用getreg获取寄存器信息。

第393行到第398行获取调式寄存器。

第399行将获取的值写入到用户空间。

Figure24

在getreg函数中,DS,ES,SS,CS是16位寄存器,所以取大于GS的寄存器要-8。

调用get_stack_long获取保存在进程堆栈中的寄存器值。

Figure25

•PTRACE_POKETEXT,PTRACE_POKEDATA

PTRACE_POKETEXT,PTRACE_POKEDATA分别向被跟踪进程的指令空间和数据空间里写入一个字长的数据,是PTRACE_PEEKTEXT,PTRACE_PEEKDATA的逆操作。

这两个操作在linux中是一样的处理,因为用户进程的指令空间和数据空间是一致的。

通过调用access_process_vm函数中向指定的内存空间写入数据,详见2.4.5。

Figure26

•PTRACE_POKEUSR

PTRACE_POKEUSR是向指定的用户空间写入一个字长的数据,第414行至第416行判断addr是否合法,第418行判断写入的是不是一般寄存器,如果是则调用putreg来写值。

如果不是,则判断是不是调试寄存器,如果是,则改变调试寄存器的值(但是实际上只有7号调试寄存器的值能够改变)。

Figure27

Figure28

•PTRACE_GETREGS

PTRACE_GETREGS读取被跟踪进程的寄存器的值到跟踪进程中,保存在data指向的位置。

第543行access_ok判断了datap指向的空间是否可写(在i386只检查了指向的空间是否在用户空间)。

第547行至550行,将记录在堆栈中的被跟踪进程的寄存器值写入到跟踪进程的data指向的用户空间。

如果成功,则551行把返回值设置为0。

Figure29

•PTRACE_SETREGS

PTRACE_SETREGS将data指向的位置的值写入到保存被跟踪进程的寄存器的堆栈。

第557行access_ok判断了datap指向的空间是否可读(在i386只检查了指向的空间是否在用户空间)。

第562行,从用户空间datap指向的位置读取一个字长的值。

第563行将读取的值写入到被跟踪进程的第i个寄存器在堆栈中的位置。

如果成功,则566行把返回值设置为0。

Figure30

 

•参考资料

•《Linux内核源代码情景分析》毛德操,胡希明

•《深入分析Linux内核源代码》陈莉君

•SourceofStrace-4.5.19

•SourceofLinuxkernel-2.6.16.18

•《玩转ptrace》

•其他不计其数的BLOG

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1