Если вы работаете с Linux и хотите понять, что происходит за кулисами ваших программ, вам обязательно нужно познакомиться с инструментом strace. Он помогает отслеживать системные вызовы и сигналы, что очень полезно при диагностике ошибок, анализе работы программ и изучении внутренней логики Linux.
В этой статье мы расскажем, что такое strace, как его использовать и какие основные команды помогут вам начать работать с этим мощным инструментом.
Что такое strace и зачем он нужен?
strace — это командная утилита в Linux, которая позволяет отслеживать системные вызовы (system calls), которые любая программа осуществляет во время работы. Каждая программа взаимодействует с ядром Linux через системные вызовы, такие как чтение или запись файла, выделение памяти, открытие сетевых соединений, и другие.
Почему стоит использовать strace: преимущества
- Быстро помогает находить причины ошибок.
- Позволяет понять, какие файлы и ресурсы использует программа.
- Облегчает диагностику проблем с запуском программ. Помогает новичкам понять внутренний механизм работы Linux.
Пример использования:
Если программа аварийно завершается, strace поможет понять, на каком системном вызове произошла ошибка.
Если нужна информация о том, какие файлы программа открывает или какие сетевые соединения устанавливает.
Для обучения — чтобы понять, как работает программа на низком уровне.
Проверка наличия strace
В большинстве дистрибутивов Linux strace уже установлен по умолчанию. Проверьте наличие следующей командой.
[waky@centos practice]# strace -V
strace -- version 6.12
Copyright (c) 1991-2024 The strace developers <https://strace.io>.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Optional features enabled: stack-trace=libdw stack-demangle m32-mpers mx32-mpers secontext
[waky@centos practice]#
Если в ответ вы получили версию утилиты, то strace установлен и готов к работе. Если нет, установите его через менеджер пакетов. В этом вам помогут наши статьи (для CentOS, для Ubuntu).
Основные способы использования strace
- 1. Запуск программы с помощью strace
- 2. Отслеживание уже запущенного процесса
- 3. Запись логов в файл
- 4. Фильтрация системных вызовов
- 5. Время выполнения и задержки
- Полезные советы по работе с strace
Теперь рассмотрим, как пользоваться strace на практике.
1. Запуск программы с помощью strace
Самый простой способ — выполнить команду через strace:
strace <команда>
Пример: запустим команду ls с отслеживанием вызовов:
[waky@centos practice]# strace ls
execve("/usr/bin/ls", ["ls"], 0x7ffce160ab40 /* 33 vars */) = 0
brk(NULL) = 0x564ed8b51000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffda9cc49a0) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2626034000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=16687, ...}) = 0
mmap(NULL, 16687, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f262602f000
...
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
[waky@centos practice]#
Это выведет список системных вызовов, сделанных командой ls. В таком виде вывод программы смешан с выводом strace и является избыточным.
2. Отслеживание уже запущенного процесса
Если программа уже запущена, можно подключиться к ней по её PID (номер процесса):
strace -p <PID>
Пример: запустим команду ping в фоновом режиме:
[waky@centos practice]# ping 192.168.0.1 > /dev/null 2>&1 &
[1] 1504
[waky@centos practice]#
и будем отслеживать работу данного процесса, в моем случае PID — 1504:
[waky@centos practice]# strace -p 1504
strace: Process 1504 attached
restart_syscall(<... resuming interrupted poll ...>) = 0
sendto(3, "\10\0)/\0\0\0\311u\330Ei\0\0\0\0P\363\3\0\0\0\0\0\20\21\22\23\24\25\26\27"..., 64, 0, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.1")}, 16) = 64
recvmsg(3, {msg_name={sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.1")}, msg_namelen=128 => 16, msg_iov=[{iov_base="\0\0001-\0\2\0\311u\330Ei\0\0\0\0P\363\3\0\0\0\0\0\20\21\22\23\24\25\26\27"..., iov_len=192}], msg_iovlen=1, msg_control=[{cmsg_len=32, cmsg_level=SOL_SOCKET, cmsg_type=SO_TIMESTAMP_OLD, cmsg_data={tv_sec=1766185077, tv_usec=260891}}, {cmsg_len=20, cmsg_level=SOL_IP, cmsg_type=IP_TTL, cmsg_data=[64]}], msg_controllen=56, msg_flags=0}, 0) = 64
write(1, "64 bytes from 192.168.0.1: icmp_"..., 60) = 60
poll([{fd=3, events=POLLIN}], 1, 991) = 0 (Timeout)
sendto(3, "\10\0j \0\0\0\312v\330Ei\0\0\0\0\16\1\4\0\0\0\0\0\20\21\22\23\24\25\26\27"..., 64, 0, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.1")}, 16) = 64
recvmsg(3, {msg_name={sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.1")}, msg_namelen=128 => 16, msg_iov=[{iov_base="\0\0r\36\0\2\0\312v\330Ei\0\0\0\0\16\1\4\0\0\0\0\0\20\21\22\23\24\25\26\27"..., iov_len=192}], msg_iovlen=1, msg_control=[{cmsg_len=32, cmsg_level=SOL_SOCKET, cmsg_type=SO_TIMESTAMP_OLD, cmsg_data={tv_sec=1766185078, tv_usec=263149}}, {cmsg_len=20, cmsg_level=SOL_IP, cmsg_type=IP_TTL, cmsg_data=[64]}], msg_controllen=56, msg_flags=0}, 0) = 64
write(1, "64 bytes from 192.168.0.1: icmp_"..., 61) = 61
poll([{fd=3, events=POLLIN}], 1, 998) = 0 (Timeout)
sendto(3, "\10\0_\24\0\0\0\313w\330Ei\0\0\0\0\30\f\4\0\0\0\0\0\20\21\22\23\24\25\26\27"..., 64, 0, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.1")}, 16) = 64
recvmsg(3, {msg_name={sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.1")}, msg_namelen=128 => 16, msg_iov=[{iov_base="\0\0g\22\0\2\0\313w\330Ei\0\0\0\0\30\f\4\0\0\0\0\0\20\21\22\23\24\25\26\27"..., iov_len=192}], msg_iovlen=1, msg_control=[{cmsg_len=32, cmsg_level=SOL_SOCKET, cmsg_type=SO_TIMESTAMP_OLD, cmsg_data={tv_sec=1766185079, tv_usec=266218}}, {cmsg_len=20, cmsg_level=SOL_IP, cmsg_type=IP_TTL, cmsg_data=[64]}], msg_controllen=56, msg_flags=0}, 0) = 64
write(1, "64 bytes from 192.168.0.1: icmp_"..., 61) = 61
poll([{fd=3, events=POLLIN}], 1, 999) = 0 (Timeout)
sendto(3, "\10\0\377\n\0\0\0\314x\330Ei\0\0\0\0w\24\4\0\0\0\0\0\20\21\22\23\24\25\26\27"..., 64, 0, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.1")}, 16) = 64
recvmsg(3, {msg_name={sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.0.1")}, msg_namelen=128 => 16, msg_iov=[{iov_base="\0\0\7\t\0\2\0\314x\330Ei\0\0\0\0w\24\4\0\0\0\0\0\20\21\22\23\24\25\26\27"..., iov_len=192}], msg_iovlen=1, msg_control=[{cmsg_len=32, cmsg_level=SOL_SOCKET, cmsg_type=SO_TIMESTAMP_OLD, cmsg_data={tv_sec=1766185080, tv_usec=268525}}, {cmsg_len=20, cmsg_level=SOL_IP, cmsg_type=IP_TTL, cmsg_data=[64]}], msg_controllen=56, msg_flags=0}, 0) = 64
write(1, "64 bytes from 192.168.0.1: icmp_"..., 60) = 60
poll([{fd=3, events=POLLIN}], 1, 999^Cstrace: Process 1504 detached
<detached ...>
[waky@centos practice]#
Не забудьте прибить процесс ping после тестов:
[waky@centos practice]# kill 1504
[waky@centos practice]#
3. Запись логов в файл
Для удобства можно сохранить вывод в файл:
strace -o log.txt <команда>
[waky@centos practice]# strace -o log.txt ls
log.txt
[waky@centos practice]#
Или для уже запущенного процесса:
strace -p <PID> -o log.txt
Это поможет проанализировать данные позже.
4. Фильтрация системных вызовов
Иногда нужно отслеживать только определенные вызовы, например, чтение и запись файлов:
strace -e read,write <команда>
[waky@centos practice]# strace -e read,write ls
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0pp\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P'\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\247\2\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220$\0\0\0\0\0\0"..., 832) = 832
read(3, "# Locale name alias data base.\n#"..., 4096) = 2998
read(3, "", 4096) = 0
write(1, "log.txt\n", 8log.txt
) = 8
+++ exited with 0 +++
[waky@centos practice]#
или
strace -e trace=file <команда>
[waky@centos practice]# strace -e trace=file ls
execve("/usr/bin/ls", ["ls"], 0x7ffe14de9970 /* 33 vars */) = 0
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libpcre2-8.so.0", O_RDONLY|O_CLOEXEC) = 3
statfs("/sys/fs/selinux", {f_type=SELINUX_MAGIC, f_bsize=4096, f_blocks=0, f_bfree=0, f_bavail=0, f_files=0, f_ffree=0, f_fsid={val=[0, 0]}, f_namelen=255, f_frsize=4096, f_flags=ST_VALID|ST_NOSUID|ST_NOEXEC|ST_RELATIME}) = 0
statfs("/sys/fs/selinux", {f_type=SELINUX_MAGIC, f_bsize=4096, f_blocks=0, f_bfree=0, f_bavail=0, f_files=0, f_ffree=0, f_fsid={val=[0, 0]}, f_namelen=255, f_frsize=4096, f_flags=ST_VALID|ST_NOSUID|ST_NOEXEC|ST_RELATIME}) = 0
access("/etc/selinux/config", F_OK) = 0
openat(AT_FDCWD, "/usr/lib/locale/en_US.utf8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
log.txt
+++ exited with 0 +++
[waky@centos practice]#
Флаг -e помогает сузить список команд и вывести только важные системные вызовы.
5. Время выполнения и задержки
Чтобы понять, сколько времени занимает каждый вызов, используйте:
strace -T <команда>
[waky@centos practice]# strace -T ls
execve("/usr/bin/ls", ["ls"], 0x7ffe3d9b7b58 /* 33 vars */) = 0 <0.000240>
brk(NULL) = 0x55b9015f1000 <0.000012>
arch_prctl(0x3001 /* ARCH_??? */, 0x7fff7ee7bb00) = -1 EINVAL (Invalid argument) <0.000012>
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc5ceb51000 <0.000037>
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000023>
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000022>
fstat(3, {st_mode=S_IFREG|0644, st_size=16687, ...}) = 0 <0.000014>
mmap(NULL, 16687, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc5ceb4c000 <0.000020>
close(3) = 0 <0.000009>
...
close(3) = 0 <0.000011>
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0 <0.000010>
write(1, "log.txt\n", 8log.txt
) = 8 <0.000015>
close(1) = 0 <0.000008>
close(2) = 0 <0.000008>
exit_group(0) = ?
+++ exited with 0 +++
[waky@centos practice]#
Это покажет в конце строки время исполнения каждого системного вызова.
Полезные советы по работе с strace
Комбинирование опций: Можно сочетать фильтры, вывод логов и отслеживание уже запущенных процессов одновременно.
Просмотр ошибок: Обратите внимание на строки с ошибками, они выделены, например, ENOENT — файл не найден.
Обучение и эксперимент: Используйте strace для изучения работы программ, чтобы лучше понять их функционирование.
Заключение
strace — незаменимый инструмент для каждого, кто хочет углубиться в работу своих программ и системы Linux. Он прост в использовании, и при немного практики станет вашим надежным помощником при диагностике и обучении.