飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

造轮子-strace(一)

时间:2022-01-02  作者:northeast-coder  

见字如面,我是东北码农。

本文是造轮子-strace的第一篇,我们先介绍strace的功能、使用。下一篇我们来用代码实现一下strace的功能,造个轮子。今天我们先观察、使用轮子。

1、什么是strace

strace是一个Linux的工具,用于记录process的system call的参数、返回值等信息。

strace是我们调试、排障的好帮手,尤其是没有源代码或不方便重新编译的时候。

1.1、小试牛刀

strace使用时非常方便,最简单的方式就是在指令前面加上strace。接下来以telnet为例来看看telnet时调用了哪些system call。

先直接运行一下telnet:

root@xxx:~$ telnet 域名.148 80
Trying 域名.148...
Connected to 域名.148.
Escape character is \'^]\'.
Connection closed by foreign host.

我们再用strace执行一下telnet:

root@xxx:~$ strace telnet 域名.148 80

...下面省略了一部分...
Trying 域名.148...
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_IP, IP_TOS, [16], 4)  = 0
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("域名.148")}, 16) = 0
Connected to 域名.148.
Escape character is \'^]\'.
setsockopt(3, SOL_SOCKET, SO_OOBINLINE, [1], 4) = 0
recvfrom(3, "", 8191, 0, NULL, NULL)    = 0
Connection closed by foreign host.
+++ exited with 1 +++

神奇吧,把telnet调用的system call的参数和返回值打印了出来。上面我只截取了一部分关于网络的system call。

1.2、什么是system call(系统调用)

strace可以跟踪system call,那么什么是system call呢?

为了安全,Linux 中分为用户态和内核态两种运行状态。对于普通进程,平时都是运行在用户态下,仅拥有基本的运行能力。当进行一些敏感操作,比如说要打开文件(open)然后进行写入(write)、分配内存(malloc)时,就会切换到内核态。内核态进行相应的检查,如果通过了,则按照进程的要求执行相应的操作,分配相应的资源。这种机制被称为system call(系统调用),用户态进程发起调用,切换到内核态,内核态完成,返回用户态继续执行,是用户态唯一主动切换到内核态的合法手段(exception 和 interrupt 是被动切换)。

关于系统调用的详细定义可以通过 man syscalls 查看,它列出了目前 Linux Kernel 提供的系统调用 ABI 。我们熟悉的调用比如 open, read ,close 之类的都属于系统调用,但它们都经过了 C 库 (glibc)的封装。实际上,只要符合 ABI 规范,我们可以自己用汇编代码来进行调用。

2、怎么使用strace

学习strace的各种用法,各种参数,推荐两个途径

  • man手册,最权威
  • 官网:https://域名/,介绍最常用的用法以及例子。

下面介绍一下我认为比较有用的一些用法。

2.1、两种trace模式

strace也有两种trace模式:

  • strace启动模式:就像上面telnet的例子,正常启动指令前面加strace即可。
  • attach模式:strace -p pid。

strace和gdb很像,二者实现的时候都是依赖ptrace。

2.2、过滤

如果不加过滤,strace会打印所有的system call。即使简单的ls指令,也会调用多达167个system call。所以我们需要根据需求对输出进行一些过滤。

root@xxx:~/code/case/case20_ptrace$ strace ls 2>&1 |wc -l
167
2.2.1、按system call类型过滤

可以按照system call类型过滤,我比较常用的有文件相关、内存相关、网络相关。

-e trace=%file     Trace all system calls which take a file name as an argument.
         %memory   Trace all memory mapping related system calls.
         %network  Trace all the network related system calls.
         %process  Trace all system calls which involve process management.

例如只看文件相关的system call

***@xxx:~$ strace -e trace=%file cat ./out
execve("/usr/bin/cat", ["cat", "./out"], 0x7ffd2b5f1f08 /* 19 vars */) = 0
access("/etc/域名oad", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/域名e", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/域名.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/share/locale/域名s", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "./out", O_RDONLY)     = 3
2.2.2、按system call名称过滤

指定system call名称,例如只跟踪connect函数

***@xxx:~$ strace -e trace=connect telnet 域名 80
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("域名.1")}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("域名.148")}, 16) = 0
connect(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("域名.251")}, 16) = 0
Trying 域名.148...
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("域名.148")}, 16) = 0
Connected to 域名.
Escape character is \'^]\'.
^Cstrace: Process 371 detached

2.3、system call故障注入

我们可以借助strace,观察process在system call失败时的表现。例如我们可以观察一下,cat打开文件失败时的表现。

在测试时很有用,起到了类似hook的效果。可以测试程序在内存不足、文件访问权限不足、网络不通等异常情况的表现。

root@xxx:~$ strace -e trace=openat -e fault=openat cat ./out
openat(AT_FDCWD, "/etc/域名e", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/haswell/x86_64/域名.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/haswell/域名.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/x86_64/域名.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/域名.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/haswell/x86_64/域名.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/haswell/域名.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/x86_64/域名.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/域名.6", O_RDONLY|O_CLOEXEC) = -1 ENOSYS (Function not implemented) (INJECTED)
cat: error while loading shared libraries: 域名.6: cannot open shared object file: Error 38
+++ exited with 127 +++

strace输出:“ (Function not implemented) (INJECTED)”。cat在打开文件失败时输出:“cat: error while loading shared libraries: 域名.6: cannot open shared object file: Error 38”

2.4、system call信息统计

strace除了可以打印每次system call信息外,还可以使用统计模式。
在性能测试时比较有用。

root@xxx:~$ strace -c -e trace=network telnet 域名 80
Trying 域名.148...
Connected to 域名.
Escape character is \'^]\'.
telnet> q
Connection closed.
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 域名    域名28         204         7         2 connect
 域名    域名17         272         3           sendto
 域名    域名13         135         6           socket
 域名    域名00         233         3           setsockopt
 域名    域名50         216         3           recvmsg
  域名    域名95         198         3           getsockname
  域名    域名42         221         2           recvfrom
  域名    域名21         321         1           sendmmsg
  域名    域名32         232         1           bind
  域名    域名31         231         1           shutdown
------ ----------- ----------- --------- --------- ----------------
域名    域名29                    30         2 total
  • time:时间占比
  • seconds:总执行时间
  • usecs/call:平均每次执行时间(微秒)
  • calls:执行次数
  • errors:执行出错次数

3、什么时候使用strace?

3.1、诊断、调试

strace的man手册里是这么介绍自己的

strace  is  a useful diagnostic, instructional, and debugging tool.  
System administrators, diagnosticians andtrouble-shooters will find it invaluable 
for solving problems with programs for which the source is not  readily  available  since  they  do  not  need to be recompiled in order to trace them.

strace是诊断、调试工具。尤其是代码不可用的时候,因为无需重新编译就可以跟踪system call。
例如一个程序调用system call失败退出,但是程序没输出关键日志。通过strace就可以获取调用的参数、返回值、err no。迅速定位问题。

3.2、学习用途

一个程序的实现,无外乎数据+算法+system call。一些“神奇”的程序往往要借助system call实现。例如tcpdump如何实现抓包,gdb如何实现调试。我们都可以使用strace来找灵感。

可以通过strace来了解程序调用了哪些system call,来大致摸清程序实现的骨架。甚至一会我们会利用strace来学习strace是如何实现的。

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

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。