Android ptrace简介

1. ptrace 本身的实现

    ptrace 提供了一种父进程可以控制子进程运行,并可以检查和改变它的核心image。它主要用于实现断点调试。一个被跟踪的进程运行中,直到发生一个信号,则进程被中止,并且通知其父进程。在进程中止的状态下,进程的内存空间可以被读写。父进程还可以使子进程继续执行,并选择是否是否忽略引起中止的信号。

1.1 用户态实现

见bionic/libc/bionic/ptrace.c

extern long __ptrace(int request, pid_t pid, void *addr, void *data);

long ptrace(int request, pid_t pid, void * addr, void * data)
{
    switch (request) {
        case PTRACE_PEEKUSR:
        case PTRACE_PEEKTEXT:
        case PTRACE_PEEKDATA:
        {
            long word;
            long ret;

            ret = __ptrace(request, pid, addr, &word);
            if (ret == 0) {
                return word;
            } else {
                // __ptrace will set errno for us
                return -1;
            }
        }

        default:
             return __ptrace(request, pid, addr, data);
    }
}

     成功返回0。错误返回-1。errno被设置。

1.2 Kernel 态实现

实现代码见:kernel/kernel/ptrace.c

SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
		unsigned long, data)
{
	struct task_struct *child;
	long ret;

	if (request == PTRACE_TRACEME) {
		ret = ptrace_traceme();
		if (!ret)
			arch_ptrace_attach(current);
		goto out;
	}

	child = ptrace_get_task_struct(pid);
	if (IS_ERR(child)) {
		ret = PTR_ERR(child);
		goto out;
	}

	if (request == PTRACE_ATTACH) {
		ret = ptrace_attach(child);
		/*
		 * Some architectures need to do book-keeping after
		 * a ptrace attach.
		 */
		if (!ret)
			arch_ptrace_attach(child);
		goto out_put_task_struct;
	}

	ret = ptrace_check_attach(child, request == PTRACE_KILL);
	if (ret < 0)
		goto out_put_task_struct;

	ret = arch_ptrace(child, request, addr, data);

 out_put_task_struct:
	put_task_struct(child);
 out:
	return ret;
}

2. 建立调试关系的方法

        用gdb调试程序,可以直接gdb ./mytest,也可以gdb <pid>(mytest的进程号)。这对应着使用ptrace建立跟踪关系的两种方式:
       1) fork: 利用fork+execve执行被测试的程序,子进程在执行execve之前调用ptrace(PTRACE_TRACEME),建立了与父进程(debugger)的跟踪关系。

      2) attach: debugger可以调用ptrace(PTRACE_ATTACH,pid,...),建立自己与进程号为pid的进程间的跟踪关系。即利用PTRACE_ATTACH,使自己变成被调试程序的父进程(用ps可以看到)。用attach建立起来的跟踪关系,可以调用ptrace(PTRACE_DETACH,pid,...)来解除。注意attach进程时的权限问题,如一个非root权限的进程是不能attach到一个root进程上的。

3. ptrace请求类型

     • PTRACE_TRACEME:

       将当前进程切换到停止状态。它通常总是与 fork/exec 一起使用,虽然也能遇到自我追踪的应用程序。对于每一个进程,PTRACE_TRACEME 只能被调用一次。追踪一个正被追踪的进程是会失败的(另一个较不重要的结果是进程不能追踪它自己。如果要这样做,应该首先从自身派生一个进程)。大量的反调试技术都是以这一事实为基础的。为了克服这类技术,必须使用绕过 ptrace 的调试器。一个信号被发送到正被调试的进程,并将该进程切换到停止状态,该进程可以使用从父进程上下文中调用的 PT_CONTINUE 和 PT_STEP 命令从停止状态退出。 wait 函数会延迟父进程的执行,直到被调试的进程切换为停止状态或者终止为止(终止时,返回值为 1407 )。其他的所有参数都被忽略。
      • PTRACE_ATTACH:

       将进程标志为 pid 的运行进程切换为停止状态,在这种情形下,调试器进程成为“父进程”。其他的所有参数都被忽略。进程必须具有与调试进程相同的用户标志( UID ),并且不能是 setuid/setduid 进程(否则就要用 root 来调试)。它会向子进程发送SIGSTOP信号,于是我们可以察看和修改子进程,然后使用 ptrace( PTRACE_DETACH, …)来使子进程继续运行下去。
      • PTRACE_DETACH:

       停止进程标志为 pid 进程(由 PTRACE_ATTACHPTRACE_TRACEME指定)的调试,并继续其常态运行。其他的所有参数都被忽略。
      • PTRACE_CONT 和 PTRACE_SYSCALL:

        继续进程标志为 pid 的被调试进程的执行,而不中断与调试器进程的通信。如果 addr == 0 ,从上次停止的地址继续执行;否则,从指定的地址继续执行。参数 data 指定发送到被调试进程的信号数量(零说明没有信号)。
      • PTRACE_SINGLESTEP:

        进行进程标志为 pid 的进程的单步执行,即执行下一条机器指令并切换为停止状态。 而 Linux 要求将参数addr置为 0 , 其他的所有参数都被忽略。
      • PTRACE_PEEKTEXT 和 PTRACE_PEEKDATA (Read):

       分别从代码区和正被调试进程的地址空间区读取机器字。在许多当代的平台中,这两个指令是等价的。 ptrace 函数接收目标地址 addr ,并返回读到的结果。

      • PTRACE_POKETEXT 和 PTRACE_POKEDATA(Write)

       将由 data 传入的机器字写入 addr 所指定的地址。

      • PTRACE_GETREGS 和 PTRACE_GETFPREGS:

        将一般用途寄存器、段寄存器和调试寄存器的值读入到地址由 addr 指针所指定的调试器进程的内存区中。寄存器结构的描述放在头文件 kernel/arch/arm/include/asm/ptrace.h或bionic/libc/kernel/arch-arm/asm/ptrace.h文件中。
      • PTRACE_SETREGS 和 PTRACE_ SETFPREGS:

        通过拷贝由addr 指针所指定的内存区域的内容来设置被调试 进程 的寄存器的值。

#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]

      • PTRACE_KILL:

        将 sigkill 发送到被调试进程,以终止其执行。

      • PTRACE_PEEKUSR
        从USER区域中读取一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为用户变量地址用于返回读到的数据。USER结构为core文件的前面一部分,它描述了进程中止时的一些状态,如:寄存器值,代码、数据段大小,代码、数据段开始地址等。在Linux(i386)中通过PTRACE_PEEKUSER和PTRACE_POKEUSR可以访问USER结构的数据有寄存器和调试寄存器。
 
      • PTRACE_POKEUSR
       往USER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据。

4. 功能详细描述

1)   PTRACE_TRACEME
形式:ptrace(PTRACE_TRACEME,0 ,0 ,0)
描述:本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。
 
2)  PTRACE_PEEKTEXT, PTRACE_PEEKDATA
形式:ptrace(PTRACE_PEEKTEXT, pid, addr, data)
         ptrace(PTRACE_PEEKDATA, pid, addr, data)
描述:从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据。在Linux(i386)中用户代码段与用户数据段重合所以读取代码段和数据段数据处理是一样的。
 
3)  PTRACE_POKETEXT, PTRACE_POKEDATA
形式:ptrace(PTRACE_POKETEXT, pid, addr, data)
         ptrace(PTRACE_POKEDATA, pid, addr, data)
描述:往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据。
 
4)  TRACE_PEEKUSR
形式:ptrace(PTRACE_PEEKUSR, pid, addr, data)
描述:从USER区域中读取一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为用户变量地址用于返回读到的数据。USER结构为core文件的前面一部分,它描述了进程中止时的一些状态,如:寄存器值,代码、数据段大小,代码、数据段开始地址等。在Linux(i386)中通过PTRACE_PEEKUSER和PTRACE_POKEUSR可以访问USER结构的数据有寄存器和调试寄存器。
 
5)  PTRACE_POKEUSR
形式:ptrace(PTRACE_POKEUSR, pid, addr, data)
描述:往USER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据。
 
6)   PTRACE_CONT
形式:ptrace(PTRACE_CONT, pid, 0, signal)
描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。
 
7)  PTRACE_SYSCALL
形式:ptrace(PTRACE_SYS, pid, 0, signal)
描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。
 
8)   PTRACE_KILL
形式:ptrace(PTRACE_KILL,pid)
描述:杀掉子进程,使它退出。pid表示被跟踪的子进程。
 
9)   PTRACE_SINGLESTEP
形式:ptrace(PTRACE_KILL, pid, 0, signle)
描述:设置单步执行标志,单步执行一条指令。pid表示被跟踪的子进程。signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。当被跟踪进程单步执行完一个指令后,被跟踪进程被中止,并通知父进程。
 
10)  PTRACE_ATTACH
形式:ptrace(PTRACE_ATTACH,pid)
描述:跟踪指定pid 进程。pid表示被跟踪进程。被跟踪进程将成为当前进程的子进程,并进入中止状态。
 
11)  PTRACE_DETACH
形式:ptrace(PTRACE_DETACH,pid)
描述:结束跟踪。 pid表示被跟踪的子进程。结束跟踪后被跟踪进程将继续执行。
 
12)  PTRACE_GETREGS
形式:ptrace(PTRACE_GETREGS, pid, 0, data)
描述:读取寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有17个基本寄存器的值。
 
13)  PTRACE_SETREGS
形式:ptrace(PTRACE_SETREGS, pid, 0, data)
描述:设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有17个基本寄存器的值。
 
14)  PTRACE_GETFPREGS
形式:ptrace(PTRACE_GETFPREGS, pid, 0, data)
描述:读取浮点寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有浮点协处理器387的所有寄存器的值。
 
15)  PTRACE_SETFPREGS
形式:ptrace(PTRACE_SETREGS, pid, 0, data)
描述:设置浮点寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有浮点协处理器387的所有寄存器的值。
 



参考:

http://hi.baidu.com/ligh0721/item/ade7ad297a4060d70f37f9f7

http://blog.sina.com.cn/s/blog_4ac74e9a0100n7w1.html

http://www.cnblogs.com/wangkangluo1/archive/2012/06/05/2535484.html

你可能感兴趣的:(Android ptrace简介)