文章目录

  • 问题
  • 运行环境
  • 程序组成
  • 实现思路
  • 模块划分
  • 完整代码
  • 程序运行及结果

问题

利用ptrace系统调用实现一个简单的软件调试器。基本功能包括能够截获被调试进程的信号,被调试进程进入断点后能够查看被调试进程任意内存区域的内容,能够查看任意CPU寄存器的内容,能够使被调试进程恢复运行。


运行环境

Ubuntu-20.04 64位虚拟机

程序组成

1,测试程序为test.c,这是后面要被测试的程序;它的可执行程序为test(采用-g进行编译)。
      2,Ptrace系统调用的实现程序为ptrace.c。

实现思路

查看阅读ptrace官方文档(man ptrace),从文档的相关说明中有了大体的实现思路,摘自手册:
A process can initiate a trace by calling fork and having the resulting child do a PTRACE_TRACEME, followed (typically) by an execve,Alternatively, one process may commence tracing another process using PTRACE_ATTACH or PTRACE_SEIZE.
      其中第一种是被动的,让别人来调试自己;第二种是主动去调试别人。后面采用第一种方法。
      Ptrace的函数原型为:
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
      参数request:请求ptrace执行的操作;参数pid:目标进程的ID;参数addr:目标进程的地址值;参数data:作用则根据request的不同而变化,如果需要向目标进程中写入数据,data存放的是需要写入的数据;如果从目标进程中读数据,data将存放返回的数据。如下图:

需注意:当采用参数request为PTRACE_PEEKTEXT时,代码段中的数据被保存在返回值中。返回值为一个long类型。(经测试long和long long在当前环境上均为8字节长)

在子进程中采用ptrace(PTRACE_TRACEME,0,0,0),来让父进程来调试它,这个方法的执行应该在exec()函数执行前,来保证执行exec()时,子进程已经处于被调试状态。

采用fork(),将父进程作为tracer,将子进程作为tracee;再通过exec ()函数族将待调试的程序装入到子进程中,并且exec()函数族在执行时若发现原进程处于调试状态下的话,当将新的代码装入后,会向自身发送信号SIGTRAP,中止执行,方便在父进程中来进行操作。

父进程中通过简单的循环操作来接受用户的输入,对不同的输入进行不同的操作。所接受的用户指令有:退出(exit),查看大部分寄存器内容(regs),继续执行被调试进程(continue),列出子进程代码,查看特定内存处的内容(examin),添加断点,查看一共有多少条指令(insc),单步调试(step),列出当前rip指向的指令(list)。

模块划分

1,获取用户输入的待调试程序,(这里输入的程序要求:不能带有参数,单线程,且需要在当前目录下)
      2,fork出一个子进程,采用execvp函数来装入待调试程序。
      3,父进程实现具体的操作来对子进程进行调试工作。

完整代码

ptrace.c:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ptrace.h>
#include<sys/user.h>//存放结构体 user_regs_struct 信息
#include<signal.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>int main()
{const int SIZE=32;//表明用户输入的最大长度char* arg[2];for(int i=0;i<2;i++)arg[i]=NULL;printf("Input the program name as the tracee.(no parameter and in the pwd.)\n");arg[0]=(char*)malloc(SIZE);fgets(arg[0],SIZE-1,stdin);//获得用户输入,包括'\n'int len=strlen(arg[0]);arg[0][len-1]='\0';//将最后的'\n'变为'\0'pid_t pid = fork();if (pid < 0)printf("error in fork().\n");else if (pid == 0){//printf("my pid is %d,I will be traced.\n\n", getpid());if(ptrace(PTRACE_TRACEME,0,0,0)<0){printf("error in ptrace().\n");exit(-2);}if (execvp(arg[0],arg))//执行用户命令,收到系统产生的SIGTRAP信号。{printf("error in execvp().\n");free(arg[0]);exit(-1);}else{free(arg[0]);exit(0);}}else{int status;int i,j,k;char instruction[SIZE];struct user_regs_struct regs;//存储子进程当前寄存器的值int count;//memset(instruction,0,SIZE*sizeof(char));const char* e="exit";const char* r="regs";const char* c="continue";const char* l="list";const char* ic="insc";const char* x="examin";//默认查看当前内存地址之后40个字const char* s="step";printf("Please wait...\n");sleep(0.5);while(1){//wait(&status);//printf("The signal child got: %s\n",strsignal(WSTOPSIG(status)));printf("ptrace> ");fgets(instruction,SIZE-1,stdin);while(instruction[i]!='\n')++i;instruction[i]='\0';if(strcmp(e,instruction)==0)//退出操作{ptrace(PTRACE_KILL,pid,NULL,NULL);break;}else if(strcmp(c,instruction)==0){ptrace(PTRACE_CONT,pid,NULL,NULL);sleep(1);break;}else if(strcmp(r,instruction)==0)//查询寄存器内容操作{ptrace(PTRACE_GETREGS,pid,NULL,&regs);printf("rax  %llx\nrbx  %llx\nrcx  %llx\nrdx  %llx\nrsi  %llx\n""rdi  %llx\nrbp  %llx\nrsp  %llx\nrip  %llx\neflags  %llx\n""cs  %llx\nss  %llx\nds  %llx\nes  %llx\n",regs.rax,regs.rbx,regs.rcx,regs.rdx,regs.rsi,regs.rdi,regs.rbp,regs.rsp,regs.rip,regs.eflags,regs.cs,regs.ss,regs.ds,regs.es);}else if(strcmp(l,instruction)==0){// long 和long long在此都是8字节。ptrace(PTRACE_GETREGS,pid,NULL,&regs);long data=ptrace(PTRACE_PEEKTEXT,pid,regs.rip,NULL);printf("The rip is %llx,  The present instruction is: %lx\n",regs.rip,data);}else if(strcmp(ic,instruction)==0){count=0;while(1){wait(&status);if(WIFEXITED(status)){printf("count is %d\n",count);break;}ptrace(PTRACE_SINGLESTEP,pid,NULL,NULL);count++;}}else if(strcmp(x,instruction)==0){printf("Please input 1,if you want to watch the 40 bytes after rip\n");printf("Please input 2,if you want to watch the 40 bytes atrer the memory you will assign\n");char ch;ch=getc(stdin);getchar();unsigned char temp[40];union u{unsigned long data;unsigned char t[8];}d;   //这里采用union联合类型来实现ptrace(PTRACE_GETREGS,pid,NULL,&regs);if(ch=='1'){for(int i=0;i<5;++i){d.data=ptrace(PTRACE_PEEKTEXT,pid,regs.rip+i*8,NULL);memcpy(temp+8*i,d.t,8);}printf("The current location is %llx:\n  The 40 bytes later are : ",regs.rip);}else if(ch =='2'){printf("The current rip is %llx:  ,Please input offset:  \n",regs.rip);//偏移单位为字节数,正负均可。int offset;scanf("%d",&offset);getchar();for(int i=0;i<5;++i){d.data=ptrace(PTRACE_PEEKTEXT,pid,regs.rip+offset+i*8,NULL);memcpy(temp+8*i,d.t,8);}printf("The current location is %llx:\n  The 40 bytes later are : ",regs.rip);}count=0;for(int i=0;i<40;i++){printf("%.2x",temp[i]);count++;if(count==8){count=0;printf("  ");}}printf("\n");}else if(strcmp(s,instruction)==0){wait(&status);if(WIFEXITED(status)){printf("Done.\n");break;}ptrace(PTRACE_SINGLESTEP,pid,NULL,NULL);}else{printf("Invalid instruction!\n");}}wait(&status);printf("\nchild process quit with status %d.\n", status);}return 0;}

test.c:

#include<stdio.h>
#include<unistd.h>
int main()
{int i,j;i=5;j=10;return 0;
}

程序运行及结果

1,编译两个.c文件:gcc –o p ptrace.c gcc –g test.c –o test
2,最终,父进程支持的命令操作如下:
      a, exit 终止被调试程序(子进程),并且父进程退出
      b, continue 恢复子进程的执行, 父进程稍后退出
      c, regs 查看当前子进程的寄存器内容(很少用到的就不列出了)
      d, list 显示当前rip的内容,以及需要待执行的指令(更好的说法应该是:rip指向的内存处的8B长度的值)。
      e, step 单步执行
      f, examin 查看当前rip所指向的内存后面40B长的内容。按照字节来显示。
      g, insc 查看子进程需要单步执行的步数。

1,运行程序(此时被调试程序相当于进入断点,等待输入命令)

2,查看寄存器内容 regs list 和step命令

3,查看寄存器rip所指向地址后40个字节的内容。 命令 examin 后输入 1 即可。
查看指定内存处内容,内存地址不好输入,在此选择相对于rip的偏移来间接查看任意内存处的值。 命令 examin 后, 输入 2 ,输入相对于rip的偏移地址,
比如:下面分别输入+20 和 -20,内存处的内容如下图所示。并且通过 命令 continue 让被调试程序继续执行。此时退出状态为0

4,命令insc 会将被调试程序单步运行一遍,算出所需的机器指令数。 再通过命令 exit 退出。

linux(4)-Ptrace 系统调用的使用相关推荐

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

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

  2. linux 分析 ptrace

    linux 分析 ptrace() 形式 #include <sys/ptrace.h> int ptrace(int request, int pid, int addr, int da ...

  3. ptrace 系统调用

    ptrace 是 Linux 环境下,许许多多分析调试工具,如 strace,ltrace 等,所使用的最核心的系统调用.ptrace,即 process trace,指进程追踪.它能让一个进程控制及 ...

  4. strace实现原理:ptrace系统调用

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

  5. linux之ptrace

    经典博客: Linux内存替换系列(包括实验 实验可行)  就是要注意下是64位还是32位的.... https://blog.csdn.net/Dearggae/article/details/47 ...

  6. python 调用linux内核api_Linux系统调用及用户编程接口(API)学习

    Linux系统学习 系统调用指操作系统提供给用户程序调用的一组"特殊"接口,用户程序可以通过这组"特殊"接口来获得操作系统内核提供的服务. 为什么用户程序不能直 ...

  7. ie传递给系统调用的数据区域太小_【Linux系列】系统调用

    在现代OS中,内核提供了用户进程与内核进行交互的一组接口.这些接口让应用程序受限地访问硬件设备,提供了创建新进程并与已有进程进行通信的机制,也提供了申请OS其他资源的能力. 系统调用在用户空间进程和硬 ...

  8. linux net子系统-系统调用层

    linux net子系统打算分下面这几个部分来理解,这些都是我初次理解net子系统,若有出错,还请不吝赐教: 1. linux net子系统-系统调用层 2. linux net子系统-套接口层 3. ...

  9. 向Linux增加一个系统调用或内核模块

    向Linux增加一个系统调用或内核模块 配置环境 获取root权限 sudo su 更新系统 sudo apt-get update 安装相关编译程序包 sudo apt-get install li ...

最新文章

  1. 新手推荐!天池数据挖掘挑战赛,2019全球数据智能大赛正式启动!60万奖金等你来拿...
  2. Browser Security-同源策略、伪URL的域
  3. GetLogicalDriveStringS获取驱动器根路径
  4. linux裸设备文件系统,Linux当中的文件系统
  5. 系统检测到您正在使用网页抓取工具_【安全】58反抓取简介
  6. C#三层架构第四课之DAL层
  7. prometheus-net.DotNetRuntime 获取 CLR 指标原理解析
  8. wifi rssi 计算 距离_WiFi和WLAN是一样的?真相在这里~别再傻傻分不清了
  9. Netflix正在搞的混沌工程到底是什么?终于有人讲明白了
  10. 03-JavaScript基础-数据及数据类型
  11. unity3D协程(Coroutine)原理深入剖析
  12. 图像的剪裁——imcrop
  13. 如何自己搭建外卖红包平台,操作外卖CPS佣金提成实现躺赚=
  14. 美国地质勘探局官网(USGS)Landsat 8 OLI_TIRS 影像数据下载详解
  15. Android使用BottomNavigationView+NavigationUI报错
  16. 软件自动化测试的的设计标准和适用范围
  17. 数据可视化笔记8 层次数据可视化
  18. Golang PDF转图片 拼接长图 压缩PDF及图片 输出JPEG
  19. 解决ModuleNotFoundError: No module named ‘celery.five‘的问题
  20. HBuilder 真机调试提示:手机上没有信任本计算机的授权,请在手机上信任该授权

热门文章

  1. LR 场景选项配置--笔记
  2. java的String构造对象的几种方法以及内存运行过程
  3. 【转】Linux查看物理CPU个数、核数、逻辑CPU个数
  4. The Geometry has no Z values 解决办法(转载)
  5. 条款20 :宁以pass-by-reference-to-const 替换pass-by-value
  6. slf4j 日志接口 统一
  7. java学习笔记(十三)----IO操作
  8. python绘图课设_python课程设计笔记(三)turtle绘图库(海龟库)
  9. UVA11174村民排队问题
  10. C语言经典例18-求累加和