strace实现原理:ptrace系统调用

本文介绍了strace的工作原理,利用ptrace系统调用来跟踪进程的系统调用、信号和状态变化。通过示例代码,阐述了如何创建一个简单的strace工具,包括启动跟踪进程、接收SIGCHLD信号、获取并解析进程寄存器值,以及如何通过系统调用号映射到系统调用名称,以实现更清晰的跟踪输出。
摘要由CSDN通过智能技术生成

什么是strace?

根据strace官网的描述,strace是一个Linux用户空间跟踪器,可用于诊断、调试和教学。我们用它来监控用户空间进程和内核之间的交互,比如系统调用、信令、进程状态变化等等。

strace 使用内核的 ptrace 特性来实现其功能。

在运维的日常工作中,故障处理和问题诊断是主要内容和必备技能。strace 可以作为跟踪故障过程的有效工具。就像侦探通过系统调用的痕迹告诉你异常的真相。

这次想分享一个实用的东西,就是写一个strace工具。

用过strace的同学都知道,strace是用来跟踪进程调用的系统调用的,还可以统计进程调用对系统调用的统计。strace有两种使用方式,如下:

strace的过程
strace -p 进程 pid
第一个用于跟踪要执行的程序,而第二个用于跟踪正在运行的进程。

下图是用 strace 跟踪 ls 命令的结果

BASH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ strace ls
--- Process 15332 created
--- Process 15332 loaded C:\Windows\System32\ntdll.dll at 00007ffdd6fd0000
--- Process 15332 loaded C:\Windows\System32\kernel32.dll at 00007ffdd4660000
--- Process 15332 loaded C:\Windows\System32\KernelBase.dll at 00007ffdd40b0000
--- Process 15332 thread 2208 created
--- Process 15332 thread 9072 created
--- Process 15332 loaded D:\Program Files (x86)\cygwin64\bin\cygwin1.dll at 0000000180040000
--- Process 15332 loaded D:\Program Files (x86)\cygwin64\bin\cygintl-8.dll at 00000003c0ba0000
--- Process 15332 thread 13508 created
--- Process 15332 loaded D:\Program Files (x86)\cygwin64\bin\cygiconv-2.dll at 00000003cf8b0000
    0       0 [main] ls (15332) **********************************************
   42      42 [main] ls (15332) Program name: D:\Program Files (x86)\cygwin64\bin\ls.exe (windows pid 15332)
   27      69 [main] ls (15332) OS version:   Windows NT-10.0
   24      93 [main] ls (15332) **********************************************
--- Process 15332 loaded C:\Windows\System32\advapi32.dll at 00007ffdd4500000
--- Process 15332 loaded C:\Windows\System32\msvcrt.dll at 00007ffdd6830000
--- Process 15332 loaded C:\Windows\System32\sechost.dll at 00007ffdd4a50000
--- Process 15332 loaded C:\Windows\System32\rpcrt4.dll at 00007ffdd48d0000

A lot is omitted here

   19   24022 [main] ls 588 proc_terminate: nprocs 0
   19   24041 [main] ls 588 proc_terminate: leaving
   25   24066 [main] ls 588 pinfo::exit: Calling dlls.cleanup_forkables n 0x0, exitcode 0x0
   21   24087 [main] ls 588 pinfo::exit: Calling ExitProcess n 0x0, exitcode 0x0
--- Process 15332 (pid: 588) thread 9072 exited with status 0x0
--- Process 15332 (pid: 588) thread 2208 exited with status 0x0
--- Process 15332 (pid: 588) thread 6340 exited with status 0x0
--- Process 15332 (pid: 588) thread 13508 exited with status 0x0
--- Process 15332 (pid: 588) exited with status 0x0

自己写一个 strace 的第一步是了解 ptrace() 系统调用的使用。我们来看看ptrace()系统调用的定义

BASH

1
int ptrace(long request, long pid, long addr, long data);

ptrace() 系统调用用于跟踪进程的运行状态。下面介绍其参数的含义:

请求:指定要跟踪的操作。换句话说,可以通过传入不同的请求参数,对进程进行不同的跟踪。可选值是:
PTRACE_TRACEME
PTRACE_PEEKTEXT
PTRACE_POKETEXT
PTRACE_CONT
PTRACE_SINGLESTEP

PID:指定要跟踪的进程的PID。
addr:指定要读取或修改的内存地址。
数据:对于不同的请求操作,数据有不同的作用,下面会分别介绍。
如前所述,有两种方法可以使用 strace 跟踪进程。一种是通过strace命令启动进程,另一种是通过-p指定要跟踪的进程。

ptrace() 系统调用还提供了两种“请求”来实现上述两种方式:

第一个是通过 ptrace_Trace

第二个是通过 PTRACE_ATTACH

本文主要介绍第一种方法。由于第一种方法使用跟踪器启动被跟踪程序,因此需要启动两个进程。通常,可以使用 fork() 系统调用来创建一个新进程,所以我们自然也使用了 fork() 系统调用。

我们新建一个文件strace.c,输入代码如下:

BASH

1
2
3
4
5
6
7
8
9
10
int main(int argc, char *argv[]){
    pid_t child;
    child = fork();
    if (child == 0) {
        // Child process
    } else {
        // Parent process
    }
    return 0;
}

上面的代码通过调用fork()创建了一个子进程,但是什么都不做。之后,我们将在子进程中运行被跟踪的程序,在父进程中运行跟踪进程代码。

运行跟踪的程序

如前所述,被跟踪的程序需要在子进程中运行。要运行程序,您可以调用 execl() 系统。因此,您可以通过以下代码在子进程中运行 ls 命令:

BASH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
    pid_t child;
    child = fork();
    if (child == 0) {
        execl("/bin/ls", "/bin/ls", NULL);
        exit(0);
    } else {
        // Parent process
    }
    return 0;
}

execl() 用于执行指定的程序。如果执行成功,则不会返回。因此,EXECL(…) 的下一行代码 exit(0) 将不会被执行。

由于我们需要跟踪ls命令,所以在执行ls命令之前必须调用ptrace(ptrace_trace,0,null,null)来告诉系统需要跟踪进程。代码如下:

BASH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <sys/ptrace.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    pid_t child;

    child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "/bin/ls", NULL);
        exit(0);
    } else {
        // Parent process
    }

    return 0;
}

这样就完成了被跟踪进程部分的代码,然后启动跟踪进程部分的代码。

编写跟踪过程代码

如果你编译运行上面的代码,你会发现没有任何效果。这是因为当子进程调用 ptrace (ptrace_trace, 0, null, null) 并调用 execl() 系统调用时,子进程会向父进程(跟踪进程)发送 SIGCHLD 信号并停止运行本身。直到父进程发送调试命令,它才会继续运行。

因为在上面的代码中,父进程(跟踪进程)并没有发送任何调试命令退出,所以子进程(被跟踪进程)没有运行就随父进程退出,所以你不会看到任何效果。

现在让我们开始编写代码来跟踪这个过程。

由于被跟踪进程会向跟踪进程发送 SIGCHLD 消息,所以我们必须首先在跟踪进程的代码中接收到 SIGCHLD 信号。接收信号通过“wait()”系统调用完成,代码如下:

BASH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sys/ptrace.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    pid_t child;
    int status;

    child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "/bin/ls", NULL);
        exit(0);
    } else {
        wait(&status); // Receive SIGCHLD signal sent by subprocess
    }

    return 0;
}

上述代码调用“wait()”系统调

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值