Продолжаем погружаться во вселенную системных вызовов - сегодня поговорим про команду strace и получим знания о том, как залезть в недра процесса и посмотреть на его общение с ядром через набор системных вызовов.
Команда strace преимущественно используется для того, чтобы отследить системные вызовы со стороны процесса. По сравнению с gdb, это довольно легкая в использовании утилита, которая позволяет вам приоткрыть ширму высокоуровневого API и посмотреть на то, что происходит "under the hood".
Давайте разберем принцип ее работы на простом примере:
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello, world\n");
return 0;
}
Теперь давайте прогоним это через strace и посмотрим на результат:
$ strace ./hello_world
execve("./hello_world", ["./hello_world"], [/* 50 vars */]) = 0
brk(0) = 0xa7e000 access("/etc/ld.so.nohwcap", F_OK) = -1
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
Воу, это лишь малая часть того, что вывелось в консоль... Как говорилось ранее, strace дает нам список всех системных вызовов, сделанных нашей программой.
Сейчас давайте обратим внимание на один конкретный вызов, который произошел входе выполнения и подробно разберем структуру, по которой strace формирует вывод информации:
write(1, "Hello, world\n", 13Hello, world ) = 13
Во-первых, можно увидеть то, что наша программа где-то, вероятно, через библиотечную функцию printf(), делает обращение к вызову write() для того, чтобы записать строку "Hello, world" в файл по дескриптору 1, также известному, как "standard output" (13 - это размер буфера на вывод):
write(1, "Hello, world\n", 13
Во-вторых, так как вывод команды strace и вывод нашей программы произошел в один и тот же терминал, строка "Hello, world" прилипла к информации про сам вызов:
write(1, "Hello, world\n", 13Hello, world
В-третьих, было напечатано возвращаемое значение 13, которое передается нашей программе и говорит о том, что вызов успешно записал 13 символов по указанному дескриптору:
write(1, "Hello, world\n", 13Hello, world ) = 13
Несколько сценариев использования:
1. Выяснить, какие конфиги читает программа на старте. Бывало же у вас такое: запускаете софт, ожидаете получить один результат, а тут Оппа, либо ничего не работает либо происходит то, чего вы вообще не ожидали...
strace может показать вам перечень интересующих вызовов, по которым вы сможете отследить код возврата и понять, в чем дело. В данном случае, нас интересует вызов open(), на который мы указываем через флаг '-e':
$ strace -e open php 2>&1 | grep php.ini
open("/usr/local/bin/php.ini", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/local/lib/php.ini", O_RDONLY) = 4
В результате видно, что процессу не получилось открыть "/usr/local/bin/php.ini" и он пошел к "/usr/local/lib/php.ini", с которым проблем не возникло. Вызов успешно отработал и вернул файловый дескриптор 4.
2. Узнать вызов, на котором процесс завис. Если программа по какой-то причине перестала отвечать на запросы, возможно, она в блокирующем режиме ожидает получения данных или что-то в этом духе. Найти причину можно попробовать через запуск с флагом "-p <pid>":
$ strace -p 15427
Process 15427 attached - interrupt to quit
futex(0x402f4900, FUTEX_WAIT, 2, NULL
Process 15427 detached
Да, мало информации, известно только то, что завис на вызове futex(). Если вы не обладаете дополнительной информацией, то, скорее всего, придется лезть в исходники. Однако, базовое представление о проблеме получить можно.
3. Понять, на что уходит процессорное время. Иногда бывает полезно быстро пристроить strace к программе и посмотреть, на полезную ли работу тратится наше CPU или мы просто чего-то ждем... Запуститься в режиме профилирования можно через флаг '-c':
$ strace -c -p 11084
% time seconds usecs/call calls errors syscall
94.59 0.001014 48 21 select
....