例子

test1.c

#include <stdio.h>

int main(int argc, char **argv)
{
    printf("hello world\n");

	return 0;
}
$ strace ./test1.out 
execve("./test1.out", ["./test1.out"], 0x7ffd55864680 /* 62 vars */) = 0
brk(NULL)                               = 0x5628470b5000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc7495aeb0) = -1 EINVAL (无效的参数)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=83237, ...}) = 0
mmap(NULL, 83237, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f314aa25000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f314aa23000
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f314a831000
mprotect(0x7f314a856000, 1847296, PROT_NONE) = 0
mmap(0x7f314a856000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f314a856000
mmap(0x7f314a9ce000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f314a9ce000
mmap(0x7f314aa19000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f314aa19000
mmap(0x7f314aa1f000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f314aa1f000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f314aa24540) = 0
mprotect(0x7f314aa19000, 12288, PROT_READ) = 0
mprotect(0x562845326000, 4096, PROT_READ) = 0
mprotect(0x7f314aa67000, 4096, PROT_READ) = 0
munmap(0x7f314aa25000, 83237)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
brk(NULL)                               = 0x5628470b5000
brk(0x5628470d6000)                     = 0x5628470d6000
write(1, "hello world\n", 12hello world
)           = 12
exit_group(0)                           = ?
+++ exited with 0 +++

strace 跟踪程序与系统交互时产生的系统调用,以上每一行就对应一个系统调用,格式为:

系统调用的名称( 参数… ) = 返回值 错误标志和描述

2022-10-30

test.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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

	while (1) {
		printf("hello\n");
		sleep(1);
	}

	return EXIT_SUCCESS;
}

strace_test.log

execve("./test.out", ["./test.out"], 0x7ffe14cb5790 /* 62 vars */) = 0
brk(NULL)                               = 0x561ebb7ea000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffe95e51070) = -1 EINVAL (无效的参数)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (没有那个文件或目录)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=91919, ...}) = 0
mmap(NULL, 91919, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa551a4a000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300A\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30x\346\264ur\f|Q\226\236i\253-'o"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029592, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa551a48000
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30x\346\264ur\f|Q\226\236i\253-'o"..., 68, 880) = 68
mmap(NULL, 2037344, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa551856000
mmap(0x7fa551878000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7fa551878000
mmap(0x7fa5519f0000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19a000) = 0x7fa5519f0000
mmap(0x7fa551a3e000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fa551a3e000
mmap(0x7fa551a44000, 13920, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa551a44000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7fa551a49540) = 0
mprotect(0x7fa551a3e000, 16384, PROT_READ) = 0
mprotect(0x561ebb769000, 4096, PROT_READ) = 0
mprotect(0x7fa551a8e000, 4096, PROT_READ) = 0
munmap(0x7fa551a4a000, 91919)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x10), ...}) = 0
brk(NULL)                               = 0x561ebb7ea000
brk(0x561ebb80b000)                     = 0x561ebb80b000
write(1, "hello\n", 6)                  = 6
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffe95e51000) = 0
write(1, "hello\n", 6)                  = 6
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffe95e51000) = 0
write(1, "hello\n", 6)                  = 6
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, 0x7ffe95e51000) = 0
write(1, "hello\n", 6)                  = 6
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, {tv_sec=0, tv_nsec=674099342}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++

line1:execve 均为 strace 输出的系统调用中的第一个。strace 首先调用 fork 或 clone 新建一个子进程,然后在子进程中调用 execve 载入需要执行的程序。

line2:调用 brk(),以 0 作为参数,返回值为内存管理的起始地址(若在子进程中调用 malloc,则从 0x561ebb7ea000 地址开始分配空间,从这个地址也可以看出系统是 64 位的)

line3:调用 srch_prctl() ?

line4:调用 access(),检验 /etc/ld.so.preload 是否存在

line5:调用 openat(),打开 /etc/ld.so.cache,返回文件描述符 3

line6:调用 fstat(),获取 /etc/ld.so.cache 文件信息

line7:调用 mmap(),将 /etc/ld.so.cache 文件映射至内存

line8:调用 close(),关闭文件描述符 3

line9:调用 openat(),打开 /lib/x86_64-linux-gnu/libc.so.6,返回文件描述符 3

line10:调用 read(),从 /lib/x86_64-linux-gnu/libc.so.6 文件读取 832 字节数据

line11:。。。

看完这么多系统调用,是不是有点莫不着头脑?让我们从整体入手,回到主题 strace 上来。

从上面输出可以发现,真正能与源码对应上的只有 write()、clock_nanosleep() 这两个系统调用。其他系统调用几乎都用于进行进程初始化工作:装载被执行程序、载入 libc 函数库、设置内存映射等。

源码中的 while 语句或其它代码在 strace 输出中并没有体现,因为它们并没有唤起系统调用。strace 只关心程序与系统之间产生的交互,因而 strace 不适用于程序逻辑代码的排错和分析。

参考

strace命令的一个例子

strace工具的实现原理

strace实现原理:ptrace系统调用

[译] 玩转ptrace (一)

linux系统调用表(system call table)

调试器工作原理(1):基础篇

调试器工作原理(2):实现断点

调试器工作原理(3):调试信息