Linux的ptrace系统调用,是Android二进制hook框架adbi的核心。因此学习adbi之前,先学习一下ptrace()函数。

ptrace介绍

ptrace可以拆开来,看作Process Trace,也就是进程跟踪,它提供了父进程观察并修改子进程的能力,并允许父进程检查和替换子进程的内核镜像(包括寄存器)等。

它的基本原理是:调用ptrace后,所有发给子进程的信号(SIGKILL除外),都会先发送到父进程,子进程被阻塞,此时子进程处于TASK_TRACED状态。父进程接收信号后,即可对子进程进程观察或修改后运行之。

ptrace函数原型为:

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

1). enum __ptrace_request request:ptrace要执行的命令。

2). pid_t pid: ptrace要跟踪的进程id。

3). void *addr: 要监控的内存地址。

4). void *data: 存放读取出的或者要写入的数据。

gdb原理

ptrace调用的功能强大,包括gdb等工具都依靠它来实现,下面通过gdb原理介绍。

gdb原理是利用ptrace系统调用,使被调试程序成为gdb子进程,发送给调试程序的信号(SIGKILL除外)被gdb先截获。gdb根据截获的信号,查看/修改程序相应内存、寄存器内容,并控制被调试程序继续执行。调试程序的核心是断点和单步,下面分开讲如何实现这两个机制。

gdb建立调试关系

使用gdb调试程序,可以直接gdb ./test,也可以gdb 的方式。这对应ptrace建立跟踪关系的两种方法。

1)通过fork+execve执行被调试程序。子进程在执行execve之前调用ptrace(PTRACE_TRACEME)。

2)通过attach的方式。gdb通过调用ptrace(PTRACE_ATTACH,pid,…),建立也被调试进程间的父子关系,使自己成为被调试程序的父进程,通过attach建立的调试关系可以通过ptrace (PTRACE_DETACH,pid,…)解除。

断点原理

程序调试时,常用的一个断点功能是“break ”,当执行到断点行时被调试程序会停止,等待gdb操作。

断点的实现原理,就是在指定位置插入断点指令,当执行到断点时cpu产生SIGTRAP信号。该信号被gdb捕获并进行断点命中判断,当判断此信号是由断点产生时等待用户输入,并执行下一步操作。否则继续。

在程序中设置断点,就是先将原来的指令保存,然后向该位置写入int 3中断指令。断点执行完后,恢复int 3处指令并将cpu的IP寄存器指向前移。这在前面写IA-32处理器的调试支持时有讲过。

单步执行原理

单步执行指在运行调试程序时,让程序运行一条指令后停下。gdb中常用的命令有next/step(语句单步)和nexti/stepi(指令单步)。在Linux上,指令单步是通过ptrace来实现。调用ptrace(PTRACE_SINGLESTEP,pid,…)可以使被调试的进程在每执行完一条指令后,产生一个SIGTRAP信号,gdb捕获后运行。下面看一个例子:

child = fork();

if(child == 0) {

execl("./HelloWorld", "HelloWorld", NULL);

} else {

ptrace(PTRACE_ATTACH,child,NULL,NULL);

while(1) {

wait(&val);

if(WIFEXITED(val))

break;

count++;

ptrace(PTRACE_SINGLESTEP,child,NULL,NULL);

}

printf("Total Instruction number= %d\n",count);

}

这个程序中,子进程调用execl执行HelloWorld程序,父进程通过ptrace(PTRACE_ATTACH,child,NULL,NULL);使子进程一步一停,以统计子进程一共执行了多少条指令。当然这时你也可以查看EIP寄存器存放的指令,或是某个变量的值。当然前提是要知识这个变量在子进程内存映像中的地址。

指令单步可以通过硬件实现,如x86架构处理器通过设置EFLAGS寄存器的TF标志位实现,每执行一条指令,产生一个异常。也可以通过软件实现,即在每条指令后面加入一条断点指令。这与上面讲的断点原理类似。而语句单步的实现基于指令单步,在《软件调试》里面有相当精彩的表述。

当然gdb的实现远比上面讲的要复杂。它能使我们方便的观察、修改被调试进程,比如通过行号、函数名、变量名等。而要实现这些,一是要在编译时提供足够信息,如使用gcc的-g调试选项,gcc会将一些程序信息放到ELF文件中,包括函数符号表、行号、变量信息、宏定义等,以给gdb提供调试资料。二是要对ELF文件格式,进程的内存映像布局以及程序指令码十分熟悉。这样才能保证在正确的时机,找到正确的地址并链接回正确的代码片段。这些可以通过阅读gdb源码分析了解。

小结

ptrace可以实时观察与修改另一个进程的运行。掌握了它的使用,就能开发出很多用户态下不可能实现的应用,当然这可能需要我们掌握编译、文件格式、程序内存布局等相当多的底层知识。

回顾一下ptrace的使用:

用PTRACE_ATTACH或者PTRACE_TRACEME 建立进程间的跟踪关系。

PTRACE_PEEKTEXT, PTRACE_PEEKDATA, PTRACE_PEEKUSR等读取子进程内存/寄存器中保留的值。

PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_POKEUSR等把值写入到被跟踪进程的内存/寄存器中。

用PTRACE_CONT,PTRACE_SYSCALL, PTRACE_SINGLESTEP控制被跟踪进程以何种方式继续运行。

PTRACE_DETACH, PTRACE_KILL 脱离进程间的跟踪关系。

提示

进程状态TASK_TRACED用以表示当前进程因为被父进程跟踪而被系统停止。

如在子进程结束前,父进程结束,则trace关系解除。

利用attach建立起来的跟踪关系,虽然ps看到双方为父子关系,但在”子进程”中调用getppid()仍会返回原来的父进程id。

已经被trace的进程,不能再次被attach。

即使是用PTRACE_TRACEME建立起来的跟踪关系,也可以用DETACH的方式予以解除。

因为进入、退出系统调用都会触发一次SIGTRAP,所以通常的做法是在进入的时候读取系统调用的参数,在退出的时候读取系统调用的返回值。

Linux ptrace 原理,从gdb原理学习ptrace调用相关推荐

  1. linux命令封装sh,shell脚本学习之调用脚本将文件打包zip的方法示例

    前言 本文主要给大家介绍的是关于调用脚本将文件打包zip的相关资料,分享出来供大家参考学习,下面来一起看看详细的介绍: 最近刚刚接触shell脚本,写了一点简单的练手.这里是用python调用脚本执行 ...

  2. GDB调试之ptrace实现原理

    目录 ptrace系统调用 ptrace使用示例 ptrace实现原理 进入被追踪模式(PTRACE_TRACEME操作) 获取被调试进程的内存数据(PTRACE_PEEKTEXT / PTRACE_ ...

  3. linux设备驱动程序架构的研究,Linux设备驱动程序学习(12)-Linux设备模型(底层原理简介)...

    Linux设备驱动程序学习(12) -Linux设备模型(底层原理简介) 以<LDD3>的说法:Linux设备模型这部分内容可以认为是高级教材,对于多数程序作者来说是不必要的.但是我个人认 ...

  4. Linux下调试器工作原理

    Linux下调试器工作原理之一-基础篇 介绍关于Linux下的调试器实现的主要组成部分--ptrace系统调用.本文中出现的代码都在32位的Ubuntu系统上开发.请注意,这里出现的代码是同平台紧密相 ...

  5. Linux ptrace系统调用详解:利用 ptrace 设置硬件断点

    <GDB调试之ptrace实现原理> <C语言程序调用栈:backtrace+backtrace_symbols+backtrace_symbols_fd> <strac ...

  6. Linux jprobe的使用和原理

    文章目录 前言 一.demo 1.1 demo演示 1.2 struct jprobe 二.jprobe 原理 2.1 原理简介 1.2 原理详解 三.源码解析 3.1 struct jprobe 3 ...

  7. linux代码工具tag,gcov-dump原理分析_Linux平台代码覆盖率测试

    第 16 页 LINES tag: tag_lines() 函数 3.4 LINES tag: tag_lines() 函数static void tag_lines ( const char * f ...

  8. 【Linux内核】内存映射原理

    [Linux内核]内存映射原理 物理地址空间 物理地址是处理器在总线上能看到的地址,使用RISC(Reduced Instruction Set Computing精简指令集)的处理器通常只实现一个物 ...

  9. Linux select函数用法和原理

    select函数的用法和原理 Linux上的select函数 select函数用于检测一组socket中是否有事件就绪.这里的事件为以下三类: 读事件就绪 在socket内核中,接收缓冲区中的字节数大 ...

最新文章

  1. .NET(C#):警惕PLINQ结果的无序性
  2. 庆祝Dojo中文博客成为CSDN博客专家!
  3. 【腾讯Bugly干货分享】Android Linker 与 SO 加壳技术
  4. 在鹅厂当程序媛是什么体验?
  5. jzoj2152-终极数【堆】
  6. linux内核驱动子系统,linux内核中的MFD子系统
  7. 案例学习BlazeDS+Spring之十:Chat(
  8. C++中algorithm头文件中一些函数使用记录
  9. Java快递物流运输管理系统源码
  10. 下载pyboard的flash中的驱动程序_如何安装爱普生打印机驱动程序
  11. ODAC的tnsnames.ora文件
  12. 《东周列国志》第十四回 卫侯朔抗王入国 齐襄公出猎遇鬼
  13. 2021最新域名授权系统网站源码 全新一键安装源码+卡密自助授权+全新UI界面
  14. 测试开发进阶——常用中间件概念——线程与线程池理解
  15. [译] 海量视频时代下的内容发现之旅
  16. Photoshop 抠图方式
  17. 最终分化的SH-SY5Y细胞为研究多巴胺激动剂的神经保护作用提供了一个模型系统
  18. 穷养儿,富养女一一原来是指这样
  19. 近期研究方向 (内部参考)
  20. ROS 机器人模型节点的运动控制原理

热门文章

  1. 开心网android客户端,开心网Android版客户端V3.9.91评测
  2. pycharm使用技巧-换行
  3. Linux 批量清除文件内容而不删除文件
  4. 腾讯云轻量服务器为什么便宜?轻量和云服务器有什么区别?
  5. 爬取寻梦环游记的评论生成词云
  6. 关于wxid转微信号的方法
  7. 嵌入式软件基础设施的建立
  8. 荒岛求生html5地图,《荒岛求生》各种物品使用方法及资源分布一览
  9. JavaScript学习之DOM详解
  10. 基于mybatis拦截器实现数据权限