这一篇文章会介绍strace如何工作,再稍微深入介绍一下什么是system call。再介绍一下ptrace、wait(strace依赖的system call)。最后再一起来造个轮子,动手用代码实现一个strace。聊天框回复“strace”,可以获取本文源码。


上一篇,我们介绍了strace工具,strace是非常实用的调试、分析工具,可以记录process调用system call的参数、返回值。

效果展示
下面展示一下我们实现简化版的strace的效果,每行打印一个system call。只不过没有根据system call的序号转换参数类型来打印,毕竟我们实现的目的是学习。

root@xxx:~/code/case/case20_ptrace/tracer$ ./xx_strace /usr/bin/lsbrk(0) = 0x5626edd99000
arch_prctl() = -22
access(0x7f5b2155f9e0, 4) = -2
openat(0xffffff9c, 0x7f5b2155cb80, 524288, 0) = 3
fstat(3, 0x7ffc965a7fe0) = 0
mmap(0, 33585, 1, 2, 3, 0) = 0x7f5b2152e000
close(3) = 0
openat(0xffffff9c, 0x7f5b21566e10, 524288, 0) = 3
read(3, 0x7ffc965a8188, 832) = 832
fstat(3, 0x7ffc965a8030) = 0
mmap(0, 8192, 3, 34, 0xffffffff, 0) = 0x7f5b2152c000
mmap(0, 174600, 1, 2050, 3, 0) = 0x7f5b21501000
mprotect(0x7f5b21507000, 135168, 0) = 0
mmap(0x7f5b21507000, 102400, 5, 2066, 3, 24576) = 0x7f5b21507000
mmap(0x7f5b21520000, 28672, 1, 2066, 3, 126976) = 0x7f5b21520000
mmap(0x7f5b21528000, 8192, 3, 2066, 3, 155648) = 0x7f5b21528000
mmap(0x7f5b2152a000, 6664, 3, 50, 0xffffffff, 0) = 0x7f5b2152a000
close(3) = 0
openat(0xffffff9c, 0x7f5b2152c4e0, 524288, 0) = 3
read(3, 0x7ffc965a8168, 832) = 832

1、system call

上一篇,我们简单介绍了系统调用(system call)。strace就是记录system call的工具,我们需要深入了解一下system call。

1.1、system call序号

每个system call都有一个序号,记录在/usr/include/x86_64-linux-gnu/asm/unistd_64.h文件中。我们常见的read、write、open都在其中定义。

#ifndef _ASM_X86_UNISTD_64_H
#define _ASM_X86_UNISTD_64_H 1#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
#define __NR_lstat 6
#define __NR_poll 7
...

1.2、使用syscall直接调用system call

我们可以调用glibc封装的system call(例如close、connect、bind等),还可以使用syscall直接调用。glibc中的封装最终也是调用了syscall。

 long syscall(long number, ...);

例如我们调用tgkill时

int tgkill(int tgid, int tid, int sig);

我们可以使用glibc封装的

tgkill(getpid(), tid, SIGHUP);

也可以使用syscall直接传system call 序号和对于参数来调用。

syscall(SYS_tgkill, getpid(), tid, SIGHUP);

1.3、syscall参数、返回值

我们需要了解一下调用syscall时,用户层与内核是交互交互返回值和参数的。


根据man syscall手册。不同的cpu通过不同寄存器来传递。

返回值:

Arch/ABI    Instruction           System  Ret  Ret  Error    Notescall #  val  val2
───────────────────────────────────────────────────────────────────
arm64       svc #0                x8      x0   x1   -
x86-64      syscall               rax     rax  rdx  -        5
x32         syscall               rax     rax  rdx  -        5

x86-64架构下,返回值在rax寄存器。


参数列表:

Arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
──────────────────────────────────────────────────────────────
arm64         x0    x1    x2    x3    x4    x5    -
x86-64        rdi   rsi   rdx   r10   r8    r9    -
x32           rdi   rsi   rdx   r10   r8    r9    -

x86-64位下,参数依次是rdi rsi rdx r10 r8 r9。

2、strace工作流程

首先介绍我们把tracer和tracee的概念:我们把跟踪者(strace)叫做tracer,被跟踪process叫做tracee。

strace整体工作流程如下:

  • 蓝色部分:建立trace关系。

  • 橙色部分:子进程执行指令。

  • 绿色部分:循环跟踪tracee的system call。

3、ptrace && wait

磨刀不误砍柴工,我们也来介绍一下strace工作时两个重要函数。

3.1、ptrace

通过上面流程图,可以看出strace在建立trace关系、跟踪system call时都依赖ptrace。

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

man ptrace

The ptrace() system call provides a means by which
one process (the "tracer") may observe and control the execution of another process (the "tracee"),
and examine and change the tracee's memory  and  registers.
It is primarily used to implement breakpoint debugging and system call tracing.

man手册是这么描述的:ptrace可以让tracer观察并控制tracee的执行,并可以获取并修改tracee的内存和寄存器。可以用来实现调试器或system call跟踪。实际上gdb和strace都是依赖ptrace来实现的。

参数

  • request:决定ptrace的行为,一会用到哪个介绍哪个。

  • pid:tracee的pid,被监控者。

  • addr,data:根据request不同有不同含义。

2.2、wait介绍

wait也是strace工作时也很重要,先看看man手册。

pid_t wait(int *wstatus);

man wait

wait is used to wait for state changes in a child of the calling process,
A state change is considered to be:
the child  terminated;
the  child  was stopped by a signal;
or the child was resumed by a signal.  If a child has already changed state, then these calls return immediately.
Otherwise, they block until either a child changes state。

man手册是这么描述的:wait用来等待子进程状态改变,包括退出、stopped、resumed。
如果子进程状态已经改变了,wait会立刻返回。否则会卡住等待状态改变。


状态通过wstatus返回,wait也提供了一系列配套宏来判断状态。

strace使用wait有2个场景:

  • 建立trace关系后,等待tracee变成stop状态。

  • 开始跟踪,等到tracee调用system call。

4、strace实现

4.1、建立trace关系

strace工作的第一步就是建立trace关系,按照不同启动模式采取不同的方式建立。无论是哪种模式,都需要与tracee建立trace关系。才能监控system call的调用。

strace的启动模式:

  • attach模式:strace -p pid,trace已经启动的进程。

  • strace启动模式:strace cmd,trace新启动进程。

attach模式建立trace关系
strace调用ptrace(request=PTRACE_ATTACH)与tracee建立trace关系。

static bool xx_trace_attach(pid_t pid){auto ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);X_CHECK(ret >=0 ,false);return true;
}

strace启动模式建立trace关系
此模式下,strace需要先执行fork。然后父进程作为tracer,子进程作为tracee。

子进程fork以后,还需要执行ptrace(request=PTRACE_TRACEME)来建立trace关系。

static bool xx_trace_me()
{auto ret = ptrace(PTRACE_TRACEME, 0L, 0L, 0L);X_CHECK(ret >=0 ,false);return true;
}

建立好trace关系以后,子进程还需要调用execv来执行tracee的逻辑。

void tracee(int argc, char *argv[])
{xx_trace_me();execv(argv[0], argv);
}

代码
无论是attach模式还是strace启动模式,建立trace关系后,都执行相同的逻辑,代码可以复用。

int main(int argc, char *argv[])
{const char *spid = xx_get_arg(argc, argv, "-p");// atach模式if (nullptr != spid){pid_t pid = atoi(spid);xx_trace_attach(pid); // attchtracer(pid);          // 开始跟踪system call}// strace启动模式else{pid_t pid = fork();if (0 == pid){tracee(argc - 1, argv + 1); // 执行tracee指令}else if (pid > 0){tracer(pid); // 开始跟踪system call}}return 0;
}

4.2、等待tracee进入stop状态

建立trace关系后,strace需要调用wait,来等待tracer变为stop状态。

// 等待tracee变为stop状态
int child_status = 0;
wait(&child_status);
printf("child_status=%s\n", xx_waitstate2str(child_status).c_str());

xx_waitstate2str是封装好,打印子进程状态的

static string xx_waitstate2str(int status)
{if(WIFEXITED(status))       return "terminated normally\n";if(WIFSIGNALED(status))     return "terminated by a signal\n";if(WIFSTOPPED(status))      return "stopped by delivery of a signal\n";if(WIFCONTINUED(status))    return "resumed\n";return "state?\n";
}

屏幕输出

child_status=stopped by delivery of a signal

4.3、循环跟踪system call

建立好trace关系后,tracee是处于stop状态的。下一步开始循环跟踪tracee的system call。

strace使用ptrace 跟踪tracee的system call时会有两次拦截,一次是调用前,一次是调用完成后。

4.3.1、调用前拦截

调用前拦截时,有以下操作:

  1. strace唤醒:strace调用ptrace(request=PTRACE_SYSCALL),唤醒tracee继续执行。

  2. strace等待:strace调用wait等待,此时wait会卡住。

  3. tracee调用system call前:tracee会进入stop状态;strace调用wait返回,被唤醒。

  4. strace获取信息:stracewait返回后,可以调用ptrace(request=PTRACE_GETREGS)获取寄存器的信息。


调用前拦截时,system call还没被调用,通过寄存器信息,可以获取:

  • 调用的system call的序号。

  • 调用前的参数信息。(不过因为有些system call会通过参数向外传递信息,我们选择system call之后的拦截来获取参数。)


4.3.2、调用后拦截

  1. strace唤醒:同调用前拦截。

  2. strace等待:同调用前拦截。

  3. tracee调用system call后:同调用前拦截。

  4. strace获取信息:同调用前拦截。


调研后拦截时,system call已调用完毕。通过寄存器可以获取返回值,以及调用后的参数。前面1.3章节介绍了system call在不同cpu架构使用哪些寄存器。

4.3.3、拦截代码实现

下面我们来看看代码实现.

步骤1(唤醒)、2(等待)
我们封装了一个函数

void wait_syscall(pid_t child)
{// 1.唤醒int child_status = 0;auto ret = ptrace(PTRACE_SYSCALL, child, 0, 0);if (ret < 0){X_P_INFO;}// 2. 等待wait(&child_status);// 3. 是否已退出?if (WIFEXITED(child_status)){printf("exited in syscalls with status=%d\n", child_status);exit(0);}
}

循环跟踪主题
循环主题主要就是两次拦截、获取信息、打印信息。

while (1)
{// 调用前拦截syscall_info info;wait_syscall(child);{// 获取寄存器信息struct user_regs_struct reg;bool get_reg = xx_trace_get_reg(child, reg);assert(get_reg);// 获取:system call 序号info.set_before_call(reg);}// 调用后拦截wait_syscall(child);{// 获取寄存器信息struct user_regs_struct reg;bool get_reg = xx_trace_get_reg(child, reg);assert(get_reg);// 获取:参数、返回值info.set_after_call(reg);}// 打印信息info.print();
}

保存system call信息

struct syscall_info
{uint64_t syscall_no = 0;uint64_t syscall_ret = 0;// 返回值uint64_t para[6] = {0};// 参数。。。
}

调用前拦截、获取system call序号

struct syscall_info
{void set_before_call(const user_regs_struct &reg){syscall_no = reg.orig_rax;}}

调用后拦截、获取参数、返回值

struct syscall_info
{void set_after_call(const user_regs_struct &reg){syscall_ret = reg.rax;para[0] = reg.rdi;para[1] = reg.rsi;para[2] = reg.rdx;para[3] = reg.r10;para[4] = reg.r8;para[5] = reg.r9;}
}

不足200行代码,实现了strace基础功能。造个轮子能更好的学习,大家学会了么?

最后,全网同名,东北码农,求关注、点赞、转发,谢谢~

造轮子-strace(二)实现相关推荐

  1. 动手造轮子:实现一个简单的依赖注入(二) --- 服务注册优化

    动手造轮子:实现一个简单的依赖注入(二) --- 服务注册优化 Intro 之前实现的那版依赖注入框架基本可用,但是感觉还是不够灵活,而且注册服务和解析服务在同一个地方感觉有点别扭,有点职责分离不够. ...

  2. 不是“重复”造轮子,百度飞桨框架2.0如何俘获人心

    2016 年,百度 PaddlePaddle 打响了国产深度学习框架开源的第一枪. 2019 年 4 月,在 Wave Summit 深度学习开发者峰会上,首次发布了PaddlePaddle 的中文名 ...

  3. 我为什么还要造轮子?欠踹?Monk.UI表单美化插件诞生记!

    背景 目前市场上有很多表单美化的UI,做的都挺不错,但是他们都有一个共同点,那就是90%以上都是前端工程师开发的,导致我们引入这些UI的时候,很难和程序绑定.所以作为程序员的我,下了一个决定!我要自己 ...

  4. 还在重复造轮子?Java开发人员必知必会的20种常用类库和API

    介绍 一个有经验的Java开发人员特征之一就是善于使用已有的轮子来造车.<Effective Java>的作者Joshua Bloch曾经说过:"建议使用现有的API来开发,而不 ...

  5. android tcp socket框架_socket网络编程知识梳理,让你学会造轮子的能力

    在前面几篇中,我给大家介绍了socket的相关知识,也给出了详细的代码,从socket的基本介绍,到IO多路复用,以及粘包拆包,最后到心跳包问题.总的来说大概都把socket网络编程中能碰见的问题都讲 ...

  6. 造轮子是什么意思_程序员为什么热衷于造轮子,升职加薪吗?

    作者:小傅哥 博客: https://bugstack.cn- 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 哪个架构师没造过轮子? 你想过这样一件事吗? 是先具备能力在安排职位,还是先安排 ...

  7. 程序员造轮子的正确姿势

    作者 | 黄峰达,CSDN 博客专家 Phodal 责编 | 唐小引 出品 | CSDN(ID:CSDNnews) 过去的几年里,我一直在打造各式各样的编程相关的工具.这些工具有的是用于指导软件开发工 ...

  8. 鸿蒙os事例代码,鸿蒙HarmonyOS App开发造轮子之自定义圆形图片组件的实例代码

    一.背景 在采用Java配合xml布局编写鸿蒙app页面的时候,发现sdk自带的Image组件并不能将图片设置成圆形,反复了翻阅了官方API手册(主要查阅了Compont和Image相关的API),起 ...

  9. 造轮子是什么意思_聊聊在阿里工作一年的收获,什么是真正的技术能力?

    应当如何面对线上的异常/故障 看起来毫无意义的一个问题,碰到线上异常/故障如何面对,排查解决了不就好了,但是这真的只是第一层. 最近在想"消防"这个词语很有意思,它其实是两层意思: ...

最新文章

  1. Verilog 中 wire 和 reg 数据类型区别
  2. axios的简单使用
  3. 都在说微服务,那么微服务的反模式和陷阱是什么(二)
  4. DayDayUp:《复仇者联盟4:终局之战》娱乐闲谈——当灭霸碰上一个处女座的程序猿
  5. JavaScript实现MaximumSubarray最大子阵列(Brute Force蛮力解决方案)算法(附完整源码)
  6. SpringBoot中mybatis配置多数据源
  7. c++输出txt格式循环一组数据后换行再循环一次_numpy、pandas以及用pandas做数据分析的案例...
  8. Enterprise Library—缓存应用程序块
  9. phpmyadmin在nginx环境下配置错误
  10. 改变MyEclipse默认编码方式
  11. 复杂系统学习(五):细胞自动机 I:1D 和 2D CAs
  12. Bus Hound使用心得#抓包工具#协议抓取
  13. cuteftp8.3序列号
  14. 苹果系统中国日历服务器,ios日历中国节日不见了(2021年苹果日历订阅地址)...
  15. 简单好用的Linux服务器管理面板——宝塔面板
  16. 代码的坏味道之十七 :Inappropriate Intimacy(狎昵关系)
  17. SHON WEBB:搞好人际关系的5点小技巧
  18. 形容词,名词记忆(三):ment, ent后缀常用词
  19. 雷达模拟器-监控摄像机模拟软件 SPx Video Simulator
  20. Hitters数据集数据分析

热门文章

  1. 计算机创新大赛参赛表,计算机科学学院 “互联网+”大学生创新创业大赛师生参赛奖励办法...
  2. matlab类间散度矩阵,协方差矩阵和散布矩阵(散度矩阵)的意义
  3. 拒绝访问:终端上运行的LabVIEW版本,与主机计算机上运行的LabVIEW版本不同。对于实时终端,可通过MAX修改终端的语言环境,确保终端语言与主机语言一致。
  4. LoRa节点开发:6、代码详解修改发射和接收信道(频率)
  5. 跨考考研难吗?选择这几个专业更容易上岸!
  6. 武大50名学生造卫星,南航“00后”学生造火箭,后浪制造未来可期
  7. 猿创征文|前端进阶必备——WebSockt实现聊天室(附源码)
  8. python正则表达式基础学习(一)
  9. JWT —— 生成Token、解析Token的简单工具类
  10. ESB产品调用场景分析